Handling model state and validation in a complex view using multiple partials in ASP.NET MVC - asp.net-mvc

I'm having a little trouble in getting my head around how to break up my views and actions into manageable chunks in ASP.NET MVC, and I've tried searching but I'm still none the wiser.
In order to try and just get my head around this particular aspect I've created a little test project where I try to understand the situation using the example of a login form and register form on the same page. My view model for this looks as below:
public class LoginOrRegisterModel
{
public LoginModel Login { get; set; }
public RegisterModel Register { get; set; }
public LoginOrRegisterModel()
{
this.Login = new LoginModel();
this.Register = new RegisterModel();
}
}
public class LoginModel
{
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
}
public class RegisterModel
{
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
}
I then started with thinking about the main action.
public ActionResult Index()
{
return View(new LoginOrRegisterModel());
}
...and view...
#model MvcSandbox.Models.LoginOrRegisterModel
#{
ViewBag.Title = "Index";
}
<h2>Login Or Register</h2>
#Html.Partial("Login", model: Model.Login)
#Html.Partial("Register", model: Model.Register)
...with partial views...
#model MvcSandbox.Models.LoginModel
#{
ViewBag.Title = "Login";
}
<h2>Login</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.EditorFor(model => model)
<button>Login</button>
}
#model MvcSandbox.Models.RegisterModel
#{
ViewBag.Title = "Register";
}
<h2>Register</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.EditorFor(model => model)
<button>Register</button>
}
I found that I had to make sure the properties of the LoginOrRegisterModel weren't null otherwise I got the error: The model item passed into the dictionary is of type 'MvcSandbox.Models.LoginOrRegisterModel', but this dictionary requires a model item of type 'MvcSandbox.Models.LoginModel'.
That's fine so far, although not very useful at the moment as both forms have the same field names and ids and both post back to an index page that does nothing.
HTML source:
<h2>Login Or Register</h2>
<h2>Login</h2>
<form action="/Membership" method="post"><div class="editor-label"><label for="UserName">UserName</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The UserName field is required." id="UserName" name="UserName" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span></div>
<div class="editor-label"><label for="Password">Password</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span></div>
<button>Login</button>
</form>
<h2>Register</h2>
<form action="/Membership" method="post"><div class="editor-label"><label for="UserName">UserName</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The UserName field is required." id="UserName" name="UserName" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span></div>
<div class="editor-label"><label for="Password">Password</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span></div>
<button>Register</button>
</form>
Anyway what I then looked to do, as I kind of wanted to keep the logic separate for each post, was make each form post to a different action. And this is where I think I'm going horribly wrong.
Essentially if validation fails I figured I needed to do something to try and actually build back up the model state when loading the page but I kind of got to what I have below and I'm kind of lost as to what approach I should be taking instead.
public class MembershipController : Controller
{
//
// GET: /Membership/
public ActionResult Index()
{
if (TempData["ModelState"] != null)
ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);
return View(new LoginOrRegisterModel());
}
[HttpPost]
public ActionResult Login(LoginModel model)
{
if (ModelState.IsValid)
{
// do something
}
TempData["ModelState"] = ModelState;
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// do something
}
TempData["ModelState"] = ModelState;
return RedirectToAction("Index");
}
}
...with the views...
#model MvcSandbox.Models.LoginModel
#{
ViewBag.Title = "Login";
}
<h2>Login</h2>
#using (Html.BeginForm("Login", "Membership"))
{
#Html.ValidationSummary(true)
#Html.EditorFor(model => model)
<button>Login</button>
}
#model MvcSandbox.Models.RegisterModel
#{
ViewBag.Title = "Register";
}
<h2>Register</h2>
#using (Html.BeginForm("Register", "Membership"))
{
#Html.ValidationSummary(true)
#Html.EditorFor(model => model)
<button>Register</button>
}
The problem is because each model has fields of the same name validation is shown on both forms regardless of which is submitted, and when I've tried using an HtmlFieldPrefix I seem to get no validation at all.
Any advice on how I can break up my actions and views into manageable and maintainable chunks without giving myself this headache over model state and validation would be greatly appreciated.
UPDATE:
I've changed approach slightly to use partial actions which seems to improve things, code below:
public ActionResult Index()
{
return View();
}
public ActionResult Login()
{
if (TempData["LoginModelState"] != null)
ModelState.Merge((ModelStateDictionary)TempData["LoginModelState"]);
return View(new LoginModel());
}
[HttpPost]
public ActionResult Login(LoginModel model)
{
if (ModelState.IsValid)
{
// do something
}
TempData["LoginModelState"] = ModelState;
return RedirectToAction("Index");
}
public ActionResult Register()
{
if (TempData["RegisterModelState"] != null)
ModelState.Merge((ModelStateDictionary)TempData["RegisterModelState"]);
return View(new RegisterModel());
}
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// do something
}
TempData["RegisterModelState"] = ModelState;
return RedirectToAction("Index");
}
My index view is now:
#{
ViewBag.Title = "Index";
}
<h2>Login Or Register</h2>
#Html.Action("Login")
#Html.Action("Register")
And login and register:
#model MvcSandbox.Models.LoginModel
<h2>Login</h2>
#using (Html.BeginForm("Login", "Membership"))
{
#Html.ValidationSummary(true)
#Html.EditorFor(model => model)
<button>Login</button>
}
#model MvcSandbox.Models.RegisterModel
<h2>Register</h2>
#using (Html.BeginForm("Register", "Membership"))
{
#Html.ValidationSummary(true)
#Html.EditorFor(model => model)
<button>Register</button>
}
Now to me this has the advantage over the previous approach that it actually seems to work a little more and gets rid of need for the quite frankly horribly messy idea of the LoginOrRegisterModel which may be fine for such a simple example but would get messy very quickly as things got more complex and UIs got refactored with potentially lots of refactoring of models and potentially code as well as just views.
I really get the impression some replacing of the default model binder to have some sort of model binding based on a descriminator and having the controller action working as some sort of command processor such that it would fire off the correct handler based which partial was posted would be better, and resolve the refresh issue that comes from redirection as mentioned by Mystere Man below.

