0

In my controller I have 2 simple methods for viewing and ordering products

[HttpGet]
public ActionResult Product(int id)
{
    Product product = Repository.GetProduct(id);
    return View(product);
}

[HttpPost]
public ActionResult Order(Order order)
{
    var orderResult = OrderProcessor.Process(order);
    return orderResult.Success ? View("OrderSuccess") : View("OrderFail");
}

The Order class contains fields from the view that customers must fill:

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

    [Range(1, 10, ErrorMessage = "Quantity must be a number from 1 to 10")]
    public int Quantity { get; set; }

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

    // other order info
}

In my view I want to use HTML helpers for form fields like @Html.TextBoxFor(x => x.ShippingAddress) to enable Javascript client validation. But the problem is that MVC only allows these helpers to be used for properties of the view model (in my case the Product class) and my view model doesn't contain ShippingAddress.

To get these helpers working I need to have the same class for the model of the Product view and the parameter of the Order method. But this doesn't make any sense in my case. The view model shold contain product properties (name, price, description, etc.) while the POST method parameter should contain user input (quantity, address, etc).

I would like to be able to use HTML helpers with the properties of the Order class like this:

@Html.TextBoxFor<Order>(x => x.ShippingAddress)

Is there a way to achieve this (maybe via an extension to MVC)? Or is there some other workaround for this problem?

Andre Borges
  • 1,360
  • 14
  • 37
  • Not clear what you trying to achieve. Why are you not just adding the properties you need to the view model (that's the whole purpose of using a view model). But it appears `Order` has property `ShippingAddress` so not clear what your issue is. –  Feb 15 '16 at 07:22
  • @StephenMuecke, my wiew model is Product, and I want to use HTML helpers for properties of Order, which is not allowed by MVC. This is the issue. – Andre Borges Feb 15 '16 at 07:25
  • What?. You creating an Order, so your view model needs to relate to an Order - it might be (say) `public class OrderVM` and contain the 3 properties you have in the `Order` data model plus additional properties (say) `string ProductName`, `decimal ProductPrice` etc if you wanting to display additional properties of `Product` in the view to create an Order (or it might just include `Product Product` if you want to display everything about the associated product) –  Feb 15 '16 at 07:29
  • I don't want to mix data for rendering the view (product name, description) and for binding user input (quantity, address) in one class as it would break my order processing logic: my `OrderProcessor` class expects the `Order` object, not some view model that has extra data that it doesn't need. – Andre Borges Feb 15 '16 at 07:29
  • Then you post back `OrderVM` and map the properties of it to a new instance of `Order` and process/save `Order` (and it appears that you confused with your terminology - your not using a view model, you just using data models). Refer [What is ViewModel in MVC?](http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc) –  Feb 15 '16 at 07:34
  • Yes, it probably wasn't clear, but when I wrote "view model" I meant just "model for my view", not "ViewModel approach/pattern". So, are you suggesting that I use OrderVM as my POST method param and then in my POST method create an Order like this `new Order { ShippingAddress = OrderVM.ShippingAddress, Quantity = OrderVM.Quantity, ... }`? But what if I have 20 properties of Order that I should map this way? – Andre Borges Feb 15 '16 at 07:49
  • Yes exactly, and the GET method for it would be something like `Create(int ID)` where `ID` is the ProductID, so you can get the `Product` and map its properties to `OrderVM`. There are tools such as [automapper](http://automapper.org/) which can make it easier to map between data and view models. –  Feb 15 '16 at 07:52
  • Ok, I understand your suggestion, but using automapper seems like an overkill. I may be wrong, but using a view model to hold both data from DB and data that user inputs in text fields seems a bit dirty. A bit cleaner approach is to have a seperate object as a POST-method param that holds only data that was actually POSTed (form fields). This saves the pain of configuring and debugging ViewModel-DataModel mappings. – Andre Borges Feb 15 '16 at 08:06
  • Then all I can add is _best of luck_ :) (but of course if you want your view to be `@model Product`, (i.e. not related to what you want to edit) you can always include a `@Html.Action("Create")` which returns a partial view containing the form for a new `Order`) –  Feb 15 '16 at 08:11

2 Answers2

1

There's a difference between your models and your view models. Models should reflect the database and your view models should be custom made to fit the view.

What you could do is creating a view model that contains all the properties you are interested in having contained in your view, and populate those fields with data from your database.

Perhaps it's not the neatest of solutions, but your view model could be as simple as:

public Order Order {get; set;}
public Product Product {get; set;}

And in the GET-method you get the order and product you want to display and and assign it to the view model.

  • Perhaps it wasn't clear in my question. The Order is not an entity in datbase. It's just a container that maps user input form the view (` `) to POST method params. So in my GET-method I don't have an order yet. The `Order` object is only created after the user submits the form. – Andre Borges Feb 15 '16 at 07:37
  • I see no reason why not to create an empty order, send it to the view to be filled in and then POST it to the controller, but instead of adding an Order into your viewmodel, you could just add the fields you want. public String ShippingAddress {get; set;} and so on. – SwedishProgrammer Feb 15 '16 at 08:24
  • I guess after all this is the best possible solution. Although I still don't understand why MVC doesn't allow such constructs: `@Html.TextBoxFor(x => x.SomeProperty)` – Andre Borges Feb 15 '16 at 13:10
0

No need To look for any magic. You can directly add "shipping address" in your Product Model as well. What is the problem in adding that??

Shashank Sood
  • 480
  • 5
  • 16