Why DefaultModelBinder in MVC3 does not Bind? - c#

I do not understand why the DefaultModelBinder in MVC3 does not map the Form Post Data to my action method. I have the following ViewModels:
public class DisplayExcelTableViewModel
{
public string UploadedFileName { get; set; }
public List<string> TableHeaders { get; set; }
public List<TableRowViewModel> TableRows { get; set; }
}
public class TableRowViewModel
{
public List<string> TableColumns { get; set; }
}
They are displayed in a (partial) View using DisplayTemplates:
#using (Html.BeginForm("SubmitExcel", "Home", FormMethod.Post))
{
<fieldset>
<table>
<tr>
<th>#Html.DisplayFor(m => m.TableHeaders)</th>//<input id="TableHeaders_0_" name="TableHeaders[0]" type="text" value="Opportunity Id" />
</tr>
<tr>
<td>#Html.DisplayFor(m => m.TableRows)</td>//<input id="TableRows_0__TableColumns_0_" name="TableRows[0].TableColumns[0]" type="text" value="1-H7PKD9" />
</tr>
</table>
<input type="submit" value="Send" />
</fieldset>
}
And the action method looks like this:
public ActionResult SubmitExcel(DisplayExcelTableViewModel excelTable)
To try whether it worked just with one TableRows I tried:
public ActionResult SubmitExcel([Bind(Prefix = "TableRows")] TableRowViewModel TableRows)
to test I also tried to put List<TableRows> and take out the Bind attribute. It does not work.
I got a runtime exception:
"System.MissingMethodException: No parameterless constructor defined for this object."
May you tell me what I am doing wrong?
Thanks Francesco

The problem is that my ViewModels DID NOT have a parameterless constructor, which is what the Default Model Binder looks for(uses .NET’s Activator.CreateInstance() method, which relies on those types having public parameterless constructors).The solutions in this case are two:
1) Add a parameterless constructor to the ViewModel and the other custom classes wrapped inside it.
2) Create a custom model binder that covers also the case of your ViewModel
Thanks
Source: Pro ASP.NET MVC2 Framework (2nd Edition)

Have you checked (for example with Firebug) whether are form values being posted to the server? I'm asking because Html.DisplayFor usually renders display elements, whereas for posting values you usually have to use Html.EditorFor.

Related

Passing multiple checkbox values to deeper level of model .net mvc

A few similar questions have been asked before but my use case is a bit different.
So this is my model:
public class YourModel
{
public string[] Suburb { get; set; }
}
And my view:
<input name="Suburb" type="checkbox" value="sydney" /><span>sydney</span>
<input name="Suburb" type="checkbox" value="melbourne" /><span>melbourne</span>
Controller:
public ActionResult AdvancedSearch(YourModel s)
{
// logic
}
So MVC is smart enough to retrieve the multiple checkbox values to put them in the Suburb array in YourModel model. I can inspect all values there. But my use case is that the YourModel is just the nested model inside another model MyModel:
public class MyModel
{
//other properties
public YourModel m { get; set; }
}
So now how do I make MVC post the checkbox values to a deeper model MyModel.YourModel? I have tried #Html.CheckBoxFor and #Html.CheckBox but neither of them worked.
Right now my work around is to add a temporary array placeholder in the outside model and then assign all the data to the inside model when available, but that is definitely not ideal.
You need to use add MyModel
<input name="m.Suburb" type="checkbox" value="sydney" /><span>sydney</span>
<input name="m.Suburb" type="checkbox" value="melbourne" /><span>melbourne</span>
In Razor, you don't have to define the name of the top-most Model, only the names of the properties of inner models:
<input name="m.Suburb" type="checkbox" value="sydney" /><span>sydney</span>
<input name="m.Suburb" type="checkbox" value="melbourne" /><span>melbourne</span>
However, I'd strongly suggest you to change that m name to something more significant.

Bind checkboxes to List in partial view

