using ASP.NET MVC, I have a Model, to which I'm attaching attributes so that I can get use the MVC Model bound validation, but to doesn't this break the rule of MVC, where you are putting items that belong to the View, into the Model? I hope I don't come of as trying to be smart, I am however curious as to other's opinions.
public class Payments
{
[DataType(DataType.Text)]
[DisplayFormat(NullDisplayText="")]
[Display(Name="Payment Id")]
[Required(ErrorMessage="Required")]
public int PaymentId { get; set; } //todo: make this into a dropdown
[DataType(DataType.Text)]
[Display(Name="Bill Name")]
[Required(ErrorMessage = "Required")]
public string PaymentName { get; set; }
[DataType(DataType.Date)]
[Display(Name="Date to Post Payment")]
[Required(ErrorMessage = "Required")]
public DateTime PaymentDate { get; set; }
[DataType(DataType.Currency)]
[Range(0, 922337203685477.5807)]
[Required(ErrorMessage = "Required")]
public double PaymentAmount { get; set; }
}
MSDN Reference 1
MSDN Reference 2
Yes. Thats why you should use ViewModels.
You can, but don't have to, put those validation attributes in your model.
But it's better to use a ViewModel:
public class PaymentsViewModel
{
[DataType(DataType.Text)]
[DisplayFormat(NullDisplayText="")]
[Display(Name="Payment Id")]
[Required(ErrorMessage="Required")]
public int PaymentId { get; set; } //todo: make this into a dropdown
[DataType(DataType.Text)]
[Display(Name="Bill Name")]
[Required(ErrorMessage = "Required")]
public string PaymentName { get; set; }
[DataType(DataType.Date)]
[Display(Name="Date to Post Payment")]
[Required(ErrorMessage = "Required")]
public DateTime PaymentDate { get; set; }
[DataType(DataType.Currency)]
[Range(0, 922337203685477.5807)]
[Required(ErrorMessage = "Required")]
public double PaymentAmount { get; set; }
}
And in your View, instead of:
#model YourProject.Models.Payments
you use:
#model YourProject.Models.PaymentsViewModel
for validation.
Does it violate MVC in the strict sense, yeah, probably. Are there times when there's no harm in violating that? Sure. However, there are mechanisms to help you out and keep the concerns separate.
You can use your persisted domain objects in the views and validate against them, but when your view starts getting complicated, ViewModels become a necessity. For dead simple domain models or view-only views (not edits/creates), I'll sometimes fudge a bit and send them into the view as part of a composite object:
class MyViewModel
{
public MyDomainModel DomainObj;
public int OtherViewInfo;
}
However, for create and edit scenarios, ViewModels are far better. They allow you total control over the data being sent to the view and from the view.
If you are using EF 4.1 and CodeFirst, then yes, you'll end up with some duplication of attributes and properties between the domain and the ViewModel. This is unavoidable but gives you the flexibility of doing different validations specific to the view.
I've found it useful to have one extra layer of protection in the controller when actually saving the domain object in case I missed some validation in the view:
public class MyController : Controller
{
[HttpPost]
public ActionResult Edit(int id, MyViewModel model)
{
try
{
...Do stuff, check ModelState.IsValid...
_context.SaveChanges()
}
catch (DbEntityValidationException dbEx)
{
// Catch any validation errors on the domain that weren't duplicated
// in the viewmodel
ModelState.AddModelError("Form", dbEx);
}
return View(model);
}
}
The next question to ask is how you map between the domain model and the ViewModel. There are a number of strategies and tools out there - AutoMapper and ValueInjecter (yes, spelled wrong). Personally, I've been using ValueInjecter although if you setup a mapping layer, you can try both. I've found that neither will work in 100% of cases, or at least I could figure out how to do what I needed, but a mapping service makes it easy to define custom left-to-right maps.
Related
No code to show. I just want to understand something. I already do some MVC code (I have a model, I ask Visual Studio to create Controller and View).
Each view has only "ONE MODEL" associated. So, with Razor, I can show data from this model. I play with my code and I understand it up to now.
BUT ...
On the same view, HOW we can work with another MODEL ?
For me, a model is simply a class with properties, etc. My database has an equivalent "data table" for each model. I can manipulate it with Entity Framework ... no problem. But, I need to use DATA from different model (different table) in the SAME VIEW and Visual Studio does not give me permission to use another MODEL in the view.
What is the strategy ? (or maybe I don't understand something ...)
Thank you.
The strategy is to build a view model, a model built to be displayed, and represents the data that you need to use.
Example :
You have these classes, classes who are a representation of your database :
public class FootballTeam{
public string Name{get;set;}
public string Logo{get;set;}
}
public class FootballGame{
public Datetime Date {get;set;}
public string Competition {get;set;}
}
public class Referee{
public string Name{get;set;}
public int Experience {get;set;}
}
To display information about a match game, you can create a view model for this, class who can references some classes of your business model if necessary :
public class GameViewModel{
[DisplayName("Home team")]
public FootballTeam HomeTeam{get;set;}
[DisplayName("Referee")]
public Referee Referee{get;set;}
[DisplayName("Visitor team")]
public FootballTeam VisitorTeam {get;set;}
[DisplayName("Comments")]
public List<string> RedactionComments{get;set;}
}
And create a view who will consume this GameViewModel.
In general, when you create a new MVC project, your have a folder named "ViewModels" in your presentation layer who contains some classes like this one.
This method allows to separate your business model to your presentation model, which are 2 completely different things.
There are very good answers here : What is ViewModel in MVC?
You can update your model type of your razor view to any type you want. It will work as long as you are passing that type from your action method.
Simply open up the razor view and change the line where it says what type the model is.
#model Customer
Now you need to make sure that you are passing a Customer object from your action
public ActionResult Create()
{
return View( new Customer());
}
Also when you create a view, You do not need to necessarily select the Model type in the Dialog box. You can keep that empty and add it to the razor view as needed ( as shown above)
If you want to bring data from 2 different table, Create a new view model which has properties needed for the view and use that as your view's model type.
You should use ViewModal to Create a ViewModal that will be combination of two modals properties as per our need
ViewModel contains the fields which are represented in the strongly-typed view. It is used to pass data from controller to strongly-typed view with Own Defined Modals
Understand how to use View Modal in MVC From Link Below -
Understand View Modal In MVC
CODE THAT DEMONSTRATE HOW TO USE VIEWMODALS IN MVC
Product.cs
public class Product
{
public Product() { Id = Guid.NewGuid(); }
public Guid Id { get; set; }
public string ProductName { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
}
ProductCategory.cs
public class ProductCategory
{
public int Id { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
ProductViewModel.cs
public class ProductViewModel
{
public Guid Id { get; set; }
[Required(ErrorMessage = "required")]
public string ProductName { get; set; }
public int SelectedValue { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
[DisplayName("Product Category")]
public virtual ICollection<ProductCategory> ProductCategories { get; set; }
}
Obviously there are a number of ways to do this, but I thought I'd ask for a little feedback on benefits and drawbacks of the approaches.
First of all, the NerdDinner tutorial's Edit Action is in the form (say Form A):
[HttpPost]
public ActionResult Edit(int id, FormCollection collection) {
It seems to me that if you shape your ViewModels well to match your views, that the approach Form B:
[HttpPost]
public ActionResult Edit(MyViewModel mvm) {
just seems like a better, cleaner approach. I then just map the VM properties to the Model properties and save. However, if this ViewModel has other entities embedded in it that are initialized via the constructor (for example in the nerddinner tutorial), then this edit action fails if there is no default constructor and you'd have to use the first approach.
So, the first question is do you agree that generally Form B is usually better? Are there drawbacks?
Secondly, it seems then if Form B is used, the decorator type validation would need to be in the ViewModel. Are there advantages of embedding entities in ViewModels and keeping the validation at the entity level only?
This is a pretty general SO question.
the first question is do you agree that generally Form B is usually better?
The only time I do not use Form B is when I upload files. Otherwise, I don't believe anyone should ever need to use Form A. The reason I think people use Form A is a lack of understanding of the abilities of ASP.Net's version of MVC.
Secondly, it seems then if Form B is used, the decorator type validation would need to be in the ViewModel.
Sort of / it Depends. I'll give you an example:
public IValidateUserName
{
[Required]
string UserName { get; set; }
}
public UserModel
{
string UserName { get; set; }
}
[MetadataType(typeof(IValidateUserName))]
public UserValiationModel : UserModel
{
}
The validation decorator is in an interface. I'm using the MetadataType on a derived class to validate the derived type. I personally like this practice because it allows reusable validation and the MetadataType/Validation is NOT part of the ASP.NET core functionality, so it can be used outside of ASP.Net (MVC) application.
Are there advantages of embedding entities in ViewModels ..
Yes, I do my absolute best to never pass a basic model to the view. This is an example of what I don't do:
public class person { public Color FavoriteColor { get; set; } }
ActionResult Details()
{
Person model = new Person();
return this.View(model);
}
What happens when you want to pass more data to your view (for partials or layout data)? That information is not Person relevant most of the time so adding it to the Person model makes no sense. Instead, my models typically look like:
public class DetailsPersonViewModel()
{
public Person Person { get; set; }
}
public ActionResult Details()
{
DetailsPersonViewModel model = new DetailsPersonViewModel();
model.Person = new Person();
return this.View(model);
}
Now I can add required data the DetailsPersonViewModel that view needs beyond what a Person knows. For example, lets say this is going to display a for with all the colors for the Person to pick a favorite. All the possible colors aren't part of a person and shouldn't be part of the person Model, so I'd add them to the DetailPersonViewModel.
public class DetailsPersonViewModel()
{
public Person Person { get; set; }
public IEnumerable<Color> Colors { get; set; }
}
.. and keeping the validation at the entity level only?
System.ComponentModel.DataAnnotations weren't designed to validate properties' properties, so doing something like:
public class DetailsPersonViewModel()
{
[Required(property="FavoriteColor")]
public Person Person { get; set; }
}
Doesn't exist and doesn't make sense. Why ViewModel shouldn't contain the validation for the entity that needs validation.
this edit action fails if there is no default constructor and you'd have to use the first approach.
Correct, but why would a ViewModel or a Entity in a ViewModel not have a parameterless constructor? Sounds like a bad design and even if there is some requirement for this, it's easily solved by ModelBinding. Here's an example:
// Lets say that this person class requires
// a Guid for a constructor for some reason
public class Person
{
public Person(Guid id){ }
public FirstName { get; set; }
}
public class PersonEditViewModel
{
public Person Person { get; set; }
}
public ActionResult Edit()
{
PersonEditViewModel model = new PersonEditViewModel();
model.Person = new Person(guidFromSomeWhere);
return this.View(PersonEditViewModel);
}
//View
#Html.EditFor(m => m.Person.FirstName)
//Generated Html
<input type="Text" name="Person.FirstName" />
Now we have a form that a user can enter a new first name. How do we get back the values in this constructor? Simple, the ModelBinder does NOT care what model it is binding to, it just binds HTTP values to matching class properties.
[MetadataType(typeof(IPersonValidation))]
public class UpdatePerson
{
public FirstName { get; set; }
}
public class PersonUpdateViewModel
{
public UpdatePerson Person { get; set; }
}
[HttpPost]
public ActionResult Edit(PersonUpdateViewModel model)
{
// the model contains a .Person with a .FirstName of the input Text box
// the ModelBinder is simply populating the parameter with the values
// pass via Query, Forms, etc
// Validate Model
// AutoMap it or or whatever
// return a view
}
I have not yet taken a look at the NerDinner project, however, I generally try to avoid having a ViewModel in the POST of an action and instead, only have the elements of the "form" submitted.
For instance, if the ViewModel has a Dictionary that is used in some kind of dropdown, the entire dropdown will not be submitted, only the selected value.
My general approach is:
[HttpGet]
public ActionResult Edit(int id)
{
var form = _service.GetForm(id);
var pageViewModel = BuildViewModel(form);
return View(pageViewModel);
}
[HttpPost]
public ActionResult Edit(int id, MyCustomForm form)
{
var isSuccess = _service.ProcessForm(id);
if(isSuccess){
//redirect
}
//There was an error. Show the form again, but preserve the input values
var pageViewModel = BuildViewModel(form);
return View(pageViewModel);
}
private MyViewModel BuildViewModel(MyCustomForm form)
{
var viewModel = new MyViewModel();
viewModel.Form = form;
viewModel.StateList = _service.GetStateList();
return viewModel;
}
I'm having a problem handling multiple values from a strongly typed Listbox.
I have an Event Class that can have multiple Technology classes.
Here's my simplified Event class:
public class Event
{
public int ID { get; set; }
public IEnumerable<Technology> Technologies { get; set; }
}
I was using this
public List<Technology> Technologies { get; set; }
and changed to
public IEnumerable<Technology> Technologies { get; set; }
but still got the same error.
Here's the Technology class, it's a really simple one
public class Technology
{
public int ID { get; set; }
public String Description{ get; set; }
}
here's my simplified Controller
public ActionResult Create()
{
var TechnologyQry = from d in db.Technology
orderby d.Description
select new { d.ID, d.Description };
ViewBag.eventTechnology = new MultiSelectList(TechnologyQry ,"ID","Description");
return View();
}
and here's the view portion that renders the ListBox
#Html.ListBoxFor(model => model.Technologies, (MultiSelectList)ViewBag.eventTechnology)
and thit's the error I'm getting
The ViewData item that has the key 'Technologies' is of type 'System.Collections.Generic.List`1[[stuff.Models.Technology, stuff, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' but must be of type 'IEnumerable'./
Sorry, English is not my main language.
I'd appreciate any help I can get.
Thank you.
What Model have do you have defined in the View #model ...?
Besides that, the Create method isn't returning something to be used for/as the model.
If you want the listbox to be called "Technologies" in the name attribute, you could instead use:
#Html.ListBox("Technologies", (MultiSelectList)ViewBag.eventTechnology)
I am trying to develop a simple MVC 2 timesheet application for my small business.
I have a sort of mock model for now until I have a database in place, just to make things simpler while I develop the functionality. It consists of the following:
public class CustomersRepository
{
public CustomersRepository()
{
Customers = new List<Customer>();
}
public List<Customer> Customers { get; set; }
}
public class Task
{
public Task()
{
Customer = new Customer();
TimeSegments = new List<TimeSegment>();
}
public override string ToString()
{
return Name;
}
public string Name { get; set; }
public Customer Customer { get; set; }
public List<TimeSegment> TimeSegments { get; set; }
}
public class TimeSegment
{
public string Id { get; set; }
public string Date { get; set; }
public int Hours { get; set; }
}
public class Customer
{
//To show the name in the combobox instead of the object name.
public override string ToString()
{
return Name;
}
public Customer()
{
Tasks = new List<Task>();
}
public List<Task> Tasks { get; set; }
public string Name { get; set; }
}
I initialize the repository in the controller, and pass the "model" to the view:
CustomersRepository model = new CustomersRepository();
public ActionResult Index()
{
InitializeRepository();
return View(model);
}
Now, in the view I populate a dropdownlist with the customers:
<div>
<%:Html.DropDownListFor(m => m.Customers, new SelectList(Model.Customers), new {#id="customerDropDownList"}) %>
</div>
But then I need to populate a second dropdownlist (taskDropDownList for the tasks associated with a particular customer) based on the selection the user chooses in the customer dropdownlist.
But how do I do this exactly? I have seen examples with jQuery, but I'm not sure how to apply them to this situation. Also, the examples seem to just populate the lists with string values. I need to be able to access the objects with all their properties. Because the next thing I need to do is to be able to populate the TimeSegments list of the selected task with values from input fields (i.e. the hours worked for particular dates). And for that to be saved to the "model" (eventually to the database) in the controller, how do I get it there, unless the objects are all part of the same model bound to the View?
I'm on rather thin ice with this since I still find the connection between the View and the Controller hard to handle, compared with e.g. Windows development, where these things are rather easy to do. So I would really appreciate a good step by step example if anyone would be so kind as to provide that!
I found the answer here:
http://www.pieterg.com/post/2010/04/12/Cascading-DropDownList-with-ASPNET-MVC-and-JQuery.aspx
It needed some tweaks, and I got help here from CGK. See this post:
Cascading dropdownlist with mvc and jQuery not working
I am new to MVC. I am developing an web application in asp.net MVC. I have a form through which users can get registered, after registration user redirected to ProfileView.aspx page. till here everything is fine.
Now I want to show the articles headings posted by that user right under his profile.
Right now I m using following code:
public ActionResult ProfileView(int id)
{
Profile profile = profileRepository.GetProfileByID(id);
var articles = articlesRepository.FindArticlesByUserID(id).ToList();
return View("ProfileView", profile);
}
Thanks for helping in advance
Baljit Grewal
I can think of two options:
Use the ViewData dictionary to store the articles.
public ActionResult ProfileView(int id)
{
Profile profile = profileRepository.GetProfileByID(id);
var articles = articlesRepository.FindArticlesByUserID(id).ToList();
ViewData["Articles"] = articles;
return View("ProfileView", profile);
}
Or, if you want to avoid using ViewData, create a ViewModel. A ViewModel is kind of a data transport object. You could create a ProfileViewModel class like this:
public class ProfileViewModel
{
public Profile Profile{ get; set; }
public IList<Article> Articles { get; set; }
}
or just include the Profile properties you are using in your view, this will make binding easier but you'll have to copy values from your Model to your ViewModel in your controller.:
public class ProfileViewModel
{
public int Id{ get; set; }
public string Name { get; set; }
.......
public IList<Article> Articles { get; set; }
}
If you go for this last option take a look at AutoMapper (an object to object mapper).
you will want your page to inherit from ViewPage and then you can use your model inside the .aspx markup like