Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to do global viewdata in an area of my ASP.NET MVC site?

I have an several controllers where I want every ActionResult to return the same viewdata. In this case, I know I will always need basic product and employee information.

Right now I've been doing something like this:

public ActionResult ProductBacklog(int id)  {
      PopulateGlobalData(id);
      // do some other things
      return View(StrongViewModel);
}

Where PopulateGlobalData() is defined as:

    public void PopulateGlobalData(int id) {
        ViewData["employeeName"] =  employeeRepo.Find(Thread.CurrentPrincipal.Identity.Name).First().FullName;
        ViewData["productName"] = productRepo.Find(id).First().Name;
     }

This is just pseudo-code so forgive any obvious errors, is there a better way to be doing this? I thought of having my controller inherit a class that pretty much does the same thing you see here, but I didn't see any great advantages to that. It feels like what I'm doing is wrong and unmaintable, what's the best way to go about this?

like image 737
chum of chance Avatar asked Nov 16 '10 19:11

chum of chance


1 Answers

You could write a custom action filter attribute which will fetch this data and store it in the view model on each action/controller decorated with this attribute.

public class GlobalDataInjectorAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        string id = filterContext.HttpContext.Request["id"];
        // TODO: use the id and fetch data
        filterContext.Controller.ViewData["employeeName"] = employeeName;
        filterContext.Controller.ViewData["productName"] = productName;
        base.OnActionExecuted(filterContext);
    }
}

Of course it would much cleaner to use a base view model and strongly typed views:

public class GlobalDataInjectorAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        string id = filterContext.HttpContext.Request["id"];
        // TODO: use the id and fetch data
        var model = filterContext.Controller.ViewData.Model as BaseViewModel;
        if (model != null)
        {
            model.EmployeeName = employeeName;
            model.ProductName = productName;
        }
        base.OnActionExecuted(filterContext);
    }
}

Now all that's left is to is to decorate your base controller with this attribute:

[GlobalDataInjector]
public abstract class BaseController: Controller
{ }

There's another more interesting solution which I personally prefer and which involves child actions. Here you define a controller which handles the retrieval of this information:

public class GlobalDataController: Index
{
    private readonly IEmployeesRepository _employeesRepository;
    private readonly IProductsRepository _productsRepository;
    public GlobalDataController(
        IEmployeesRepository employeesRepository,
        IProductsRepository productsRepository
    )
    {
        // usual constructor DI stuff
        _employeesRepository = employeesRepository;
        _productsRepository = productsRepository;
    }

    [ChildActionOnly]
    public ActionResult Index(int id)
    {
        var model = new MyViewModel
        {
            EmployeeName = _employeesRepository.Find(Thread.CurrentPrincipal.Identity.Name).First().FullName,
            ProductName = _productsRepository.Find(id).First().Name;
        };
        return View(model);
    }
}

And now all that's left is to include this wherever needed (probably the master page if global):

<%= Html.Action("Index", "GlobalData", new { id = Request["id"] }) %>

or if the id is part of the routes:

<%= Html.Action("Index", "GlobalData", 
    new { id = ViewContext.RouteData.GetRequiredString("id") }) %>
like image 136
Darin Dimitrov Avatar answered Nov 06 '22 01:11

Darin Dimitrov