ViewModel vs. InputModel in Razor View - c#

As I found on 10 Good practices for asp.net mvc webapplications it is a good practice to split mvc models in ViewModel (Models representing the view) and InputModels (Representing data entered by user).
The ViewModel gets a property of Type InputModel. The InputModel carries the data that can be edited by the user.
public class EmployeeInputModel{
public string Name {get;set;}
public Id? DepartmentId{get;set;}
}
public class EmployeeCreateViewModel{
public IList<Department> Departments{get;set;}
public EmployeeInputModel EmployeeModel{ get;set;}
}
The Controller-methods look like:
public class EmployeeController : Controller{
public ActionResult Create(){
var viewModel = new EmployeeCreateViewModel(DepartmentService.GetAll(), new EmployeeInputModel());
return View(viewModel);
}
public ActionResult Create(EmployeeInputModel createModel){
try{
EmployeeService.Create(...);
return RedirectToAction("Index");
} catch (Exception ex){
var viewModel = new EmployeeCreateViewModel(DepartmentService.GetAll(), createModel);
return View(viewModel)
}
}
}
The View looks like:
#model EmployeeCreateViewModel
#Html.EditorFor(m => m.EmployeeModel)
The Editor Partial is just like:
#model EmployeeInputModel
#Html.TextBoxFor(m => m.Name)
#Html.DropDownFor(m => m.Department, ???)
This worked great for me, unless I came to the point of DropDownLists (in the sample see departments). Because the EditorTemplate doesnt know the ViewModel but just the InputModel.
I dont want to put the department list into the input model, because it is not the supposed place to be for this list (I would have to bind them). It has to be in the viewmodel. The properties of the input model should also not be in the ViewModel.
Does someone have any idea how to achieve a separation of viewmodel and input model in one view?

You would have to create a model abstraction of your departments. Something like this:
public class EmployeeInputModel{
public string Name {get;set;}
public List<DepartmentInputModel> Departments{get;set;}
}
public class DepartmentInputModel{
public int Id;
public string Name;
}
Then you can display just the name of the department in the drop down list. The value will then be the id of the department.
You can have a look at this SO question for a example.

You can use the ViewBag yo pass the list of departments to your partial.
Or use a partial view that accepts the view model as its model

Related

Accessing model properties in Razor-4 view

I have the following EF generated data model:
public partial class PrinterMapping
{
public string MTPrinterID { get; set; }
public string NTPrinterID { get; set; }
public string Active { get; set; }
}
I then have the following view model:
public class PrinterViewModel
{
public PrinterMapping PrinterMapping;
public Exceptions Exceptions;
public IEnumerable<PrinterMapping> Printers;
}
In my Index Action in HomeController I am passing my view model to the Index view.
private eFormsEntities db = new eFormsEntities();
public ActionResult Index()
{
PrinterViewModel printerModel = new PrinterViewModel();
printerModel.Printers = from pt in db.PrinterMapping select pt;
return View(printerModel);
}
My Index view is calling a partial view in the following manner towards the end (probably wrong):
#Html.Partial("~/Views/Home/GridView.cshtml")
My GridView.cshtml looks like:
#model AccessPrinterMapping.Models.PrinterViewModel
<h2> This is Where the Grid Will Show</h2>
#{
new WebGrid(#model.Printers, "");
}
#grid.GetHtml()
I learned about the WebGrid method from http://msdn.microsoft.com/en-us/magazine/hh288075.aspx.
My WebGrid line isn't happy at all since it doesn't recognize #model within that line.
How do I access the Printers in the view model that I passed in? Is this even possible?
Thanks very much to you all.
Theres two issues with your code.
First, you should explicitly pass your model in like this:
#Html.Partial("~/Views/Home/GridView.cshtml", Model) #* explicitly pass the model in *#
Then, because you are already in a code block in your partial view.. you don't need the # symbol.. and Model has an uppercase M.
new WebGrid(Model.Printers, "");
#model is a directive for your views/partial views. Think of it as a "configuration" command. Model is an actual property. It is the object that is passed into the view.. and is of the type you specified with the #model directive.
#{
new WebGrid(Model.Printers, "");
}
and also you have to pass your model into partial view in
#Html.Partial("~/Views/Home/GridView.cshtml")
in second parameter. I guess this call should be
#Html.Partial("~/Views/Home/GridView.cshtml", Model)

Render multiple strongly typed partial views inside a view?

