Form Binding with MVC and Reading Values from Model [duplicate] - c#

The Model:
class Address
{
public string City { get; set; }
public string Zip { get; set; }
}
The Controller:
[HttpPost]
public ActionResult GetAddress(Address model)
{
if (!String.IsNullOrEmpty(model.Zip))
{
model.City = GetCityByZip(model.Zip);
}
return View(model);
}
The View:
<div class="formrow">
#Html.LabelFor(model => model.City)
#Html.TextBoxFor(model => model.City)
#Html.ValidationMessageFor(model => model.City)
</div>
<div class="formrow">
#Html.LabelFor(model => model.Zip)
#Html.TextBoxFor(model => model.Zip)
#Html.ValidationMessageFor(model => model.Zip)
</div>
The problem is whenever the city is being modified, it never gets reflected on the view. During debugging, the model.City contains the correct value but it doesn't show up on view. Even something as simple as #Html.TextBoxFor(model => model.City) doesn't display the correct model.City value.

HtmlHelpers get the model values from the model state and not the model when you update and return the model. In order to update and return the model, add this line of code in your post method:
ModelState.Clear();
or you could set the value of city in the ModelState itself:
ModelState["City"].Value = GetCityByZip(model.Zip);

As Tommy noted, this is, somewhat counterintuitively, the correct behavior since form data submitted on post gets first priority when binding the data to the returned view. This makes some sense as the user is likely to have made a validation error when re-returning the same view and gets to resume their form entry as is without the problems of losing form input when restoring a page
One other option is to manually insert the value for the input
So instead of this:
#Html.TextBoxFor(model => model.City)
Do this instead:
<input type="text" name="City" value="#Model.City" />
* which will grab the value directly off the model
Or even better:
<input type="text" value="#Model.City"
name="#Html.NameFor(model => model.City)"
id="#Html.IdFor(model => model.City)" />
*Note: this won't bring in data-val attributes. If you're using them on this property for client side validation, you'll need to build list of data validation attributes for a given element
Additional Resources
HiddenFor not getting correct value from view model
HTML.HiddenFor is not updating on a postback
ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes

Related

