A `ViewModel` for each page (`Create.cshtml` and `Edit.cshtml`)? - asp.net-mvc-3

Questions
There are actually two related questions:
Should I create a ViewModel for each page?
If you do not have problems in creating a single ViewModel class for the two pages (Create.cshtml and Edit.cshtml) how can I validate the ViewModel in different ways (depending on the page that is being used)
Source
ViewModel
public class ProjectViewModel
{
public string Name { get; set; }
public string Url { get; set; }
public string Description { get; set; }
}
Edit.cshtml
#using BindSolution.ViewModel.Project
#model ProjectViewModel
#{
ViewBag.Title = Model.Name;
}
#Html.EditorForModel()
Create.cshtml
#using BindSolution.ViewModel.Project
#model ProjectViewModel
#{
ViewBag.Title = "New Project";
}
#Html.EditorForModel()
ProjectValidator.cs
public class ProjectValidator : AbstractValidator<ProjectViewModel>
{
private readonly IProjectService _projectService;
public ProjectValidator(IProjectService projectService)
{
_projectService = projectService;
RuleFor(p => p.Name)
.NotEmpty().WithMessage("required field")
/*The validation should be made only if the page is Create.cshtml. That is, if you are creating a new project.*/
.When(p => p.??) //Problem Here!!
.Must(n => !_projectService.Exist(n)).WithMessage("name already exists");
RuleFor(p => p.Url)
.NotEmpty().WithMessage("required field");
}
}
Note that if the user is editing an existing project, validation of the property name should not be done again.
ProjectController.cs > Edit method
[HttpPost]
public ActionResult Edit(Guid projectID, ProjectViewModel model)
{
var project = _projectService.Repository.Get(projectID);
if (ModelState.IsValid && TryUpdateModel(project))
{
_projectService.Repository.Attach(project);
if (_projectImageWrap.Create(project) && _projectService.Repository.Save() > 0)
return AjaxRedirect("Index");
}
return View(model);
}
Notes
If I create a ViewModel for each page, there is a duplication of code since pages have the same properties.
Add a property on the ViewModel indicating what page it is being displayed does not solve my problem as to instantiate the ViewModel, I use AutoMapper.
To validate the data, I use FluentValidator.
Thank you all for your help!

My understanding is that there isn't a 1:1 correlation between ViewModels and Views. Oftentimes you will have a View that will not require a ViewModel to go alongside with it.
You will want to create a ViewModel if and only if you need a Model absolutely paralleled and tailored to a specific View. This will not be the case 100% of the time.

When the functionality / use case /validation is different between the pages I use different models. If its the exact same besides the presence of an ID or something similar I use the same model, and its also possible to just use the same view if the differences are pretty minor.
Since your validation is different, if I were doing it I would create two different models so that I could use the out of the box DataAnnotations, with your validation though it may not be required. You could also on the edit model have a readonly property for name since its not editable any longer.

For me the same object must have the same validation on every time, in main to ensure the consistence of the object, independently if it was created or edited.
i think that you should create only one validation, and edit your "exists" method to pass to verify if it is a new object or the current object in repository.

Personally, I don't have a problem with 2 view models, especially if (as Paul Tyng suggested) you use a base class for the fields that are common to edit and create scenarios.
However, if you really only want a single view model then you would either need to:
add a flag to the view model and use the When() method in your validator. Note though that this will not generate the appropriate client-side only validation
define a second validator and invoke the appropriate one from the controller (i.e. instead of the "automatic" validation)

