I've just started to get into using ViewModels. Can you guys check out this code to see if I'm following best practice? Is there anything out of the ordinary? Would you do the validation differently?
Sorry if code is lengthy (there's so many parts to it). I've tried to make it as easy to understand as possible.
Thanks!
Model
public class CustomerModel
{
[Required(ErrorMessage="Primer nombre!")]
public string FirstName { get; set; }
[Required(ErrorMessage="Segundo nombre!")]
public string LastName { get; set; }
[Required(ErrorMessage="Edad")]
public int? Age { get; set; }
public string State { get; set; }
public string CountryID { get; set; }
[Required(ErrorMessage="Phone Number")]
public string PhoneNumber { get; set; }
}
ViewModel
public class CustomerViewModel
{
public CustomerModel Customer { get; set; }
public string Phone1a { get; set; }
public string Phone1b { get; set; }
public string Phone1c { get; set; }
}
Controller
public ActionResult Index()
{
CustomerViewModel Customer = new CustomerViewModel()
{
Customer = new CustomerModel(),
};
return View(Customer);
}
[HttpPost]
public ActionResult Index(CustomerViewModel c)
{
//ModelState.Add("Customer.PhoneNumber", ModelState["Phone1a"]);
// Let's manually bind the phone number fields to the PhoneNumber properties in
// Customer object.
c.Customer.PhoneNumber = c.Phone1a + c.Phone1b + c.Phone1c;
// Let's check that it's not empty and that it's a valid phone number (logic not listed here)
if (!String.IsNullOrEmpty(c.Customer.PhoneNumber))
{
// Let's remove the fact that there was an error!
ModelState["Customer.PhoneNumber"].Errors.Clear();
} // Else keep the error there.
if (ModelState.IsValid)
{
Response.Write("<H1 style'background-color:white;color:black'>VALIDATED</H1>");
}
return View("Index", c);
}
}
View
#model MVVM1.Models.CustomerViewModel
#using (Html.BeginForm("Index", "Detail"))
{
<table border="1" cellpadding="1" cellspacing="1">
<tr>
<td>#Html.LabelFor(m => m.Customer.FirstName)</td>
<td>
#Html.TextBoxFor(m => m.Customer.FirstName)
#Html.ValidationMessageFor(m => m.Customer.FirstName)
</td>
</tr>
<tr>
<td>#Html.LabelFor(m => m.Customer.LastName)</td>
<td>
#Html.TextBoxFor(m => m.Customer.LastName)
#Html.ValidationMessageFor(m => m.Customer.LastName)
</td>
</tr>
<tr>
<td>#Html.LabelFor(m => m.Customer.Age)</td>
<td>
#Html.TextBoxFor(m => m.Customer.Age)
#Html.ValidationMessageFor(m => m.Customer.Age)
</td>
</tr>
<tr>
<td>#Html.LabelFor(m => m.Customer.PhoneNumber)</td>
<td width="350">
#Html.TextBoxFor(m => m.Phone1a, new { size="4", maxlength="3" })
#Html.TextBoxFor(m => m.Phone1b)
#Html.TextBoxFor(m => m.Phone1c)
<div>
#Html.ValidationMessageFor(m => m.Customer.PhoneNumber)
</div>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" value="Submit" /></td>
</tr>
</table>
}
One thing that jumps out at me is this:
if (ModelState.IsValid)
{
Response.Write("<H1 style'background-color:white;color:black'>VALIDATED</H1>");
}
return View("Index", c);
Remember that view models are good for passing data to your controller AND back to your model. I recommend you add an IsValid property to your view model and then setting that to true instead of calling Response.Write. Then simply add this to the top of your partial view:
#if (Model.IsValid)
{
<H1 style'background-color:white;color:black'>VALIDATED</H1>
}
You can also get to ModelState in your view but some would argue that isn't a best practice. However, if you don't want to add a property to your model for something you can just see in your view you can just do this:
#if (ViewData.ModelState.IsValid)
Another nitpicky thing is that MVC validation attributes are typically used for validation on the UI. This validation can be reused in other areas but in some cases is sub-optimal. Also, you may not always be able to modify your domain models. Therefore, to keep all of my UI validation in one place I usually wrap my domain models in my view models so you get something like this:
public class CustomerViewModel
{
public CustomerModel Customer { get; set; }
[Required(ErrorMessage="Primer nombre!")]
public string FirstName
{
get { return Customer.FirstName; }
set { Customer.FirstName = value; }
}
...
This may seem redundant and isn't always worth the effort but it is a good practice to consider when using Entity Framework domain models or other classes which are difficult or impossible to modify.
I'm just getting the hang of MVC myself, but I researched this same topic yesterday and came to the conclusion that one should not directly include a model object in the ViewModel. So my understanding is that it would be a bad practice to include your CustomerModel directly in the CustomerViewModel.
Instead, you want to list out each of the properties from CustomerModel that you want to include in your ViewModel. Then you either want to manually map the data from CustomerModel to the CustomerViewModel or use a tool like AutoMapper which does it automatically with a line of code like this inside of your action method:
public ViewResult Example()
{
// Populate/retrieve yourCustomer here
Customer yourCustomer = new CustomerModel();
var model = Mapper.Map<CustomerModel, CustomerViewModel>(yourCustomer);
return View(model);
}
In this case, Mapper.Map will return a CustomerViewModel that you can pass to your View.
You will also need to include the following in your Application_Start method:
Mapper.CreateMap<CustomerModel, CustomerViewModel>();
In general I found AutoMapper pretty easy to get to work. It's automatic when the field names match, if they don't or you have a nested Object, you can specify those mappings in the CreateMap line. So if your CustomerModel uses an Address object instead of individual properties, you would do this:
Mapper.CreateMap<CustomerModel, CustomerViewModel>()
.ForMember(dest => dest.StreetAddress, opt => opt.MapFrom(src => src.Address.Street));
Please anyone correct me if I'm wrong as I'm just getting my head around MVC as well.
I would say your ViewModel implementation is pretty standard. You are using the ViewModel to act as the intermediate object between your View, and your Domain Model. Which is good practice.
The only thing I'd be weary about is how you handle Model errors, and also your ViewModel should have some attributes. For instance, you might want to use the RegularExpressionAttribute Class:
public class CustomerViewModel
{
public CustomerModel Customer { get; set; }
[RegularExpression(#"^\d{3}$")]
public string Phone1a { get; set; }
[RegularExpression(#"^\d{3}$")]
public string Phone1b { get; set; }
[RegularExpression(#"^\d{4}$")]
public string Phone1c { get; set; }
}
Related
I have the following model :
public class Business
{
[Key]
public int BusinessId { get; set; }
public string Name {get;set;}
....
.....
....
[Display(Name = "Owner")]
public int OwnerId { get; set; }
[ForeignKey("OwnerId")]
public ApplicationUser Owner { get; set; }
}
In my controller I have the following function :
public async Task<IActionResult> Index()
{
return View(await _context.Business.ToListAsync());
}
and my view :
#model IEnumerable<ServiceProviderApp.Models.Business>
#{
ViewData["Title"] = "Business Engine";
}
.....
......
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
......
<td>
#Html.DisplayFor(modelItem => item.Owner.Name)
</td>
<td>
As you can understand, the Owner inside the Business is an Object. Therefore, when I try to access it in the view (item.Owner.Name) I don't get anything (null). I can change the query on the controller to return a record that is joined but then the model on the view won't be business any more.
I searched online but didn't find a good and clear explanation. Not sure why the EF doesn't bind the nested objects automatically.
One option that I saw on YouTube but I think that it sucks to do:
Create a new model with all the columns after the join
In my controller, run the join and pass it to the view
In the view accept the new model as parameter
Link - https://www.youtube.com/watch?v=v1B3R-kb9CU
Solution : You need to use the include func in the controller :
public async Task<IActionResult> Index()
{
var business = _context.Business.Include("Owner");
return View(await business.ToListAsync());
}
Thanks to DanielShabi for the help.
I have a List containing string objects that should be displayed in the view. But i have problems getting the output correctly. In the list there are words like Computer, Screen, Mouse. Now it´s displayed like ComputerScreenMouse and want it displayed like
Computer
Screen
Mouse
I´ve checked the object during run-time and it´s adding the strings correctly like
[0] Computer
[1] Screen
[2] Mouse
This is the code that I´m using
#Html.DisplayFor(m => Model.MyParts, new { htmlAttributes = new { #class = "form-control " } })
I've tried a couple different stuff with foeach loops but nothing seems to work. If i use the #Html.Raw and put in a br tag it prints out the list like this:
ComputerScreenMouse
ComputerScreenMouse
ComputerScreenMouse
#foreach (var item in Model.MyParts)
{
#Html.Raw(Html.DisplayFor(m => Model.MyParts, new { htmlAttributes = new { #class = "form-control " } }) + "</br>")
}
The model looks like this. In the view I´m using #modelGlossary_mvc.Models.Parts
public class DoTestModel
{
public string Stuff{ get; set; }
public string OtherStuff { get; set; }
public List<Parts> Parts { get; set; }
}
public class Parts
{
public List<string> MyParts{ get; set; }
public List<string> SpareParts{ get; set; }
public int NumberOfParts { get; set; }
}
Display(For) / Editor(For) really aren't ideal for List<string> properties. They should be reserved for specialized display and editing templates, or properties that have the [Display] attribute.
Your model property MyParts is a simple list of strings.
public List<string> MyParts{ get; set; }
Keep it simple, output each string (e.g. in an un-ordered list):
<ul>
#foreach(var part in Model.MyParts)
{
<li>#part</li>
}
</ul>
Ok, but what if the MyParts property is a list of PartModel objects? This is where the MVC DisplayFor / EditorFor shines.
PartModel
public class PartModel
{
public string Name { get; set; }
public int Quantity { get; set; }
}
View Model
// your view model
public class Parts
{
// now a list of PartModels not strings
public List<PartModel> MyParts{ get; set; }
public List<string> SpareParts{ get; set; }
public int NumberOfParts { get; set; }
}
Razor View
#model Parts
#Html.DisplayFor(m => m.MyParts)
Display Template for PartModel
#model PartModel
<div>
<span>#Html.DisplayFor(m => m.Name)</span>
<span>#Html.DisplayFor(m => m.Quantity)</span>
</div>
This file can be located in couple places. It's up to you based on how much you need to share it throughout the project:
\Views\Shared\DisplayTemplates\PartModel.cshtml
\Views\\DisplayTemplates\PartModel.cshtml
Depending on how you are wrapping your output in HTML will depend on the outcome. Are you using this in a form? as i generally only use form-control for fields.
Since your output is not HTML you need to try this if you need the form-control.
#foreach (var item in Model.MyParts)
{
#Html.DisplayFor(modelItem => item, new { htmlAttributes = new { #class = "form-control " } })
<br />
}
Without form-control to just display the list:
#foreach (var item in Model.MyParts)
{
#Html.DisplayFor(modelItem => item )
<br />
}
And depending how you have your HTML wrapper you may or may not need the <br />
I'm using entity framework and jquery chosen plugin to get multiselected dropdownlist values for Skills property. I've struggled to make the chosen plugin work in my view, and figured it out, but now I face another problem of passing those multiselected values (ex: Skills such as Java, c#, javascript) into controller and save into my Employee table.
public IEnumerable<string> Skills { get; set; }
Above code is currently on top of my head to save multiple values, but not sure how to properly use it. Thinking about multiple ways of doing it, but I definitely need guidance.
I have a model that looks like:
public class Employee
{
public string Name { get; set; }
public string Skills { get; set; }
}
and my controller:
public ActionResult Create([Bind(Include = "Name,Skills")] Employee employee)
{
if (ModelState.IsValid)
{
db.Employees.Add(employee);
db.SaveChanges();
return View("Success");
}
return View(employee);
}
View:
#using (Html.BeginForm())
{
#Html.LabelFor(model => model.Name)
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
#Html.LabelFor(model => model.Skills)
#Html.ListBoxFor(model => model.Skills, ViewBag.skillList as MultiSelectList,
new { #class = "chzn-select", id="skills", data_placeholder = "Choose Skills..." })
#Html.ValidationMessageFor(model => model.Skills)
<input type="submit" value="Create" class="btn btn-default" />
}
Another approach I am thinking is to create a Skill table that Employee table can have navigation property. An employee can have any number of skills, so the Skill navigation property is a collection. But honestly have little knowledge about this and need guidance for this too. For example:
public class Employee
{
public string Name { get; set; }
public int SkillID { get; set; }
public virtual ICollection<Skill> Skills { get; set; }
}
If any of these approaches don't make sense, I'd appreciate it if you can tell me why and how I can properly use it. Thanks!
I used chosen to create a filter based on multiselects, and the data model uses arrays, so in your case it would be
public string[] Skills { get; set; }
I am trying to use two models in one view, but from what I understand my program just don't see any objects within models.
Here is my code.
Models:
public class Album
{
[Key]
public int ThreadId { get; set; }
public int GenreId { get; set; }
public string Title { get; set; }
public string ThreadByUser { get; set; }
public string ThreadCreationDate { get; set; }
public string ThreadContent { get; set; }
public Genre Genre { get; set; }
public List<Posts> Posty { get; set; }
}
public class Posts
{
[Key]
public int PostId { get; set; }
public int ThreadId { get; set; }
public string PostTitle { get; set; }
public string PostContent { get; set; }
public string PostDate { get; set; }
public string PosterName { get; set; }
public Album Album { get; set; }
}
public class ModelMix
{
public IEnumerable<Posts> PostsObject { get; set; }
public IEnumerable<Album> ThreadsObject { get; set; }
}
Index controller code:
public ActionResult Index(int id)
{
ViewBag.ThreadId = id;
var posts = db.Posts.Include(p => p.Album).ToList();
var albums = db.Albums.Include(a => a.Genre).ToList();
var mixmodel = new ModelMix
{
PostsObject = posts,
ThreadsObject = albums
};
return View(mixmodel);
}
View code:
#model MvcMusicStore.Models.ModelMix
<h2>Index</h2>
#Html.DisplayNameFor(model => model.PostsObject.PostContent)
And when I try to execute my program I am getting this error:
CS1061: The "
System.Collections.Generic.IEnumerable 'does not contain a definition
of" PostContent "not found method of expanding" PostContent ", which
takes a first argument of type' System.Collections.Generic.IEnumerable
"
How I can make it work as intended? There are a lot of questions like mine on the internet but I couldn't find any matching my case.
Looping over models can be a little confusing to begin with in MVC, only because the templated helpers (i.e. Html.DisplayFor and Html.EditorFor) can be provided templates which the helper will automatically invoke for every element in a collection. That means if you're new to MVC, and you don't realise a DisplayTemplate or an EditorTemplate has not been provided for the collection already, it looks as though a simple:
#Html.DisplayFor(m => m.SomePropertyThatHoldsACollection)
is all you need. So if you've seen something like that already, that might be why you made the assumption it would work. However, let's assume for a moment that a template has not been provided. You have two options.
Firstly, and most simply, would be to use foreach over the collection:
#foreach (var post in Model.PostsObject)
{
#Html.DisplayFor(m => post.PostTitle)
// display other properties
}
You could also use a for loop, but with IEnumerable<T>, there is no indexer, so this won't work:
#for (int i = 0; i < Model.PostsObject.Count(); i++)
{
// This generates a compile-time error because
// the index post[i] does not exist.
// This syntax would work for a List<T> though.
#Html.DisplayFor(m => post[i].PostTitle)
// display other properties
}
If you did want to use the for loop still, you can use it like so:
#for (int i = 0; i < Model.PostsObject.Count(); i++)
{
// This works correctly
#Html.DisplayFor(m => post.ElementAt(i).PostTitle)
// display other properties
}
So use whichever you prefer. However, at some point it would be a good idea to look into providing templates for your types. (Note: Although this article was written for MVC 2, the advice still applies.) They allow you to remove looping logic from your views, keeping them cleaner. When combined with Html.DisplayFor, or Html.EditorFor, they will also generate correct element naming for model binding (which is great). They also allow you to reuse presentation for a type.
One final comment I'd make is that the naming of your properties is a little verbose:
public class ModelMix
{
public IEnumerable<Posts> PostsObject { get; set; }
public IEnumerable<Album> ThreadsObject { get; set; }
}
We already know they're objects, so there's no need to add that on the end. This is more readable:
public class ModelMix
{
public IEnumerable<Posts> Posts { get; set; }
public IEnumerable<Album> Threads { get; set; }
}
You need to iterate them like this:
#model MvcMusicStore.Models.ModelMix
<h2>Index</h2>
#for(var i=0; i<model.PostsObject.Count(); i++)
{
#Html.DisplayNameFor(model => model.PostsObject[i].PostContent)
}
And also it's better to save IList instead of IEnumerable, as it will have Count property, instead of using Count() method
Typically you'll need to iterate through each item if you're passing in any type of IEnumerable<>. Since you built a semi-complex model, you'll want to display foreach item in each list. Here is an example based from the ASP.NET MVC tutorials that I think would help you a bit:
#model IEnumerable<ContosoUniversity.Models.Course>
#{
ViewBag.Title = "Courses";
}
<h2>Courses</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.CourseID)
</th>
<th>
#Html.DisplayNameFor(model => model.Title)
</th>
<th>
#Html.DisplayNameFor(model => model.Credits)
</th>
<th>
Department
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
#Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
#Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.CourseID }) |
#Html.ActionLink("Details", "Details", new { id=item.CourseID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.CourseID })
</td>
</tr>
}
</table>
Generally most people use an ICollection for their lists, where an example might be in an object:
public virtual ICollection<Post> Posts { get; set; }
Source:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
I would recommend starting at the beginning as it'll help you understand why you need to do this:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc
I have my view models :
public class POReceiptViewModel
{
public virtual int PONumber { get; set; }
public virtual string VendorCode { get; set; }
public virtual IList<POReceiptItemViewModel> POReceiptItems { get; set; }
public POReceiptViewModel()
{
POReceiptItems = new List<POReceiptItemViewModel>();
}
}
public class POReceiptItemViewModel
{
public virtual string ItemCode { get; set; }
public virtual string ItemDesription { get; set; }
public virtual decimal OrderedQuantity { get; set; }
public virtual decimal ReceivedQuantity { get; set; }
public virtual DateTime ReceivedDate { get; set; }
public POReceiptItemViewModel()
{
ReceivedDate = DateTime.Now;
}
}
Then my controller has two actions, one get and one post:
public ActionResult CreatePOReceipt(int poNumber)
{
PurchaseOrder po = PurchasingService.GetPurchaseOrder(poNumber);
POReceiptViewModel poReceiptViewModel = ModelBuilder.POToPOReceiptViewModel(po);
return View("CreatePOReceipt", poReceiptViewModel);
}
[HttpPost]
public ActionResult CreatePOReceipt(POReceiptViewModel poReceiptViewModel)
{
// Here the problem goes. The items in the poReceiptViewModel.POReceiptItems has lost. the count became zero.
return View("Index");
}
And in my View, I can display the model properly and by using #Html.HiddenFor<> I can persist view model data as I wanted to. But not on the List<> navigation property.
#model POReceiptViewModel
#using (Html.BeginForm())
{
<fieldset>
<legend>Purchase Order</legend>
<label>For PO # :</label>
#Html.HiddenFor(m => m.PONumber)
#Html.DisplayTextFor(m => m.PONumber)
<label>Vendor Code :</label>
#Html.HiddenFor(m => m.VendorCode)
#Html.DisplayTextFor(m => m.VendorCode)
</fieldset>
<fieldset>
<legend>Received Items</legend>
<table class="tbl" id="tbl">
<thead>
<tr>
<th>Item Code</th><th>Item Description</th><th>OrderedQuantity</th><th>Received Quantity</th><th>Received Date</th>
</tr>
</thead>
<tbody>
#Html.HiddenFor(m => m.POReceiptItems) // I'm not really sure if this is valid
#if (Model.POReceiptItems.Count > 0)
{
foreach (var item in Model.POReceiptItems)
{
<tr>
<td>#Html.DisplayTextFor(i => item.ItemCode)</td>#Html.HiddenFor(i => item.ItemCode)
<td>#Html.DisplayTextFor(i => item.ItemDesription)</td>#Html.HiddenFor(i => item.ItemDesription)
<td>#Html.DisplayTextFor(i => item.OrderedQuantity)</td>#Html.HiddenFor(i => item.OrderedQuantity)
<td>#Html.TextBoxFor(i => item.ReceivedQuantity)</td>
<td>#Html.TextBoxFor(i => item.ReceivedDate)</td>
</tr>
}
}
</tbody>
</table>
</fieldset>
<input type="submit" name="Received" value="Received" />
}
PROBLEM:
POReceiptItems lost when the form submitted. As much as possible I don't want to use TempData["POReceiptItems"] = Model.POReceiptItems but even if I use it, the value entered into ReceivedQuantity and ReceivedDate are not save into the TempData.
Thanks in advance!
try
#for (int i = 0; i < Model.POReceiptItems.Count(); i++)
{
<tr>
<td>#Html.DisplayTextFor(m => m.POReceiptItems[i].ItemCode)</td>#Html.HiddenFor(m => m.POReceiptItems[i].ItemCode)
<td>#Html.DisplayTextFor(m => m.POReceiptItems[i].ItemDesription)</td>#Html.HiddenFor(m => m.POReceiptItems.ItemDesription) <td>#Html.DisplayTextFor(m => m.POReceiptItems[i].OrderedQuantity)</td>#Html.HiddenFor(m => m.POReceiptItems[i].OrderedQuantity)
<td>#Html.TextBoxFor(m => m.POReceiptItems[i].ReceivedQuantity)</td>
<td>#Html.TextBoxFor(m => m.POReceiptItems[i].ReceivedDate)</td>
</tr>
}
also read this blog post to understand how model binding to a list works
You lose your list because MVC don't handle the List the way you think.
You should use BeginCollectionItem look at this post
I had a similar problem, the "List" attribute returned without values(count = 0), I tried different ways and answers and nither works.
Then I tried by myself and now it is working, this is my solution:
I send an object with some normal attributes and a "List", after that I used the normal attributes and my "list" in a For.
In my controller (Post ActionResult), in the parameters section I added two parameters, my original object and my "List" as second parameter and it works!!!
I hope this helps you and others with similar problems.