Reputation: 11988
I am writing a project using ASP.NET MVC 5 framework along with Unity-Container to handle my dependencies.
I am hunting for a best practice to deal with constructing view-models while using IoC
container in an Asp.Net MVC 5 app. I hope this question isn't considered opinion based and gets closed.
I have the following view-model class
public class CategoryReportViewModel
{
public IUnitOfWork UnitOfWork { get; set; }
public IEnumerable<object> Records { get; set; }
// ....
public TasksSummaryByProductViewModel(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
public void SetFilters()
{
// Do something.....
UnitOfWork.....
}
public void SetData()
{
// Do something.....
Records = UnitOfWork.....
}
}
As you can see, my CategoryReportViewModel
class needs an instance of the IUnitOfWork
to allow my methods to to perform their task "unless I manually pass the IUnitOfWork
to every method that needs it." I am wondering what is the right way to new up an instance of CategoryReportViewModel
. Is it via IoC
container or using the good-old way by passing an instance of the IUnitOfWork
from the constructor?
In other words, what is the right way to consume my view-model?
Follow are the approaches that comes to mind when trying to deal with this scenario, but which is the right way? Perhaps there is alternative?
First, good-old manual new up strategy
public class ReportsController : Controller
{
protected IUnitOfWork UnitOfWork { get; set; }
public ReportsController(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
[HttpGet]
public ActionResult CategoryByName()
{
var viewModel = new CategoryReportViewModel(UnitOfWork);
viewModel.SetFilters();
return View(viewModel);
}
[HttpPost, ValidateAntiForgeryToken]
public ActionResult CategoryByName(CategoryReportViewModel viewModel)
{
viewModel.UnitOfWork = UnitOfWork; // This is ugly
if (ModelState.IsValid)
{
viewModel.SetData();
}
viewModel.SetFilters();
return View(viewModel);
}
}
Second, manual new up but pass the UnitOfWork
to any methods that needs it
public class ReportsController : Controller
{
protected IUnitOfWork UnitOfWork { get; set; }
public ReportsController(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
[HttpGet]
public ActionResult CategoryByName()
{
var viewModel = new CategoryReportViewModel();
viewModel.SetFilters();
return View(viewModel);
}
[HttpPost, ValidateAntiForgeryToken]
public ActionResult CategoryByName(CategoryReportViewModel viewModel)
{
if (ModelState.IsValid)
{
viewModel.SetData(UnitOfWork);
}
viewModel.SetFilters(UnitOfWork);
return View(viewModel);
}
}
Third, using IoC
container after registering the CategoryReportViewModel
. But this will require me to modify Asp.Net MVC 5 default behavior since my view-model may/does not have a default constructor.
public class ReportsController : Controller
{
[HttpGet]
public ActionResult CategoryByName()
{
var viewModel = DependencyResolver.Current.GetService<CategoryReportViewModel>();
viewModel.SetFilters();
return View(viewModel);
}
// This will now work since I my view-model does not have a default constructor
// unless I override the `DefaultModelBinder` class somehow to resolve the class
// from the IoC container
[HttpPost, ValidateAntiForgeryToken]
public ActionResult CategoryByName(CategoryReportViewModel viewModel)
{
if (ModelState.IsValid)
{
viewModel.SetData();
}
viewModel.SetFilters();
return View(viewModel);
}
}
Upvotes: 0
Views: 261
Reputation: 156634
There's room for different approaches and opinions on this, but the pattern I've found to work best is to make the ViewModels be "dumb" DTOs whose properties are created and populated by the controller. Inject services into the controller, and have all the business logic in your action methods.
In other words, SetFilters()
and SetData()
should probably be on the controller, and take the model as an argument.
public class ReportsController : Controller
{
protected IUnitOfWork UnitOfWork { get; set; }
public ReportsController(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
[HttpGet]
public ActionResult CategoryByName()
{
var viewModel = new CategoryReportViewModel();
this.SetFilters(viewModel);
return View(viewModel);
}
[HttpPost, ValidateAntiForgeryToken]
public ActionResult CategoryByName(CategoryReportViewModel viewModel)
{
if (ModelState.IsValid)
{
this.SetData(viewModel);
}
this.SetFilters(viewModel);
return View(viewModel);
}
private void SetFilters(CategoryReportViewModel viewModel)
{
// Do something.....
UnitOfWork.....
}
private void SetData(CategoryReportViewModel viewModel)
{
// Do something.....
Records = UnitOfWork.....
}
}
Upvotes: 2