Don't use a redirect, as you lose model state. I know you are trying to fix that by passing model state in TempData, but the problem is that TempData is only valid for one access afterwards. If the users presses F5 or hits the refresh button, the model state is gone and things are even more messed up.
In general, only use TempData for things like showing an alert or message once to a user.
Using partial views like this is always a pain, particularly when trying to post a child model to a different form, as you have found out. The way I would do it is to use EditorTemplates instead of Partials, and then post your composite view model to both methods.
public ActionResult Login(LoginOrRegisterModel model)
{
if (ModelState.IsValid) {
// access only the Login properties, do same for Register
}
return View("LoginOrRegister", model)
}
In your view
...
#Html.EditorFor(m => m.LoginModel)
#Html.EditorFor(m => m.RegisterModel)
in ~/Views/Membership/EditorTemplates/LoginModel.cshtml (and RegisterModel.cshtml)
#model MvcSandbox.Models.LoginModel
// Not sure why you were setting the title in a partial view,
// particularly when you had two of them on a single page
<h2>Login</h2>
#using (Html.BeginForm("Login", "Membership"))
{
#Html.ValidationSummary(true)
#Html.EditorFor(model => model)
<button>Login</button>
}
The advantage of this is that it will correctly bind the parent model to the correct child model, and you can access whatever you want from that point forward.

You certainly can have multiple models and partials on a single "page" and post to separate actions - this is an approach that I used in this scenario. Also I've used this with "popup" dialogs and it works fine.
Regarding models, you basically either need to have a composite model (LoginOrRegisterModel) in the "parent" view then either use that same model in each "child" view (just use the bits you need in each) OR you have separate child models as properties off the parent model (LoginModel, RegisterModel). These are both valid approaches which will work but I think you get better separation with the second option (2 distinct child models).
Regarding posting, I would use AJAX to perform the separate form posts and then return partial views from the controller's individual post actions in the event of an error. I wouldn't attempt to use redirect to try and re-render the whole page due to the kind of problems you've already found.

Related

Update a field in view based on the model in ASP.NET MVC

