I have an applicant model that contains a list of tags:
public class Applicant
{
public virtual IList<Tag> Tags { get; protected set; }
}
When the form is submitted, there is an input field that contains a comma-delimited list of tags the user has input. I have a custom model binder to convert this list to a collection:
public class TagListModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var incomingData = bindingContext.ValueProvider.GetValue("tags").AttemptedValue;
IList<Tag> tags = incomingData.Split(',').Select(data => new Tag { TagName = data.Trim() }).ToList();
return tags;
}
}
However, when my model is populated and passed into the controller action on POST, the Tags property is still an empty list. Any idea why it isn't populating the list correctly?
The problem is you have the protected set accessor in Tags property. If you change that into public as below things will work fine.
public class Applicant
{
public virtual IList<Tag> Tags { get; set; }
}
A model binder only binds submitted values. It does not bind values rendered in the view.
You need to create a custom EditorTemplate to render the tags as you need them.
MVC can already bind to a List, I would recommend using the built in technology that already does what you need.
I didn't notice any code about adding the binder, did you add your ModelBinder to the Binders?
protected void Application_Start()
{
ModelBinders.Binders.Add(typeof(IList<Tag>), new TagListModelBinder());
}
Related
I have a ViewModel that has a complex object as one of its members. The complex object has 4 properties (all strings). I'm trying to create a re-usable partial view where I can pass in the complex object and have it generate the html with html helpers for its properties. That's all working great. However, when I submit the form, the model binder isn't mapping the values back to the ViewModel's member so I don't get anything back on the server side. How can I read the values a user types into the html helpers for the complex object.
ViewModel
public class MyViewModel
{
public string SomeProperty { get; set; }
public MyComplexModel ComplexModel { get; set; }
}
MyComplexModel
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
....
}
Controller
public class MyController : Controller
{
public ActionResult Index()
{
MyViewModel model = new MyViewModel();
model.ComplexModel = new MyComplexModel();
model.ComplexModel.id = 15;
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// model here never has my nested model populated in the partial view
return View(model);
}
}
View
#using(Html.BeginForm("Index", "MyController", FormMethod.Post))
{
....
#Html.Partial("MyPartialView", Model.ComplexModel)
}
Partial View
#model my.path.to.namespace.MyComplexModel
#Html.TextBoxFor(m => m.Name)
...
how can I bind this data on form submission so that the parent model contains the data entered on the web form from the partial view?
thanks
EDIT: I've figured out that I need to prepend "ComplexModel." to all of my control's names in the partial view (textboxes) so that it maps to the nested object, but I can't pass the ViewModel type to the partial view to get that extra layer because it needs to be generic to accept several ViewModel types. I could just rewrite the name attribute with javascript, but that seems overly ghetto to me. How else can I do this?
EDIT 2: I can statically set the name attribute with new { Name="ComplexModel.Name" } so I think I'm in business unless someone has a better method?
You can pass the prefix to the partial using
#Html.Partial("MyPartialView", Model.ComplexModel,
new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ComplexModel" }})
which will perpend the prefix to you controls name attribute so that <input name="Name" ../> will become <input name="ComplexModel.Name" ../> and correctly bind to typeof MyViewModel on post back
Edit
To make it a little easier, you can encapsulate this in a html helper
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = string.IsNullOrEmpty(helper.ViewData.TemplateInfo.HtmlFieldPrefix) ?
name : $"{helper.ViewData.TemplateInfo.HtmlFieldPrefix}.{name}"
}
};
return helper.Partial(partialViewName, model, viewData);
}
and use it as
#Html.PartialFor(m => m.ComplexModel, "MyPartialView")
If you use tag helpers, the partial tag helper accepts a for attribute, which does what you expect.
<partial name="MyPartialView" for="ComplexModel" />
Using the for attribute, rather than the typical model attribute, will cause all of the form fields within the partial to be named with the ComplexModel. prefix.
You can try passing the ViewModel to the partial.
#model my.path.to.namespace.MyViewModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
Edit
You can create a base model and push the complex model in there and pass the based model to the partial.
public class MyViewModel :BaseModel
{
public string SomeProperty { get; set; }
}
public class MyViewModel2 :BaseModel
{
public string SomeProperty2 { get; set; }
}
public class BaseModel
{
public MyComplexModel ComplexModel { get; set; }
}
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
...
}
Then your partial will be like below :
#model my.path.to.namespace.BaseModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
If this is not an acceptable solution, you may have to think in terms of overriding the model binder. You can read about that here.
I came across the same situation and with the help of such informative posts changed my partial code to have prefix on generated in input elements generated by partial view
I have used Html.partial helper giving partialview name and object of ModelType and an instance of ViewDataDictionary object with Html Field Prefix to constructor of Html.partial.
This results in GET request of "xyz url" of "Main view" and rendering partial view inside it with input elements generated with prefix e.g. earlier Name="Title" now becomes Name="MySubType.Title" in respective HTML element and same for rest of the form input elements.
The problem occurred when POST request is made to "xyz url", expecting the Form which is filled in gets saved in to my database. But the MVC Modelbinder didn't bind my POSTed model data with form values filled in and also ModelState is also lost. The model in viewdata was also coming to null.
Finally I tried to update model data in Posted form using TryUppdateModel method which takes model instance and html prefix which was passed earlier to partial view,and can see now model is bound with values and model state is also present.
Please let me know if this approach is fine or bit diversified!
I am currently using model binding and ASP.NET MVC 3 and .NET 4.0.
View Model Class:
public class BasicViewModel
{
[Display(Name = #"Names")]
[Required(ErrorMessage = #"Names is required")]
[DisplayFormat(ConvertEmptyStringToNull = true)]
List<string> Names { get; set; }
[Display(Name = #"Email")]
[Required(ErrorMessage = #"Email is required")]
string Email { get; set; }
}
Controller
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult NameEmail( BasicViewModel basicModel)
{
// some manipulation of data
}
View in cshtml file (razor view engine)
// model declared here using #model BasivViewModel
// only required part shown labels part of code removed
#Html.TextBoxFor(model => model.Names)
...
#Html.TextBoxFor(model => model.Email)
...
The model binding provided by ASP.NET MVC binds the string Email to null if it is empty but binds the List Names to empty string (""). I want it to be null. I made the binding work using JavaScript by parsing the values of form fields on click of submit button. But i want the asp.net model binding to do this. Furthermore, it would be great if there is some field in Data Annotations class like Required for this functionality. I tried this Null Display Text Property and refer to the remarks section. Is there a solution or is this how it is implemented?. I am not sure whether i have understood this part of model binding correctly.
By default, if the field, representing an array, is in the html, the controller will receive an array of length 0. However, to make the array null, you can define a custom ModelBinder.
public class MyModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(List<string>))
{
HttpRequestBase request = controllerContext.HttpContext.Request;
// Check to see if any of the elements the array is not empty and
// returns null if they all are.
return request.Form.GetValues("Names").Any(x => !string.IsNullOrWhiteSpace(x)) ?
base.BindModel(controllerContext, bindingContext) :
null;
//You can also remove empty element from the array as well, by using
// a where clause
}
return base.BindModel(controllerContext, bindingContext);
}
}
Alternatively, you can also implement IModelBinder instead of DefaultModelBinder.
The next step is to register the custom binder in your Application_Start function in the Global.asax.cs file.
ModelBinders.Binders.Add(typeof(List<string>), new MyModelBinder());
This basically tells the mvc engine to use the MyModelBinder whenever the field is List<string>
To know more about modelbinder, goolge "MVC custom model binding". Let me know you go :)
I have a view model
public class ViewModel
{
public string Text { get; set; }
public string Name { get; set; }
}
The submited form provides only the Text value. I'd like to set the the Name property in my custom model binder.
So I derived my custom model binder from the DefaultModelBinder class and overrided the BindModel method.
The problem is that the BindModel method is called only for the incomning properties.
My question is how can I set the Name value in my cystom model binder ?
If you do not have an incoming value for Name, then you are not doing (custom) model binding. Instead, you want to supply some data in your model object before the action executes, right ? If so, use ActionFilter for it, override OnActionExecuting() and supply the data you need into action parameters.
public class SupplyNameAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionParameters != null)
{
foreach (KeyValuePair<string, object> parameter in filterContext.ActionParameters)
{
if (parameter.Key == "Name") parameter.Value == "Hey";
}
}
}
}
EDIT :
You can also use custom ValueProvider for default model binding, see
http://mgolchin.net/posts/19/dive-deep-into-mvc-ivalueprovider
I've looked at most of the ModelBinding examples but can't seem to glean what I'm looking for.
I'd like:
<%= Html.TextBox("User.FirstName") %>
<%= Html.TextBox("User.LastName") %>
to bind to this method on post
public ActionResult Index(UserInputModel input) {}
where UserInputModel is
public class UserInputModel {
public string FirstName {get; set;}
public string LastName {get; set;}
}
The convention is to use the class name sans "InputModel", but I'd like to not have to specify this each time with the BindAttribute, ie:
public ActionResult Index([Bind(Prefix="User")]UserInputModel input) {}
I've tried overriding the DefaultModelBinder but can't seem to find the proper place to inject this tiny bit of functionality.
The ModelName property in the ModelBindingContext object passed to the BindModel function is what you want to set. Here's a model binder that does this:
public class PrefixedModelBinder : DefaultModelBinder
{
public string ModelPrefix
{
get;
set;
}
public PrefixedModelBinder(string modelPrefix)
{
ModelPrefix = modelPrefix;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelName = ModelPrefix;
return base.BindModel(controllerContext, bindingContext);
}
}
Register it in your Application_Start like so:
ModelBinders.Binders.Add(typeof(MyType), new PrefixedModelBinder("Content"));
Now you will no longer to need to add the Bind attribute for types you specify use this model binder!
The BindAttribute can be used at the class level to avoid duplicating it for each instance of the UserInputModel parameter.
======EDIT======
Just dropping the prefix from your form or using the BindAttribute on the view model would be the easiest option, but an alternative would be to register a custom model binder for the UserInputModel type and explicitly looking for the prefix you want.
I'm using asp.net mvc and I'm trying to create a new Employee, in my form I use the Html.DropDown(...) to display a list of Departments to select from.
Ideally I would like MVC to just figure out which Department was selected (Id property is the value in dropdown), fetch it and set it in the incoming Employee object. instead I get a null value and I have to fetch the Department myself using the Request.Form[...].
I saw an example here: http://blog.rodj.org/archive/2008/03/31/activerecord-the-asp.net-mvc-framework.aspx but that doesn't seem to work with asp.net mvc beta
This is basic CRUD with a well-proven ORM.... need it really be so hard?
ASP.NET MVC does not know how to translate the DepartmentId form value to a Department.Load(DepartmentId) call. To do this you need to implement a binder for your model.
[ActiveRecord("Employees")]
[ModelBinder(EmployeeBinder]
public class Employee : ActiveRecordBase<Employee>
{
[PrimaryKey]
public int EmployeeId
{
get;
set;
}
[BelongsTo(NotNull = true)]
public Department Department
{
get;
set;
}
// ...
}
The EmployeeBinder is responsible for turning route/form data into an object.
public class EmployeeBinder : IModelBinder
{
#region IModelBinder Members
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// ...
if (controllerContext.HttpContext.Request.Form.AllKeys.Contains("DepartmentId"))
{
// The Department Id was passed, call find
employee.Department = Department.Find(Convert.ToInt32(controllerContext.HttpContext.Request.Form["DepartmentId"]));
}
// ...
}
#endregion
}
With this in place anytime an Employee is used as a parameter for an action the binder will be called.
public ActionResult Create(Employee employee)
{
// Do stuff with your bound and loaded employee object!
}
See This blog post for further information
I ported ARFetch to ASP.NET MVC a couple of weeks ago... maybe that could help you.