I have a CreateViewModel.
public class CreateViewModel
{
public AttributesViewModel AttributesInfo { get; set; }
}
The AttributesViewModel is sent to a partial view.
public class AttributesViewModel
{
public AttributesViewModel()
{
ChosenAttributes = new List<int>();
}
public List<Attributes> Attributes { get; set; }
public List<int> ChosenAttributes { get; set; }
}
The List of Attributes is outputted in the partial view. Each one has a checkbox.
foreach (var attribute in Model.Attributes)
{
<input type="checkbox" name="ChosenAttributes" value="#attribute.ID" /> #Attribute.Name
}
When I post CreateViewModel, AttributesInfo.ChosenAttributes is always empty even though I checked some boxes. How do I properly name each checkbox so that it binds to the ChosenAttributes List?
My Solution
I took Stephen Muecke's suggestion to do the two way binding. So, I created a CheckboxInfo class that contained Value, Text, and IsChecked. I created a EditorTemplate for it:
#model Project.CheckboxInfo
#Html.HiddenFor(model => model.Text)
#Html.HiddenFor(model => model.Value)
#Html.CheckBoxFor(model => model.IsChecked) #Model.Text
One GIANT caveat. To get this to work properly, I had to create an EditorTemplate for the AttributesViewModel class. Without it, when CreateViewModel is posted, it cannot link the checkboxes to AttributesInfo.
Your naming the checkbox name="ChosenAttributes" but CreateViewModel does not contain a property named ChosenAttributes (only one named AttributesInfo). You may be able make this work using
<input type="checkbox" name="AttributesInfo.ChosenAttributes" value="#attribute.ID" /> #Attribute.Name
but the correct approach is to use a proper view model that would contain a boolean property (say) bool IsSelected and use strongly typed helpers to bind to your properties in a for loop or using a custom EditorTemplate so that your controls are correctly names and you get 2-way model binding.
I had a similar scenario, but this was how I did it. The solution is not perfect so please excuse if I have left something out, but you should be able to relate. I tried to simplify your solution as well :)
I changed the Attribute class name to CustomerAttribute, rename it to whatever you like, use a singular name, not plural. Add a property to your CustomerAttribute class, call it whatever you like, I called mine IsChange.
public class CustomerAttribute
{
public bool IsChange { get; set; }
// The rest stays the same as what you have it in your Attributes class
public string Name { get; set; } // I'm assuming you have a name property
}
Delete your AttributesViewModel class, you don't really need it, I like simplicity.
Modify your CreateViewModel class to look like this:
public class CreateViewModel
{
public CreateViewModel()
{
CustomerAttributes = new List<CustomerAttribute>();
}
public List<CustomerAttribute> CustomerAttributes { get; set; }
}
Your controller will look something like this:
public ActionResult Create()
{
CreateViewModel model = new CreateViewModel();
// Populate your customer attributes
return View(model);
}
Your post controller action method would look something like this:
[HttpPost]
public ActionResult Create(CreateViewModel model)
{
// Do whatever you need to do
}
In your view, you will have something like this:
<table>
<tbody>
#for (int i = 0; i < Model.CustomerAttributes.Count(); i++)
{
<tr>
<td>#Html.DisplayFor(x => x.CustomerAttributes[i].Name)</td>
<td>#Html.CheckBoxFor(x => x.CustomerAttributes[i].IsChange)</td>
</tr>
}
<tbody>
</table>
Create a sample app and try out the code above and see if it works for you.

ViewModel does not update Model correctly on submit

I am trying to use a strongly-typed ViewModel and find that when I replace the Model class with a ViewModel, changes are not submitted correctly from the Edit template. Whereas just using the straight Model class in the View, edits happen successfully. My model class is MaterialDefinition and the ViewModel class is MaterialDefinitionViewModel as shown below. I've updated the edit template correctly to reference the ViewModel but as I say editing just does not work. I'm using VS2013 and MVC4. Any ideas, someone?
First the ViewModel class...
public class MaterialDefinitionViewModel
{
// Properties
public MaterialDefinition Definition { get; private set; }
// Constructor
public MaterialDefinitionViewModel(MaterialDefinition def)
{
Definition = def;
}
}
and now some code from the View...
<div class="editor-field">
#Html.EditorFor(model => model.Definition.mdDescription)
#Html.ValidationMessageFor(model => model.Definition.mdDescription)
</div>
<p>
<input type="submit" value="Save" />
</p>
Finally discovered the answer to this after much searching...
[HttpPost]
public ActionResult Edit(string id, FormCollection collection)
{
MaterialDefinition def = repository.GetMaterialDefinition(id);
UpdateModel(def, "Definition");
//UpdateModel(def);
repository.Save();
return RedirectToAction("Details", new { id = def.mdID });
}
Turns out there is a not-so-obvious overload for the UpdateModel method that takes a prefix "Name" property. This is the name of the original encapsulated model class inside the ViewModel. Corrected code fragment from the Controller's Edit Post method is shown above.

Hide property of model in dynamic view