I have this Index.cshtml class:
#model ProOptInteractive.ViewModels.InvoicePageViewModel
#{
ViewBag.Title = "Index";
}
<div>#Html.Partial("ListServices", Model.Services)</div>
<div>#Html.Partial("ListProducts", Model.Products)</div>
<div>#Html.Partial("Invoice", Model.InvoiceViewModel)</div>
And this is my InvoicePageViewModel:
namespace ProOptInteractive.ViewModels
{
public class InvoicePageViewModel
{
public InvoiceViewModel InvoiceViewModel { get; set; }
public IEnumerable<Service> Services { get; set; }
public IEnumerable<Product> Products { get; set; }
}
}
The issue is that whenever I load this view, I get an error saying that I have passed the wrong model to the dictionary, and it doesn't specify which one of the partial views is causing the problem. Each partial has a different model type, one IEnumerable called Product, another IEnumerable called Service, and the Invoice partial view having a viewmodel called InvoiceViewModel.
Can anybody explain how to go about making this work? I'm a bit noob at Razor and MVC by the way.
UPDATE
I get this error message:
The model item passed into the dictionary is of type 'ProOptInteractive.ViewModels.InvoiceViewModel', but this dictionary requires a model item of type 'ProOptInteractive.ViewModels.InvoicePageViewModel'.
The error is because you have set your Invoice partial view to have a model of type InvoicePageViewModel, yet you are passing it a model of type InvoiceViewModel.
Either update your InvoiceViewModel property to be of type InvoicePageViewModel, or change the Invoice view to use a model of type InvoiceViewModel.
Error is in line <div>#Html.Partial("Invoice", Model.InvoiceViewModel)</div>
Your view Invoice is accepting model of type
InvoicePageViewModel and you are passing InvoiceViewModel
Change your code to <div>#Html.Partial("Invoice", Model)</div>
or modify your Invoice view to accept InvoiceViewModel as
#model ProOptInteractive.ViewModels.InvoiceViewModel
Invoice.cshtml likely starts with:
#model ProOptInteractive.ViewModels.InvoicePageViewModel
replace it with:
#model ProOptInteractive.ViewModels.InvoiceViewModel
You can pass model to your partial view like this:
#Html.Partial("Invoice", Model.InvoiceViewModel)
or something similar.
Model for main view:
class InvoicePageViewModel {
...
public InvoiceViewModel InvoiceViewModel { get; set; }
public IEnumerable<Service> Services { get; set; }
public IEnumerable<Product> Products { get; set; }
...
}
Then update your partial view to accept view model like this:
#model InvoiceViewModel
...
I discovered the error. It was lying in the controller method. When I called the Index view (which corresponds to the ActionResult Index method) I was returning the viewmodel InvoiceViewModel to the Index page, even though it was strongly typed to InvoicePageViewModel. I changed it to this, which works:
public ActionResult Index()
{
var invoice = InvoiceLogic.GetInvoice(this.HttpContext);
// Set up our ViewModel
var pageViewModel = new InvoicePageViewModel
{
Products = proent.Products.ToList(),
Services = proent.Services.ToList(),
InvoiceViewModel = new InvoiceViewModel
{
InvoiceItems = invoice.GetInvoiceItems(),
Clients = proent.Clients.ToList(),
InvoiceTotal = invoice.GetTotal()
}
};
// Return the view
return View(pageViewModel);
}
Thanks to everyone for all the help and suggestions.

Sending IEnumerable to a view that already contains a model

This is the view:
#model tgpwebged.Models.sistema_DocType
...
this model is an entity used with textBoxFor and others html helpers
This is the controller.
public ActionResult AdminSettingAddTipo()
{
IEnumerable<string> indices;
using (tgpwebgedEntities context = new tgpwebgedEntities())
{
var obj = from u in context.sistema_Indexes select u.idName;
indices = obj.ToList();
}
return PartialView(indices);
}
I have all I need here, I am using a model to create with the view so I am not allowed to send ´indices´ as a model because it´s not allowed to have 2 models in one view.
I don´t want to use ´Tupe´ now a parent view. I just want to know how is the best way to send my IEnumerable to the view.
I was thinking of ViewBag for the last option but I am avoiding ViewBag.
thanks
ViewBag is not a good choice. Create ViewModel using your list and your current Model:
public class YourViewModel
{
public sistema_DocType Type { get; set; }
public IEnumerable<string> Indices {get;set;}
}
Hope,it will help.
If you don't want to use ViewBag for whatever reason, you could create a Model specifically for the view that contains the info from the old model and the new indices you want. This is a common pattern in MVC development. You can even have the ViewModel be a Decorator for your current Model.
http://geekswithblogs.net/michelotti/archive/2009/10/25/asp.net-mvc-view-model-patterns.aspx
Use strongly defined where you can, apply this to a model and send that model:
Model
public class MyModel{
public List<sistema_Indexes> indecies {get;set;}
}
Controller
MyModel model = new MyModel();
model.indecies = context.sistema_Indexes.Select(u=> u.idName).ToList();

Asp.Net MVC layout and partial views

