I have a new MVC 4 Application with a fairly basic View/Controller. The associated Model contains a couple properties that I've mapped to Hidden form fields. When the Page renders the first time (e.g. via the HttpGet Action) it all looks fine. But when the form is Post'ed by selecting the Submit button the resulting Model presented to the Action no longer has the Hidden field values set. Here is a walkthrough of the particulars.
Here is a sample of the Model:
public class Application
{
public bool ShowSideBars { get; set; }
}
Here is the initial Controller *Action* (which seems to work fine):
[HttpGet]
public ActionResult Application()
{
var model = Request.ParseFromQueryString<Application>();
model.ShowSideBars = true;
return View(model);
}
This maps to the View as follows:
<fieldset>
#Html.HiddenFor(m => m.ShowSideBars)
...
</fieldset>
This results in the following mark-up to be rendered inside the fieldset:
<input data-val="true" data-val-required="The ShowSideBars field is required." id="ShowSideBars" name="ShowSideBars" type="hidden" value="True" />
Note: I sure wish I knew why MVC has decided to add the '... field is required' content when I didn't flag it as required, but that's for another question
Here is the Action that is called when the form is submitted. At this point the aforementioned property will no longer be set to 'true'.
[HttpPost]
public ActionResult Application(Application application)
{
// Other work done here
return View(application);
}
At present, there are no custom Model Binders. Also, I've tested some other data types and I'm seeing the same thing.
Can someone explain why hidden form values are not being returned? Am I just doing this all wrong?
If you have the property in your model decorated with a ReadOnlyAttribute the value will not be populated back into the model for you. After all, it is read only.
I just had the same problem. The form didn't submitted the hidden property because the model class didn't had a proper getter and setter for that property.
I know that is not the issue you had, just figured it might help other people that will lend in this page.
I cannot reproduce the issue (ASP.NET MVC 4 Beta running on VS 2010 .NET 4.0).
Model:
public class Application
{
public bool ShowSideBars { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Application()
{
var model = new Application();
model.ShowSideBars = true;
return View(model);
}
[HttpPost]
public ActionResult Application(Application application)
{
return Content(application.ShowSideBars.ToString());
}
}
View:
#model Application
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.ShowSideBars)
<button type="submit">OK</button>
}
When I submit the form, the model binder correctly assigns the ShowSideBars property in the POST action to true.
Note: I sure wish I knew why MVC has decided to add the '... field is
required' content when I didn't flag it as required, but that's for
another question
That's because non-nullable types such as booleans are always required. You could stop ASP.NET MVC helpers from emitting HTML5 data-* client side validation attributes for them by putting the following line in Application_Start:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I think the fields MUST be within the form html tags for the hidden ones to be posted back and not ignored
try this:
public class Model
{
[ScaffoldColumn(false)]
public bool InvisibleProperty { get; set; }
}
more info here (ScaffoldColumn(bool value) vs HiddenInput(DisplayValue = bool value) in MVC)
In my case it was because I had declared a field instead of a property:
public BaseController.Modes Mode;
doesn't work. But:
public BaseController.Modes Mode { get; set; }
works. The default model binder only works with properties.
I kid you not, this is another reason it could happen.
My form had the same field in it twice. The other field was actually not in the form, but that doesn't matter.
Run this jQuery in the developer console to see how many elements come back:
$("[id$=PropertyName]"); // Search for ids ending with property name.
Example:
For me, in Core 6 the solution was removing [Editable(false)] attribute from the model class Id property which I wanted to tunnel through get/post as a hidden form field. In .Net 4.8 it was not a problem.
Related
Just need to know if this is possible to do or what exactly is standard practice in MVC as this is my first large scale MVC application.
So I've got a form I want the user to be able to edit and on that form page to pull up all the necessary data I need I'm bringing in a viewmodel,
public class Tier2IssueFormViewModel
{
public Tier2IssueDTO Tier2Issue { get; set; }
public IEnumerable<SelectListItem> VersionList { get; set; }
public IEnumerable<SelectListItem> BugList { get; set; }
public IEnumerable<SelectListItem> IssueStatusList { get; set; }
}
Then once I've finished collecting the form data from the user using things like,
#Html.TextBoxFor(m => m.Tier2Issue.Tier2Notes, new { #class = "form-control"})
#Html.DropDownListFor(m => m.Tier2Issue.FishbowlVersion, Model.VersionList, "Select Application Version")
#Html.HiddenFor(m => m.Tier2Issue.ID)
I want to post back to this action with the following signature for my model to bind to,
[HttpPost]
[Route("Issues/{id}/Edit")]
public ActionResult EditIssue(Tier2IssueDTO model)
{
...
// Update DB with the DTO model
...
}
But so far nothing really gets bound to this object. I thought the model binder might be smart enough to pair the two but I'm guessing this logic is incorrect. So I'm currently doing a workaround by using this,
[HttpPost]
[Route("Issues/{id}/Edit")]
public ActionResult EditIssue(Tier2IssueFormViewModel model)
{
...
// Get Tier2IssueDTO values from viewmodel
// Update DB with the DTO model
...
}
I mean it works, but it seems odd to me that you would model bind to a view model. Is this standard practice or is there a way to bind to an object contained within the viewmodel directly?
This will not work because the input text box names are differnt from the model inside your action, ex: the text box will have a name Tier2Issue.Tier2Notes while the model parameter in your action is expecting a property name Tier2Notes only without the Tier2Issue prefix.
You can overcome this issue by either making the model the same as the action parameter or give an explicit name and value to the text box, ex:
#Html.TextBox("Tier2Notes",Model.Tier2Issue.Tier2Notes, new { #class = "form-control"})
This should make it work
You have the right of it. It often seems pretty repetitive to have a viewmodel, dto and entity that all seem to have the same properties, but they all do different jobs an usually end up diverging a bit. A dto could act as a viewmodel, but it's a square peg in a round hole. If you're not using automapper to map these objects to one an other (this may be opinion baesed but it's broadly shared) - then use automapper to save you mindless keystrokes.
I have two classes like this (to make it simple)
public class Page
{
int pageNumber;
string content;
}
public class Book
{
public string name;
public List<Page> pages;
}
Now, in a razor view, I have a form allowing to add a book, with a list of pages in the form, to add pages.
Currently, the pages are added dynamically to the form, and I get the values in my controller using Request.Form for building my list of Pages in the controller. The problem is, how can I validate the model with the pages (something like a page must have a content not empty for example) before entering in the action in the controller.
The fields are added with JQuery in the form. I think it not possible to bind a list of Pages directly to the model in the view, especially when the fields are generated with javascript. But maybe I am missing something.
Thanks
You can use model binding in ASP.NET, even if you're adding jquery form elements, see this to create collections that are binding to model:
ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries
I think your form can be done as that in order to run model binding correctly:
<input type="text" name="name"/>
<input type="text" name="pages[0].pageNumber"/>
<input type="text" name="pages[0].content" />
<input type="text" name="pages[1].pageNumber"/>
<input type="text" name="pages[1].content"/>
<input type="text" name="pages[3].pageNumber"/>
<input type="text" name="pages[3].content"/>
With that use, you can receive the Book object from the view to your controller:
public ActionResult Create(Book myBook)
{}
Then, for validation, I suggest you tu use Data Annotation to your model, use [Required] tag before the content property of page object, or [MinLenght], see this Microsoft documentation about Data Annotations.
[Required]
public string Content { get; set; }
Then in your view, use jquery.validate (don't forget to enable it in the view) and in the controller, when you receive your model, you can check the modelstate with
public ActionResult Create(YourObject object)
{
if (ModelState.IsValid)
{
// code when model is valid
}
}
you can validate the pages in JQuery before the data is send to the server.
or you can user a filter. a filter is a method that is executed before an action is executed. (you may have seen or used the [Authorize] filter)
you can create a filter that validates the data and if the validation fails the request will be redirected to an error page.
here is a tutorial
here is another tutorial
With MVC you can use DataAnnotations on your model properties.
Using using System.ComponentModel.DataAnnotations;
public class Book{
[Required]
public string Name{ get; set; }
public List<Page> Pages { get; set; }
}
public class Page{
[Required]
public int PageNumber{ get; set; }
[Required]
public string Content { get; set; }
}
For a deeper understanding read this: http://www.asp.net/mvc/overview/older-versions/getting-started-with-aspnet-mvc4/adding-validation-to-the-model
I've been searching around and I'm not able to find an answer on what seems like a simple requirement:
With MVC Data Annotation validation, can you show the validation message ('must be a string with a maximum length of 5') in the validation summary or next to field, but clear the value of the text box (when validation fails).
I've tried to use ModelState.Clear() and ModelState.Remove("CompanyName"), but this clears both the value and validation message (validation state).
I'm asking this because recently we had a penetration test and one of the recommendations was to not pre-populate secure values (credit card number etc) if validation fails. This is obviously a minor issue, but the recommendation was to not send the value back across the internet (from the server) if we didn't have to.
Here is the code I'm working with:
public ActionResult Edit()
{
return View();
}
[HttpPost]
public ActionResult Edit(CompanyInput input)
{
if (ModelState.IsValid)
{
return View("Success");
}
//ModelState.Clear // clears both the value and validation message
//ModelState.Remove("CompanyName") // same result
return View(new CompanyInput());
}
And the view model:
public class CompanyInput
{
[Required]
[StringLength(5)]
public string CompanyName { get; set; }
[DataType(DataType.EmailAddress)]
public string EmailAddress { get; set; }
}
And the view:
#model Test.Models.CompanyInput
<h2>Edit</h2>
#using (Html.BeginForm("Edit", "Company"))
{
#Html.EditorForModel()
<button type="submit">Submit</button>
}
The ModelState of each field holds more than just the value, so removing it from the collection outright removed your error message as expected. I believe you should be able to clear just the value however, by doing something like.
ModelState["CompanyName"].Value = null;
EDIT: Upon closer inspection I found that the Value property is of type ValueProviderResult, simply nulling it doesn't give the desired result, and because the properties of this class appear to be getters only you have to replace the instance with your own. I've tested the following and it works for me.
ModelState["CompanyName"].Value = new ValueProviderResult(string.Empty, string.Empty, ModelState["CompanyName"].Value.Culture);
Because the ModelState isn't valid, you will either have to create a custom validator or a jQuery ajax/json call to determine if the data needs to be cleared or not.
Just changing the model property to string.Empty or something like that won't do the trick because the entire view gets re-rendered with the previous successful posted model but with the ModelState validation errors.
Yes you can add error message like this
[Required(ErrorMessage = "must be a string with a maximum length of 5")]
Update after clarity from OP:
To clear e.g. Input.Field = string.Empty;
You can create a custom validation class which is inherited from ValidationAttribute class
The following link gives a clear idea about how to implement custom validation class suitable for your problem.
Custom Data Annotation
I have web application based on ASP.Net MVC3. I have a need for a "Create" view which will not know the model type until the user picks a sub-type to create from a drop-down. To attempt to solve this problem, I have created an editor template under Shared/EditorTemplates for each derived model type. This allows me to create a single "Create.cs" which is strongly-typed to a view model. The view model only has two members, an enum and a complex type. The idea is that the view will initially show only a drop-down (editor for the enum member) then when the user initially submits the specified "model type" (drop-down selected value), the POST action can check the "model type" specified and instantiate the correct derived model type for the view model's single complex member who's type is the base type for all possible "model types".
The abstract + derived type model objects...
public abstract class MyModelBase
{
public MyModelType_e ModelType {get; set; }
[Required]
public string Name { get; set; }
}
public class DerivedType1 : MyModelBase
{ ... }
public class DerivedType2 : MyModelBase
{ ... }
public class DerivedType3 : MyModelBase
{ ... }
I have a complex view model as follows...
public enum MyModelType_e
{
DerivedType1 = 0,
DerivedType2 = 1,
DerivedType3 = 2
}
public class MyModelCreate
{
public MyModelType_e ModelTypeForSelectList { get; set; }
public MyModelBase ModelBase { get; set; }
}
My GET controller action instantiates the above view model for the view i.e., only a drop-down list displayed with items based on the MyModelType_e enum + the value of the model's "ModelBase" property is initially null. So the GET action method looks like this...
[HttpGet]
public ActionResult Create()
{
return View(new MyModelCreate());
}
Note the comment in caps bellow regarding the crux of my issue which is that TryUpdateModel fails (see below) even though it sets the properties of the ModelBase (derived-type) member to the corresponding form values as expected...
[HttpPost]
public ActionResult Create(MyModelCreate model)
{
if (model.ModelBase == null ||
(int)model.ModelTypeForSelectList != model.ModelBase.ModelType)
{
switch (model.ModelType)
{
case MyModelType_e.DerivedType1:
model.ModelBase = new DerivedType1();
break;
case MyModelType_e.DerivedType2:
model.ModelBase = new DerivedType2();
break;
case MyModelType_e.DerivedType3:
model.ModelBase = new DerivedType3();
break;
}
return View(model);
}
if (!TryUpdateModel(model.ModelBase))
return View(model); // <<< THIS HAPPENS EVEN THOUGH ModelBase APPEARS TO BE UPDATED PROPERLY...
// For instance, I can see right here with intellisense that model.ModelBase.Name
// is NOT null or empty but rather is truly updated with the correct form value(s)...
// TODO: Insert the record, etc... (currently we never get here...)
}
So the above section is where the problem stems from but here is my view to help understand...
#model MyNamespace.MyModelCreate
<h2>Create</h2>
...
#using (Html.BeginForm())
{
#Html.ValidationSummary(false)
<fieldset>
<legend>Input</legend>
<div class="editor-label">
#Html.Label("Select Model Type")
</div>
<div>
#Html.EnumDropDownListFor(model => model.ModelType)
#Html.ValidationMessageFor(model => model.ModelType)
</div>
#*
Conditionally show the correct editor template...
There is one existing under ../Shared/EditorTemplates for each
derived type (DerivedType1, DerivedType2, DerivedType3, etc...)
This much is working in the sense that the correct editor fields
are displayed based on what the user selects in the above drop-down.
*#
#if (Model.InputModel != null)
{
#Html.EditorFor(model => model.ModelBase);
}
<p>
<input type="submit" value="Continue" />
</p>
</fieldset>
}
So once with the initial POST (model type is selected), my POST action method falls through to the TryUpdateModel line by design but for some reason the validation fails. The part I really don't understand is that the validation summary reports "Name is required" even though I can clearly watch TryUpdateModel set the Name propery properly on the ModelBase member of the view model.
I'd greatly appreciate any help or guidance here... I am fairly new to ASP.NET MVC and I am open to doing things differently since I understand there are probably other ways I could design my requests/actions + views to accomplish this "multi-step" problem but I really am just going for the simplest possible thing which I why I like the EditorTemplate approach for handling the derived model types, etc.
Thanks in advance...
Check ModelState. There should be errors set after TryUpdateModel runs.
I had to remove a Required attribute from some properties.
My solution basically involved growing two additional controller actions (GET and POST). I created separate views under ../Shared/EditorTemplates which are strongly typed (one for each derived model type). This way the initial "Create" POST action only receives the selected type (model is just an enum value specifying the desired type) and then redirects to another controller action like "CreateByType" which gets a new instance of the requested concrete type and returns the correct edit view. The additional "CreateByType" controller actions (GET and POST) only needs to deal with the abstract base because it requests the instance from the service layer (passing the enum value). Then all I had to do was create a view under EditorTemplates for each concrete type.
How can I override keys in FormCollection (I need this because I have a property bound to multiple CheckBoxes in a View)? I did try this when a post back is in Action:
formCollection["DecisionReasons"] = formCollection["DecisionReasons"].Replace(",false", "").Replace("false,", "").Replace(",","|");
...but when I UpdateModel only the first value is updated in the model (in my model I have a DecisionReason string).
Do I need a ModelBinder (how can I do that?) or is there another way to do this?
Part of View
<div style="width:300px;height:250px;overflow:auto;">
#foreach (var a in (IEnumerable<SelectListItem>)ViewBag.AllDecisionReasons)
{
#Html.CheckBox("DecisionReasons", a.Selected, new { value = a.Value })
<label>#a.Text</label><br />
}
#Html.ValidationMessage("DecisionReasons", (string)Model.DecisionReasons)
if i check more than one checkbox in my View my Model property wich is string is updated with only one value example (if in View i check 2 checkboxes my Model will be updated with first value, so i need that Model property have value "valu1,valu2" and so on.)
sorry for my bad english.
I commented, above, asking for more details, but I get the impression that you're using checkboxes to represent the individual values in a Flags enumeration.
The default model binder doesn't handle mapping flags in this way, but I found a custom model binder that does in this article.
Edit:
OK, I see from your update (which would be better if you added it to your question, rather than posting it as an answer), your model has a comma-delimited string property containing each of the selected DecisionReasons.
I suggest that you consider the use of a ViewModel. The ViewModel is an abstraction of your model that is tailored for the way in which you present it in your view.
You can derive your ViewModel from your Model class, to reduce the amount of work. Consider this (untested) code:
public class MyModel
{
public virtual string DecisionReasons { get; set; }
}
public class MyViewModel : MyModel
{
public string[] DecisionReasonValues { get; set; }
public override string DecisionReasons
{
get
{
return string.Join(",", DecisionReasonValues);
}
set
{
DecisionReasonValues = value.Split(',');
}
}
}
Use MyViewModel as the model for your View and use DecisionReasonValues to render the checkboxes, not DecisionReasons. ASP.NET MVC will populate DecisionReasonValues from your checkboxes but you can access them as a comma-delimeted string through the overridden DecisionReasons property.