ModelBinding asp.net MVC List - asp.net

I have the following class:
public class Movie
{
string Name get; set;
string Director get; set;
IList<String> Tags get; set;
}
How do I bind the tags properties? to a simple imput text, separated by commas. But only to the controller I'am codding, no for the hole application.
Thanks

You could start with writing a custom model binder:
public class MovieModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
{
if (propertyDescriptor.Name == "Tags")
{
var values = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
if (values != null)
{
value = values.AttemptedValue.Split(',');
}
}
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
}
}
and then applying it to a particular controller action which is supposed to receive the input:
public ActionResult Index([ModelBinder(typeof(MovieModelBinder))] Movie movie)
{
// The movie model will be correctly bound here => do some processing
}
Now when you send the following GET request:
/index?tags=tag1,tag2,tag3&name=somename&director=somedirector
Or POST request with an HTML <form>:
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(x => x.Name)
#Html.EditorFor(x => x.Name)
</div>
<div>
#Html.LabelFor(x => x.Director)
#Html.EditorFor(x => x.Director)
</div>
<div>
#Html.LabelFor(x => x.Tags)
#Html.TextBoxFor(x => x.Tags)
</div>
<input type="submit" value="OK" />
}
The Movie model should be bound correctly in the controller action and only inside this controller action.

Related

Client-side unobtrusive validation for EditorTemplate when sending IEnumerable<T>

There is a possibility that I just wasn't able to find the solution, or lack thereof, through my searches. Maybe I didn't word it properly, but my problem is trying to get client-side unobtrusive validation to fire on an EditorTemplate when I pass an IEnumerable<T> to it. My setup:
ParentModel.cs
[Validator(typeof(ParentModelValidator))]
public class ParentModel
{
...
public IEnumerable<ChildModel> ChildModels { get; set; }
}
public class ParentModelValidator : AbstractValidator<ParentModel>
{
public ParentModelValidator()
{
RuleFor(x => x.ChildModels).SetCollectionValidator(new ChildModelValidator());
}
}
ChildModel.cs
[Validator(typeof(ChildModelValidator))]
public class ChildModel
{
public bool IsRequired { get; set; }
public string foo { get; set; }
}
public class ChildModelValidator : AbstractValidator<ChildModel>
{
public ChildModelValidator ()
{
RuleFor(x => x.foo)
.NotEmpty().When(x => x.IsRequired);
}
}
ParentShell.cshtml
#model ParentModel
#using (Html.BeginForm("Index", "Application", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.Partial("_Parent", Model)
#Html.EditorFor(m => m.ChildModels)
<input type="submit" value="submit" />
}
The _Parent partial just contains a handful of common, reusable #Html.TextBoxFor(m => m.bar) and #Html.ValidationMessageFor(m => m.bar) fields.
ChildModel.cshtml EditorTemplate
#model ChildModel
#Html.TextBoxFor(m => m.foo)
#if (Model.IsRequired)
{
#Html.ValidationMessageFor(m => m.foo)
}
The client-side validation fires for all fields in the _Parent partial, but I get nothing when IsRequired is true and should have a ValidationMessageFor. Is this a known constraint of the client-side unobtrusive validation with EditorTemplate that receives an IEnumerable<T>? Is it due to the indexer that gets inserted during rendering (ChildModels[0].foo and ChildModels_0__.foo)?
From the documentation for FluentValidation
Note that FluentValidation will also work with ASP.NET MVC's client-side validation, but not all rules are supported. For example, any rules defined using a condition (with When/Unless), custom validators, or calls to Must will not run on the client side
Because you have used a .When condition, you will not get client side validation.
Using an alternative such as foolproof [RequiredIfTrue] attribute will work for a simple property, but not for a complex object or collection.
You can solve this by creating you own custom ValidationAttribute that implements IClientValidatable
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ComplexRequiredIfTrue : ValidationAttribute, IClientValidatable
{
private const string _DefaultErrorMessage = "The {0} field is required.";
public string OtherProperty { get; private set; }
public ComplexRequiredIfTrue(string otherProperty) : base(_DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
PropertyInfo otherProperty = validationContext.ObjectInstance.GetType().GetProperty(OtherProperty);
bool isRequired = (bool)otherProperty.GetValue(validationContext.ObjectInstance, null);
if (isRequired)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "complexrequirediftrue"
};
clientValidationRule.ValidationParameters.Add("otherproperty", OtherProperty);
return new[] { clientValidationRule };
}
}
and the associated script
function nameToIndex (value) {
return value.replace(/[\[\].]/g, '_');
}
(function ($) {
$.validator.addMethod("complexrequirediftrue", function (value, element, params) {
// We need to get the prefix of the control we are validating
// so we can get the corresponding 'other property'
var name = $(element).attr('name');
var index = name.lastIndexOf('.');
var prefix = nameToIndex(name.substr(0, index + 1));
var otherProp = $('#' + prefix + params);
if (otherProp.val() == "True" && !value) {
return false;
}
return true;
});
$.validator.unobtrusive.adapters.addSingleVal("complexrequirediftrue", "otherproperty");
}(jQuery));
then apply it to you property
public class ChildModel
{
public bool IsRequired { get; set; }
[ComplexRequiredIfTrue("IsRequired")]
public string foo { get; set; }
}
and in the EditorTemplate, include #Html.HiddenFor(m => m.IsRequired)
#model ChildModel
#Html.HiddenFor(m => m.IsRequired)
#Html.TextBoxFor(m => m.foo)
#Html.ValidationMessageFor(m => m.foo)
Edit: Further to comments, if the controller is
model.ChildModels = new List<ChildModel>() { new ChildModel() { IsRequired = true }, new ChildModel() };
return View(model);
then the html generated when the submit button is clicked is:
<input data-val="true" data-val-required="The IsRequired field is required." id="ChildModels_0__IsRequired" name="ChildModels[0].IsRequired" type="hidden" value="True">
<input class="input-validation-error" data-val="true" data-val-complexrequirediftrue="The foo field is required." data-val-complexrequirediftrue-otherproperty="IsRequired" id="ChildModels_0__foo" name="ChildModels[0].foo" type="text" value="">
<span class="field-validation-error" data-valmsg-for="ChildModels[0].foo" data-valmsg-replace="true">The foo field is required.</span>
<input data-val="true" data-val-required="The IsRequired field is required." id="ChildModels_1__IsRequired" name="ChildModels[1].IsRequired" type="hidden" value="False">
<input data-val="true" data-val-complexrequirediftrue="The foo field is required." data-val-complexrequirediftrue-otherproperty="IsRequired" id="ChildModels_1__foo" name="ChildModels[1].foo" type="text" value="">
<span class="field-validation-valid" data-valmsg-for="ChildModels[1].foo" data-valmsg-replace="true"></span>
Note the form did not submit and the error message was displayed for the first textbox