let's consider two views that use the same layout composed of:
A left column containing a "body" (which is filled differently by both views)
A right column that displays general information (passed via the model)
Instead of defining the right part twice, I wondered if I could create a PartialView to link directly from the layout page.
The problem is that the partial views implicitely inherit their models from the view that is being rendered. And since each view has its own model, I end up with a model type mismatch in the partial view.
From here I see two solutions:
I could insert the common part of the view model in the ViewBag. Unfortunately this means that each view that uses this layout has to implement this "convention" but nothing warns the developer about it at compile time...
I could use polymorphism to make each view model inherit from the same base class (edit: or interface) that the Partial Views uses. This would work up to a certain extend but would potentially exponentially increase in complexity as soon as I have a second partial view in the same layout.
So here are the questions:
Am I right with the assumptions above?
Do you see any other possibility?
Any return on experience on this?
Thanks a lot,
TB.
Use an Interface and implement it on the two models, this is exactly the kind of thing they're used for.
Here is an example of two different Views using two different Models that both implement an interface. This is subtyping instead of ad-hoc polymorphism.
public class ViewModelOne : IReusableView
{
public string Name { get; set; }
public string Something { get; set; }
public int ANumber { get; set; }
}
public class ViewModelTwo : IReusableView
{
public string Name { get; set; }
public string Thing { get; set; }
public string SomethingElse { get; set; }
public int ANumber2 { get; set; }
}
public interface IReusableView
{
string Name { get; }
}
So we have the really simple partial view here that is 'InnerPartialView':
#model TestIntegration.Models.IReusableView
<div>
#Model.Name
</div>
Which is used in the home and about pages of this example controller:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View(new ViewModelOne() { Name = "hello", Something="sdfsdfs", ANumber = 1 });
}
public ActionResult About()
{
return View(new ViewModelTwo() { Name = "hello 2", SomethingElse = "aaaddd", ANumber2 = 10, Thing="rand" });
}
}
The home view:
#model TestIntegration.Models.ViewModelOne
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
#Html.Partial("InnerPartialView")
</p>
The about view:
#model TestIntegration.Models.ViewModelTwo
#{
ViewBag.Title = "About Us";
}
<h2>About</h2>
<p>
Put content here.
#Html.Partial("InnerPartialView")
</p>
When you render the partial view, you can send it a model:
#Html.RenderPartial(MVC.Partials.Views.Sidebar, Model.SideBarModel);
So you could send down data as part of the parent model that is the model for the partial sidebar.
In partial views, models are of type dynamic so you don't need to know what type they are. However, you just need to make sure the model has the property you need. In other words you can use Model.MyProperty or Model.MyProperty as MyPropertyType when using Html.Partial.

How to Post Form from View strongly Typed to ViewModel?

I have a view model as such:
public class MyViewModel
{
public MyObject myObject{ get; set; }
public List<MyList> myList{ get; set; }
}
I have a view with a form strongly typed to MyViewModel
This view allows you to enter values for the properties of MyObject, as well as create a list of MyList objects. The List part works fine although I thought that would be the more difficult of the two.
Assuming MyObject has a property Description I create a textbox to enter the value as such:
#Html.EditorFor(x => x.myObject.Description);
The text box renders with an id of MyObject_Description...The problem is when I post this to my controller action, MyObject does not get bound at all(althought the list items do as they recieve the appropriate IDs of "MyViewModel_MyList[guid].myListValue")
What am I doing wrong here??
EDIT: more info
The first line of the view is:
#model MyApp.ViewModels.MyViewModel
And the Action method:
[HttpPost]
public ActionResult Create(MyViewModel myViewModel)
{
}
I am passing a new MyViewModel into the partial view to begin...
public ActionResult Create()
{
MyViewModel model = new MyViewModel();
return PartialView(model);
}
EDIT 2
Ok When I render my partial view that contains the forms I call :
#{Html.RenderAction("Create", "MyController");}
this is called from within a View of type #model IEnumerable<MyApp.Models.MyObject>
(this view displays a list of currently existing MyOjects, and at the bottom the partial is rendered to allow the user to add another MyObject to the DB)
If you are not already doing so, try creating a editor template (e.g., Views->Shared->EditorTemplates) called MyObject.cshtml. Move your partial view content to this view and then call
#Html.Editor("myObject").
from your parent partial view.
Change your ViewModel to have the Description directly
public class MyViewModel
{
public string Description { get; set; }
public List<MyList> myList{ get; set; }
}
then bind accordingly
#Html.EditorFor(x => x.Description);
I would expect the top line of your view to look something like this:
<%# Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<MyViewModel>" %>
This tells the view that the model it is supplied is of type MyViewModel (a la <T> style).
I don't think the out of the box model binding knows how to bind to complex objects. You're probably going to have to write up some sort of custom model binder.
I'm afraid it's not something I've done since MVC1 so I'm a bit hesitant to give you any sample code because the mechanism may well have changed completely since then. A quick google did turn up this article http://www.learnxpress.com/asp-net-mvc-hosting-6-tips-for-asp-net-mvc-model-binding-2.html and this article http://bradwilson.typepad.com/blog/2010/10/service-location-pt9-model-binders.html.
Edit: I've just seen this answer which might help Retrieving data from view, should I use model binder?

Categories