Reputation: 6290
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?
Upvotes: 8
Views: 4051
Reputation: 1038800
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") }) %>
Upvotes: 10
Reputation: 8759
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.
This is the way to go, in my opinion. You'd create a base Controller class that would provide this functionality. If you are familiar with the ASP.NET WebForms model then this is similar to creating a custom base Page
class.
As to the advantages of putting it in a base class, the main advantages are readability, maintainability and reusability. If you copy and paste the above method into each controller that needs it, you are going to have a more difficult time if, down the road, you need to add new information to the ViewData
collection.
In short, anytime you catch yourself copying and pasting code among classes or views in your application you should stop and think about how to put such logic in a single place. For more, read up on DRY - Don't Repeat Yourself.
Upvotes: 0