MVC metatdataprovider and html helpers

I have my own implementation of the metadataprovider, in it a check for my custom attribute and get the metadata from the database.
public class EntityPropertyMetadataAttribute: Attribute
{
[MaxLength(256)]
public string EntityFullName { get; set; }
[MaxLength(64)]
public string PropertyName { get; set; }
public string DisplayName { get; set; }
public string DisplayFormatString { get; set; }
public string EditFormatString { get; set; }
public object DefaultValue { get; set; }
}
Now I observed the following if I have a View like this:
<div class="editor-label">
#Html.LabelFor(model => model.Id)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Id)
#Html.ValidationMessageFor(model => model.Id)
</div>
The function
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
gets called 3 times for property Id, if I remove one for example:
<div class="editor-field">
#Html.EditorFor(model => model.Id)
#Html.ValidationMessageFor(model => model.Id)
</div>
it gets called 2 times.
Now when I use this:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
#Html.EditorForModel(Model)
<p>
<input type="submit" value="Create" />
</p>
}
CreateMetadata gets called a whopping 22 for each property in the model.
That's not really what you want performance wise. Probably I should hook up the DB code (currently inside CreateMetadata method) somewhere else.
any suggestion appreciated.
cheers
Ok finally on the right track with a bit of help from MVC extensions
http://mvcextensions.codeplex.com/SourceControl/changeset/view/f71bcadf0e76#Trunk%2fMvcExtensions%2fModelMetadata%2fModelMetadataRegistry.cs
In my case I shouldn't override
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType,
Func<object> modelAccessor, Type modelType, string propertyName)
from DataAnnotationsModelMetadataProvider but override the Get methods from AssociatedMetadataProvider like this one:
public override IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType)
And put hte database calls there.

Asp.net MVC 3 "parameter conversion from type 'System.String' to type failed" when using SelectList Dropdown box

