Which section of ASP.NET MVC should be overrided to be able to change the name of fields (Model Properties) in output to a custom things? Something like below:
<input id="IsActive" name="IsActive" type="checkbox" />
to this:
<input id="MYCUSTOMFORMAT-IsActive" name="MYCUSTOMFORMAT-IsActive" type="checkbox" />
This custom formatting shouldn't break down anything such client-side and server-side validation.
Thanks in advance ;)
More Info
I know that we can do this in Display/Editor Templates but i think this will cause infringement.
You can override the name in the HtmlAttributes parameter of the Html.TextBoxFor (etc) helper methods - such as:
#Html.TextBoxFor(o=>o.FirstName, new {id = "customId_originalId"})
However since you are changing this on the client side, the server side will not be able to recognize these changed names and will not bind properly unless you write your own model binder. As such it probably isn't aware of the server side validations to link this to either so again you are stuck handling this in a custom rolled manner.
A simple workaround for this if you want a constant prefix, but not necessarily the right way to do things, would be to use a viewmodel that contains your properties. in this case:
public class CustomViewModel
{
public bool IsActive {get;set;}
}
[HttpGet]
public ActionResult MyView()
{
CustomViewModel MYCUSTOMFORMAT = new CustomViewModel();
return View(MYCUSTOMFORMAT);
}
[HttpPost]
public ActionResult MyView(CustomViewModel MYCUSTOMFORMAT){
return View(MYCUSTOMFORMAT);
}
This will give you an Id of MYCUSTOMFORMAT.IsActive.
The proper way to do this would likely be overriding the default model binder and how it handles translating names to properties but I don't know model binders well enough to give you much direction on this.
Related
I have to create a textbox from a viewbag property in MVC. I could do the mapping like #Html.TextBox("Comments", (string)ViewBag.Comments) but how do I read it back when the page is posted to the server. It is not filling the viewbag property back. I am very new to MVC so maybe don't understand the concept totally .
Thanks
Your ViewBag wont get updated from your view and that is not the way to get data from your form. Rather, you should either use strongly typed model binding to read your data from your Action Method or you can simply check for the key in your Forms data. I am showing you example for both:
Example 1: Strongly typed model binding.
[HttpPost]
public ActionResult MyAction(string comments)
{
// the Comment from the text box.
return View();
}
Example 2: Reading from Posted Data:
[HttpPost]
public ActionResult MyAction()
{
// the Comment from the text box.
string comments = Request.Form["comments"];
return View();
}
I hope, you will like to use the Example 1.
Anyway, the best practice would be to bind your View with a Model class and use HtmlHelper for generating the text box like :
Html.EditorFor(model => model.Comments)
Where your Model class contains a property named Comments.
And your action method should accept the same Model type as argument. Here is an example:
[HttpPost]
public ActionResult MyAction(MyModel model)
{
string comments = model.Comments;
}
And you should bind your View with the model of type MyModel.
I can understand that, as you are new to MVC, this may not make clear sense now, so, I would suggest you to check out some basic MVC tutorial. You can start from here : http://www.asp.net/mvc/tutorials
I have a ViewModel which holds my InputModel as a property.
I use HTML helpers like so: #Html.TextBoxFor(m => m.InputModel.UserId) for example.
The problem is that this generates: <input name="InputModel.UserId" />
And the model-binder does not populate the properties in my HttpPost method:
public ActionResult Index(InputModel model)
I see there is a generic version of HTML helpers, I was thinking maybe I can leverage that somehow, but I havn`t yet figured out how. Or maybe there is a completley different way around this, that I'm not seeing since I am new to MVC.
You could either change your action to
public ActionResult Index(ViewModel model)
in which case the model.InputData would be populated or use Bind attribute
public ActionResult Index([Bind(Prefix="InputModel")] InputModel model)
If you're using the Html.TextBoxFor() type methods, you may well end up with Form controls that have dots in their names, like this:
<input type="text" name="Contact.FirstName" id="Contact_FirstName" />
If you want MVC to map those named fields to parameters in your controller (as opposed to an object parameter or whatever), you have to get the parameter names right. What to do about the dots?
Neither this:
[HttpPost]
public ActionResult FooAction(string firstName)
not this:
[HttpPost]
public ActionResult FooAction(string contact_FirstName)
seem to work.
Edit: Having a suitable object parameter would work (eg see clicktricity's answer), but I'm looking for a way to do it with named value parameters.
I have found another way, a kind of hack because I believe this is misuse of BindAttribute, to associate firstName parameter with Contact.FirstName input element:
[HttpPost]
public ActionResult FooAction([Bind(Prefix="Contact.FirstName")]string firstName)
This for sure works with ASP.NET MVC 1.
Depending on the other form controls, you should be able to have the MVC default model binder construct a Contact object for you. Then the signature of your action method would be:
[HttpPost]
public ActionResult FooAction(Contact contact)
Then the Contact.FirstName (and any other fileds) will be bound correctly
As Clicktricity suggests in comments you may use
[HttpPost]
public ActionResult FooAction(FormCollection form)
{
firstName = form["Contact.FirstName"];
}
I have a question regarding how to get the white- and black-listing feature of the MVC controller's UpdateModel/TryUpdateModel to work on individual properties of child objects. For example, lets say I have a questionnaire collecting details about the person filling out the form and about his or her company.
My [simplified] form fields would be named, for example:
YourName
YourEmail
Company.Name
Company.Phone
Now in my model, lets say I don't want Company.ID or Company.IsPremiumMember to be tampered with, so I'd like to exclude them from the model-binding. I have tried a combination of whitelisting, blacklisting, and both in order to get this to work. I have not had any success. Here is what I am running into:
When I explicitly include in my whitelist the same four fieldnames I wrote above, the entire Company does not get bound (i.e., questionnaire.Company is left null) unless I also include "Company" in my whitelist. But then this has the undesirable effect of binding the ENTIRE company, and not just the two properties I want.
So, I then tried to include Company.ID and Company.IsPremiumMember in my blacklist, but this seems to be trumped by the whitelist and does not filter out these properties "after the fact" I suppose.
I know that there are other ways to express the "bindability", such as via the [Bind] attribute on members, but this is not ideal as I would like to have the same model classes used in other situations with different binding rules, such as allowing an admin to set whatever properties she would like.
I expect an obvious answer is that I should write my own model binder, and I've already starting trying to look into how to perhaps do this, but I was really hoping to use an "out-of-the-box" solution for what (in my opinion) seems like a very common scenario. Another idea I'm pondering is to fabricate my own ValueProvider dictionary to hand to the UpdateModel method, but again, something I'd rather avoid if there is an easier way.
Thanks for any help!
-Mike
Addendum #1
Here are the fields I present on my form:
YourName
YourEmail
Company.Name
Company.Phone
And here is what a black hat sends my way:
YourName=Joe+Smith&YourEmail=joe#example.com&Company.Name=ACME+Corp&Company.Phone=555-555-5555&Company.CreditLimit=10000000
(be sure you notice the extra parameter tacked on there at the end!)
And here is the problem:
As I originally posted, it doesn't seem possible (using the default model binder) to prevent CreditLimit from being set---it's either the entire Company or nothing---without some big workaround. Am I wrong?
Addendum #2
I'm pretty much convinced now that the simple objective I have is not possible "out of the box." My solution has been to walk through the posted form fields and construct my own ValueProvider dictionary, thus whitelisting the fields I want to allow, and handing that to UpdateModel.
Addendum #3
I still have not yet checked out AutoMapper, but with something like that at hand, the solution of creating some ViewModels/DTOs to handle this type of complex whitelisting---plus the ability to easily attach the same server-side validation (FluentValidation) I'm already using on my domain objects---seems a viable solution. Thank you everyone!
In general, the best way to go is to create view-models, models built specifically for your views. These models are not domain objects. They are data-transfer objects, built to transfer data from your controller actions to your view templates. You can use a tool like AutoMapper painlessly to create/update a domain model object from your view-model object or to create/update a view-model object from your domain model.
I have worked around this problem by making the action accepting two objects (the parent and the child object)
Example:
Suppose we have the following model:
public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
public Company Comapany { get; set; }
}
public class Company
{
public int Phone { get; set; }
}
You build your form like this:
<form action="Home/Create" method="post">
<label for="Employee.Name">Name</label>
<%=Html.TextBox("Employee.Name") %><br />
<label for="Employee.Name">Age</label>
<%=Html.TextBox("Employee.Age") %><br />
<label for="Employee.Name">Comapany Phone</label>
<%=Html.TextBox("Company.Phone") %><br />
<input type="submit" value="Send" />
</form>
Then build a "Create" action that accept two objects one of type Employee and the other of type Comapny and assign the Company object to the Employee.Company property inside the action:
public ActionResult Create(Employee employee,Company company)
{
employee.Comapany = company;
UpdateModel(employee);
return View();
}
I hope this help you.
Edit:
public ActionResult Create(Employee employee,Company company)
{
employee.Comapany = company;
UpdateModel(employee,new[] {"Name","Email","Phone"});
return View();
}
Have you tried using the following?:
public ActionResult Create([Bind(Exclude="PropertyToExclude1, PropertyToExclude2")] Employee employee)
{
//action code here
}
or use Include instead of Exclude to List what fields can be bound rather than which can't
I have two checkboxes in my MVC app which are both boolean / bit fields. "NotifyEmail" & "NotifySMS".
Whenever I post back to the server and an error occurs, the state of the checkbox is gone event though I set attempted values.
Why not something as simple as this?
<%=Html.CheckBox("AgreeToRules", (Request.Form["AgreeToRules"] ?? string.Empty).Contains("true"))%>
Update: This is fixed in RC2.
This question together with this one,
Html.Checkbox does not preserve its
state in ASP.net MVC,
addresses a very undocumented feature of the ASPNET.MVC RC1. I have been searching around for hours to find a good answer, but there are very few to find.
There is a bug, apparently, which prohibit checkboxes and radiobuttons to maintain their state from ModelState. As we also know by now, is that these two controls are handled specially by the Html helpers.
The best I managed to come up with, was to build my own ViewBinder:
From a very simple view:
<h2>Keep checkbox value between posts</h2>
<% using (Html.BeginForm("update", "checkbox")) {%>
<p><%= Html.CheckBox("a") %></p>
<p><%= Html.CheckBox("b") %></p>
<p><%= Html.TextBox("dummy") %></p>
<input type="submit" value="update" />
<% } %>
Associated with an equally simple controller:
public class CheckboxController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Update()
{
var binder = new ViewBinder(ViewData, ValueProvider);
binder.UpdateBooleanValues("a", "b");
binder.UpdateMissingValues();
return View("Index");
}
}
And a simple class to make it all work:
internal class ViewBinder
{
private readonly IDictionary<string, ValueProviderResult> valueProvider;
private readonly ViewDataDictionary viewData;
public ViewBinder(ViewDataDictionary viewData, IDictionary<string, ValueProviderResult> valueProvider)
{
this.valueProvider = valueProvider;
this.viewData = viewData;
}
public void UpdateMissingValues()
{
foreach (var key in valueProvider.Keys)
{
if (ValueIsMissing(key)) UpdateValue(key);
}
}
public void UpdateBooleanValues(params string[] names)
{
foreach (var name in names)
{
UpdateValue(name, BooleanValueFor(name));
}
}
private bool BooleanValueFor(string name)
{
return valueProvider[name].AttemptedValue != "false";
}
private bool ValueIsMissing(string key)
{
return viewData.ContainsKey(key) == false;
}
private void UpdateValue(string key)
{
var value = valueProvider[key].AttemptedValue;
UpdateValue(key, value);
}
private void UpdateValue(string key, object value)
{
viewData[key] = value;
}
}
MVC does not have ViewState like WebForms does - this means that you now have the responsibility of maintaining those values.
This will require that you store whether or not the checkbox was checked and then applying that setting to the checkbox again before the page is rendered to the browser in your View.
Adam,
Assuming you understand that MVC does not include server functionality to handle Asp.Net post-backs, If you need to send a message back to the server that informs your application that the check box was checked, then it's probably best to do that with Javascript and an Ajax request. .Net MVC includes a the JQuery Javascript library so doing this may be easier then you think. Here's an SO post that kind of covers how to use checkboxes correctly in MVC.
Otherwise, realize that MVC doesn't support post-back, and like Andrew said above, doesn't support Asp.Net view-state either. However, you could go back to the old school way of view-state and use a regular HTML hidden input element with a little bit of javascript to maintain your state.
That all being said, you might want to take a minute to read this article. A lot of MVC frameworks assume that you will be using the Post -> Redirect -> Get pattern for handling user input in your web forms. If you continue to use your current pattern of post-back and view state, you may run into more problems in the future like the one you're currently trying to solve.
How are you specifying your checkbox HTML? Binding will require a hidden input element in addition to the checkbox input element. Html.Checkbox will handle this for you, or you can study how it does it and do it yourself.
Using an HTML Helper such as Html.CheckBox will persist the state automatically on POST.
Andrei Rinea is partially right. But I have done - is use the helpers and pass back into the page the previous values into the DataClass (accessed by Model. etc). It works well.
Others may find this solution useful:
Maintain state of a dynamic list of checkboxes in ASP.NET MVC