Junior
Junior

Reputation: 11988

How to construct an instance of a view-model when using Asp.Net MVC 5 framework and IoC container?

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

Answers (1)

StriplingWarrior
StriplingWarrior

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

Related Questions