ASP.Net MVC - model with collection not populating on postback - c#

I have an ASP.Net MVC application with a model which is several layers deep containing a collection.
I believe that the view to create the objects is all set up correctly, but it just does not populate the collection within the model when I post the form to the server.
I have a piece of data which is found in the class hierarchy thus:
person.PersonDetails.ContactInformation[0].Data;
This class structure is created by LinqToSQL, and ContactInformation is of type EntitySet<ContactData>. To create the view I pass the following:
return View(person);
and within the view I have a form which contains a single text box with a name associated to the above mentioned field:
<%= Html.TextBox("person.PersonDetails.ContactInformation[0].Data")%>
The post method within my controller is then as follows:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create (Person person)
{
//Do stuff to validate and add to the database
}
It is at this point where I get lost as person.PersonDetails.ContactInformation.Count() ==0. So the ModelBinder has created a ContactInformation object but not populated it with the object which it should hold (i.e ContactData) at index 0.
My question is two fold:
1. Have I taken the correct approach.. i.e. should this work?
2. Any ideas as to why it might be failing to populate the ContactInformation object?
Many thanks,
Richard

I think that your model is too complex for the default model binder to work with. You could try using multiple parameters and binding them with prefixes:
public ActionResult Create(
Person person,
[Bind(Prefix="Person.PersonDetails")]
PersonDetails details,
[Bind(Prefix="Person.PersonDetails.ContactInformation")]
ContactInformation[] info )
{
person.PersonDetails = details;
person.PersonDetails.ContactInformation = info;
...
}
Or you could develop your own custom model binder that would understand how to derive your complex model from the form inputs.

If a property is null, then the model binder other could not find it or could not find values in the submitted form necessary to make an instance of the type of the property. For example, if the property has a non-nullable ID and your form does not contain any data for that ID , the model binder will leave the property as null since it cannot make a new instance of the type without knowing the ID.
In other words, to diagnose this problem you must carefully compare the data in the submitted form (this is easy to see with Firebug or Fiddler) with the structure of the object you are expecting the model binder to populate. If any required fields are missing, or if the values are submitted in such a way that they cannot be converted to the type of a required field, then the entire object will be left null.

