I ran into an interesting problem (hopefully, interesting not just for me :)
I am running Entity Framework 1 (.NET 3.5) and ASP.NET MVC 2. I have a Customer class that has many-to-one relationship with Country class (in other words, Country is a lookup table for customers - I described more in this post: Explicit casting doesn't work in default model binding )
I got TypeConverter to work; so I am getting a perfect object into controller's Post method. Create works fine; however, in Edit I am getting the following error when I call ApplyPropertyChanges:
The existing object in the ObjectContext is in the Added state. Changes can only be applied when the existing object is in an unchanged or modified state.
The controller code is fairly trivial:
public ActionResult Edit(Customer customerToEdit) {
if (ModelState.IsValid) {
Customer cust = (Customer)context.GetObjectByKey(
new EntityKey("BAEntities.Customers", "CustomerID", customerToEdit.CustomerID));
context.ApplyPropertyChanges(cust.EntityKey.EntitySetName, customerToEdit);
context.SaveChanges();
}
return View(...);
}
If I remove country from the form, it works fine; and if I assign dropdown value to EntityReference "manually" - it works as well. UPDATE: it looks that if I don't have lookup field, customerToEdit is in Unchanged state (as I expect); however, if the lookup is there and TypeConverter is invoked - the object is now in Added state. So, the ultimate question is, how can I prevent the object from becoming Added while still using TypeConverter...
TypeConverter code is also fairly simple, but I've never used TypeConverter before, so I may be missing something here:
public override object ConvertFrom(ITypeDescriptorContext typeContext, CultureInfo culture, object value) {
if (value is string) {
int countryID = Int16.Parse((string)value);
Country country = (Country)context.GetObjectByKey(
new EntityKey("BAEntities.Countries", "CountryID", countryID));
return country;
}
return base.ConvertFrom(typeContext, culture, value);
}
UPDATE 2: FWIW, I can see that the state gets changed when DefaultModelBinder::SetProperty is calling propertyDescriptor.SetValue(bindingContext.Model, value); Since PropertyDescriptor is not part of MVC, I can't debug any further. Anybody knows why PropertyDescriptor marks Model as "added" and what can I do about it?
Related
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'm testing some concepts in ASP.NET MVC multisteps (Style Wizards) with a small application which allow me to records organizations in a database.
To make things easier, I've a class OrganizationFormModelView that contains an object of class Organization and a property called ParentOrgList of SelectList type. The only purpose of the selectList property is to be used by a DropDownList.
I've also serialize OrganizationFormModelView to get the multisteps Wizard effect.
In my first view (or first step), I use a dropdownlist helper to assign a value to one of the the Organization's property called ParentOrganization, which draws data from the ParentOrgList.
...
<% = Html.DropDownList("Organization.ParentOrganization", Model.ParentOrgList)%>
...
The first time the page loads, I'm able to make a choice. And, my choice is reflected in my object Model all along the wizard' steps(see Visual studio in debugging mode).
But, when any time I'm redirected back to the first view (first step), I get the following error message:
"The ViewData item with the key 'Organization.ParentOrganization' is of type 'System.String' but needs to be of type 'IEnumerable'."
Thanks for helping
After considering carefully my code, I understand now what's going on. OrganizationFormModelView is the class that is being serialized, and here's its definition.
[Serializable]
public class OrganizationFormViewModel
{
public Organization Organization { get; set; }
[NonSerialized]
public SelectList ParentOrgList = null;
public OrganizationFormViewModel(Organization organization, SelectList cList)
{
Organization = organization ?? new Organization();
ParentOrgList = pList;
}
}
From that, I've concluded that, After each serialization process, ParentOrgList is set to null, so I need to find a way of re-assigning value to it. So, below is what I did:
public ActionResult CreateOrganization(string nextButton)
{
//Omitted for brievety
if (formViewModel.ParentOrgList == null)
formViewModel.ParentOrgList = repository.CommuneList;
//Omitted for brievety
}
I also, modified the View so that, even if the value of the ParentOrgList is continuously re-assigned, but the DropDownList keeps the user's choice. So, I choose an Helper overload with default value.
...
<% = Html.DropDownList("Organization.ParentOrganization", Model.ParentOrgList,
Model.Organization.ParentOrganization)%>
...
Now, everything is working perfectly.
However, If someone knows how to proceed differently with the Serialization business, it'd be helpful to share.
Thanks
I am having a weird issue in ASP.NET MVC with objects not being updated with UpdateModel when passed a formCollection. UpdateModel does not appear to be working properly when the object being updated is created through reflection.
Scenario: I have an application which has approximately 50 lookup tables--each of which includes exactly the same schema including typical fields like id, title, description, isactive, and createdon. Rather than build 50 views, I wanted to have a single view which could display the data from all of the lookup tables. I created an Interface called IReferenceEntity and implemented it in each of the POCOs representing my lookup tables.
Using this interface, I am able to easily populate a view with a record from the lookup table. (I pass the items to the view via the following.)
System.Web.Mvc.ViewPage<MyNamespece.IReferenceEntity>
From the database to the view, every thing works perfectly.
However, when I attempt to update the model on post, I am running into some problems.
If I explicitly declare an object reference like the following, every thing works perfectly and the values of my object are updated with the values from my form. Hence, I can then update the database.
AccountStatus a = new AccountStatus();
UpdateModel(a, formCollection.ToValueProvider());
Unfortunately, hard coding the object type would completely defeat the reason for using an interface.
(A primary objective of the application is to be able to dynamically add new tables such as lookup tables without having to do anything "special". This is accomplished by reflecting on the loaded assemblies and locating any classes which implement a specific interface or base class)
My strategy is to determine the concrete type of the object at postback and then create an instance of the type through reflection. (The mechanism I use to determine type is somewhat primitive. I include it as a hidden field within the form. Better ideas are welcome.)
When I create an instance of the object using reflection through any of the following methods, none of the objects are being updated by UpdateModel.
Type t = {Magically Determined Type}
object b = Activator.CreatorInstance(t);
UpdateModel(b, formCollection.ToValueProvider());
Type t = {Magically Determined Type}
var c = Activator.CreatorInstance(t);
UpdateModel(c, formCollection.ToValueProvider());
Type t = {Magically Determined Type}
IReferenceEntity d = Activator.CreatorInstance(t);
UpdateModel(d, formCollection.ToValueProvider());
Note: I have verified that the objects which are being created through relection are all of the proper type.
Does anyone have any idea why this might be happening? I am somewhat stumped.
If I was really "hard up", I could create factory object which would many instantiate any one of these reference entity/lookup objects. However, this would break the application's ability to allow for new lookup tables to be added and discovered transparently and is just not quite as clean.
Also, I could try deriving from an actual ReferenceEntity base class as opposed to an interface, but I am doubtful whether this would make any difference. The issue appears to be with using reflection created objects in the modelbinder.
Any help is appreciated.
Anthony
Augi answered this on ASP.NET forums. It worked with only a couple of minor modifications. Thank you Augi.
The problem is that [Try]UpdateModel methods allow to specify model type using generic parameter only so they don't allow dynamic model type specification. I have created issue ticket for this.
You can see TryModelUpdate method implementation here. So it's not difficult to write own overload:
public virtual bool TryUpdateModelDynamic<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IDictionary<string, ValueProviderResult> valueProvider) where TModel : class
{
if (model == null)
{
throw new ArgumentNullException("model");
}
if (valueProvider == null)
{
throw new ArgumentNullException("valueProvider");
}
//Predicate<string> propertyFilter = propertyName => BindAttribute.IsPropertyAllowed(propertyName, includeProperties, excludeProperties);
IModelBinder binder = Binders.GetBinder( /*typeof(TModel)*/model.GetType());
ModelBindingContext bindingContext = new ModelBindingContext()
{
Model = model,
ModelName = prefix,
ModelState = ModelState,
//ModelType = typeof(TModel), // old
ModelType = model.GetType(),
// new
//PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};
binder.BindModel(ControllerContext, bindingContext);
return ModelState.IsValid;
}
Does your IReferenceEntity contain setters on the properties as well as getters? I would think that the last sample would work if the interface had property setters, though you'd have to cast it to get it to compile.
Type t = {Magically Determined Type}
IReferenceEntity d = Activator.CreatorInstance(t) as IReferenceEntity;
UpdateModel(d, formCollection.ToValueProvider());
Normally the reason that it won't set a property on a class is because it can't find a public setter method available to use via reflection.
Just a quick "another thing to try":
UpdateModel(d as IReferenceEntity, formCollection.ToValueProvider());
Not sure if that will work, and I haven't tried it myself, but it's the first thing that came to mind.
If I get a chance later I'll peek at the Default Model Binder code and see if there's anything in there that is obvious...
Let's say I have a DB table with columns A and B and I've used the Visual Studio designer to create a Linq objects for this table. All fields are marked NOT NULL.
Now I'm trying to edit this record using typical MVC form editing and model binding, but field B doesn't need to be editable, so I don't include it in the form.
When the post handler binds the object it doesn't populate field B, leaving it null. Now when I save the object I get a DB error saying field B can't be NULL.
The code to save looks something like:
m_DataContext.MyObjects.Attach(myObject);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues, myObject);
m_DataContext.SubmitChanges();
How do I get this to work? Do I need to include field B as a hidden field on the form - I don't really want to do this as it may be updated by other users at the same time and I don't want to stomp over it.
I've found the solution to this problem revolves around getting the entity object associated with the data context before applying the changes. There's a couple of ways of doing this which I've described in separate answers below.
Descend into SQL
This approach ditches LINQ in favour of straight SQL:
public override void SaveMyObject(MyObject o)
{
// Submit
m_DataContext.ExecuteCommand("UPDATE MyObjects SET A={0} WHERE ID={1}", o.ID, o.A);
}
I like this approach the best because of it's simplicity. As much as I like LINQ I just can't justify it's messiness with this problem.
Use a Custom Model Binder
This approach uses a custom model binder to create the entity object and associate with the data context, before the binding takes place.
public class MyObjectBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
MyObject a = ((MyObjectController)controllerContext.Controller).Repository.GetMyObjectForUpdate(bindingContext.ValueProvider["ID"].AttemptedValue.ToString());
return a;
}
}
The repository then creates the object and associates it with the data context:
public Object GetMyObjectForUpdate(string id)
{
MyObject o=new MyObject();
o.ID=id;
m_DataContext.Articles.Attach(o);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
return o;
}
The action handler needs to be attributed to use the model binder...
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditMyObject([ModelBinder(typeof(MyObjectBinder))] MyObject o)
{
if (!ModelState.IsValid)
return View("EditMyObject", a);
Repository.SaveMyObject(a);
return RedirectToAction("Index");
}
and finally SaveMyObject simply calls datacontext.SubmitChanges().
For this to work I also needed to set the update check attributes on all columns to Never (in the dbml file).
Overall, this approach works but is messy.
Use Two Entity Objects
This approach uses two entity objects, one for the model binding and one LINQ:
public override void SaveMyObject(MyObject o)
{
// Create a second object for use with linq and attach to the data context
MyObject o2 = new MyObject();
o2.ID = o.ID;
m_DataContext.Articles.Attach(o2);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
// Apply fields edited by the form
o2.A = o.A;
// Submit
m_DataContext.SubmitChanges();
}
This approeach doesn't require any special handling in the controller (ie: no custom model binding) but still requires
the Update Check property to be set to Never in the dbml file.
You could add a timestamp field and check one on the page with the one in the DB (hiding the timestamp field as well). If a user has updated the record, a concurrency error is returned and the page is refreshed, or left the same iwth the users changes.
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.