Provide another view Edit.cshtml which will allow the user to edit the data for a selected item.
Create another view Query.cshtml which based on the ItemName will allow the users to query the Inventory table.
Perform the calculation for the total profit (numbersold times (saleprice-purchasecost). Display the total profit.
(BONUS) Create another view Sell.cshtml that will indicate the sale of an item. Adding one to NumberSold and subtract one from NumberInventory for the selected record.

Related

Razor partial view prefix field names

When using Razor to render a form for a complex Model that has sub-models, we'd usually use Partial Views to render the sub-models.
Simple example Model:
public class BlogPost
{
public string Content { get; set; }
public List<Comment> Comments { get; set; }
}
public class Comment
{
public string Content { get; set; }
}
BlogPost.cshtml:
#model BlogPost
#Html.TextAreaFor(x => x.Content)
#for (int i = 0; i < Model.Comments.Count; i++)
{
#Html.Partial('Comment', Model.Comments[i])
}
Comment.cshtml:
#model Comment
#Html.TextAreaFor(x => x.Content)
Now for the issue:
Say we want to send the values of all fields to a controller action that takes BlogPost as a parameter. The fields are going to be posted back to the controller like so:
Content=The+content+of+the+BlogPost&Content=The+first+Comment&Content=The+second+Comment
But what we need to have MVC map them correctly to the BlogPost view model, we need this naming convention:
Content=The+content+of+the+BlogPost&Comments[0].Content=The+first+Comment&Comments[1].Content=The+second+Comment
How can this be achieved in a clean way? We can only think of two ways which both seem to compromise the design:
Either we pass the BlogPost as a model to the partial view such that we can define the text area like so: #Html.TextAreaFor(x => x.Comments[i].Content). But this means we couple the partial view for comments to the parent view model - you could think of a scenario where the same partial view should be used in a different context, which is not possible if the partial view depends on the parent view model. Futhermore, the i would have to be passed to the partial view somehow.
Or we fall back to explicitely defining the name of every single field with strings: #Html.TextArea(ViewBag.Prefix + ".Content").
Is there any way to tell the partial view to apply a certain prefix to all field names?
chiccodoro,
if you create and EditorFor template of type Comment, mvc will handle all of this beautifully for you. However, that will only work well in a scenario where the rows are already present in the DB. Exampel from SO:
Submiting Parent & Children in razor
If you need to create new rows on the fly, then you'll have to use a little bit of trickery to allow the fields to operate as required. I used an article from steven sandersons website which allows you to add collection items at runtime and still retains unobtrusive validation etc. see this SO question and related article ref:
Editing a Variable Length List, ASP.NET MVC 3 Style with Table

How to Post Partial View Data?

Any input much appreciated :)
I want to know one thing whether I can post multiple partial views data in MVC?(means i want to update partial views data to DATABASE)
Here is the Example:
Model:-
public class PassengerViewModel
{
public List<PassengerModel> Passengers { get; set; }
public ContactModel Contact { get; set; }
}
Controller:-
[RequiredAuthentication]
public ActionResult Passenger()
{
var passengrViewMdl = new PassengerViewModel()
{
Contact = new ContactModel(),
Passengers = psngrService.LoadPassengers(Convert.ToInt32(Session["LPORefNO"]))
};
return View(passengrViewMdl);
}
[HttpPost]
public ActionResult Passenger(PassengerViewModel passengerViewModel)
{
Here i want to update Passengers & Contact information
}
View:-
#model QR.LPO.Core.Models.PassengerViewModel
#{
ViewBag.Title = "Add Passengers";
}
#using (Html.BeginForm())
{
#Html.Partial("_Passenger", Model.Passengers);
#Html.Partial("_PassengerContact", Model.Contact);
<input type="submit" value="Submit" />
}
Thanks.
Yes, indeed you can, but, controller usually works only with one model per request, so either your model should have declared within it properties of both partial submodels, or submodels themselves.
This is possible due to HTML specifications, all data on form, which has submit buttom is send to submit action url.
This will almost work as you have it - there's nothing inherent to partials that would prevent this, in the end the html that's output is all that's important.
The problem with your code is that presumably the model of your _Passenger view is of type Passengers and the model of your _PassangerContact view is of type Contact. What this means is that if you standard HtmlHelper extensions (like Html.Textbox(...) or Html.TextboxFor(...) the fields they generate will not have full names like Contact.Name, but instead just names relative to their model, like Name. This will cause modelbinding to fail in your post action.
You can solve this in a number of ways.
Simply use the same model type (PassengerViewModel) in your sub-views, and write code like #Html.TextboxFor(m => m.Contact.Name).
Instead of using Html.Partial, use Html.EditorFor(...). This passes the proper prefix information into the child view so the field names are generated properly.
Explicitly set the prefix yourself
Like this:
#{
var childViewData = new ViewDataDictionary(this.ViewData);
childView.TemplateInfo.HtmlFieldPrefix = "Contact";
}
#Html.Partial("_PassengerContact", Model.Contact, childViewData)
You could also look at creating a Html.PartialFor overload yourself, as described in this stackoverflow question: ASP.NET MVC partial views: input name prefixes

How do you exclude properties from binding when calling UpdateModel()?