I need to do a calculator in ASP.NET MVC.
For the beginning I want to receive the value from the input field in controller and prefix it with the string "123". At the end I will process the expresion received and return the result.
I have the following model:
namespace CalculatorCloud.Models {
public class Calculator
{
public string nr { get; set; }
} }
In the view I am using the model:
#model CalculatorCloud.Models.Calculator
#{
ViewBag.Title = "Calculator";
}
#using (Html.BeginForm("Index", "Home"))
{
<div>
<div class="header">
#Html.TextBoxFor(m => m.nr, new { #id = "nr"})
<input type="button" id="C" name="C" value="C" />
<input type="button" id="back" name="back" value="<-" />
[...]
<div class="sum">
<input type="submit" value="=" />
</div>
</div>
}
The controller is like this:
namespace CalculatorCloud.Controllers
{
public class HomeController : Controller
{
Calculator model = new Calculator();
public ActionResult Index(string nr)
{
model.nr = "123" + nr;
return View(model);
}
}
}
I have the following problem: when pressing on submit button I am expecting to be displayed on the textbox the value from that was previously in the textbox, prefixed with the string "123".
But now it is kept the value from the textbox without the string "123".
Can someone help me with this?
Thank you! :)
If you want to modify the value of a model property in a postback action you will need to remove it from the ModelState:
public ActionResult Index(string nr)
{
ModelState.Remove("nr");
model.nr = "123" + nr;
return View(model);
}
The reason for this is that Html helpers such as TextBoxFor will first look at the value present in the ModelState and then in your view model property when rendering the value. This is by design.

What should I do when more than one submit cases exist

