TryUpdateModel not working as expected - asp.net-mvc

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.

Related

EF Reference gets lost when Web API returns IQueryable

I have been looking for quite some time now on this problem.
Here's the deal.
I'm building a website that calls to a Web API to get its data. My Web API uses a library, working with repository pattern. My database model (EF Model-first) was build in the library. In that model I have a base class Pass. Then I have two derived classes, CustomerCard : Pass and Voucher : Pass. My model from EF Designer
I have a method to get all the CustomerCards.
public IQueryable<CustomerCard> GetAllPasses() {
IList<CustomerCard> allCards = new List<CustomerCard>();
var c_cards = context.Passes;
foreach (var c_card in c_cards) {
if (c_card is CustomerCard) {
allCards.Add((CustomerCard)c_card);
}
}
return allCards.AsQueryable<CustomerCard>();
}
In my ApiController, I use this method to get the passes and return them to the website, like this:
[HttpGet]
[Queryable]
public IQueryable<CustomerCard> GetAllPasses(string version) {
return passRepo.GetAllPasses().AsQueryable();
}
My Web API returns JSON format. This is my config to preserve referencing and stuff:
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.Objects;
json.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
config.Formatters.Remove(config.Formatters.XmlFormatter);
I'm using IQueryable because I want to be able to page the data on my website. The api method is available at '/api/v1/passes/all'.
Here's the strange part. To test my paging, I call 1 pass per page.
For my first Pass, it works fine. But when I go to my second page, he also gets the correct pass, but the reference to User is gone.
As you can see in my model, the CustomerCard class has a property User. This indicates who owns the customer card.
So this call loads the user from the pass: 'api/v1/passes/all?$top=1'
but when I call to this one, the user instance is NULL: 'api/v1/passes/all?$top=1&$skip=1'.
However, when I call to 'api/v1/passes/all?$top=2', the User for the second pass IS loaded.
So this is where my mind get's blown! I don't get it? Why doesn't the user-reference comes along with the second one? Could it have something to do with the Lazy loading feature of the EF?
EDIT
When I use the extension method Include on context.passes, an error is thrown:
A specified Include path is not valid. The EntityType 'LCS_Model.Pass'
does not declare a navigation property with the name 'User'.
This is because Passes as a dbset, contains CustomerCard as well as Voucher. Is there a way I can tell my context to expect or convert it to a CustomerCard?
Can someone please help me. If you don't understand my question, ask away!
Thanks allready!
EDIT 2
The method on my API controller is now
[HttpGet]
[Queryable]
public IQueryable<CustomerCard> GetAllPasses(string version) {
return context.Passes.Include("User").OfType<CustomerCard>();
}
This gives me my correct items. I have 2 customer cards in my db. Both are from the same user. My API has the user still loaded. The moment my website receives the response, the User property becomes null. My guess is that it's because it is still referencing to the same user from the first element of the array. Is that possible? And if yes, how can I prevent that from happening?
Yes, you need to make sure any related records are included when you do your query. See this for some examples. Secondly... I fail to understand why you are doing all that work with the for loop... That's a lot of absolutely needless and wasted work for the server to do if you want to do any paging. I'm thinking, besides any other filters you might want to apply, your GetAllPasses should look something like this.
public IQueryable<CustomerCard> GetAllPasses() {
return context.Passes.Include(r => r.User);
}
Edit (2): I need to read better. I have to confess, I'm not familiar with type inheritance in EF. I found some things that might work here: table per hierarchy, table per concrete type, and see also also MSDN Queryable.OfType<TResult>. This is a guess, but let's try:
public IQueryable<CustomerCard> GetAllPasses() {
return context.Passes.OfType<CustomerCard>().Include(r => r.User);
}

MVC3 Service Layer Validation. Returning Exception, Custom Object, Model State Dictionary?

