I have an edit page to edit some info. the page fills a complex object. one of the properties of this object is a generic list.
If I just edit information and save, updatemodel works fine. if i remove (I do this using jquery to remove the form elements client side) something from the list the updatemodel fails with an "object not set to an instance".
I guess the update model is expecting the list to remain of the same length or something but cannot find any information about this, any ideas?
OK, figured out the problem (and it was of course programmer error) on the jquery remove routine I had removed all the elements EXCEPT the hidden field that the model binder uses for lists :(
The model binder will try to map your complex object properties retrieving data from:
1) values from the RouteData
2) URI query string
3) request form submission
Check this places to see why your property is null. If you're deleting your form elements your property will not receive any data.
Some info here and a bug analysis by Scott Hanselman here.
Related
In an MVC4 project, I have a form that contains a partial view which is an index view of languages studied at school. It is a default type view template index, with Add, Delete, Edit links per row etc. When you Add or Edit, it opens an Add or Edit view for a Language. After e.g. adding a language, the updated partial view is returned.
My problem is that if the user opens the Language form, edits and captures on the main form will be lost. I can't just do an Ajax save before opening the Language form, as the main form may only be partially complete and fail validation. What I am thinking of doing though is using an AjaxPreserve action that takes a FormCollection, and stores it in session (o on disk, or anywhere) and therefore no model binding and server validation is performed.
I then have two problems: I will need to disable client validation before calling the AJAX action, and I will need to repopulate the main form using the FormCollection I saved earlier. I think there should surly be some jQuery voodoo to disable client validation, but I am completely stumped on repopulating the form.
ALTERNATE SOLUTION: Instead of using 'sub-forms', I can use editor templates, in pop-ip forms, where the FK IDs are not required, but that us only in certain cases, so my question still stands.
Could you use something like Knockout where you create javascript model and bind it to a grid/dialog edit/template view. I would transform the whole data to a JS model, bind it to a table/grid and then track all changes on the client side. When all is done, just serialize the whole model back to the server and update the data store.
If this is an acceptable scenario, it will save you a lot of trouble.
Familiarity with Knockout is required, but if you've used it before, you will be able to solve this in a very clean and efficient way.
This example on the Knockout website gives an idea of what I'm trying to suggest. Editing, deleting, adding is done on the client side until you send all of the data back to the server. You will need to track flags for each object to know if it's added, edited or deleted.
http://knockoutjs.com/examples/contactsEditor.html
Simple make the sub request for adding language using Ajax and repopulate the dropdown or what ever way you are accepting language on the main form on sucessfully save.
*This will save a lot of effort. *
Why don't you just use javascript?
E.g. You have main form, that stores some data. And when you need to add something specific like languages you open popup using partial view, allow user to fill form, but when user press submit you intercept action with js, save stuff to javascript array/object or whatever else and maybe store it in a hidden field of main form - for final submit
var newData = new Object();
newData.Field1 = $("#yourField1");
...
lanuageData.push(newData);
$("#languageContainer").val(JSON.stringify(languageData));
...
DataAnnotation validation works here as well like:
$.validator.unobtrusive.parse("your_partial_view_container");
When you need to edit some object that was already added to js array - open popup and fill it up with element of you js array. So basically you do all CRUD on client-side, saving changes only on final submit.
To make your code in a conrolller more clear you can use custom model binder which deserialize some string field from JSon to List or any other kind of object -> so it can be validated on server side.
Would saving your values to local storage be acceptable? What about using TempData?
Ok, I looked a bit and couldn't find a good answer to this question.
Using ASP MVC3 I have a strongly typed ViewModel that has a list of custom objects retrieved from a repository. I render out using DropDownListFor as a dropdown selection. A value is selected and during the post I have a custom binder that rebinds my selected value to the custom object....Life is good...
I check my Model.IsValid and it is not valid for some reason. Uh, oh...I need to display the view again but I dont have the complete list of all options. Is there a way to repopulate all of the select option values in the custom model binder or some other method or do I have to hit my repository again?
Thanks.
You hit your repository again. Implement caching in your repository using a MemoryCache to save on DB hits and cache by key. This is the advantage of the repository pattern and the caller is unaware. Since by definition a repository is essentially an in memory representation this works out great.
Check out the implementation here it's similar to what I use and it works great.
http://stevescodingblog.co.uk/net4-caching-with-mvc/
Your currently selected item should remain selected since the HTML helpers read it from the posted data and reuse it.
I typically rebuild the list again and send back to the View. Often I'll use a factory method to inflate the viewmodel(s) with the necessary list(s). Hopefully, an invalid model will be a rare occurrence due to client-side validation in addition to the server side you're working with now.
Im trying to perform update of some records.
When im calling a create action after i insert the record i take out some of the records and change a non-primary key value. Then i call UpdateModel on each to try to save them.
But i get error: The model of type "" was not successfully updated.
If i check ModelState i see that the id value (wich is PK and IDENTITY fails -> A value is required.). The value at id isnt there.
What am i doing wrong?
Hard to guess what the reason might be, but if you trigger the whole thing from a form, I would do "view source" of the html form and make sure your id is there (in a hidden field?). Maybe you forgot to set the id property in your viewmodel before passing it to the view?
That's my best guess without seeing some sample code :)
I'm working on my first ASP.NET MVC (beta for version 3) application (using EF4) and I'm struggling a bit with some of the conventions around saving a new record and updating an existing one. I am using the standard route mapping.
When the user goes to the page /session/Evaluate they can enter a new record and save it. I have an action defined like this:
[ActionName("Evaluate")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EvaluateSave(EvaluteSessionViewModel evaluatedSession)
{
}
When they save I grab an entity off the view model and attach it to my context and save. So far, so good. Now I want the user to be able to edit this record via the url /session/Evaluate/1 where '1' is the record ID.
Edit: I have my EF entity attached as a property to the View Model.
If I add an overloaded method, like this (so I can retrieve the '1' portion automatically).
[ActionName("Evaluate")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EvaluateSave(ID, EvaluteSessionViewModel evaluatedSession)
{
}
I get an "The current request for action 'Evaluate' on controller type 'SessionsController' is ambiguous between the following action" error. I'm not sure why they're ambiguous since they look unique to me.
I decided that I was just going to skip over this issue for now and see if I could get it to update an existing record, so I commented out the EvaluateSave that didn't have the ID parameter.
What I'd like to do is this:
// Load the original entity from EF
// Rebind the postback so that the values posted update the entity
// Save the result
Since the entity is populated as the parameter (evaluatedSession) the rebinding is happening too soon. But as I look at the approach I'd like to take I realized that it opens my code up to hacking (since a user could add in fields into the posted back page and these could override the values I set in the entity).
So it seems I'm left with having to manually check each field to see if it has changed and if it has, update it. Something like this:
if (evaluatedSession.MyEntity.myField <> savedSession.myField)
savedSession.myField = evaluatedSession.MyEntity.myField;
Or, save a copy of the entity and make sure none of the non-user editable ones have changed. Yuck.
So two questions:
First: how do I disambiguate the overloaded methods?
Second: is there a better way of handling updating a previously saved record?
Edit: I guess I could use something like Automapper...
Edit 9/22/2010 - OK, it looks like this is supposed to work with a combination of two items: you can control what fields bind (and specifically exclude some of them) via the [Bind(Exclude="field1,field2")] attribute either on the class level or as part of the method doing the saving, ex.
public ActionResult EvaluateSave([Bind(Exclude="field1")] EvaluateSessionViewModel evaluatedSession)
From the EF side of things you are supposed to be able to use the ApplyCurrentValues() method from the context, ex.
context.ApplyCurrentValues(savedEval.EntityKey.EntitySetName, evaluatedSession);
Of course, that doesn't appear to work for me. I keep getting "An object with a key that matches the key of the supplied object could not be found in the ObjectStateManager. Verify that the key values of the supplied object match the key values of the object to which changes must be applied.".
I tried attaching the original entity that I had just loaded, just in case it wasn't attached to the context for some reason (before ApplyCurrentValues):
context.AttachTo(savedEval.EntityKey.EntitySetName, savedEval);
It still fails. I'm guessing it has something to do with the type of EF entity object MVC creates (perhaps it's not filled in enough for EF4 to do anything with it?). I had hoped to enable .NET framework stepping to walk through it to see what it was attempting to do, but it appears EF4 isn't part of the deal. I looked at it with Reflector but it's a little hard for me to visualize what is happening.
Well, the way it works is you can only have one method name per httpverb. So the easiest way is to create a new action name. Something like "Create" for new records and "Edit" for existing records.
You can use the AntiForgeryToken ( http://msdn.microsoft.com/en-us/library/dd492767.aspx ) to validate the data. It doesn't stop all attempts at hacking but it's an added benefit.
Additional
The reason you can only have one action name per httpverb is because the model binders only attempt to model bind and really aren't type specific. If you had two methods with the same action name and two different types of parameters it can't just try and find the best match because your intent might be clearly one thing while the program only sees some sort of best match. For instance, your might have a parameter Id and a model that contains a property Id and it might not know which one you intend to use.
I encounter an error of the form: "The model item passed into the dictionary is of type FooViewData but this dictionary requires a model item of type bar" even though I am passing in an object of the correct type (bar) for the typed user control.
What #MattMitchell said is probably the reason you're seeing this error.
If you want to know why; it is because when you pass null as the controlData parameter when using RenderUserControl(), the framework will try to pass the view data from the current view context onto the user control instead (see UserControlExtensions.DoRendering method in System.Web.Mvc).
What has probably happened is that the object provided when rendering the user control is actually null.