I have a form where I fill in a blank and then save it, which takes me to the read only version of the entry I've just saved. At this step I have 2 options:
Either "Confirm" or "Cancel". Both actions will update some values in the database, so they're both good candidates for submit button(HttpPost, of course). I know this shouldn't be tied to a particular web development technology as
all-in-all it's all about Request and Response, but still I'll talk in ASP.NET MVC "language" as that is what I'm using now. So far, the only idea that has come to my mind is to have a separate submit buttons with different names and on the server side check for the name and act accordingly. But this seems a bit ugly to me as I might have to have a huge and universal action method. Do you have any better approach? Maybe MVC has some built in way for this purpose?
huge and universal action method
That's entirely your responsibility. You write action methods to map HTTP requests to business logic.
public class PostModel<T>
{
public string SubmitAction { get; set; }
public T TheModel { get; set; }
}
public ActionResult HandleSomePost(PostModel<Foo> model)
{
switch (model.SubmitAction.ToUpperInvariant())
{
case "SAVE":
_yourBLL.Save(model.TheModel);
break;
case "CANCEL":
_yourBLL.Cancel(model.TheModel);
break;
default:
throw new ArgumentException("SubmitAction");
}
return View();
}
Then you can handle both submit buttons from this form:
#using (Html.BeginForm("HandleSomePost", "TheController", FormMethod.Post))
{
#Html.EditorFor(m => m.TheModel)
<input type="submit" name="SubmitAction" value="Cancel">
<input type="submit" name="SubmitAction" value="Save">
}
However, this will quickly become a mess. You can also use attributes to let submit buttons map to action methods (though for simplicity I removed the "action:" prefix used for localization, be sure to read that Q&A and the linked blog):
[HttpPost]
[MultipleSubmit(SubmitAction = "Save")]
public ActionResult Save(Foo model)
{
_yourBLL.Save(model);
return View();
}
[HttpPost]
[MultipleSubmit(SubmitAction = "Cancel")]
public ActionResult Cancel(Foo model)
{
_yourBLL.Cancel(model);
return View();
}
You could do this with 2 forms, each posting to different action methods:
<form method="post" action="/cancelAction">
<input name="id" type="hidden" value="some-id-value">
<input type="submit" value="Cancel">
</form>
<form method="post" action="/confirmAction">
<input name="id" type="hidden" value="some-id-value">
<input type="submit" value="Confirm">
</form>
Or using MVC Razor syntax since you mentioned it:
#using (Html.BeginForm("cancelAction", "MyController", FormMethod.Post))
{
#Html.HiddenFor(model => model.ID)
<input type="submit" value="Cancel">
}
#using (Html.BeginForm("confirmAction", "MyController", FormMethod.Post))
{
#Html.HiddenFor(model => model.ID)
<input type="submit" value="Confirm">
}
From the information above it seems hard to pin down the exact use case here, however it might not be neccessary to have both a post for the confirm and cancel.
Consider using the submit event for "confirmation" and then just call the cancel event using normal HTTP-GET and passing the item that needs to be cancelled's ID? Then you can either handle the confirm or cancel events in either of the Actions directly or do a RedirectToAction to the HugeUniversalAction.
#using (Html.BeginForm("Confirm","ExampleController",FormMethod.Post))
{
<input type/>
<input type="submit" value="confirm"/>
#Html.ActionLink("Cancel", "ExampleController", id = Model.Id)
}
Then in your controller you can call the larger universal method.
public ActionResult Cancel(int id)
{
// Cancel directly
// or
return RedirectToAction("HugeUniversalAction", new { confirm = "false", id = id });
}
[HttpPost]
public ActionResult Confirm(Foo model)
{
// Confirm Directly
// or
return RedirectToAction("HugeUniversalAction", new { confirm = "true", id = model.Id });
}
Then handle the two paths in whichever way you need to in your HugeUniversalAction
public ActionResult HugeUniversalAction(int id, confirm = false){ // If confirm confirm it else cancel it }

MVC - How to simply do something at button click?

I am new to ASP.NET MVC. I was used to program using just ASP.NET. I want to do something when the user clicks a button. I am not understanding what do I do at Controller.
I have this View:
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#Html.Action("RegisterHour", "Register")
and the Controller:
public ActionResult RegisterHour()
{
//TODO: do anything
return View("Index");
}
When I click at the button, I would like to stay in the same page (it can reload). I simply want to do something like go to the database and create a new entity, and then show a messagebox.
This code causes an stackoverflow. What am I missing? What do I have to change at Controller?
Thanks.
The line
#Html.Action("RegisterHour", "Register")
actually makes a request to the server in order to render the result of the "RegisterHour" action. So in order to render the result of the action the first time, you need to make a request to the same action. This causes an endless loop, hence the stack overflow.
You are thinking in events, rather than thinking about HTTP and the web.
ASP.NET MVC embraces the HTTP protocol and you have to know what happens when a request is made and how HTML is rendered.
If you want to implement the scenario you are describing, you have to put a form on the page. The button can submit that form by making a POST request to some other action, and then the action can render a view showing the result. But for simply showing a message box, I don't think it is a good idea.
This is how desktop apps work, not web apps. You are trying to fit a square peg through a round hole.
there are many ways to get back to the controller. without post back look into ajax calls. the simplest way is the post back. put your view in a form tag either html or html.beginform
#using (Html.BeginForm()){
<input type="submit" value="submit"/>
}
as #Chuck mentioned on your controller then have a post method with the same name as the get you show
[HttpPost]
public ActionResult RegisterHour()
{
//TODO: do anything
return View(model);
}
the #Html.Action that you have will return a url so that is put inside another element. something like
<a src="#Html.Action("Action", "Controller")">Click Here</a>
if you want to stay in the same page instead of
return View("index");
use
return View();
Edit:
If you want a complete code of a do something stuff here you are:
Model
public ActionResult MyModel()
{
[Required]
public int propriety1 { get; set; }
}
Controller
public ActionResult DoSomething()
{
var model = new MyModel();
return View(model);
}
[HttpPost]
public ActionResult DoSomething(MyModel model)
{
if(ModelState.isValid){
//DO something
}else{
return View(model);
}
}
View
#model Models.MyModel
#using (Html.BeginForm())
{
#Html.LaberlFor(m=>m.propriety1) #Html.TextBoxFor(m=>m.propriety1)
<input type="submit" value="Click me" />
}
Create an endpoint and have the form submit to it.
UI Code
<form action="/Registration/RegisterHour" >
<p>
<label>Username</label>
<input type="text" name="username" />
</p>
<p>
<label>First Name</label>
<input type="text" name="firstname" />
</p>
<p>
<label>Last Name</label>
<input type="text" name="lastname" />
</p>
<p>
<label>Password</label>
<input type="text" name="password" />
</p>
<p>
<label>Confirm</label>
<input type="text" name="confirm" />
</p>
<input type="submit" value="Save" />
</form>
Model
public class Registration
{
public string Username {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
public string Password {get; set;}
public string Confirm {get; set;}
}
Typically you'll have two of the same endpoints, one is for the get, the other is for the post.
public class RegistrationController : Controller
{
//Get
[HttpGet]
public ActionResult RegisterHour()
{
//TODO: do anything
return View("Index");
}
//Post
[HttpPost]
public ActionResult RegisterHour(Registration newUser)
{
if(Model.IsValid)
{
//Save user to the database
userRepository.AddUser(newUser);
//load success screen.
return RedirectAction("SuccessfulRegistration");
}
//If Model is invalid handle error on the client.
return View("Index");
}
}

How can i maintain objects between postbacks?

I'm not so experienced using MVC. I'm dealing with this situation. Everything works well until call the HttpPost method where has all its members null. I don't know why is not persisting all the data on it.
And everything works well, because I can see the data in my Html page, only when the user submit the information is when happens this.
[HttpGet]
public ActionResult DoTest()
{
Worksheet w = new Worksheet(..);
return View(w);
}
[HttpPost]
public ActionResult DoTest(Worksheet worksheet)
{
return PartialView("_Problems", worksheet);
}
This is class which I'm using.
public class Worksheet
{
public Worksheet() { }
public Worksheet(string title, List<Problem> problems)
{
this.Title = title;
this.Problems = problems;
}
public Worksheet(IEnumerable<Problem> problems, WorksheetMetadata metadata, ProblemRepositoryHistory history)
{
this.Metadata = metadata;
this.Problems = problems.ToList();
this.History = history;
}
public string Title { get; set; }
public List<Problem> Problems { get; set; } // Problem is an abstract class
public WorksheetMetadata Metadata { get; set; }
public ProblemRepositoryHistory History { get; set; }
}
And my razor view.... the razor view shows successfully my view. I realized something rare, please note in my 5 and 6 lines that I have HiddenFor method, well if I used that, when calls HTTPPOST persists the data, I don't know why.
#model Contoso.ExercisesLibrary.Core.Worksheet
<div id="problemList">
<h2>#Html.DisplayFor(model => model.Metadata.ExerciseName)</h2>
#Html.HiddenFor(model => model.Metadata.ExerciseName)
#Html.HiddenFor(model => model.Metadata.ObjectiveFullName)
#for (int i = 0; i < Model.Problems.Count; i++)
{
<div>
#Html.Partial(Contoso.ExercisesLibrary.ExerciseMap.GetProblemView(Model.Problems[i]), Model.Problems[i])
</div>
}
</div>
UPDATE
I'm using a static class to get the view name, but as I'm testing I'm just using this Partial view
#model Contoso.ExercisesLibrary.AbsoluteArithmetic.Problem1
<div>
<span style="padding:3px; font-size:18px;">#Model.Number1</span>
<span style="padding:5px; font-size:18px;">+</span>
<span style="padding:5px; font-size:18px;">#Model.Number2</span>
<span style="padding:5px; font-size:18px;">=</span>
<span style="font-size:18px">
#Html.EditorFor(model => model.Result, new { style = "width:60px; font-size:18px;" })
#Html.ValidationMessageFor(model => model.Result)
</span>
</div>
#section Scripts {
}
And here the user do the post
#model Contoso.ExercisesLibrary.Core.Worksheet
<form method="post">
#Html.Partial("_Problems", Model)
<input type="submit" value="Continue" />
</form>
The Model Binder will 'bind' or link input fields on your view to the model. It will not bind display fields (like label), that is why you need the HiddenFor it will add an <input type="hidden" which will then be bound to the Model when you Post.
You can use 'TempData'. It is used to pass data from current request to subsequent request means incase of redirection.
This link also helps you.
TempData
SO Tempdata
Make sure your form tag looks like the following, for instance the controller name, action method, the form method and an id for the form. I am referring to the #using statement. In my case the controller name is RunLogEntry, the action method is Create and the id is form.
Normal Post from View to Controller
#using (Html.BeginForm("Create", "RunLogEntry", FormMethod.Post, new { id = "form", enctype = "multipart/form-data" }))
{
<div id="main">
#Html.Partial("_RunLogEntryPartialView", Model)
</div>
}
If you want to post via Jquery, could do the following:
$.post("/RunLogEntry/LogFileConfirmation",
$("#form").serialize(),
function (data) {
//this is the success event
//do anything here you like
}, "html");
You must specify a form with correct attribute in your view to perform post action
<form action="Test/DoTest" method="post">
...
</form>
or
#using(Html.BeginForm("DoTest", "Test", FormMethod.Post)) {
...
}
The second is recommended.
Put your entire HTML code under:
#using(Html.BeginForm())
tag.