I have a view model sent to the edit action of my controller. The ViewModel contains references to EntityObjects. (yea i'm fine with it and don't need to want to duplicate all the entities properties in the viewmodel).
I instantiate the view model and then call UpdateModel. I get an error that a property is "null" which is fine since it is a related model. I am trying to exclude the property from being bound during model binding. On debugging it I see in the entity where the model binder is trying to set the value of the property to null.
Here is my edit action:
var model = new SimplifiedCompanyViewModel(id);
var excludeProperties = new string[] {
"Entity.RetainedEarningsAccount.AccountNo"
,"Property.DiscountEarnedAccount.ExpenseCodeValue"
,"Entity.EntityAlternate.EntityID"
,"Property.BankAccount.BankAccountID"
,"Entity.PLSummaryAccount.AccountNo"
,"Property.RefundBank.BankAccountID"
,"Company.Transmitter.TCC"
};
try
{
UpdateModel<SimplifiedCompanyViewModel>(model, String.Empty, null, excludeProperties);
if (ModelState.IsValid)
{
//db.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View(model);
}
I have looked at a few other issues about specifying a "prefix" but I don't think that is the issue since I am telling it to bind to the viewmodel instance not just the entity object.
Am I excluding the properties correctly? Strange thing is is only seems to happen on this item. I suspect it may be an issue with the fact that there is actually no refund bank related to my entity. But I have other related items that don't exist and don't see the same issue.
More info... since I'm told me model isn't designed well.
The Company is related to a BankAccount. The Company view shows the currently related BankAccount.BankAccountId and there is a hidden field with the BankAccount.Key. I use jQueryUI autocomplete feature to provide a dropdown of bank account displaying the BankAccount.BankAccountId and when one is selected the jQuery code changes the hidden field to have the correct Key value. So, when this is posted I don't want the current bankaccounts BankAccountID modified, hence I want it to skip binding that field.
If I exclude BankAccountId in the model then on the BankAccount edit view the user would never be able to change the BankAccountId since it won't be bound. I'm not sure how this indicates a poor model design.
Use the Exclude property of the Bind attribute:
[Bind(Exclude="Id,SomeOtherProperty")]
public class SimplifiedCompanyViewModel
{
public int Id { get; set; }
// ...
}
This is part of the System.Web.Mvc namespace. It takes a comma-separated list of property names to exclude when binding.
Also you should consider using TryUpdateModel instead of UpdateModel. You can also just have the default model binder figure it out by passing it as an argument to the constructor:
public ActionResult Create([Bind(Exclude="Id")]SimplifiedCompanyViewModel model)
{
// ...
}
A very simple solution that I figured out.
try
{
UpdateModel<SimplifiedCompanyViewModel>(model, String.Empty, null, excludeProperties);
ModelState.Remove("Entity.RetainedEarningsAccount.AccountNo");
ModelState.Remove("Property.DiscountEarnedAccount.ExpenseCodeValue");
ModelState.Remove("Entity.EntityAlternate.EntityID");
ModelState.Remove("Property.BankAccount.BankAccountID");
ModelState.Remove("Entity.PLSummaryAccount.AccountNo");
ModelState.Remove("Property.RefundBank.BankAccountID");
ModelState.Remove("ompany.Transmitter.TCC");
if (ModelState.IsValid)
{
//db.SaveChanges();
}
return RedirectToAction("Index");
}
catch
{
return View(model);
}
Another option here is simply don't include this attribute in your view and it won't be bound. Yes - you are still open to model injection then if someone creates it on the page but it is another alternative. The default templates in MVC will create your EditorFor, etc as separate items so you can just remove them. This prevents you from using a single line view editor with EditorForModel, but the templates don't generate it that way for you anyways.
EDIT (adding above comment)
DRY generally applies to logic, not to view models. One view = one view model. Use automapper to easily map between them. Jimmy Bogard has a great attribute for this that makes it almost automatic - ie you create the view model, load up your Customer entity for example, and return it in the action method. The AutpMap attribute will then convert it to a ViewModel. See lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models
Try the Exclude attribute.
I admit that I haven't ever used it.
[Exclude]
public Entity Name {get; set;}

Using one Partial View Multiple times on the same Parent View

I am using MVC3 razor. I have a scenario where I have to use a partial view multiple times on the same parent view. The problem I am having is that when the Parent View gets rendered, it generates same names and ids of the input controls within those partial views. Since my partial views are binded to different models, when the view is posted back on "Save" it crashes. Any idea how can i make the control id/names unique, probably some how prefix them ?
Awaiting
Nabeel
Personally I prefer using editor templates, as they take care of this. For example you could have the following view model:
public class MyViewModel
{
public ChildViewModel Child1 { get; set; }
public ChildViewModel Child2 { get; set; }
}
public class ChildViewModel
{
public string Foo { get; set; }
}
and the following controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
Child1 = new ChildViewModel(),
Child2 = new ChildViewModel(),
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and inside the Index.cshtml view:
#model MyViewModel
#using (Html.BeginForm())
{
<h3>Child1</h3>
#Html.EditorFor(x => x.Child1)
<h3>Child2</h3>
#Html.EditorFor(x => x.Child2)
<input type="submit" value="OK" />
}
and the last part is the editor template (~/Views/Home/EditorTemplates/ChildViewModel.cshtml):
#model ChildViewModel
#Html.LabelFor(x => x.Foo)
#Html.EditorFor(x => x.Foo)
Using the EditorFor you can include the template for different properties of your main view model and correct names/ids will be generated. In addition to this you will get your view model properly populated in the POST action.
There is an alternative:
Add a prefix to the PartialView
Bind the model, removing the prefix
For 1, set the prefix in your View:
ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "prefix";
For 2, you can recover the data with UpdateModel, like this:
UpdateModel(producto, "prefix");
This is not very advisable because your action doesn't receive the data as a parameter, but updates the model later. This has several inconvenients: 1) it's not clear what your action needs by looking at its signature 2) it's not easy to provide the input to the action for unit testing it 3) the action is vulnerable to overflow parameters (parameters provided by the user that shouldn't be there and are mapped to the model).
However, for 2 there is an alternative: register a custom Model Binder that allows you to do remove the prefix. And the custom Model Binder must know about it.
A good solution is in this SO Q&A: How to handle MVC model binding prefix with same form repeated for each row of a collection? But it has a little flaw: if you add a hidden field with the name "__prefix" in a partial view, and you render it several times as a partial view, this ID will be repeated for several different elements in the page, which is not allowed, and can provoke some trouble. And one of the most important reasons to provide a prefix is precisely rendering the same "edit" view as partial views for several instances of an entity. I.e. this would happen in a page like gmail, where you can edit several emails at once.
There are several possible solutions for this problem.
One of them is providing the prefix as a query string or routedata value, and not as a form field, which avoid the Id conflicts, and can be found by the model binder. (It can always have the same name).
Another solution is to use a hidden field, with a fixed pattern, but which is different for every rendered view. The prefix could follow this pattern for uniqueness: "PP$ActionControllerId" like "PP$EditProduct23", which is unique for each rendered view, and can be easily found between the request parameters looking for one that starts with "PP$".
And a final solution would be to create the prefix only in the view, and not providing it in any kind of request parameter. The Model binder would have to look for the prefix examining the names of the request parameters, until it finds one whose prefix follow the pattern.
Of course, the custom ModelBinder must be adapted to work tieh the chosen convention.