I have a dynamic view, this will display any model that has been passed to it.
#model dynamic
#using (Html.BeginForm("Edit", null, FormMethod.Post, new { id="FrmIndex" }))
{
#Html.ValidationSummary(true);
#Html.EditorForModel()
<input type="submit" value="Edit" />
}
Say one of my model is PartyRole
public partial class PartyRole
{
[Key, Display(Name = "Id"]
[UIHint("Hidden")]
public int PartyRoleId { get; set; }
[UIHint("TextBox")]
public string Title { get; set; }
}
I dont want to show Id in edit mode, so I am hiding it in Hidden.cshtml editorfortemplate as below:
#Html.HiddenFor(m => Model)
This is hiding the editor, but not the label "Id".
And I cannot use the answers provided here, How to exclude a field from #Html.EditForModel() but have it show using Html.DisplayForModel()
because IMetadataAware requires System.Web.Mvc namespace which I cannot add in my Biz projects that are having the poco model classes.
I cannot use [HiddenInput(DisplayValue = false)] also because this is also party of web.mvc
can somebody give a solution??
I think that the thing to do is create a custom Object.cshtml editor template, as described in
http://www.headcrash.us/blog/2011/09/custom-display-and-editor-templates-with-asp-net-mvc-3-razor/
(nb. I found How to add assembly in web.config file of mvc 4 to be helpful with the System.Data.EntityState reference.)
Within that template you can put appropriate code to hide the label. The following is a dumb example - I guess that I'd probably try to pick up a custom attribute, though apparently this would involve an overload of DataAnnotationsModelMetadataProvider.
if (prop.HideSurroundingHtml)
{
#Html.Editor(prop.PropertyName)
}
else if (prop.PropertyName == "PartyRoleId")
{
<div></div>
}
else if (!string.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString()))
{
<div class="editor-label">#Html.Label(prop.PropertyName)</div>
}

Why does a model accessor with the name "State" cause the model to be posted as null?

I was having a weird issue with a very simple model. When posted back to the controller, the model was always null. Not being able to find the issue, I pulled it apart rebuilt the model, adding an accessor at a time.
I finally discovered that having a string accessor called "State" and using it in a view was causing the issue:
<%= Html.HiddenFor(m => m.State) %>
Why would this happen?
Here is the model:
public class StudentSelectState
{
public string State { get; set; }
public int SelectedYear { get; set; }
public IDictionary<string, string> Years { get; set; }
}
Here is the controller:
[HttpGet]
public ActionResult SelectStudent()
{
var StudentYears = absenceServices.GetStudentYears();
var state = new StudentSelectState {Years = Lists.StudentYearListToDictionary(StudentYears)};
return View(state);
}
[HttpPost]
public ActionResult SelectStudent(StudentSelectState state)
{
var StudentYears = absenceServices.GetStudentYears();
state.Years = Lists.StudentYearListToDictionary(StudentYears);
return View(state);
}
and here is the view:
<% using (Html.BeginForm())
{%>
<%= Html.ValidationSummary() %>
<%= Html.TextBoxFor(m => m.State) %>
<%= Html.RadioButtonListFor(m => m.SelectedYear, Model.Years, "StudentYears") %>
<div style="clear: both;">
<input value="submit" />
</div>
<% } %>
The RadioButtonListFor is a HtmlHelper I wrote to populate RadioButtonLists.
I am using Ninject 2.0 to inject services into the contructor, but I don't think this has a bearing on this issue.
I could rename the accessor, but I'm curious as to why this is happening.
You could also rename the argument of your POST action.
[HttpPost]
public ActionResult SelectStudent(StudentSelectState model)
When you POST the form the following is sent in the request:
State=abcd
Now the default model binder sees that your action argument is called state and it tries to bind the abcd value to it which obviously fails because the state variable is not a string. So be careful when naming your view model properties.
To avoid those kind of conflicts I prefer to name my action arguments model or viewModel.
Yet another possibility if you don't want to rename anything is to use the [BindPrefix] attribute, like so:
[HttpPost]
public ActionResult SelectStudent([Bind(Prefix="")]StudentSelectState state)
When StudentSelectState is posted back to the controller the default mode binder (because you are not using a IModelBinder) can not know when to put in the StudentSelectState instance.
The view will not hold the state for the State property and it has to be specified in the form or obtained from a different method to be returned to the controller action.
You could use a hidden field for this or bind it using a custom IModelBinder class.
Hope this helps.

Categories

Resources