I'm stuck and after looking this up for hours, I think I need more eyeballs.
The situation is the following:
It's an Asp.Net MVC3 with Entity Framework 4 project. And I have two classes. One ConfigurationFile and another one Action. There is a one-to-many relationship between the two. Here is a simplified view on the code:
public class ConfigurationFile
{
[Key, Required]
[Column(TypeName = "uniqueidentifier")]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
[Column(TypeName = "uniqueidentifier")]
[Required]
public Guid ActionId { get; set; }
public virtual Models.Action Action { get; set; }
}
public class Action
{
[Key, Required]
[Column(TypeName = "uniqueidentifier")]
public Guid Id { get; set; }
[Required]
public string ActionValue { get; set; }
}
Then I want to create a new ConfigurationFile, and are my two controller methods (and at this point, this is 95% Visual Studio 10 generated code):
// db is my context class.
//
// GET: /Configuration/Create
public ActionResult Create()
{
ViewBag.ActionId = new SelectList(db.Actions, "Id", "ActionValue");
return View();
}
//
// POST: /Configuration/Create
[HttpPost]
public ActionResult Create(Models.ConfigurationFile configurationfile)
{
if (ModelState.IsValid)
{
configurationfile.Id = Guid.NewGuid();
db.ConfigurationFiles.Add(configurationfile);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ActionId = new SelectList(db.Actions, "Id", "ActionValue", configurationfile.ActionId);
return View(configurationfile);
}
And here is a snippet of my Create view:
#model MyProject.Areas.ConfigurationFile.Models.ConfigurationFile
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Configuration File</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ActionId, "Action")
</div>
<div class="editor-field">
#Html.DropDownList("ActionId", String.Empty)
#Html.ValidationMessageFor(model => model.ActionId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I open the Create page, I can clearly see that my dropdown for the Action class is fine (correct value -- the Action.Id -- and text -- Action.ActionValue -- ) but when I submit the form, I have the following error: "The parameter conversion from type 'System.String' to type 'MyProject.Models.Action' failed because no type converter can convert between these types."
Help please !!
Right now MVC has no way of connecting your dropdownlist from your view to the ActionId of your ConfigurationFile object.
I would try replacing this line:
#Html.DropDownList("ActionId", String.Empty)
for this
#Html.DropDownListFor(model => model.ActionId, ViewBag.ActionId)
Other than that, I can't think of what else you might have done wrong.
I hope that helps!
This is how I did to circumvent the problem. I just changed my controller this way:
Models.Action act = db.Actions.Find(configurationfile.ActionId);
ModelState.Clear();
configurationfile.Action = act;
TryValidateModel(configurationfile);
And after that, the validation was Ok. A bit hacky (and another possible hit on the DB), but at least, I can keep going.

ASP.NET MVC3: Interaction between Partial View and Main View