I've been struggling with this same type of scenario and eventually came to realize that the underlying problem is that the MVC default model binder does not seem to work on EntitySet<T> fields, only List<T> fields. I did however find a simple workaround that seems acceptable. In my case, I have a Company entity that has one to many relationship to Contacts (my Linq-to-Sql EntitySet).
Since it seems that when I change my code from EntitySet<Contact> to List<Contact>, the MVC default model binder starts working as expected (even though the LTS isn't now), I figured I would provide an alternate, "aliased" property to MVC that is of type List<Contact>, and sure enough, this seems to work.
In my Company entity class:
// This is what LINQ-to-SQL will use:
private EntitySet<Contact> _Contacts = new EntitySet<Contact>();
[Association(Storage="_Contacts", OtherKey="CompanyID", ThisKey="ID")]
public EntitySet<Contact> Contacts
{
get { return _Contacts; }
set { _Contacts.Assign(value); }
}
// This is what MVC default model binder (and my View) will use:
public List<Contact> MvcContacts
{
get { return _Contacts.ToList<Contact>(); }
set { _Contacts.AddRange(value); }
}
So now, in my View, I have the following:
<label>First Name*
<%= Html.TextBox("Company.MvcContacts[" + i + "].FirstName") %>
</label>
<label>Last Name*
<%= Html.TextBox("Company.MvcContacts[" + i + "].LastName") %>
</label>
Seems to work like a charm!
Best of luck!
-Mike

Maybe lack of Bind attribute is the case:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create ([Bind] Person person)
{
// Do stuff to validate and add to the database
}

The first argument of Html.TextBox is the name of the textbox, the second would be the value.
"Wrong":
<%= Html.TextBox("person.PersonDetails.ContactInformation[0].Data")%>
"Right":
<%= Html.TextBox("nameoftextbox", person.PersonDetails.ContactInformation[0].Data)%>

Make sure your models (and all nested models) are using properties (getters/setters) instead of fields. Apparently the default binder needs properties to function properly. I had a very similar situation that was fixed by changing the necessary fields to properties.

Related

Handling partial updates (under-posting) with AutoMapper

I'm trying to find a method of handling partial updates; A ViewModel has 5 fields, but only one field is sent in the Request (i.e. only send fields that were modified instead of posing the entire view model), since null values were not explicitly sent for the other fields, I don't think that they should be set to null. Since AutoMapper does not have any kind of support for a Bind list, it's proving difficult to find an elegant solution...
public virtual IActionResult Edit(int id, ViewModel1 viewModel)
{
var model = GetModel(id);
mapper.Map(viewModel, model);
// any field that was not posted, but exists in the ViewModel1 is now null in the model
...
}
The only approach I can think of, is to use Request.Form.Keys and Reflection to build an ExpandoObject that only contains the posted properties, then set CreateMissingTypeMaps and ValidateInlineMaps to allow AutoMapper to map the dynamic types... It just feels like this is a dirty workaround to compensate for functionality that's missing in AutoMapper... Is there a standard way of handling this?

How to access properties in the model passed to the view?

I'm sending the following model to my view.
return View(new { url = returnUrl });
In the view, I'm don't want to specify any particular class for my object (since I wish to keep it flexible and elastic for now). So, the #Model is the apparently an object and as such, it's got no url property. Hence the error.
Additional information: 'object' does not contain a definition for 'url'
Now, I do know that the thing inside the object has url property. I have assigned it to it and I also see it when watching the variable as the exception's been thrown.
My question is how to access the field. Is my only option declaring a class and type the model using #model Something? I can't use as keyword to type it to var...
In "plain" C# we can do something like this.
var some = new {thing = "poof"};
string output = some.thing;
How do I do the equivalent of it in CSHTML file under Razor?
Strongly-typed view models are the way to go. Create a type that suits the needs of the view and treat reusability/duplication as a secondary concern.
However, let me explain why your attempt did not work.
It is legal to pass an anonymous type--even between assemblies[1]--as long as it is cast to object. In fact, the MVC framework assemblies consume anonymous types in many helper methods. Those anonymous types are then evaluated using reflection (optimized by caching).
1: I believe there are some caveats to this, and it certainly isn't good practice in most cases.
A view is compiled into a class so that it can be executed. Part of the class's contract is the type of model it expects, as indicated by #model in your view markup.
This presents a problem with anonymous types, as you cannot indicate their type in your view and type object doesn't expose the properties you set when declaring the type. Thus, you end up with a view that only knows that its model is an object.
Again, use strongly-typed models, or the ViewBag if you truly only need one or two values.
However, to prove that the anonymous type can be passed to the view, look at this (horrible) example:
Controller
return View( new { property1 = "hello world"} );
View
#model object
#{
var rvd = new RouteValueDictionary( Model );
}
#rvd["property1"]
We passed an anonymous type to the view as an object, and then read the object's properties using RouteValueDictionary.
You can use ViewData and ViewBag to send objects to the view page, in your case you can write in the controller something like this:
ViewData["url"] = url ; //Or whatever
return View();
Now in the view you can simply use your object example:<div>#ViewData["url"]</div>
But mainly in MVC it is more recommended to use strongly typed View Models
You may want to look into using the dynamic type in C#. See https://msdn.microsoft.com/en-us/library/dd264736.aspx for details.
While the standard would be to use a strongly-typed view model, there are some scenarios where you might want to use dynamic as your model type (or as a property of your strongly-typed view model), such as in a CMS where the properties are built dynamically by the CMS Provider.
Example view:
#model dynamic
<p>
Url: #Model.url
</p>

How do you put a ViewBag item into a Text Box?

