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

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

Related

Mudblazor Select with multiselect and Fluentvalidation For-Expression

I am binding to a select field in multiselect mode and I ran into a problem with the "For" property of the select field".
Here is a code snippet
When using a select field an options type must be set and in this example it will be string. To make validation work the "For"-Property needs to be set and pointing to a valid property of the same type as the select fields option (and thats string).
But I am expecting a multiselect, so I am binding to an IEnumerable<string> in my model and the validation code is also set for this property.
I don`t have the necessary property to bind to and even if I did, the validation would not work as expected.
How do I make this work? I tried building a custom expression which would point to the first element of the array, but I am bad with expressions and couldn`t make it work.
#using FluentValidation
<MudCard>
<MudForm Model="#model" #ref="#form" Validation="#(testValidator.ValidateValue)" ValidationDelay="0">
<MudCardContent>
<MudSelect T="string" Label="Name"
HelperText="Pick your favorite name" MultiSelection="false" #bind-Value="model.Name" For="() => model.Name">
#foreach (var name in _names)
{
<MudSelectItem T="string" Value="#name">#name</MudSelectItem>
}
</MudSelect>
<MudSelect T="string" Label="Names"
HelperText="Pick your favorite names" MultiSelection="true" #bind-SelectedValues="model.Names"
#* For="() => model.Names" This needs to be set to make validation work *#
>
#foreach (var name in _names)
{
<MudSelectItem T="string" Value="#name">#name</MudSelectItem>
}
</MudSelect>
</MudCardContent>
</MudForm>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto" OnClick="#(async () => await Submit())">Order</MudButton>
</MudCardActions>
</MudCard>
#code {
[Inject] ISnackbar Snackbar { get; set; }
private string[] _names = new string[] {
"Toni", "Matthew", "David"
};
MudForm form;
TestModelFluentValidator testValidator = new TestModelFluentValidator();
TestModel model = new TestModel();
public class TestModel
{
public string Name { get; set; }
public IEnumerable<string> Names { get; set; }
}
private async Task Submit()
{
await form.Validate();
if (form.IsValid)
{
Snackbar.Add("Submited!");
}
}
/// <summary>
/// A standard AbstractValidator which contains multiple rules and can be shared with the back end API
/// </summary>
/// <typeparam name="OrderModel"></typeparam>
public class TestModelFluentValidator : AbstractValidator<TestModel>
{
public TestModelFluentValidator()
{
RuleFor(x => x.Name)
.NotEmpty();
RuleFor(x => x.Names).Must((parent, property) => property.Contains("Toni"))
.WithMessage("Toni not found in those names!");
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<TestModel>.CreateWithOptions((TestModel)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
}
Edit: Added code sample and trimmed unecessary code.
Mudblazor snippet.
Ok, so you can trick the component by introducing a dummy property and binding the multi-select component to it then testing its name during validation.
When the form component passes the dummy property name to the validation method, you change the passed dummy name to the name of your collection so it's matched when fluent validation kicks in.
Something like this:
#using FluentValidation
#using System.Reflection
<MudCard>
<MudForm Model="#model" #ref="#form" Validation="#(testValidator.ValidateValue)" ValidationDelay="0">
<MudCardContent>
<MudSelect T="string" Label="Name"
HelperText="Pick your favorite name" MultiSelection="false" #bind-Value="model.Name" For="() => model.Name">
#foreach (var name in _names)
{
<MudSelectItem T="string" Value="#name">#name</MudSelectItem>
}
</MudSelect>
<MudSelect T="string" Label="Names"
HelperText="Pick your favorite names" MultiSelection="true" #bind-Value="model.NameCollection" #bind-SelectedValues="model.Names"
For="#(() => model.NameCollection)"
>
#foreach (var name in _names)
{
<MudSelectItem T="string" Value="#name">#name</MudSelectItem>
}
</MudSelect>
</MudCardContent>
</MudForm>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto" OnClick="#(async () => await Submit())">Order</MudButton>
</MudCardActions>
</MudCard>
#code {
[Inject] ISnackbar Snackbar { get; set; }
private string[] _names = new string[] {
"Toni", "Matthew", "David"
};
MudForm form;
TestModelFluentValidator testValidator = new TestModelFluentValidator();
TestModel model = new TestModel();
public class TestModel
{
public string Name { get; set; }
public string NameCollection { get; set; }
public IEnumerable<string> Names { get; set; }
}
private async Task Submit()
{
await form.Validate();
if (form.IsValid)
{
Snackbar.Add("Submited!");
}
}
/// <summary>
/// A standard AbstractValidator which contains multiple rules and can be shared with the back end API
/// </summary>
/// <typeparam name="OrderModel"></typeparam>
public class TestModelFluentValidator : AbstractValidator<TestModel>
{
public TestModelFluentValidator()
{
RuleFor(x => x.Name)
.NotEmpty();
RuleFor(x => x.Names).Must((parent, property) => property.Contains("Toni"))
.WithMessage("Toni not found in those names!");
}
private async Task<bool> IsUniqueAsync(string email)
{
// Simulates a long running http call
await Task.Delay(2000);
return email.ToLower() != "test#test.com";
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
propertyName = propertyName == nameof(TestModel.NameCollection) ? nameof(TestModel.Names) : propertyName;
var result = await ValidateAsync(ValidationContext<TestModel>.CreateWithOptions((TestModel)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
}

How to send data from view to controller action in asp.net mvc?

I developed a custom HtmlHelper extension method but that data is not
posting Action.
HtmlHelper extension class:
public static class TestHtmlHelper
{
public static MvcHtmlString CreateControl(this HtmlHelper helper, string tagName, IDictionary<string, string> attributes)
{
var newTag = new TagBuilder(tagName);
newTag.MergeAttributes(attributes, true);
return MvcHtmlString.Create(newTag.ToString(TagRenderMode.Normal));
}
public static string Image(this HtmlHelper helper, string id, string url, string alternateText, object htmlAttributes)
{
// Create tag builder
var builder = new TagBuilder("img");
// Create valid id
builder.GenerateId(id);
// Add attributes
builder.MergeAttribute("src", url);
builder.MergeAttribute("alt", alternateText);
builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
// Render tag
return builder.ToString(TagRenderMode.Normal);
}
}
//View code
#using (Html.BeginForm("Go","Home",FormMethod.Post))
{
IDictionary<string, string> d = new Dictionary<string, string>();
d.Add("type", "text");
d.Add("id", "text1");
d.Add("required", "required");
#Html.Raw(Html.CreateControl("input", d))
#Html.Raw(Html.Image("image1", "/Images/bullet.png", "bullet", new { border = "4px" }))
d = null;
d = new Dictionary<string, string>();
d.Add("type", "submit");
d.Add("value", "Go");
#Html.Raw(Html.CreateControl("input", d))
<span></span>
d = null;
d = new Dictionary<string, string>();
d.Add("value", "text");
d.Add("id", "span1");
d.Add("text", "required");
#Html.Raw(Html.CreateControl("span", d))
}
// Controller code
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
[HttpPost]
public ActionResult Go(string test)
{
return Content(test);
}
I didn't get data in string test. I want to submit that data to DB.
To get input values as parameters for an MVC action, you need to include NAME for the input types.
I do not see NAME for any input types in your code.
Also I do not see TEST in your code
For example, if your form is -
#using (Html.BeginForm("Submit","Ajax",FormMethod.Post))
{
<input type="text" name="Rami"/>
<input type="submit" value="Go"/>
}
Output ScreenShot -
Put your inputs inside a form tag. All the input data will be sent to the controller on form submit. Please see the example:
View:
#using (Html.BeginForm("Search", "Events"))
{
#Html.TextBox("name")
<input type="submit" value="Search" />
}
Controller:
public class EventsController: Controller
{
public ActionResult Search(string name)
{
//some operations goes here
return View(); //return some view to the user
}
}
If you need to work with more complex types just lern how to use models in ASP.NET MVC. Here is short example:
Razor:
#model UserModel
#using (Html.BeginForm("Search", "Events"))
{
#Html.TextBoxFor(m => m.FirstName)
#Html.TextBoxFor(m => m.LastName)
<input type="submit" value="Search" />
}
Controller:
public class EventsController: Controller
{
public ActionResult Search(UserModel model)
{
//some operations goes here
return View(); //return some view to the user
}
}
Model (C#):
public class UserModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

Partial views inside my asp.net mvc will ignore all the model data annotations

I have the following action method which render the following partial view:-
public ActionResult CreateVMNetwork(int vmid)
{
AssignIps vmips = new AssignIps()
{
TechnologyIP = new TechnologyIP() { TechnologyID = vmid},
IsTMSIPUnique = true,
IsTMSMACUnique = true
};
return PartialView("_CreateNetworkInfo",vmips);
}
The partial view is :-
model TMS.ViewModels.AssignIps
#if (this.ViewContext.FormContext == null)
{
this.ViewContext.FormContext = new FormContext();
}
#using (Ajax.BeginForm("CreateVMNetwork", "VirtualMachine", new AjaxOptions
{
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "networktable",
LoadingElementId = "loadingimag",
HttpMethod= "POST",
OnSuccess="submitform"
}))
{
#Html.ValidationSummary(true)
#Html.HiddenFor(model=>model.TechnologyIP.TechnologyID)
#Html.AntiForgeryToken()
<div>
<span class="f">IP Address</span>
#Html.EditorFor(model => model.TechnologyIP.IPAddress)
#Html.ValidationMessageFor(model => model.TechnologyIP.IPAddress)
<input type="CheckBox" name="IsTMSIPUnique" value="true" #(Html.Raw(Model.IsTMSMACUnique ? "checked=\"checked\"" : "")) /> IP Unique. |
<span class="f"> MAC Address</span>
#Html.EditorFor(model => model.TechnologyIP.MACAddress)
#Html.ValidationMessageFor(model => model.TechnologyIP.MACAddress)
<input type="CheckBox" name="IsTMSMACUnique" value="true" #(Html.Raw(Model.IsTMSMACUnique ? "checked=\"checked\"" : "")) /> MAC Unique.
</div>
<input type="submit" value="Save" class="btn btn-primary"/>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
but all the data annotation on the view model will not fire on the partial view , the AssignIps view model class is :-
namespace TMS.ViewModels
{
public class AssignIps
{
public TechnologyIP TechnologyIP { get; set; }
public bool IsTMSIPUnique { get; set; }
public bool IsTMSMACUnique { get; set; }
}
}
and he TechnologyIP model class is :-
namespace TMS.Models
{
[MetadataType(typeof(TechnologyIP_Validation))]
public partial class TechnologyIP
{}}
namespace TMS.Models
{
public class TechnologyIP_Validation
{
[Required]
public string IPAddress { get; set; }
but the [Required] data annotation on the TechnologyIP model, will not fire on the partial view,, can anyone adovce please?
Thanks.
Sections defined in your layout view don't get populated when using PartialViewResult, so the unobtrusive validation scripts are not being added.
You can test this by creating an action on your controller that just returns a partial view, and then call this via $.get() (using jQuery, obviously). If you use console.log(), you can inspect the result in Firebug. You could also use something like Fiddler; regardless, if you look at the HTML returned you will not see the script references anywhere.
Exactly, a wrong practice is to have:
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
in the partial view, try to do it just putting
#Scripts.Render("~/bundles/jqueryval")
and it should work!

ASP.NET MVC how to achieve to use the same model with different error message

I am having this issue at the moment, I had address model (use required attribute to decorate) which can be used more than once on the same page, one is billing address and the other one is shipping address. when validation failed, I'd like to have suffix in front of my generic error message indicate which address is required e.g. "{0} - address line 1 required", either billing or shipping
Here is my model
public class AddressBaseModel
{
[Display(Name="Address line 1")]
[Required(ErrorMessageResourceType = typeof(ModelValidation), ErrorMessageResourceName = "AddrLine1Required")]
public string AddressLine1 { get; set; }
[Display(Name="Address line 2")]
[Required(ErrorMessageResourceType = typeof(ModelValidation), ErrorMessageResourceName = "AddrLine2Required")]
public string AddressLine2 { get; set; }
[Display(Name="Address line 3")]
public string AddressLine3 { get; set; }
[Display(Name="Address line 4")]
public string AddressLine4 { get; set; }
}
}
Here is the code segment I used in my page
<fieldset class="space-bottom">
<legend>Please enter your home address</legend>
<div id="home_fields">
#Html.EditorFor(m => m.HomeAddress)
</div>
</fieldset>
<fieldset class="space-bottom">
<legend>Please enter your delivery address</legend>
<div id="delivery_fields">
#Html.EditorFor(m => m.DeliveryAddress)
</div>
</fieldset>
Thanks
Personally I use the FluentValidation.NET library instead of Data Annotations as it makes things so much easier and provides a lot more power. Here's an example of how to achieve your goal using this ilbrary.
Create a new ASP.NET MVC 3 project using the default Visual Studio template
Install the FluentValidation.MVC3 NuGet package.
Add the following line to Application_Start:
ModelValidatorProviders.Providers.Add(
new FluentValidationModelValidatorProvider(
new AttributedValidatorFactory()
)
);
Define the following models:
public class AddressBaseModel
{
public string AddressLine1 { get; set; }
}
[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
public AddressBaseModel HomeAddress { get; set; }
public AddressBaseModel DeliveryAddress { get; set; }
}
And the following Validators:
public class AddressBaseModelValidator : AbstractValidator<AddressBaseModel>
{
private readonly string _addressType;
public AddressBaseModelValidator(string addressType)
{
_addressType = addressType;
RuleFor(x => x.AddressLine1)
.NotEmpty()
.WithMessage(string.Format("{0} - address line 1 required", addressType));
}
}
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.HomeAddress)
.SetValidator(new AddressBaseModelValidator("billing"));
RuleFor(x => x.DeliveryAddress)
.SetValidator(new AddressBaseModelValidator("shipping"));
}
}
Modify the HomeController:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
HomeAddress = new AddressBaseModel(),
DeliveryAddress = new AddressBaseModel()
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
And the corresponding Index.cshtml view:
#model MyViewModel
#using (Html.BeginForm())
{
<fieldset class="space-bottom">
<legend>Please enter your home address</legend>
<div id="home_fields">
#Html.EditorFor(m => m.HomeAddress)
</div>
</fieldset>
<fieldset class="space-bottom">
<legend>Please enter your delivery address</legend>
<div id="delivery_fields">
#Html.EditorFor(m => m.DeliveryAddress)
</div>
</fieldset>
<input type="submit" value="Register" />
}
You could create a custom attribute that does the dynamic formatting for you. You would just tag your address fields with the Address attribute like this:
[Address]
public string AddressLine1 { get; set; }
You would need to add a property in the AddressBaseModel where you tell the system what type of address this is (you would set this to "Billing" or "Shipping" when you instantiate the view model right before you pass the view model to the View in the controller get action):
public string AddressType { get; set; }
A custom attribute like this should work (I haven't tested it, I wrote it just now). This automatically gets the address type you specified when you create the model instance and formats it with the display name of the address field).
public class AddressAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "{0} - {1} required";
public AddressAttribute()
: base(DefaultErrorMessage) { }
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
if (!base.IsValid(value))
{
// get the property called "AddressType" from the model so we know if it's Billing or Shipping
var addressType = validationContext.ObjectInstance.GetType()
.GetProperty("AddressType")
.GetValue(validationContext.ObjectInstance, null);
// use the display name of the address field in the error message
return new ValidationResult(
string.Format(DefaultErrorMessage, addressType, validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
This should work:
[Required(ErrorMessage = "The Address 2 is required.")]

ModelBinding asp.net MVC List

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.

Resources