Need help using the DefaultModelBinder for a nested model - asp.net-mvc-2

There are a few related questions, but I can't find an answer that works.
Assuming I have the following models:
public class EditorViewModel
{
public Account Account {get;set;}
public string SomeSimpleStuff {get;set;}
}
public class Account
{
public string AccountName {get;set;}
public int MorePrimitivesFollow {get;set;}
}
and a view that extends ViewPage<EditorViewModel> which does the following:
<%= Html.TextBoxFor(model => model.Account.AccountName)%>
<%= Html.ValidationMessageFor(model => model.Account.AccountName)%>
<%= Html.TextBoxFor(model => model.SomeSimpleStuff )%>
<%= Html.ValidationMessageFor(model => model.SomeSimpleStuff )%>
and my controller looks like:
[HttpPost]
public virtual ActionResult Edit(EditorViewModel account)
{ /*...*/ }
How can I get the DefaultModelBinder to properly bind my EditorViewModel? Without doing anything special, I get an empty instance of my EditorViewModel with everything null or default.
The closest I've come is by calling UpdateModel manually:
[HttpPost]
public virtual ActionResult Edit(EditorViewModel account)
{
account.Account = new Account();
UpdateModel(account.Account, "Account");
// this kills me:
UpdateModel(account);
This successfully updates my Account property model, but when I call UpdateModel on account (to get the rest of the public properties of my EditorViewModel) I get the completely unhelpful "The model of type ... could not be updated." There is no inner exception, so I can't figure out what's going wrong.
What should I do with this?

The binder is getting confused because it sees that the parameter to your action method is named account, and it sees incoming form fields named account.accountname, so it's looking for an AccountName property on your EditorViewModel.
You can fix this by renaming the parameter to something else that doesn't conflict with an incoming form field, or you can stick an [Bind(Prefix = "")] attribute on the parameter. This attribute says "ignore the fact that the parameter is named account, and pretend I had given it an empty string name instead." Then the binder will look for account.accountname instead of account.account.accountname.
Edit - further info:
When the binder sees a complex parameter named foo, it looks at the current request for anything named *foo.**. So if your parameter were named foo and its type had a property named FirstName, the incoming value would be expected to be foo.FirstName=John, for example.
However, if the binder does not see a *foo.** as part of the request, it just looks for the * directly (without the foo prefix). So as long as there wasn't a *foo.** present in the request, you could submit FirstName=John and the binder would understand this correctly. But if there is any *foo.** as part of the request, the FirstName=John value will not match the FirstName property.
You can see now how giving the parameter to your action method the same name as one of its properties would throw off this logic.

Related

Returning a subset of a navigation propertie's object

I have a one to many relationship as outlined below. In some parts of the business layer there are queries of the Item table and in others the Client table (as well as its Items). LazyLoading and ProxyCreation are both false, reference loop handling is set to ignore.
public class Client {
public virtual ICollection<Item> Items { get; set; }
public string Name {get;set;}
}
public class Item {
public virtual Client TheClient {get;set;}
public string ItemProp {get;set;}
// another 10 properties or so
}
myitems = dbContextScopeX.Items.Include(x => x.Client).ToList();
The view has a list of items with the need to show the Client's Name (in my example). I am looking for item.Client.Name ultimate, however when myitems gets queries/serialized it contains:
myitems.Client.Items
If I set the attribute [JsonIgnore] on the Client's Item property it never comes through the graph which I need it to in other places. Is there a way to get myItems.Client.Name without having to get myitems.Client.Items in the query or without having to create an anonymous projection for the Item array?
Project the Item properties you want (be they simple or complex type) along with just the Client name into an anonymous type and serialize that.
myitems = dbContextScopeX.Items.Include(x => x.Client)
.Select(i=>new {
ItemProp = i.ItemProp,
ItemCollection = i.ItemCollection,
...
ClientName = i.Client.Name
}).ToList();
Only caveat is you have to do some manual work if you want to deserialize this back into entities.

Spring MVC form:checkbox not working

I am having an issue with the Spring form:checkbox tag.
I currently have a JSP page with a form:checkbox tag bound to a Java boolean field. When I put a tick in the checkbox and submit the form the value is false.
Here is the checkbox on my JSP:
<form:checkbox id="field_termsandconditions" path="agreeTermsAndConditions" />
My GET controller:
#RequestMapping(value = "/page1.htm", method = RequestMethod.GET)
public String getPage(HttpServletRequest request, ModelMap model) {
model.addAttribute("MyObject", new MyObject());
return getURL(request);
}
My POST controller:
#RequestMapping(value = "/page1.htm", method = RequestMethod.POST)
public String processPage(HttpServletRequest request,
HttpServletResponse response,
ModelMap model,
MyObject myObject,
BindingResult bindingResult) {
System.out.println(myObject.isAgreeTermsAndConditions);
}
myObject.isAgreeTermsAndConditions is always false when it hits the POST controllers even when checked!
Any ideas?
This might be a little late to answer, but maybe it will help some other person.
When you auto-generate getter and setters for boolean values it is very often generated without 'is' prefix.
For instance, in the case mentioned above the generated setter for 'isAgreeTermsAndConditions' property might be the following: 'setAgreeTermsAndConditions()', note there is no 'is' prefix in the method. The same true for the getters as well.
Since property getter and setters names are used find and bind to the model property, the checkbox might be not shown on the UI or not working properly if there are property name and getters/setters mismatch.
Make sure the property 'isAgreeTermsAndConditions' has the following getters/setters method names: getIsAgreeTermsAndConditions()/setIsAgreeTermsAndConditions(...)

Serialization in ASP.NET Web API

I am using XmlSerializer instead of DataContractSerializer in my ASP.NET Web API project and have a return object defined as
Response Object
public class MyResponse
{
public string Name {get;set;}
public CustomField<string> Username {get;set;}
public CustomField<float?> Score {get;set;}
}
Custom Field
public class CustomField<T>
{
public T Value {get;set;}
public long LastModified {get;set;}
}
I want to generate an XML response as
<MyResponse>
<FirstName>ABC</FirstName>
<Username lastModified="1234">XYZ</Username>
<Score lastModified="45678">12002</Score>
</MyResponse>
ASP.NET Web API returns a JSON object (I am aware that this happens when XmlSerialization does not work correctly) when I decorate the CustomField class as
public class CustomField<T>
{
[XmlText]
public T Value {get;set;}
[XmlAttribute]
public long LastModified {get;set;}
}
How can I get the desired XML response ?
Alright, I think I know what's going on.
If you try to run
new XmlSerializer(typeof(MyResponse))
you'll get this error:
System.InvalidOperationException: Cannot serialize member 'Value' of type System.Nullable`1[System.Single]. XmlAttribute/XmlText cannot be used to encode complex types.
So the issue is that you have a field of type 'float?' as an [XmlText]. [XmlText] can only be applied to primitives, and it doesn't look like XmlSerializer recognizes 'float?' as a primitive. If you use 'float' instead of 'float?', everything looks to be working right. If you want to indicate that sometimes there is no Score, you may want to set the Score to null instead of the Score's value to null.
Hope that helps.

Read DataAnnotations from a collection of models in an MCV2 view

In my MVC2 AdminArea I'd like to create an overview table for each of my domain models.
I am using DataAnnotations like the following for the properties of those domain model objects:
[DisplayName("MyPropertyName")]
public string Name { get; set; }
Now my question is: How can I access the DisplayName Attribute if my view receives a collection of my domain models? I need this to build the table headers which are defined outside of the usual
<% foreach (var item in Model) { %>
loop. Inside this loop I can write
<%: Html.LabelFor(c => item.Name) %>
but is there any way to access this information using the collection of items instead of a concrete instance?
Thanks in advance!
There is a ModelMetaData class that has a static method called FromLambdaExpression. If you call it and pass in your property, along with your ViewData, it will return an instance of ModelMetaData. That class has a DisplayName property that should give you what you need. You can also get other meta data information from this object.
For example, you can create an empty ViewDataDictionary object to get this information. It can be empty because the ModelMetaData doesn't actually use the instance, it just needs the generic class to define the type being used.
//This would typically be just your view model data.
ViewDataDictionary<IEnumerable<Person>> data = new ViewDataDictionary<IEnumerable<Person>>();
ModelMetadata result = ModelMetadata.FromLambdaExpression(p => p.First().Name, data);
string displayName = result.DisplayName;
The First() method call doesn't break even if you have no actual Person object because the lambda is simply trying to find the property you want the meta data about. Similarly, you could d this for a single Person object:
//This would typically be just your view model data.
ViewDataDictionary<Person> data = new ViewDataDictionary<Person>();
ModelMetadata result = ModelMetadata.FromLambdaExpression(p => p.Name, data);
You could clean this up significantly with a helper or extension method, but this should put you on the right path.
Alright, I followed sgriffinusa's advise (thanks again!) and created a strongly typed HtmlHelper:
public static MvcHtmlString MetaDisplayName<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression) where TModel : class
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
return MvcHtmlString.Create(metadata.GetDisplayName());
}
Of course TModel still is a collection of domain models like stated in my inital question but we can call the helper in the view like this:
<%: Html.MetaDisplayName(p => p.First().Name) %>

Creating a generic edit view with ASP.NET, MVC, and Entity Framework

In my database, I have 40 tables that contain only an ID number and a name. My database is accessed using Entity Framework. While I have no trouble editing them each by generating a strongly-typed view and postback methods for each object, I would like to create a more generic method and view for viewing and editing these objects.
I am currently using the following code to access each object. In this case, it is for an object of 'AddressType':
public ActionMethod EditAddressType(int ID)
{
var result = database.AddressType.Single(a => a.ID == ID);
View(result);
}
[HttpPost]
public ActionMethod EditAddressType(int ID, FormCollection formValues)
{
var result = database.AddressType.Single(a => a.ID == ID);
UpdateModel(result);
database.SaveChanges();
return View("SaveSuccess");
}
The view 'EditAddressType' is strongly typed and works fine, but there's a lot of repeated code (one instance of this for each object). I've been told that I need to use reflection, but I'm at a loss for how to implement this. My understanding is that I need to retrieve the object type so I can replace the hardcoded reference to the object, but I'm not sure how to get this information from the postback.
I've had success binding the information to ViewData in the controller and passing that to a ViewPage view that knows to look for this ViewData, but I don't know how to postback the changes to a controller.
Thanks for any help you can give me!
If you are going to edit the object you don't need to refetch it from the database in your POST action. The first thing would of course be to abstract my data access code from the controller:
public class AddressesController: Controller
{
private readonly IAddressesRepository _repository;
public AddressesController(IAddressesRepository repository)
{
_repository = repository;
}
public ActionMethod Edit(int id)
{
var result = _repository.GetAddress(id);
return View(result);
}
[HttpPut]
public ActionMethod Update(AddressViewModel address)
{
_repository.Save(address);
return View("SaveSuccess");
}
}
You will notice that I have renamed some of the actions and accept verbs to make this controller a bit more RESTFul.
The associated view might look like this:
<% using (Html.BeginForm<AddressesController>(c => c.Update(null))) { %>
<%: Html.HttpMethodOverride(HttpVerbs.Put) %>
<%: Html.HiddenFor(model => model.Id) %>
<%: Html.TextBoxFor(model => model.Name) %>
<input type="submit" value="Save" />
<% } %>
As far as the implementation of this IAddressesRepository interface is concerned, that's totally up to you: Entity Framework, NHibernate, XML File, Remote Web Service call, ..., that's an implementation detail that has nothing to do with ASP.NET MVC.