MVC, post partial model [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 5 years ago.
I have object A which has a List<ObjectB>
ObjectB has several properties. Id, Mandatory, Name etc.
So I return a viewmodel (ObjectA) and have this in my razor:
#model ObjectA
<div>
<div>#Html.HiddenFor(m => ObjectA.ObjectC.ID)
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => ObjectA.ObjectC.Name)
</dt>
<dd>
#Html.DisplayFor(model => ObjectA.ObjectC.Name)
</dd>
</dl></div>
// display stuff from objectA
#using (Html.BeginForm())
{
foreach (var ft in ObjectA.ObjectB)
{
#Html.HiddenFor(c => ft.ID)
<div class="row">
<div class="col">
#if (ft.Mandatory)
{
#Html.CheckBoxFor(c => ft.Mandatory, new { id = ft.ID, disabled = "disabled" })
#Html.HiddenFor(c => ft.Mandatory)
}
else
{
#Html.CheckBoxFor(c => ft.Mandatory, new { id = ft.ID })
}
</div>
</div>
</div>
</div>
and in my Controller I tried as input parameter:
List<ObjectB> items
but it was null. Now I know I could try FormCollection which I did and found out that form.Get("ft.id") had the amount of items in de objectB list. same for mandatory. But I'd like it strong typed. Either:
1 object A with all subobjects of type B
2 a list/ienumerable of type objectB.
It's probably a small thing, but I can't see it right now.
edit my model:
public class ObjectA : BaseViewModel
{
public ObjectC DisplayOnly { get; internal set; }
public List<ObjectB> Features { get; set; }
}
My view: (see above)
My controller:
[Route("Details/{id:int}")]
[HttpPost]
public ActionResult Details(ObjectA vm)
{
if (ModelState.IsValid)
{
int hid = Convert.ToInt32(RouteData.Values["id"]);
}
}
With your current code, it will render the checkbox input elements with name attribute values set to ft.Mandatory. When the form is submitted, model binder has no idea where to map this to because it does not match with the view model property names/property hierarchy. For model binding to work, the names of input should match with the parameter class's property name.
You can use Editor Templates to handle this use case.
Create a folder called EditorTemplates in ~/Views/Shared or ~/Views/YourControllerName and create a new view and give it the same name as your class name which you are using to represent the data needed for the checkbox. In your case it will be ObjectB.cshtml
Now make this view strongly typed to OptionB and render the checkbox and hidden input.
#model OptionB
<div>
#Html.CheckBoxFor(x => x.Mandatory)
#Html.HiddenFor(c => c.Id)
</div>
Remember, the browser will not send the values of disabled form elements when the form is submitted.
Now in your main view, you can call EditorFor helper method.
#using (Html.BeginForm())
{
#Html.EditorFor(a=>a.OptionBList)
<button type="submit">Send form</button>
}
This will render the checkboxes with name attribute values like this (Assuming you have 3 items in OptionBList) , along with hidden inputs for checkboxes
Options[0].Mandatory
Options[1].Mandatory
Options[2].Mandatory
Now you can simply use OptionA as the parameter of your HttpPost action method
[HttpPost]
public ActionResult Create(OptionA model)
{
foreach(var item in model.OptionBList)
{
//check item.Mandatory and item.Id
}
//to do : return something
}

Why is my model not passed as parameter with form post

I'm having trouble understanding why my model is not passed along with its values to my controller when posting a form.
I have a view with a strongly typed model (UnitContract) that is being fetched from a webservice, that holds a set of values. In my action I'm trying to fetch int ID and bool Disabled fields that exists in my model. When debugging, I see that my model being passed from the form doesn't contain any values at all. What am I missing?
My view (UnitContract as strongly typed model):
...
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
My controller action:
[HttpPost]
public ActionResult EnableDisableUnit(UnitContract model)
{
var client = new UnitServiceClient();
if (model.Disabled)
{
client.EnableUnit(model.Id);
}
else
{
client.DisableUnit(model.Id);
}
return RedirectToAction("Index", model.Id);
}
Sounds like you need to add the fields from your model to your form. Assuming your view accepts a UnitContract model, then something like this should work:
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.Disabled)
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
Now when you submit the form, it should submit the fields to your model.
The MVC framework will use the data from the form to create the model. As your form is essentially empty, there is no data to create the model from, so you get an object without any data populated.
The only data that is sent from the browser in the request when you post the form, is the data that is inside the form. You have to put the data for the properties in the model as fields in the form, so that there is something to populate the model with.
Look into using #Html.HiddenFor(). Put these in your form, and the data you want to see posted back to your controller should be there. For example, your form would look something like...
<form class="pull-right" action="~/UnitDetails/EnableDisableUnit" method="POST">
#Html.HiddenFor(x => x.Id)
#Html.HiddenFor(x => x.IsDisabled)
<input type="submit" class="k-button" value="Enable Unit"/>
</form>
Let's say you have a model like this:
public class UnitContract
{
public int Id { get; set; }
public DateTime SignedOn { get; set; }
public string UnitName { get; set; }
}
Your view would look something like this:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>UnitContract</legend>
#Html.HiddenFor(model => model.Id)
<div class="editor-label">
#Html.LabelFor(model => model.SignedOn)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SignedOn)
#Html.ValidationMessageFor(model => model.SignedOn)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.UnitName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UnitName)
#Html.ValidationMessageFor(model => model.UnitName)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
In your controller:
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Edit(UnitContract unitContract)
{
// do your business here .... unitContract.Id has a value at this point
return View();
}
Hope this is helpful.

Set a default value for a dropdownlist from a list coming from another controller

I have this web page which shows the property of an object so that I may edit it, and I populate a DropDownListwith strings coming from another class.
Here's the method I use to populate the DropDownList:
private void PopulateOBJSetDropdownList(object selectedobj = null)
{
List<string> listOBJSetName = m_OBJSetManager.GetListOBJSets().OrderBy(x => x.m_Name)
.Select(x => x.m_Name.ToString())
.Distinct()
.ToList();
ViewBag.objSetID = new SelectList(listOBJSetName );
}
The ViewBagproperty does its job quite well, but the list comes empty when editing the item.
I'm pretty sure it is because of this line:
<div class="editor-label">
#Html.LabelFor(model => model.m_OBJSetID, "Obj Set")
</div>
<div class="editor-field">
#Html.DropDownList("objSetID ", String.Empty)
#Html.ValidationMessageFor(model => model.m_OBJSetID)
</div>
Because the dropdownlist is populated with String.Empty. This comes from a controller of objs.
Basically, I want this DropDownList to show me all the names of the objSets available, but I would also want it to have the correct objSet selected by default when editing an obj.
Does anyone can help? Am I clear enough? Thank you everyone.
i would avoid the viewbag. you might want to create a view model, and pass that instead.
but this can be done with the viewbag as well.
first, on your view, i would change your dropdown to the following
#Html.DropDownListFor(model => model.m_OBJSetID, DDLSelectitemListGoesHere)
if you do a view model, you can contain everything this page needs to use in one class, and send it to the view
public class MyViewModel{
public List<YourModel> theModel { get; set; }
public IEnumerable<SelectListItem> DDLItems { get; set; }
}
then on your view, at the top
#model PROJECTNAME.NAMESPACE.MyViewModel
and you can fill in the drop down like so
#Html.DropDownListFor(model => model.theModel.m_OBJSetID, model.DDLItems)
hopefully one of those will get you through

Validation on ASP.net MVC3 Model with Dynamic Properties

I am working on an app where I have a requirement to be able to load object properties at runtime from a database. The customer wants to be able to add attributes to the database and have them show up in the app. I am accomplishing this by giving my model a list of Field objects that contain a name, a type, and a value. This works well for displaying and editing project properties, but I'm having trouble with validation in the editor view. Thanks for your help.
I want to be able to do this in my Edit action:
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Normal view:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
</fieldset>
}
What I need to do:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
for(i = 1 to n) {
<div class="editor-label">
#Html.LabelFor(model => model.Fields[i].Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Fields[i].Value)
#Html.ValidationMessageFor(model => model.Fields[i].Value)
</div>
}
</fieldset>
}
Model:
public class Movie
{
public Movie()
{
this.Fields = new List<Field>();
}
public List<Movie> Movies { get; set; }
}
Field Class:
public class Field
{
public string Name { get; set; }
public Type Type { get; set; }
public object Value { get; set; }
}
I believe something like this should work:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
#Html.HiddenFor(model => model.ID)
#{int i = 0;}
#foreach(var field in model.Fields) {
var htmlFieldName = string.Format("Fields[{0}]", i);
<div class="editor-label">
<label for="#htmlFieldName">#field.Title</label>
</div>
<div class="editor-field">
#Html.EditorFor(model => field, null, htmlFieldName)
#Html.ValidationMessage(htmlFieldName)
</div>
}
</fieldset>
}
(Note that I made up how you're producing your label text, since using the actual value as the label didn't make sense to me).
The POST should end up with values like this:
ID=123
Fields[0]=Jaws
Fields[1]=VeggieTales
...
... and that should automatically bind to your Movie model, provided the model has, for example, a List<string> named Fields. If your model doesn't look like that, this should at least get you on the right track.
Update
In your comment, you explain that you are trying to produce an editor for an object. There are two major points of difficulty here:
MVC relies on the static type returned in the lambda expression you give to EditorFor to determine which kind of editor it should produce. To override this, you will need to provide a specific template name where my original suggestion shows you providing null:
#Html.EditorFor(model => field.Value, field.Type.Name, htmlFieldName + ".Value")
You'll probably need to tweak this to make it provide the right template name for types like Integer, but this should give you the general idea.
When posting back, there is no way for the server to know that Field[0] is an int, etc. You can either:
Provide hidden values to specify each type, and then use a custom model binder that can consume this information to build each Field based on the combined Type and Value.
Recreate the structure of the Movie object on the server side based on the Movie's ID, and then walk through each of it's Fields calling:
TryUpdateModel((dynamic)field, string.Format("Field[{0}]", i));
There are probably other options, but that's about all the time I'm willing to put into this today.

