I have a simple form that posts a ViewModel to an Action method. Before saving the information the ModelState is checked with a standard if(ModelState.IsValid). Then a new object is created and saved. Great, it works.
Recently another dev went in and created a new view with my original ViewModel. He also added a new [Required] property to the ViewModel to make his logic work.
By doing that his logic broke my initial logic. Because my initial view doesn't use his new Required property so ModelState.IsValid check now fails and my code doesn't run.
What is the best approach to take here ? Though I don't want to but should I get rid of ModelState.IsValid check on my Post actions or can I somehow flag his new property to Not be required when used in my original views or when being posted in my action method ?
Thank you in advance.
You can use attribute [Bind(Exclude="")] in your action method like below. Then, when you submit the form, model binder will ignore that property even it is required.
[HttpPost]
public ActionResult Index([Bind(Exclude = "AdditionalProperty")]YourModel model)
{
//
}
You can derive the model from IValidatableObject, and then perform your own custom validations using
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
}
How do I use IValidatableObject?
Edtied to add: If it were me, it would seem to make more sense to have him create an inherited model from your model, even if it only has 1 property in it. This would keep the native MVC validations working correctly with minimal effort on your part.
you have two choice(to the best of my knowledge!), first you can unbind the that required property while posting it to the action:
[HttpPost]
public ActionResult Create([Bind(Exclude = "RequiredProperty")]MyViewModel myViewModel)
{
if(ModelState.IsValid)
{
//
}
}
but you can solve this issue for your application by mapping the ViewModel to View in your get action, and send it to the view. try this great article
Related
When I put a breakpoint on a getter of a property in my view model, asp.net core model binding is reading my property value. This is before the view model is used in the actual view. Is there a reason it is doing this? Model binding should be for setting the properties on your view model from the value provider, not for reading them from your view model. Is there a way to prevent this?
Edit: Since there is a vote to close this question due to not providing steps to easily reproduce, here they are. Create the following controller in an asp.net core project:
public class TestController : Controller
{
public IActionResult Test(TestViewModel model)
{
return View(model);
}
public class TestViewModel
{
public string TestProperty
{
get
{
return "";
}
set
{
return;
}
}
}
}
If you put your break point in the getter, and a break point in the test controller action, you will see that the getter is accessed before it actually enters the controller action to be consumed by the view. It doesn't seem like properties in your view model should be read at this point. Just look for ideas on why this is happening, and if it is possible (or a good idea) to prevent this behavior. Thanks!
I'm not sure what version of MVC you're using, but in MVC version 5.2.3, the DefaultModelBinder calls the model class getters before setting the request values on each property during the binding process prior to the controller method being called. I could not figure out why it does this, but I was able to change that behavior by implementing a custom model binder based on the original DefaultModelBinder source code that removes the calls to the getters.
See a more detailed explanation and my full solution here: https://stackoverflow.com/a/54431404/10987278.
In your comments you mention you already have a custom model binder, so you might be able to just add the BindProperty(...) override from my solution to get the behavior you're after.
Is it a better idea to have a single ViewModel per view or one per controller action?
Example:
public ProjectController : Controller
{
public ActionResult Edit(int id)
{
var project = ...;
return View(new ProjectEditViewModel(project));
}
[HttpPost]
public ActionResult Edit(ProjectEditViewModel model)
{
}
**OR**
[HttpPost]
public ActionResult Edit(Project model)
{
}
[HttpPost]
public ActionResult Edit(ProjectEditPostViewModel model)
{
}
}
Here are the three options, which is best?
Use the same ViewModel for my POST/GET actions.
Use a ViewModel for my GET action and my domain model for my POST action.
Use a different ViewModel for GET and a different ViewModel for POST.
Using a different view model for the GET and POST actions is the best and most flexible design. But using the same view model for the GET and POST actions also works in 90% of the cases and it is fine a good design. So if using the same view model works in your scenario don't hesitate to reuse it like this.
In the case where different view models are used for the GET and POST actions there is still some relation between those classes: inheritance or composition.
The correct answer
Neither. There's no silver bullet and shouldn't be.
The correct answer is therefore: use as many view models as your user interface process demands. That's regardless of views or controller actions.
Sometimes an action demands a view, other a view. But don't follow some strict guidelines that would hinder your development. View models will come naturally as you develop your application. And should. Otherwise you may end up with unreasonable views that are based on some guideline you've set in stone.
This is actually a similar answer as #DarinDimitrov's, but with a direct conclusion.
Use different model to receive input parameters in Post action (I don't even call it ViewModel in that case) than to pass output parameters to the view.
That way you can customize exactly what input parameters do you accept.
I follow this approach for basic forms:
One view model for the GET
One view model for the POST
The GET model inherits the POST model.
I will often pass a domain object to the GET model's constructor, and do 2 things with it:
Populate the POST model properties with data from the domain object.
Encapsulate the domain object as a local variable in the GET model. I use this for displaying some (read-only) data from the domain object. Saves a bit of effort. Some people will tell you not to do this.
I am building an ASP.NET MVC application, and I am trying to find a way where I can collect data from the user in a custom view model, try and set these values to one or more of my entities, then based on validation logic on those entities, collect error messages if any and get them back to the view. I am new to MVC and web design in general, it is therefore quite possible that I am making major conceptual errors, but I have tried to research as far as I could.
I realize that this is more work than having the view be strongly typed to the entity, where it would then be easy to have the validation errors display, as in this tutorial. However, I don't want to do this for security and because there are some places where I want to have values collected from a single view model to be set in multiple different entities.
I also realize that I could set validation rules on the view model itself, rather then on the entity, but this seems like poor architecture, as I would have to define them redundantly in different view models, and I would then be less sure whether I had prevented bad values from being persisted to the database.
My plan is therefore to have the validation rules be set on the entity itself and to have the view model as a dumb container. Then, in a different location in the application, I would apply the values from the view model to my entity(ies) in accordance my business logic. At this point, I would like my validation logic to be called. If the data is invalid, I plan on setting the error string in the custom attribute on the view model to the error from the validation logic on the entity. I am thinking it would go something like this:
public class CustomViewModel()
{
[SomeCustomValidation()] //Has a place for an error string and a boolean IsValid
String Property { get; set; }
}
public class BusinessLogic()
{
CustomViewModel TestForValidity(CustomViewModel viewModel)
{
MyEntity.Property = viewModel.Property;
// if(MyEntity.IsValid)? catch SomeSortOfException?
// collect error message, put it in the attribute on the view model, set IsValid to false
}
}
public class MyEntity()
{
[MoreCustomValidation()]
public String Property { get; set; }
}
I therefore have three questions:
When I try to pass data that does not satisfy my validation rules, will some sort of error or exception be thrown? Is there some indication I can use or collect when I try to pass in invalid data?
If there is some error or exception thrown, how can I collect the error message so I can assign it to my view model?
Most importantly, am I going about this all wrong? Can attributes not be modified at runtime, for example to include a new error message or to change IsValid to false? I know that I can use reflection to access the attributes. If I can modify them, how would I do so?
Thank you in advance for your help. I apologize if I misunderstand something big.
It seems you might be over-complicating things a bit. I think what you want to do is prevent the model binder from binding to properties that it should not be able to, as well as retaining the ability to check ModelState.IsValid when properties on your model do not meet the requirements of their validation attributes.
IMO the best way to accomplish this is through the use of what I call "strongly-typed binding filters". First define an interface with only the properties that you want the model binder to be allowed to bind on your model.
public interface INewBlogPost
{
string Title { get; set; }
string Body { get; set; }
}
Then ensure your entity inherits from the binding filter interface.
public class BlogPost : INewBlogPost
{
...
}
Next, modify your action method to create a new entity and manually invoke the model binder whilst typing it to the interface you just defined.
public ActionMethod NewBlogPost()
{
BlogPost newBlogPost = new BlogPost();
TryUpdateModel<INewBlogPost>(newBlogPost);
if (ModelState.IsValid)
{
...
}
}
Because you passed in a type when invoking the model binder via TryUpdateModel you explicitly told the model binder what type to bind to. This means that the model binder will only have access to the properties listed in the interface. Now when you pass a model into the method to be bound it will have to be of type INewBlogPost. Because your entity inherits from your binding filter interface, an instance of it will satisfy this requirement. The model binder will happily bind to the properties on the interface completely oblivious of any other properties your model object may have.
See this blog post for more information.
Aside
It is sometimes easy to run into action method ambiguity when you have two action methods with the same name; one for POST and one for GET like this:
[HttpGet]
public ActionResult NewBlogPost()
{
return View();
}
[HttpPost]
public ActionResult NewBlogPost()
{
BlogPost newBlogPost = new BlogPost();
TryUpdateModel<INewBlogPost>(newBlogPost);
if (ModelState.IsValid) { ... }
}
An easy way to fix that is to modify your POST action method to look like this:
[HttpPost]
public ActionResult NewBlogPost(FormCollection formCollection)
{
BlogPost newBlogPost = new BlogPost();
TryUpdateModel<INewBlogPost>(newBlogPost, formCollection);
if (ModelState.IsValid) { ... }
}
The MVC model binder knows how to bind the request form collection to an argument of type FormCollection so it will populate this just fine. Because your POST action now accepts an argument, it is no longer ambiguous with your GET method. You can pass this formCollection into TryUpdateModel to be used as the binding source if you wish, but you don't have to as it will default to the request form collection anyway. But since you are passing it in you may as well use it :)
if i start off on a Detail page:
http:\\www.mysite.com\App\Detail
i have a controller action called Update which normally will call redirectToAction back to the detail page. but i have an error that is caught in validation and i need to return before redirect (to avoid losing all of my ModelState). Here is my controller code:
public override ActionResult Update(Application entity)
{
base.Update(entity);
if (!ModelState.IsValid)
{
return View("Detail", GetAppViewModel(entity.Id));
}
return RedirectToAction("Detail", new { id = entity.Id })
but now I see the view with the validation error messages (as i am using HTML.ValidationSummary() ) but the url looks like this:
http:\\www.mysite.com\App\Update
is there anyway i can avoid the URL from changing without some hack of putting modelstate into some temp variables? Is there a best practice here as the only examples i have seen have been putting ModelState in some tempdata between calling redirectToAction.
As of ASP.NET MVC 2, there isn't any such API call that maintains the URL of the original action method when return View() is called from another action method.
Therefore as such, the recommended solution and a generally accepted convention in ASP.NET MVC is to have a corresponding, similarly named action method that only accepts a HTTP POST verb. So in your case, having another action method named Detail like so should solve your problem of having a different URL when validation fails.
[HttpPost]
public ActionResult Detail(Application entity)
{
base.Update(entity);
if (ModelState.IsValid)
{
//Save the entity here
}
return View("Detail", new { id = entity.Id });
}
This solution is in line with ASP.NET MVC best practices and also avoids having to fiddle around with modestate and tempdate.
In addition, if you haven't explored this option already then client side validation in asp.net mvc might also provide for some solution with regards to your URL problem. I emphasize some since this approach won't work when javascript is disabled on the browser.
So, the best solution would be have an action method named Detail but accepting only HTTP POST verb.
The problem here is actually caused by your implementation. This doesn't answer your question, but it describes where you've gone wrong in the first place.
If you want a page that is used to update or edit an item, the URL should reflect this. For example.
You visit http:\www.mysite.com\App\Detail and it displays some information about something. That is what the URL describes it is going to do. In your controller, the Detail() method would return the Detail.aspx view.
To edit an item, you would visit http:\www.mysite.com\App\Edit and change the information you wish to update, the form would post back to the same URL - you can handle this in the controller with these methods:
[HttpGet]
public ActionResult Edit() {
MyModel model = new MyModel();
...
return View(model);
}
[HttpPost]
public ActionResult Edit(MyModel model) {
...
if (ModelState.IsValid) {
// Save and redirect
...
return RedirectToAction("Detail");
}
return View(model);
}
If you ever find yourself doing this...
return View("SomeView", model);
You are making your own life harder (as well as breaking the principles behind URLs).
If you want to re-use part of a view, make it a partial view and render it inside of the view that is named after the method on the controller.
I apologise that this potentially isn't very helpful, but you are falling into an MVC anti-pattern trap by displaying the same view from a differently named method.
As #Malcolm sais, best practice is to put ModelState in TempData, but don't do it manually! If you'd do this manually in every controller action where it's relevant, you would introduce immense amounts of repeated code, and increase the maintenance cost vastly.
Instead, implement a couple of attributes that do the job for you. Kazi Manzur has an approach (scroll down to the end of the post) that has been widely spread, and Evan Nagle shows an implementation with tests that is essentially the same as Kazi's, but with different names. Since he also provides unit tests that make sure the attributes work, implementing them in your code will mean little or no maintenance cost. The only thing you'll have to keep track of is that the controller actions are decorated with the appropriate attributes, which can also be tested.
When you have the attributes in place, your controller might look something like this (I deliberately simplified, because I don't know the class you inherit from):
[HttpPost, PassState]
public ActionResult Update(EntityType entity)
{
// Only update if the model is valid
if (ModelState.IsValid) {
base.Update(entity);
}
// Always redirect to Detail view.
// Any errors will be passed along automagically, thanks to the attribute.
return RedirectToAction("Detail", new { id = entity.Id });
}
[HttpGet, GetState]
public ActionResult Detail(int id)
{
// Get stuff from the database and show the view
// The model state, if there is any, will be imported by the attribute.
}
You mention that you feel putting ModelState in TempData feels like a "hack" - why? I agree with you that doing it with repeated code in every single controller action seems hacky, but that's not what we're doing here. In fact, this is exactly what TempData is for. And I don't think the above code looks hacky... do you?
Although there are solutions to this problem that might appear simpler, such as just renaming the action method to preserve the URL, I would strongly advise against that approach. It does solve this problem, but introduces a couple of others - for example, you'll still have no protection against double form submission, and you'll have pretty confusing action names (where a call to Detail actually changes stuff on the server).
The best practice you ask for is actually what you explained not to do: putting modelstate into tempdata. Tempdata is meant for it, that's why I would not call it a hack.
If this is to much repetitive code you could use the attribute modeldatatotempdata of MVCContrib. But the store is still TempData.
I have a service which has a method that's called when a certain controller method is triggered.
My service returns a custom result object PlacementResult in which I want to communicate errors that may have happened (validation) back to the controller method.
Should PlacementResult have a ModelState or a ModelStateDictionary to communicate errors back to the controller (and finally view)? How would I string this together?
Finally, how do I get the ModelState/ModelStateDictionary (whichever you tell me I should choose) back into the view (highlighting the appropriate text box, show the error message etc.)?
Thank you !
This is a good link that shows how a service can perform validation and communicate the result back to the controller:
http://www.asp.net/mvc/tutorials/validating-with-a-service-layer-cs (fixed link)
No, you do not want to add a ModelStateDictionary to your result type. There is already a ModelStateDictionary on the Controller (in the ModelState property). It is not appropriate for results to set the controller's model state. That should be done during binding or within the controller action itself. Use a custom model binder if you need to.
Your choose one can see the model state errors by examining the controller's ViewData.ModelState property.
Your PlacementResult should return a dictionary object or a List which you should merge with the model state at the beginning of each action.
If you step through you will notice the controllers model state dictionary contains all your input fields, their values and the errors associated with them. You want to merge the PlacementResult errors into the model state dictionary at the appropriate keys. This is how the view engine knows which fields to flag as invalid.
ModelState.Merge(PlacementResult);
if(ModelState.IsValid)
{
...
}
I don't know what your PlacementResult looks like, see if you can possibly utilize this in your view:
ModelState.AddModelError(ErroredProperty, ErrorMessage);
Make sure you return the object that failed back to the view
return View(myObjectInstance);
Based on SoC I think you have to return errors from your services and merge them in your ModelState if needed.
But our objective is maintain decoupling and also to use ModelState.Merge() method. it isn't?
There is an concrete implementation that could help
You can pass the Controller to your method, since the Controller class contains the ModelState property. Once in your method you can do the following:
private PlacementResult BuildResult(Controller controller)
{
controller.ModelState.AddModelError(propertyName, errorMessage);
}
In your action...
BuildResult(this);
if(ModelState.IsValid) {...