I have the following code and I get an error saying:
has no applicable method named 'TextBoxFor' but appears to have an extension method by that name.
My Code:
#Html.TextBoxFor(ViewBag.taglist)
Why don't you use strongly typed model in your view instead of ViewBag. This will make your life easier.
In fact, you must use a model to with TextBoxFor, otherwise it just won't work. See the definition of TextBoxFor - as a second parameter it takes a lambda expression that takes a property form a model.
If you want just a text box, two options:
#Html.TextBox("NameOfTheTextbox", (String)ViewBag.SomeValue)
or just go
<input type="text" value="#ViewBag.SomeValue" />
No complex solutions required.
I agree with other suggestions of using a strongly-typed model, because the compile-time error support is so much better than debugging exceptions. Having said that, in order to do what you want, you can use this:
#Html.TextBox("NameOfTextBox", (string)ViewBag.taglist)
Update: A Simple Example
Now that you've provided some details in your comments, I've taken a guess at what you might be doing, in order to provide a simple example.
I'm assuming you have a list of tags (like SO has per question) that you'd like to display neatly in a textbox, with each tag separated by a space. I'm going to assume your Tag domain model looks something like this:
public class Tag
{
public int Id { get; set; }
public string Description { get; set; }
}
Now, your view will need a list of the tags but will likely need some other information to be displayed as well. However, let's just focus on the tags. Below is a view model to represent all the tags, taking into account that you want to display them as a string inside a textbox:
public class SomeViewModel
{
public string Tags { get; set; }
// Other properties
}
In order to get the data you want you could grab all of the tags like this:
public ActionResult Index()
{
using (YourContext db = new YourContext())
{
var model = new SomeViewModel();
model.Tags = string.Join(" ", db.Tags.Select(t => t.Description).ToList());
return View(model);
}
}
Notice how I'm directly passing model to the view.
The view is now very simple:
#model SomeViewModel
#Html.EditorFor(m => m.Tags)
The model directive is what signifies that a view is strongly-typed. That means this view will expect to receive an instance of SomeViewModel. As you can see from my action code above, we will be providing this view the type that it wants. This now allows us to make use of the strongly-typed HtmlHelper (i.e. Html.XxxFor) methods.
In this particular case, I've used Html.EditorFor, as it will choose an appropriate input element to render the data with. (In this case, because Description is a string, it will render a textbox.)
You cannot use Html.TextBoxFor without explicitly setting a type for your model within the view. If you don't specify a type it defaults to dynamic. If you want to do model binding then you must use an explicit type rather than a dynamic type like ViewBag. To use Html.TextBoxFor you must define a model type that defines the property that you wish to bind. Otherwise you have to use Html.TextBox and set the value manually from ViewBag. As others have said, you will make your life much easier if you use a statically typed model and take advantage of the inbuilt MVC model binding.
You have to use a lambda expression to select the property, plus you will have to cast the ViewBag member to the correct type.
#Html.TextBoxFor(model => (string)ViewBag.taglist)

How can I collect model validation error messages?

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 :)

Map More Than 1 Form Field to a Model Property

I have a form where I want the user to put it's phone number. However, the form looks like so:
[FormField-1] - [FormField-2] - [FormField-3]
Where there's three textboxes to facilitate the user in putting it in the format xxx-xxx-xxxx. However, my model class is:
class Person
{
[Required(ErrorMessage="Phone Mandatory!")]
public string phone {get; set;}
}
My view looks like so:
#model MvcApplication1.Models.Person
#using ( Html.BeginForm("Create", "Home"))
{
#Html.TextBoxFor( model => model.phone )
#Html.ValidationMessageFor( model => model.phone )<br />
<input type="submit" value="submit" />
}
How do I get around this, so that I don't have to have three different class properties to match the 3 different form fields that represent the phone number?
Thanks!
You could use a custom model binder to achieve this.
Basically you register a type (which you create) to handle a specific model, and you can read in the posted form fields and populate your model manually, rather than getting the default model binder to do it. I think that's your only option in this case.
You can make it easier by inheriting from the default model binder, then just manually processing the more complex fields yourself.
Have a look at this for a bit more info on working with custom model binding: Custom Model Binder
Edit: You can also just accept a type of FormCollection in your action to read the raw posted data and process it in there, if you don't want to go the whole stretch with custom model binding, but it depends on how you want to expand your model in the future I guess.
A more simple solution could be to use some javascript on the form to save the three separate values into one hidden input and bind that single input to the model.
I'd go with a custom ViewModel in this situation.
Another solution might also be to just build the phone number value in the Action method with something like:
[HttpPost]
public ActionResult FormSubmit(Person person)
{
person.phone = Request.Form["FormField-1"] + Request.Form["FormField-2"] + Request.Form["FormField-3"];
...
}

Categories