MVC3 Razor model binder and inherited collections

I hope I'm not missing something incredibly obvious here but is there any reason why model binder is always having trouble binding a view model that inherits from a collection?
Lets say I want to show a paged list and display a combo box and add button above it (dealing with simple lists). Involved classes would look like:
public class PagedList<T> : List<T>
{
public int TotalCount { get; set; }
}
And then a view model that looks like:
public class MyViewModel : PagedList<ConcreteModel>
{
public IEnumerable<ChildModel> List { get; set; }
public int? SelectedChildModelId { get; set; }
}
So in the view (Razor):
#model MyViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(model => model.SelectedChildModelId, new SelectList(Model.List, "ChildModelId", "DisplayName"))
}
And the controller HttpPost action:
public ActionResult(MyViewModel viewModel)
{
...
}
The above will cause viewModel in ActionResult to be null. Is there a logical explanation for it? From what I can tell it's specific only to view models that inherit from collections.
I know I can get around it with custom binder but the properties involved are primitive types and there isn't even any generics or inheritance.
I've reworked the view models to have the collection inherited type as properties and that fixes the issue. However I'm still scratching my head over why the binder breaks down on it. Any constructive thoughts appreciated.
To answer my own question: All my models that have anything to do with collections no longer inherit from generic list or similar but instead have a property of the required collection type. This works much better because when rendering you can use
#Html.EditorFor(m => m.CollectionProperty)
And create a custom editor under Views/Shared/EditorTemplates for contained type. It also works beautifully with model binder since all individual items from collection get a index and the binder is able to auto bind it when submitted.
Lesson learned: if you plan to use models in views, don't inherit from collection types.
Sometimes model binding to a collection works better if the data in the form post is formatted differently.
I use a plugin called postify.
http://www.nickriggs.com/posts/post-complex-javascript-objects-to-asp-net-mvc-controllers/

Resources