Lion
Lion

Reputation: 17879

Set ViewBag property in the constructor of a ASP.NET MVC Core controller

My theme has some sort of breadcrumb. The controller is always the category. To avoid repeat myself, I want to set it in the constructor of the controller for all actions like this:

class MyController:Controller{
    public MyController() {
        ViewBag.BreadcrumbCategory = "MyCategory";
    }
}

When I access ViewBag.BreadcrumbCategory in the layout-view, its null. In a Action it works:

class MyController:Controller{
    public IActionResult DoSomething() {
        ViewBag.BreadcrumbCategory = "MyCategory";
    }
}

I'm wondering that setting a ViewBag property is not possible in a constructor? It would be annoying and no good practice to have a function called on every action which do this work. In another question using the constructor was an accepted answear, but as I said this doesn't work, at least for ASP.NET Core.

Upvotes: 18

Views: 13303

Answers (3)

mike gold
mike gold

Reputation: 1611

Here is a better way to do this for .NET Core 3.x, use the ResultFilterAttribute:

Create your own custom filter attribute that inherits from ResultFilterAttribute as shown below:

   public class PopulateViewBagAttribute : ResultFilterAttribute
    {


        public PopulateViewBagAttribute()
        {
        }

        public override void OnResultExecuting(ResultExecutingContext context)
        {
            //   context.HttpContext.Response.Headers.Add(_name, new string[] { _value });
            (context.Controller as MyController).SetViewBagItems();
            base.OnResultExecuting(context);
        }
    }

You'll need to implement the method SetViewBagItems to populate your ViewBag

public void SetViewBagItems() { ViewBag.Orders = Orders; }

Then Decorate your Controller class with the new attribute:

  [PopulateViewBag]
  public class ShippingManifestController : Controller

That's all there is to it! If you are populating ViewBags all over the place from your constructor, then you may consider creating a controller base class with the abstract method SetViewBagItems. Then you only need one ResultFilterAttribute class to do all the work.

Upvotes: 3

Tseng
Tseng

Reputation: 64170

There is an GitHub issue about it and it's stated that this is by design. The answer you linked is about ASP.NET MVC3, the old legacy ASP.NET stack.

ASP.NET Core is written from scratch and uses different concepts, designed for both portability (multiple platforms) as well as for performance and modern practices like built-in support for Dependency Injection.

The last one makes it impossible to set ViewBag in the constructor, because certain properties of the Constructor base class must be injected via Property Injection as you may have noticed that you don't have to pass these dependencies in your derived controllers.

This means, when the Controller's constructor is called, the properties for HttpContext, ControllerContext etc. are not set. They are only set after the constructor is called and there is a valid instance/reference to this object.

And as pointed in the GitHub issues, it won't be fixed because this is by design.

As you can see here, ViewBag has a dependency on ViewData and ViewData is populated after the controller is initialized. If you call ViewBag.Something = "something", then you it will create a new instance of the DynamicViewData class, which will be replaced by the one after the constructor gets initialized.

As @SLaks pointed out, you can use an action filter which you configure per controller.

The following example assumes that you always derive your controllers from Controller base class.

public class BreadCrumbAttribute : IActionFilter
{
    private readonly string _name;

    public BreadCrumbAttribute(string name)
    {
        _name = name;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        base.OnActionExecuting(context);

        var controller = context.Controller as Controller;
        if (controller != null) 
        {
            controller.ViewBag.BreadcrumbCategory = _name;
        }
    }
}

Now you should be able to decorate your controller with it.

[BreadCrumb("MyCategory")]
class MyController:Controller
{
}

Upvotes: 25

FcoJavier99
FcoJavier99

Reputation: 327

I have the same issue and solve it overriding the OnActionExecuted method of the controller:

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        base.OnActionExecuted(context);
        ViewBag.Module = "Production";
    }

Upvotes: 19

Related Questions