Just curious on your thoughts or experiences around service layer validation.
I have to process fairly standard validation such as "object with name property doesn't already exist", but I wasn't sure how to return these validation failures back to the controller.
My initial thought was to implement a standard List<ValidationError> but I've seen it done each and every way so was curious the pros/cons of each.
Thanks for any input.
If you go with System.ComponentModel.DataAnnotations entries you can (as you seem to know) decorate your properties with required and many more tags
public class Person
{
[Required(ErrorMessage="object with name property doesn't already exist")]
public string Name { get; set; }
}
although I personally use ViewModels rather than exposing domain mdoels to the view, your controller action can now do something like:
[HttpPost]
public ActionResult SavePerson(Person model)
{
if (ModelState.IsValid)
{
// your model validates - do things
return RedirectToAction("success view here");
}
return View(model);
}
This is one of the standard 'post' handler patterns in MVC. This is the simplest path to getting your object model validating in my opinion.
From there, there are a few other options - your domain object can implement IValidatedableObject and you can yield return the errors (see http://buildstarted.com/2010/09/20/mvc-3s-ivalidatableobject/ as an example).
I'd recommend not mixing the two though, as if you are using dataannotations and have even a single invalid property, the IsValid method on IValidatableObject will not be called.
From there, there's lots you can do with custom validation attributes (the extended version of IsValid seems to give you more flexibility http://msdn.microsoft.com/en-us/library/gg480674%28v=vs.98%29.aspx)
Hope some of the above helps - once you get past the basics there's a lot you can do with it and things like client validation of custom attributes etc. are all fun.
Cheers,
Terry
[edit to add:
After re-reading your post, it may be that you want to only validate at the service layer? If so, I've used the following approach:
public void Setname(string newName)
{
Validator.ValidateProperty(newName, new ValidationContext(this, null, null) { MemberName = "Name" });
Name = newName;
}
obviously your Name property would need a { get; private set; } for this, though you could always add the Validator.ValidateProperty into an extended setter for the public property either.
]
On a new project I'm working on (first time mvc) I've been using the ms code contracts (which throw exceptions) and do all the validation on my domain objects themselves. For things that can't be validated there (such as validations that require database access) I validate in my services and throw exceptions. Additionally like the poster above I have whole separate view models for everything that have data annotations validators on them. The exceptions bubble up and I catch them in the controller and append to the ModelState. There's a lot of overlap with those and the view model validation but it's not much extra effort and allows me to vary the validation per view and yet still have the "core" validations be required.
The book pro asp mvc 2 has another nice way - write a class that inherits Exception and contains a collection of errors. Then you do your validations, add to the collection then throw the exception then he catches it in the controller and copies over to ModelState. This method will let you catch ALL the errors in one exception instead of just one at the service layer.

How to transfer value from a DropDownList to a property of a serialized model object

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

ASP.NET MVC - State and Architecture

After a pair programming session, an interesting question came up which I think I know the answer for.
Question: Is there any other desired way in ASP.NET MVC to retain 'state' other than writing to database or a text file?
I'm going to define state here to mean that we have a collection of person objects, we create a new one, and go to another page, and expect to see the newly created person. (so no Ajax)
My thoughts are we don't want any kung-fu ViewState or other mechanisms, this framework is about going back to a stateless web.
What about user session? There are plenty of valid use cases to store things in session. And what about a distributed caching system like memcached? You also seem to leave out the query string - which is an excellent state saver (?page=2). To me those seem like other desirable methods to save state across requests...?
My thoughts are we don't want any kung-fu ViewState or other mechanisms, this framework is about going back to a stateless web.
The example you provided is pretty easy to do without any sort of "view state kung fu" using capabilities that are already in MVC. "User adds a person and sees that on the next screen." Let me code up a simple PersonController that does exactly what you want:
public ActionResult Add()
{
return View(new Person());
}
[HttpPost]
public ActionResult Add(PersonViewModel myNewPersonViewModel)
{
//validate, user entered everything correctly
if(!ModelState.IsValid)
return View();
//map model to my database/entity/domain object
var myNewPerson = new Person()
{
FirstName = myNewPersonViewModel.FirstName,
LastName = myNewPersonViewModel.LastName
}
// 1. maintains person state, sends the user to the next view in the chain
// using same action
if(MyDataLayer.Save(myNewPerson))
{
var persons = MyDataLayer.GetPersons();
persons.Add(myNewPersion);
return View("PersonGrid", persons);
}
//2. pass along the unique id of person to a different action or controller
//yes, another database call, but probably not a big deal
if(MyDataLayer.Save(myNewPerson))
return RedirecToAction("PersonGrid", ...etc pass the int as route value);
return View("PersonSaveError", myNewPersonViewModel);
}
Now, what I'm sensing is that you want person on yet another page after PersonSaveSuccess or something else. In that case, you probably want to use TempData[""] which is a single serving session and only saves state from one request to another or manage the traditional Session[""] yourself somehow.
What is confusing to me is you're probably going to the db to get all your persons anyway. If you save a person it should be in your persons collection in the next call to your GetPersons(). If you're not using Ajax, than what state are you trying to persist?
ASP.NET MVC offers a cleaner way of working with session storage using model binding. You can write a custom model binder that can supply instances from session to your action methods. Look it up.

ASP.NET MVC Issue with Using Reflection Created Objects with the Default Model Binder

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...

Resources