Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC - Proper usage of View Model and Command pattern

I've been writing ASP.NET MVC applications for some time and I found them to be a good place for using the command pattern: we represent every user request as a command - a set of input params - then this command is processed (processing includes validation and other domain logic) and the result is sent back to the user.

Another thing I've been using in my applications is view models. I found them to be a more convenient way of passing data to the view than using domain objects as models or filling ViewData/ViewBag.

These 2 concepts work great for separating data that is shown to the user from user input and its handling, but they don't quite agree with each other in ASP.NET MVC.

Let's say I want to use commands and view models when developing a simple web store where users look through products and can order a product by providing their name and email address:

class ProductViewModel 
{
    public ProductViewModel(int id) { /* init */ }
    public int Id { get; set; }
    public string Name { get; set; }
    // a LOT of other properties (let's say 50)
}

class OrderProductCommand
{
    public int ProductId { get; set; }

    [Required(ErrorMessage = "Name not specified")]
    public string Name { get; set; }

    [Required(ErrorMessage ="E-Mail not specified")]
    public string Email { get; set; }

    public CommandResult Process() { /* validate, save to DB, send email, etc. */ }
}

When looking through tutorials and SO I've seen people suggest several ways of doing this.

Option 1

Controller:

[HttpGet]
public ActionResult Product(int id)
{
    return View(new ProductViewModel(id));
}

[HttpPost]
public ActionResult Product(OrderProductCommand command)
{
    if (ModelState.IsValid)
    {
        var result = command.Process();
        if(result.Success)
            return View("ThankYou");
        else
            result.CopyErrorsToModelState(ModelState);
    }
    return Product(command.Id);
}

View:

@using (Html.BeginForm())
{
    @Html.Hidden("ProductId", Model.Id)
    @Html.TextBox("Name")
    @Html.TextBox("Email")
    <input type="submit" value="Place order" />
}

Pros: view model and command are separated from each other, the HttpPost method looks clean

Cons: I can't use convenient HTML helpers like @Html.TextBoxFor(model => model.Email), I can't use client validation (see my other question)

Option 2

We copy Id, Name and Email together with their validation attributes from command to viewModel.

Controller:

[HttpPost]    
public ActionResult Product(ProductViewModel viewModel)
{
        var command = new OrderProductCommand();
        command.Id = viewModel.Id;
        command.Name = viewModel.Name;
        command.Email = viewModel.Email;        
        if (ModelState.IsValid)
        // ...
}

View:

@Html.TextBoxFor(m => m.Email)
...

Pros: all of option 1 cons go away

Cons: copying of properties seems inconvenient (what if I have 50 of them?), validation of Name and Email in view model (it should be done in command where the rest of the domain logic resides), model as a POST parameter (see below)

Option 3

We make command a property of viewModel.

Controller:

[HttpPost]
public ActionResult Product(ProductViewModel viewModel)
{
        var command = viewModel.Command;
        if (ModelState.IsValid)
        // ...
}

View:

@Html.TextBoxFor(m => m.Command.Email)
...

Pros: all of option 1 cons go away

Cons: view model should only contain data that is displayed to the user (and command is not displayed), model as POST parameter (see below)

--

What I don't like about options 2 and 3 is that we use a view model as a POST method parameter. This method is meant for handling user input (only 2 fields + 1 hidden in this case) and the model contains 50 more properties that I'll never use in this method and that will always be empty. Not to mention the necessity to create an empty constructor for the view model just to handle this POST request and the unnecessary memory consumption when creating large view model objects for every POST request.

My question is (that's like the longest question ever, I know): is there a secret Option 4 for properly using commands and view models that has all of the pros and none of the cons of the other ones? Or am I being paranoid and these cons are not that important and can be ignored?

like image 295
holdenmcgrohen Avatar asked Oct 30 '15 08:10

holdenmcgrohen


People also ask

What is ViewModel in MVC design pattern?

ViewModel in the MVC design pattern is very similar to a "model". The major difference between "Model" and "ViewModel" is that we use a ViewModel only in rendering views. We put all our ViewModel classes in a "ViewModels" named folder, we create this folder. Understand it with an example.

What is the use of MVC pattern?

MVC is a design pattern used to decouple user-interface (view), data (model), and application logic (controller). This pattern helps to achieve separation of concerns. Using the MVC pattern for websites, requests are routed to a Controller that is responsible for working with the Model to perform actions and/or retrieve data.

What is ASP NET Core MVC design pattern?

ASP.NET Core MVC follows the tried and tested MVC Design pattern at its core and everything revolves around it. MVC is a powerful and elegant means of separating concerns within an application. MVC Architecture splits the application into three separate and distinct layers. The Model layer, View Layer and the Controller Layer.

How to display more than one model on view in MVC?

If we want to display more than one model on view in asp.net mvc, we need to create a new viewmodel. The following image shows a visual representation of the view model in asp.net mvc.


1 Answers

Seems like the only other decent way go is to use a partial view for rendering the form and use OrderProductCommand as the view model.

Product.cshtml:

@model ProductViewModel
...
@Html.Partial("Product_OrderForm", new OrderProductCommand { ProductId = Model.Id })
...

Product_OrderForm.cshtml:

@model OrderProductCommand
...
@using (Html.BeginForm("Product", "Home"))
{
    @Html.HiddenFor(cmd => cmd.ProductId)
    @Html.TextBoxFor(cmd => cmd.Name)
    @Html.TextBoxFor(cmd => cmd.Email)
    <input type="submit" value="Place order" />
}
...

This way there is no need to create a data map between view models and business objects, and the controller code can be left clean as it was in in Option 1:

[HttpGet]
public ActionResult Product(int id)
{
    return View(new ProductViewModel(id));
}

[HttpPost]
public ActionResult Product(OrderProductCommand command)
{
    // process command...
}
like image 185
holdenmcgrohen Avatar answered Sep 30 '22 18:09

holdenmcgrohen