Has anyone found a trick for handling multiple forms on a Razor Page?
My page has two forms, each with a corresponding model that is decorated with a BindProperty attribute.
[BindProperty]
public TripDetailsUpdateDto UpdateTrip { get; set; }
[BindProperty]
public TripNoteUpdateDto UpdateNote { get; set; }
The problem is that, although either one works fine on its own, having both of them causes ModelState.IsValid to return false. Both models are combined and when one model is submitted, the properties of the other model haven't been set.
Surely I'm not the first to struggle with this. Is there a way to deal with this case without writing manual code to remove the unused items from ModelState?
So, as suggested, the problem is that every property of every model decorated with the [BindProperty] is combined into the ModelState.
To resolve the issue, first remove all [BindProperty] attributes.
Then bind to your values with a parameter:
public async Task<IActionResult> OnPostUpdateNoteAsync(int noteId, TripNoteUpdateDto updateNote)
{
// ...
}
Notes:
You can still have the original model members (in my case, UpdateTrip and UpdateNote). You can still reference them from your markup. This allows your markup to consider validation attributes, and also lets you specify default values.
If your markup references your original model members, your model argument must have the same name in order to match.
Related
Problem Description
My problem is similar to this question but instead of applying Data Annotations to the property Name via reflection (handled by ModelMetadata.DisplayName) I am applying them to the value (not handled by ModelMetadata).
Detailed Description
In the context of an ASP.NET MVC program.
Suppose I have a Model class
public class Model
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string NickName { get; set; }
public string Address { get; set; }
public int Phone { get; set; }
[Display(Name = "Start Date")]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime StartDate { get; set }
[Display(Name = "End Date")]
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime EndDate { get; set }
}
Then suppose this Model is used in at least 5 different views where the value of every property must be displayed in full. (Sometimes for every instance, other times for a handful, other times for one).
I can manually list each Property access within it's own
<td>#Html.DisplayFor(item.<Property>)</td>
for every view.
But that won't help me if later on the definition for Model expands to include new Properties (like Description and Relation and Reliability). Then I'd need to manually update every occurrence of the complete Model listing.
I can use reflection to iterate over a list of PropertyInfo's and save having to manually list each property by using
<td>#property.GetValue(item)</td>
But DisplayFor(x) does not support an expression as complex as x=>property.GetValue(item), and this means I lose the Data Annotations that format my DateTime as
01/01/1990
instead of
01-Jan-90 12:00:00 AM
and would likely also result in the loss of all types of annotation including validation.
Problem Solutions
So far I have considered (and in some cases attempted) the following solutions:
[Failed] Manually craft an Expression which emulates the functionality of #property.GetValue(item)
[Edit]
[Failed] Pass DisplayFor a MethodInfo object representing the Property Accessor DisplayFor(x => property.GetGetMethod()) as well as .Invokeing it on x.
[/Edit]
Retrieve the value manually as normal, and
execute a method on it to manually retrieve and implement Annotation Data on it prior to insertion in the view element as this question suggests, or
Re-implement the DisplayFor handling of Data Annotations on an as-needed basis in a Display Template View and apply that directly to the value via DisplayFor as this question suggested
Refactor the Model class to contain only a list(SortedList?) of 'Prop' instances, where 'Prop' is a class representing a Property with a Name and Value element.
This last solution would turn the broken
#Html.DisplayFor(m=>property.GetValue(item)
into a theoretically working
#Html.DisplayFor(m=>item.Properties[i].Value)
Which aside from the slightly unintuitive need for getting a property called Name (Properties["Name"]) by (.Value), seems the most workable solution, at the cost of Model clarity.
[Edit]
Most recently I have created a Utility method which retrieves the DisplayFormatAttribute from the PropertyInfo and returns either the DisplayFormatString or the default of "{0}" if a format string was not annotated. I have then used it to create a collection of preformatted property values within a ViewModel.
This seems for now, to be the most elegant way I know of to decouple the View from the Model as much as possible while still retrieving the necessary data from it.
[/Edit]
The Question
This is at the moment, purely a learning exercise, but I would like to know...
Is it possible to succeed where I have failed and both have my Reflection cake and eat the Data Annotations too? Or must I seek an alternative solution?
If I must seek an alternative solution, are there routes I have missed, or am I at least on the right track?
Maybe something similar to:
#foreach (var property in Model.GetType().GetProperties())
{
<li>#property.GetValue(Model, null)</li>
}
Great Success
Revisiting my original attempt to manually craft the expression dynamically, I discovered this article which did precisely what I wanted to do, and using mostly Microsoft provided code as well!
Though the Microsoft code was difficult to find (the link in the article is broken and the example slightly outdated for the code I did find), I was able to use it to good effect to implement my own DisplayFor extension method.
Unfortunately, due to my model being a list rather than a single instance, I still needed to create a partial view to pass an instance to, so that I could access the properties via Model from within the generated expression.
My View code now looks like this:
#foreach (var thing in Model.CollectionOfThings)
{
<tr>
#foreach (var prop in typeof(Thing).GetProperties())
{
<td>
#{
Html.RenderPartial("~/Views/Shared/_DisplayForReflectedProperty.cshtml",
new Tuple<Thing, PropertyInfo>(thing, prop));
}
</td>
}
}
With _DisplayForReflectedProperty as simple as
#using WebApplication1.Models
#using System.Reflection
#using WebApplication1.Extensions
#model Tuple<Thing, PropertyInfo>
#Html.DisplayFor("Item1."+Model.Item2.Name)
And the only difference between my DisplayFor extension and the one in the article is the null object parameter in this function call (plus the obvious conversions from EditorFor to DisplayFor):
var lambda = System.Linq.Dynamic.DynamicExpression.ParseLambda(typeof(TModel),
null, expression);
Using this template I can now generate arbitrary (from code perspective, specific from business rule perspective) subsets of a model's properties to display in any manner I wish, without having to custom tailor my views to each particular subset, while retaining all the benefits of using the 'For' Helpers!
One of the key features of a project I'm working on is the ability for the user to configure Forms (as in "Forms" to fill-up) based on a pool of pre-existing field types (well known types, for instance "user name", "date of birth" etc. but also "generic types" like "string", "DateTime" etc.).
We used to have a static ViewModel that worked fine for the "well known" types and looked like this:
public class UserInputModel
{
[StringLength(200)]
public string Name { get; set; }
[Required(ErrorMessageResourceName = "BirthDateEmptyError", ErrorMessageResourceType = typeof(Resources.ErrorMessages))]
public DateTime BirthDate { get; set; }
//Here comes a lot of other properties
}
All the known properties were listed and we were showing or hiding them given the context.
But the last requirement came and changed all that. The user shall now be able to add as many generic type fields as he wants. In order to do this, we decided to make this InputModel entirely dynamic. It now looks like this:
public class UserInputModel
{
// Each ModelProperty has an "Id" and a "Value" property
public ICollection<ModelProperty> Properties { get; set; }
}
This works like a charm. The razor view only has to iterates over the collection, create the corresponding controls for each property of the collection in a more than standard way:
#Html.TextBoxFor(m => m.Properties[index].Value);
... and we nicely get the data back as a filled form.
=> This works fine, but we don't have any client-side validation. For this, we would need some Metadata... which we don't have via annotations anymore since we're dynamically creating the model.
In order to provide those MetaData, I created a CustomModelMetadataProvider that inherits from DataAnnotationsModelMetadataProvider and registered it as the new ModelMetadataProvider in the Global.asax. The CreateMetadata() function gets called upon creation of the ViewModel, and that for each of the properties of my ViewModel... sofar so good.
Where the problem starts: in order to add some metadata to the current property, I first need to identify which property I am currently looking at ("Name" has a maxlength of 200, "date of birth" hasn't so I cannot assign a maxlength to every property per default). And somewhow I didn't manage to do that yet since all the properties have the same name Value and the same container type ModelProperty.
I tried accessing the container of the property via reflection, but since the ModelAccessor's target is the ViewModel itself (because of the lambda expression m => m.Properties), the following construct gives me the ViewModel as a whole, not just the ModelProperty:
var container = modelAccessor.Target.GetType().GetField("container");
var containerObject = (UserInputModel)container.GetValue(modelAccessor.Target);
I've been flipping this over and over but cannot find a way to identify which ModelProperty I have in hand. Is there a way to do this?
Update: after flipping this in every possible direction for a while, we finally went another way. We are basically using unobstrusive javascript to use MVC's validation capabilities without touching attributes nor metadata. In short, we add HTML attributes like value-data="true" (and all other required attributes) to the #Html.TextBoxFor() statements. This works wonderfully for all the atomic validations (required, stringlength etc.).
Tim, you can leverage what appears to be client-side validation through Ajax with the Remote attribute on your properties.
Basically, you'll need to set up a validation controller and then write some smarts into that controller. But at least you'd be able to write some helper methods and keep it all in one place. You would have a series of validators, based on the meta data that you are presenting to the end users, and each validator method would work for a particular type with good re-use.
The one pitfall to this approach would be that you would need to write a validation method for each type and condition that you want to support. Sounds like you're having to go down that road anyways, though.
Hope this helps.
See if this article help you: Technique for carrying metadata to View Models with AutoMapper.
Also use this one for ideas (custom model metadata provider): changing viewmodel's MetadataType attribute at runtime
Fluent validation is probably the best option for you in my mind, but its obviously up to you to select the best match among those above.
Update
Try use ModelMetadata and override ModelMetadataProvider: Dive Deep Into MVC: ModelMetadata and ModelMetadataProvider. This way you completely customize your model metadata (this replaces data annotations) and you have complete control on what is happening, rather than relying on ASP.NET MVC.
Another good place to look at it is Creating your own ModelMetadataProvider to handle custom attributes.
Hope this all is of help to you.
I am using from attribute validation in my project.
[Required(ErrorMessage = "DepartmentCode is Required")]
public string DepartmentCode { get; set; }
In some case DepartmentCode isn't required. How can I dynamically ignore Validation in my case?
Take a look at: Remove C# attribute of a property dynamically
Anyway I think the proper solution is to inherit an attribute from RequiredAttribute and override the Validate() method (so you can check when that field is required or not). You may check CompareAttribute implementation if you want to keep client side validation working.
Instead of dynamically adding and removing validation, you would be better served to create an attribute that better serves this purpose.
The following article demonstrates this (MVC3 with client-side validation too):
http://blogs.msdn.com/b/simonince/archive/2011/02/04/conditional-validation-in-asp-net-mvc-3.aspx
I would remove the RequiredAttribute from your model and check it once you've hit your controller and check it against whatever causes it to not be required.
If it falls into a case where it is required and the value is not filled in, add the error to the ModelState manually
ModelState.AddModelError("DepartmantCode", "DepartmantCode is Required");
You would just lose the validation on the client side this way
I've got round this issue in the model, in some cases it's not ideal but it's the cheapest and quickest way.
public string NonMandatoryDepartmentCode
{
get
{
return DepartmentCode;
}
set
{
DepartmentCode = value;
}
}
I used this approach for MVC when a base model I inherited contained attributes I wanted to override.
Whenever there is some custom validation to do or any fiddling with ModelState I had to rely on magic strings (that reflect property names) so far. Surely there must be a better way.
say you have a form with 2 submit buttons. You've already set validation rules by adding Required attributes:
public class MyModel
{
[Required]
public string ValueOne { get; set; }
[Required]
public string ValueTwo { get; set; }
}
This will always validate both fields. But say I've added two buttons to the form showing and editor for above model. Button one only requires ValueOne and button two only required ValueTwo.
Typically I would have custom validation code that checks which button was clicked and do something along the lines:
private void ValidateViewModel(MyModel viewModel)
{
foreach (var key in ModelState.Keys)
ModelState[key].Errors.Clear();
if (Request[{ButtonOneName}] != null && string.IsNullOrEmpty(viewModel.ValueOne))
ModelState.AddModelError("ValueOne", "Required");
else if (Request[{ButtonTwoName}] != null && string.IsNullOrEmpty(viewModel.ValueTwo))
ModelState.AddModelError("ValueTwo", "Required");
}
Not very pretty, I know but ... My beef is with magic string "ValueOne" and "ValueTwo". Also with the way errors are cleared out. Is there a way to generate those keys? I'm looking for something like:
ModelState.KeyFor<MyModel>(m => m.ValueOne)
And then a logical extension:
ModelState.Get<MyModel>(m => m.ValueOne)
Before I start reinventing the wheel - is there something like this hidden somewhere already?
Before you ask, I normally define static class SubmitActions, containing constant strings that represent submit button names. And no, I can't split it up into multiple forms because of the was the view is rendered.
Thanks for any suggestions.
There is a Model Validation Improvements in MVC 3, which will make you be able to check validate based on property related to each other
So by using the IValidatableObject interface built-into .NET 4 to implement a custom validation method on a class. This method can apply validation rules across multiple properties and yield back multiple validation errors,
Have you look at it?
Model Validation Improvements part
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 :)