Not able to bind html.checkbox on form post

So I have a view model call ProductViewModel which has a list of sites where a product can be produced. I am trying to figure out how to show this on a form using a checkbox for the user to select the sites where the product can be produced. Seems straight forward, right? Well it doesn't work like I want/need. Hoping someone can help guide me to the correct way to do this.
My classes:
public class ProductViewModel
{
public List<Sites> SiteList {get; set;}
public string ProductName {get; set;}
public int ProductId {get; set;}
public User ProductOwner{get; set;}
}
public class Sites
{
public int SiteId {get; set;}
public string SiteName {get; set;}
public bool IsSelected {get; set;}
}
Part of my view:
#Html.LabelFor(m=>m.Sites):
#foreach (var site in Model.Sites)
{
#Html.CheckBox("Sites", site.IsSelected, new { value = site.SiteName })
#Html.Label(site.SiteName)
}
When using #Html.Checkbox() I see the following output in the html from the browser:
<input checked="checked" id="Sites" name="Sites" type="checkbox" value="Miami" />
<input name="Sites" type="hidden" value="false" />
I understand the hidden field but what I really need is to get the value for the selected item. So I need to get back the list with Miami in it. I don't need the false/true thing that the html helper seem to want to send (i.e. Miami=true)
So instead I tried this.
#for(int id=0; id < Model.Sites.Count(); id++)
{
<input type="checkbox" id="#Model.Sites[id].SiteName" name="Sites[#id]" value="#Model.BoxingSites[id].SiteName" #(Model.Sites[id].IsSelected ? #"checked=""checked""": "") />
#Html.Label(Model.Sites[id].SiteName)
}
And the output is:
<input type="checkbox" id="Miami" name="Sites[0]" value="Miami" checked="checked" />
<label for="Miami">Miami</label>
In both of these cases I am not able to get the binder to map the form values to the Product.Sites list when posting to the action.
The action is like this:
[HttpPost]
public ActionResult Edit(ProductViewModel Product)
{
//Does something with the form data.
}
The other values (ProductName etc...) map fine.
What am I doing wrong? I feel I am missing something as this should be easier due to how MVC simplifies so many other form handling situations.
Thanks in advance...
How about using an editor template instead of struggling with loops:
#model ProductViewModel
#using (Html.BeginForm())
{
... some other form fields
#Html.LabelFor(x => x.SiteList)
#Html.EditorFor(x => x.SiteList)
<input type="submit" value="Create" />
}
and inside the corresponding editor template ~/Views/Shared/EditorTemplates/Sites.cshtml:
#model Sites
<div>
#Html.HiddenFor(x => x.SiteId)
#Html.CheckBoxFor(x => x.IsSelected)
#Html.LabelFor(x => x.SiteName)
</div>
Now not only that your view code is much more clean but proper names will be generated for the input fields so that the model binder will be able to bind the selected values back in the POST action.
[HttpPost]
public ActionResult Create(ProductViewModel model)
{
...
}
Here is what is working for me.
// View Model
[Display(Name="Boolean Property")]
[UIHint("booleancheckbox"]
public bool? booleanProperty;
View
// View
#Html.EditorFor(m => m.booleanProperty, new { #onclick = "Toggle(this);" })
Editor Template - add some more code to handle null values
// Editor Template booleancheckbox.cshtml
#model bool?
#{
labelText = ViewData.ModelMetadata.DisplayName != null ?
ViewData.ModelMetadata.DisplayName :
ViewData.ModelMetadata.PropertyName;
}
<label for="#ViewData.ModelMetadata.PropertyName">#labelText
#Html.CheckBox(string.Empty, Model.Value, ViewContext.ViewData)
</label>

Resources