I am receiving a model from a view.
Some of the values are automatically filled in.
However, some of the required values need to be added manually, like so:
[HttpPost]
public ActionResult Foobar(FooModel model, FormCollection collection)
{
// "timePicker" is a dropdown list containing different times
var time = collection["timePicker"].Split(':');
model.Hours = int.Parse(time[0]);
model.Minutes = int.Parse(time[1]);
if (ModelState.IsValid)
{
... // Do stuff
}
}
So here's the problem:
ModelState.IsValid is false.
I debugged it, and it claims that model.Minutes and model.Hours are not assigned to.
...Which isn't true, because I had just assigned them values!
I considered using ModelState.Clear(), but I don't want to need to manually check whether all the rest of the information is valid.
Is there any other way to resolve the issue?
Well, the model binding, putting errors in ModelState, has been done before entering your action.
So the Minutes and Hours errors are in the ModelState before you add values in Hours and Minutes (which makes it invalid).
You might just remove these errors (and not all errors, as Clear() does) , by doing :
ModelState.Remove("Hours");
ModelState.Remove("Minutes");
Another way to resolve this would be to create (and use) a custom ModelBinder.
Related
I have a view model like such:
public class MyViewModel
{
public string Name { get; set; }
public List<Purchases> Purchases { get; set; }
}
This viewmodel is sent to a view that allows the user to edit the name property. The Purchases property is used only to create a dropdown box for it:
<%: Html.DropDownListFor(t => t.Name, new SelectList(Model.Purchases, "Value", "Text")) %></p>
This works fine.
However, when I perform server-side validation and then return to the View, I'm getting an object null reference error because the Purchases property is now set to null. I'm guessing this is because when the form is submitted because the Purchases property isn't bound to any editable control, it isn't being passed back with the viewmodel.
How can I prevent this happening? I want to send back the List to be send back with the Post request always.
You don't need to send back the list. If validation fails then simply rebuild the view model from scratch. One of the main selling points of MVC is how well it works in a stateless environment. Web Forms used ViewState to do this kind of thing, I don't think you want to replicate this kind of functionality though.
I like to have two overloaded Action methods for this (both with the same name but different method signatures). One with an [HttpGet()] attribute and the other with an [HttpPost()]. If your model is found to be invalid on the POST then simply return the "GET" method (NOTE: you'll need to to pass in any parameters required to rebuild the view).
When I say return, I mean:
return MyGetAction();
and not a Redirect to the GET action.
If the model is valid then you could/should perform a RedirectToAction() to a GET Action (this means if the user hits the refresh button it won't submit the form again, this is called the Post/Redirect/Get (PRG) pattern)
You'd have to create a hidden input for each of the elements in the list in addition to the select list. Having said, that I think caching the results of the query on the server is a better way to handle repopulating the list if you don't want to perform the query again. There's no sense in sending the data back across the wire if the server can just hang on to it. Personally, I wouldn't even bother with the caching unless it proved to be a performance bottleneck. Just populate the model selection list from the DB.
<% for (int i = 0; i < Model.Purchases.Length; ++i) { %>
<%: Html.Hidden( string.Format( "Purchases[{0}]", i ), Model.Purchases[i] ) %>
<% } %>
Lee Gunn is spot on. To make the answer a little more concrete, it is very common to re-build non-scalar values in the event ModelState is not valid. This can be done by simply returning the [HttpGet] version in your Controller. However, you could simply re-build the purchases collection manually. It's up to you.
[HttpGet]
public ActionResult MyView(string name)
{
//get entity and build up a view model
var entity = _myDb.GetEntity(name);
MyViewModel vm = AutoMapper.Map<Entity, MyViewModel>(entity);
return vm;
}
[HttpPost]
public ActionResult MyView(MyViewModel vm)
{
If(!ModelState.IsValid)
{
//here is one way of doing it
return MyView("");
//OR
vm.Purchases = GetSomePurchasesBro();
return View(vm);
}
//continue on persisting and doing the Post Redirect Get
}
P.S.
Calling
return MyView("");
can be replaced with
return MyView(vm.Name);
Both will do the same thing (provided you're using the Html Helper Extensions, i.e. Html.TextBoxFor(x=>x.Name))
Defaut model binding looks in the ModelState for attemptedValues when rendering Html. This is described here by Steve Sanderson.
I'm working on an ASP.NET MVC project that will allow users to perform batch edits on the attributes of objects. The implementation is in a sort of "wizard" like form with four phases to the process as follows:
"Select the attributes you want to edit" - the first page will present the user with a list of checkboxes representing each of the attributes they want to edit. The user should check the attributes they wish to edit and select "Continue".
"Edit the selected attributes" - the second page will present the user with a list of distinct "editors" which will be unique for each of the attributes they selected on the first page.
"Review your changes" - this page will allow the user to review the changes they've made to the attributes they selected.
"Submit your changes" - this page will actually submit the information about the edits the user wishes to make to the selected attributes against the selected collection of objects.
Fairly straight-forward.
As I mentioned, the "editor" will be unique to each attribute, and could have any combination of different controls on it. Once a user has made their edits and the application posts that information to the "Review" page is where I'm currently having my problem.
We've developed the concept of an "EditorWorker" class that is unique to each attribute, which is responsible for generating the ViewModel necessary for each editor, but is also responsible for creating/returning (within the "Review" page controller action) an object that is the "model" object for the editor that the post data can be bound to, which can then be use to display the edited data for review. This object should have properties that match up with the IDs of the controls in the editor so that model binding can occur.
I've got the "EditorWorker" creating and returning the class needed, but for some reason, when I call TryUpdateModel and pass in that class, its properties aren't getting populated as a result of that method call as I would expect them to. I have verified that the values are in the posted FormCollection. Below is the code for my controller action where I'm attempting to do this. If someone can help me understand why TryUpdateModel isn't working in this scenario, I would be very appreciative.
[HttpPost]
public virtual ActionResult Review(ReviewBatchViewModel model)
{
var selectedAttributes = GetSelectedAttributes(model.SelectedAttributeIds.Split(',').Select(i => Int64.Parse(i)).ToArray());
var workers = new List<IEditorWorker>();
var reviewData = new Dictionary<ViewAttribute, IEditData>();
foreach (var attribute in selectedAttributes)
{
if (!string.IsNullOrEmpty(attribute.EditorWorker)) // If there is no EditorWorker defined for this object, move on...
{
var worker = ServiceLocator.Current.GetInstance(Type.GetType(string.Format("{0}.{1}", EditorWorkerNamespace, attribute.EditorWorker)));
var attributeEditData = ((IEditorWorker)worker).LoadEditData();
if (TryUpdateModel(attributeEditData))
model.EditData.Add(attributeEditData); // model.EditData is a List<IEditData> that will be iterated on the Review page
reviewData.Add(attribute, attributeEditData);
}
}
return View(model);
}
// ReviewBatchViewModel.cs
public class ReviewBatchViewModel : BaseViewModel
{
public ReviewBatchViewModel() { EditData = new List<IEditData>(); }
public string SelectedAttributeIds { get; set; }
public List<ViewAttribute> SelectedAttributes { get; set; }
public List<IEditData> EditData { get; set; }
}
// IEditData.cs
public interface IEditData
{
}
// BroadcastStatusEditData.cs
public class BroadcastStatusEditData : IEditData
{
public int BroadcastStatus { get; set; }
}
I totally understand that this controller action is incomplete in its current state. I'm presently working on just trying to get those EditData objects populated correctly before I move on. As mentioned, any thoughts would be greatly appreciated. Thanks.
UPDATE: With regards to #mare's comment, I should have explained that part more clearly, sorry. The call to TryUpdateModel actually is returning true, but the fields on the model object being passed into it aren't actually being populated from the values that have been confirmed present in the posted form data. The model object being passed into the call is not a List, its just a poco. The resulting, ultimately hopefully populated model object is then being added to a List collection of model objects that will then be used for displaying the posted data for review on the Review page. I'm not loading anything from a datastore at all. Unique editors for each selected attribute are being rendered to the Edit screen, and I'm attempting to capture the edit values for display on a Review screen prior to submitting the batch of edits to a service. Hopefully that's more clear. Thanks.
UPDATE 2: I've included the definition of the ReviewBatchViewModel class as requested by #mare in the comments. The use of the var keyword in most cases in this code sample is largely due to the fact that the methods that are populating those variables is going to be returning an object of a different type for each attribute selected, so I never know exactly what its going to be at runtime (although it will always implement an interface, in this case either IEditorWorker and/or IEditData). There is a single class in the Model called "Attribute". The provided code sample has three variables relative that class: 1) SelectedAttributeIds is a comma-separated list of the Id's of the attributes that the user has selected to edit, which gets passed from the Edit page to the Review page via hidden field, 2) selectedAttributes is a collection of the actual Attribute objects that correspond to those Ids that I can work with, and 3) attributeEditData is an instance of the IEditData class specific to each given attribute that I'm attempting to bind the posted data from the Edit page to.
Hopefully this additional information clears things up even more.
TryUpdateModel is a generic method, and therefore attempts to infer all type information based on the Generic Type Parameter.
From what I understand in your example above, you are always passing in a IEditData correct?
In effect you are saying:
TryUpdateModel<IEditData>(attributeEditData)
This is most likely the cause for not seeing any properties being set, since IEditData doesn't have any properties ;)
To do what you want you will probably have to create a custom ModelBinder.
As a quick code review side note, your solution seems overly complicated. I had to stare at your solution for a good while just to figure out where to start. Creating a custom model binder may solve your immediate problem, but you might be looking at a big time maintenance headache here. I'm willing to bet there is a simpler approach that will lead to fewer problems down the road.
Based on your comments I have changed the code around from System.Object to your IEditData interface, but everything still holds. I noticed in an earlier comment you mentioned using var because you didn't know the type until runtime. However, there is nothing magic about the var keyword. The only thing it does is give you implicit typing, but it is still statically typed.
The nice thing about MVC is that you can just pop over to Codeplex and have a look at the source for TryUpdateModel if you want. Digging down a few layers you will eventually find a call to this internal method:
protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IDictionary<string, ValueProviderResult> valueProvider) where TModel : class {
if (model == null) {
throw new ArgumentNullException("model");
}
//valueProvider is passed into this internal method by
// referencing the public ControlerBase.ValueProvider property
if (valueProvider == null) {
throw new ArgumentNullException("valueProvider");
}
Predicate<string> propertyFilter = propertyName => BindAttribute.IsPropertyAllowed(propertyName, includeProperties, excludeProperties);
//Binders is an internal property that can be replaced by
// referencing the static class ModelBinders.Binders
IModelBinder binder = Binders.GetBinder(typeof(TModel));
ModelBindingContext bindingContext = new ModelBindingContext() {
Model = model,
ModelName = prefix,
ModelState = ModelState,
ModelType = typeof(TModel),
PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};
binder.BindModel(ControllerContext, bindingContext);
return ModelState.IsValid;
}
Notice the use of typeof(TModel) everywhere... in your case that is getting translated into typeof(IEditData), which isn't very useful since it is only a marker interface. You should be able to adapt this code for your own use, making sure to use GetType() in order to get the actual type at runtime.
I hope this helps out!
P.S. I've added some comments to the above code to help out a little
#Josh, you were very helpful in helping me understand why TryUpdateModel wasn't working for me, and I appreciate that. Unfortunately, I think the larger issue here was that fact that I (not exactly sure which) was either unable or unwilling to try to document all of the details of the requirements for the problem I'm trying to solve here, which I think made it difficult for anyone to be able to provide much meaningful input. The biggest problem for us is that, because we have no idea until runtime which attributes a user has selected for editing, we don't know which objects we'll be working with in the context of these controller actions, or what their types will be. The one place that we safely can work with known data and types, is within the context of each of the unique EditorWorker objects, which is where I've chosen to do the heavy lifting here.
I was hoping and attempting to take advantage of all of the heavy lifting that MSFT has done for us within the MVC framework to handle model binding, but I've come to the conclusion at this point that I don't think that's going to work for us. The solution that I've come up with at this point, is to allow the LoadEditData method of the EditorWorker classes handle loading up the EditData classes for for me. As each EditorWorker class is unique to, and has knowledge of the attribute that it is associated with. The problem I was having originally was that I was letting the EditorWorker.LoadEditData method just return an empty instance of the specific type of EditData class that I needed for the attribute I was currently working with, and let the MVC framework handle model binding to that object for me. That wasn't working because that method is designed to return an object of type IEditData, and I never really knew exactly what type it was that I was currently working with, so I had no way of specifying the type in the call to either of the typed methods: TryUpdateModel<T> or UpdateModel<T>.
So the solution I've come up with, and am going with at least for now (re-education and/or refactoring may very well change this in the future, who knows) is to just pass the Request.Form object into the call to EditorWorker.LoadEditData and let that method handle actually loading up the EditData object that it knows it needs to return for the attribute it's responsible for, which it can do as it knows what information should be in the posted form collection to load up its EditData object.
So that's where I'm at for now. Thanks for the help.
I have a small application where I am creating a customer
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateCustomer(GWCustomer customer)
{
if (string.IsNullOrEmpty(customer.CustomerName))
{
ModelState.AddModelError("CustomerName", "The name cannot be empty");
}
//...
if (ModelState.IsValid)
{
//insert in db
}
}
My problem is that the GWCustomer object has an Id, which is primary key and cannot be null. This makes the validation framework flag it as an error. But it's not an error, I haven't created the customer yet, and for now is should be null until it gets saved. How do I bypass this? Or fix it?
I never get to insert it in the DB because the ModelState is never valid.
Edit I am using Linq to SQL, and a repository pattern.
This will exclude value from binding, but not validation:
public ActionResult CreateCustomer([Bind(Exclude = "Id")]GWCustomer customer)
Even when validation occurs, you can still correct ModelState by calling:
ModelState.Remove("Id");
It will remove entries related to Id and change ModelState.Valid property to true if only Id was causing errors.
Using data layer objects in view layer is not recommended. You should definitely think about creating dedicated view model, without Id field.
Maybe you have this line in your view:
#Html.HiddenFor(model => model.Id)
Delete it and the view won't send that parameter with the model.
This is why I always say that the ViewModel objects (input and output) should be separated from the Domain Objects.
The input model should be validated in the way you are above; the domain object state should be validated before it gets written to the database (and exceptions thrown if it is somehow invalid).
I am trying to implement Optimistic Locking in an asp.net MVC application, as well as provide audit trailing.
The audit framework relies on being able to call DataContext.GetModifiedMembers during SubmitChanges, which makes good sense, i guess.
The optimistic locking uses ROWVERSION timestamps, serialized to base64 and put in a hidden field in the view.
My Edit action looks like this:
[AcceptVerb(HttpVerb.Post)]
public ActionResult Edit(MyType myType)
{
context.MyTypes.Attach(myType, true);
context.SubmitChanges(ConflictMode.FailOnFirstConflict);
}
When doing this, the DataContext.GetModifiedMembers will always return ALL the properties on MyType, rather than just the ones that are changed between the database and the values provided, which breaks the auditing.
Specifically it returns every property as having been changed from their new value to their new value, so its not even like I can do anything clever to the list.
I tried loading the object first, before attaching it, but this gives a duplicate key exception.
I then tried using UpdateModel, i.e.
[AcceptVerb(HttpVerb.Post)]
public ActionResult Edit(int id, FormCollection col)
{
var mt = context.MyTypes.Single( mt => mt.id = id);
UpdateModel(mt);
context.SubmitChanges(ConflictMode.FailOnFirstConflict);
}
This works with the auditing, but fails the optimistic locking.
Rather than a ChangeConflictException i get an InvalidOperationException because the UpdateModel is changing the concurrentTS field (which apparently is readonly).
What am I doing wrong?
Progress so far consists of doing the last part, and catching InvalidOperationException and looking for the text "Value of member 'ConcurrencyTimestamp'", and rethrowing that as a ChangeConflictException.
That seems to do the trick, but it is not pretty.
I followed these tutorials:
http://www.asp.net/learn/mvc/tutorial-39-cs.aspx
http://schotime.net/blog/index.php/2009/03/31/integrating-xval-validation-with-linq-to-sql/
in order to enforce data validation using Data Annotation for a LINQ-To-SQL-generated class. The metadata class looks like this:
[MetadataType(typeof(PositionValidation))]
public partial class Position
{
}
public class PositionValidation
{
[Required]
public string Title { get; set; }
}
The validation works correctly, but only if I do this in the controller:
if (ModelState.IsValid)
{
_positions.AddPosition(newPosition);
return RedirectToAction("List");
}
If I leave out the check for valid ModelState, it will attempt to add it to the database, even if Title is empty. As a result, I get an entry with a blank title (this applies to edits as well).
I was under the impression that data validation enforces it for the model side too, in addition to the controller/view. Does this mean that I have to add in additional code to do validation in the Position class too? If so, doesn't this violate DRY?
So in other words (please let me know if I am wrong), you expected that your action was NOT executed at all if the Data Annotation validation failed. This was the only way to omit the if(Model.IsValid) statement.
Your assumption is not correct, and this is by design. It's a very nice feature in fact, not a nuisance. You add only a line of code, to check whether there are errors, and in return you get the possibility to:
add your own errors which come from the business logic, so they are displayed to the user immediately, and not upon the next submission, when the DA is ok
reset errors or customize errors
redirect to some other view, or do any action (e.g. logging) upon certain conditions