I have a partial view for contact. Currently the index view shows this partial view for contact details. There is a save button inside the partial view to save the edited data. There is a validation for age while saving the edited data. This much is working fine.
Whenever user edit age and save it, I need to show the corresponding horoscope prediction on the main view. How do we achieve it?
public class ContactEntity
{
public int ContactID { get; set; }
public string ContactName { get; set; }
[Range(18, 50, ErrorMessage = "Must be between 18 and 50")]
public int ContactAge { get; set; }
}
public class AgeHoroscope
{
public int Age { get; set; }
public string HoroscopePrediction { get; set; }
}
//Home Controller
namespace MYContactEditPartialViewTEST.Controllers
{
public class HomeController : Controller
{
List<AgeHoroscope> horoList = new List<AgeHoroscope>()
{
new AgeHoroscope{Age=16,HoroscopePrediction="You are confused"},
new AgeHoroscope{Age=26,HoroscopePrediction="You are very brilliant"},
new AgeHoroscope{Age=27,HoroscopePrediction="You are practical"}
};
public ActionResult Index()
{
AgeHoroscope selectedHoro = horoList[1];
return View(selectedHoro);
}
}
}
//Contact Controller
namespace MYContactEditPartialViewTEST.Controllers
{
public class ContactController : Controller
{
public PartialViewResult MyContactDetailEdit()
{
Thread.Sleep(500);
return PartialView(GetContact());
}
[HttpPost]
public PartialViewResult MyContactDetailEdit(string conatcclick)
{
//Save to database
Thread.Sleep(500);
return PartialView(GetContact());
}
private ContactEntity GetContact()
{
ContactEntity contactEntity = new ContactEntity();
contactEntity.ContactID = 1;
contactEntity.ContactName = "Lijo";
contactEntity.ContactAge = 26;
return contactEntity;
}
}
}
//Index.cshtml
#model MYContactEditPartialViewTEST.AgeHoroscope
#{
ViewBag.Title = "Index";
}
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<h2>
Index</h2>
<div>
<a>Your age is <b>#Html.DisplayFor(x => x.Age) </b>and the prediction is <b>" #Html.DisplayFor(x => x.HoroscopePrediction)
" </b></a>
<br />
</div>
<div style="border: 3px solid Teal">
#Html.Action("MyContactDetailEdit", "contact")
</div>
// MyContactDetailEdit.cshtml
#model MYContactEditPartialViewTEST.ContactEntity
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<h3>MyContactDetailEdit PARTIAL</h3>
<div>
#Html.HiddenFor(x => x.ContactID)
<br />
<div style="font-weight:bold">
Name:
<br />
</div>
#Html.DisplayFor(x => x.ContactName)
<br />
<br />
<div style="font-weight:bold">
Age
<br />
</div>
#Html.EditorFor(x => x.ContactAge)
#Html.ValidationMessageFor(model => model.ContactAge)
<br />
<br />
</div>
<input type="submit" id="saveButton" value="Save" />
}
READING
ASP.Net MVC Passing multiple parameters to a view
ASP.Net MVC 3 RC2, Partial Views Form Handling
I would like just use jQuery to do ajax post and then change the parent view client side directly
you'll need to create a new ViewModel to do this. This ViewModel (IndexViewModel.cs) would look something like this (I'm guessing at this):
public class IndexViewModel
{
public int ContactID { get; set; }
public string ContactName { get; set; }
public int ContactAge { get; set; }
public string HoroscopePrediction { get; set; }
}
you'd then use it in your controller index action (and view):
#model MYContactEditPartialViewTEST.IndexViewModel
the idea being that you'd populate the HoroscopePrediction in a join between ContactEntity and AgeHoroscope (or via Linq etc) and thus show each line in the index as a complete object (showing contact and horoscope).
As data is posted to "HomeController" and "Index" action, so changes are reflected when you change age in View.
Try to modify the home controller as follows,then it will work as expected.
1) Instead of having a list of AgeHoroscope, we can have a dictionary of age and prediction.
2) Create two Index Action for HttpGet and HttpPost as follows.
public class HomeController : Controller
{
Dictionary<int, string> AgePred = new Dictionary<int, string>()
{
{16,"You are confused"},
{26,"You are very brilliant"},
{27,"You are practical"}
};
[HttpGet]
public ActionResult Index()
{
AgeHoroscope selectedHoro = new AgeHoroscope() { Age = 26 };
selectedHoro.HoroscopePrediction = AgePred[selectedHoro.Age];
return View(selectedHoro);
}
[HttpPost]
public ActionResult Index(AgeHoroscope model,ContactEntity entity)
{
model.Age = entity.ContactAge;
model.HoroscopePrediction = AgePred[entity.ContactAge];
return View(model);
}
}

populate view based on multi-level model

Suppose, I have models:
public class Person
{
public sting Name {get;set;}
public List<Book> Books {get;set;}
}
public class Book
{
public sting NameBook {get;set;}
}
How represent view for Edit method based on Person model (MVC 3)?
You may try something along the lines of:
#model Person
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(x => x.Name)
#Html.EditorFor(x => x.Name)
</div>
#Html.EditorFor(x => x.Book)
<button type="submit">Edit</button>
}
and then you will define an editor template for the Book type (~/Views/Shared/EditorTemplates/Book.cshtml) which will be rendered for each element of the Book property collection (which by the way you would have named Books in order to follow standard conventions) on your view model:
#model Book
<div>
#Html.LabelFor(x => x.NameBook)
#Html.EditorFor(x => x.NameBook)
</div>
As far as your controller actions are concerned, it's pretty standard stuff:
public ActionResult Edit(int id)
{
var person = _personRepository.Get(id);
return View(model);
}
[HttpPost]
public ActionResult Edit(Person person)
{
if (!ModelState.IsValid)
{
return View(person);
}
_personRepository.Update(person);
return RedirectToAction("Success");
}

Resources