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?
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") }) %>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With