AllowHtml attribute not working

I have a model with this property:
[AllowHtml]
[DisplayName("Widget for Table")]
[StringLength(1000, ErrorMessage = "Maximum chars 1000")]
[DataType(DataType.Html)]
public object TableWidget { get; set; }
And here is the create methods in controller:
//
// GET: /Admin/Table/Create
public ActionResult Create(int id)
{
Season season = _seasonRepository.GetSeason(id);
var table = new Table
{
SeasonId = season.SeasonId
};
return View(table);
}
//
// POST: /Admin/Table/Create
[HttpPost]
public ActionResult Create(Table a)
{
if (ModelState.IsValid)
{
_tableRepository.Add(a);
_tableRepository.Save();
return RedirectToAction("Details", "Season", new { id = a.SeasonId });
}
return View();
}
And last here is my view:
#model Stridh.Data.Models.Table
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Fields</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.TableURL)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TableURL) #Html.ValidationMessageFor(model => model.TableURL)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.SortOrder)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SortOrder) #Html.ValidationMessageFor(model => model.SortOrder)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.TableWidget)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TableWidget) #Html.ValidationMessageFor(model => model.TableWidget)
</div>
<div class="editor-label invisible">
#Html.LabelFor(model => model.SeasonId)
</div>
<div class="editor-field invisible">
#Html.EditorFor(model => model.SeasonId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I add a "normal" message without html everything is saved OK, but when saving it says A potentially dangerous Request.Form...
Another strange thing is that I got this [AllowHtml] to work in another model class. I cant find why this is causing me troubble. Need your help. :-)
The way you are using AllowHtml should work. Make sure that you are not accessing the HttpRequest.Form collection anywhere else in your code (controller, filter, etc) as this will trigger ASP.NET Request Validation and the error you are seeing. If you do want access to that variable then you should access it via the following code.
using System.Web.Helpers;
HttpRequestBase request = .. // the request object
request.Unvalidated().Form;
I get the same problem and i solve it with the help of this post.
If you are on .net 4.0 make sure you add this in your web.config
<httpRuntime requestValidationMode="2.0" />
Inside the <system.web> tags
I had the same problem. My model class is named "GeneralContent" and has the property "Content". In my action method i used attribute like this:
public ActionResult Update(GeneralContent content)
when i renamed content argument to cnt, everything works well. I think MVC is confused when some attribude of model class has the same name as the argument in action method.
I also had this issue. I could not get a model property marked with [AllowHtml] to actually allow HTML, and instead encountered the same error you describe. My solution ended up being to mark the Controller action that accepts the posted model with the [ValidateInput(false)] attribute.
The answer that #marcind put me on the right track but my issue was that I was passing the FormCollection into the Controller method, so changing this...
public ActionResult Edit(MyClass myClass, FormCollection collection)
To this...
public ActionResult Edit(MyClass myClass)
Solved the problem.
Subsequently, I was able to access the heck out of the form collection with code like this without issue.
foreach (var key in Request.Form.AllKeys)
{
...
}
So, it was the passing the form collection parameter that caused the problem, not merely accessing the form collection.

Categories

Resources