This question already has answers here:
ASP.NET MVC 3 Model Id using Route Id value
(3 answers)
Closed 6 years ago.
I'm creating a strongly-typed partial view for a form in ASP.NET mvc, in which I want to use a hidden input to convey the Id property of the particular view model. The controller that renders the view accepts an id parameter, registered as an option url parameter in my RouteConfig.cs, which is meant to be unrelated.
If the controller is passed a username of "Tim" as a routing paramter, and the model that is passed to the partial view has an Id of "e1ac2e44-1e09-4bb4-a7f0-85e1eedf3448" then here is what happens:
#Html.DisplayFor(m => m.Id) correctly displays e1ac2e44-1e09-4bb4-a7f0-85e1eedf3448 as I want it to.
#Html.HiddenFor(m => m.Id) incorrectly creates a hidden input with a value of "Tim":
<input id="Id" name="Id" type="hidden" value="Tim">
The same happens for:
#Html.HiddenFor(m => Model.Id)
#Html.HiddenFor(m => m.Id, new { value = Model.Id })
#Html.HiddenFor(m => m.Id, new { value = "Obvious eye-catching string" })
#Html.Hidden("Id", Model.Id)
#Html.Hidden("Id", "Obvious string")
However, #Html.Hidden("NotId", Model.Id) creates a hidden input with the correct value, but not with a name matching the intended property in my view model:
<input id="NotId" name="NotId" type="hidden" value="e1ac2e44-1e09-4bb4-a7f0-85e1eedf3448">
With further toying around , I've found that TextBoxFor, TextAreaFor and EditorFor all bind in the same way as HiddenFor and show up displaying "Tim", not the alphanumeric user ID. This leaves me with two basic questions:
Why does #Html.DisplayFor bind differently from other Razor helpers?
What is the correct way / easiest work-around to use my view model's property Id and not the route parameter id apart from changing names?
Posting comment with elaboration here..
HiddenFor along with EditorFor are (extension) methods that are used to indicate that the value will be needed to be passed back back with the form. Thus it makes sense that they would look for a specific override from the user on that RouteValue (here you over wrote Id). DisplayFor is the method used just to display the value without it being passed back when the form is submitted. Here since you don't wan't to pass back the current value of Model.Id you should use #Html.DisplayFor(m=> m.Id,new {Hidden})
Related
I have a project which contains a simple form for collecting signup info. Recently I have been working to add localization to the project, as all of the text shown to the user was hardcoded. I'm not sure what changed, but for some reason, now when Razor renders an HTML element using the Html.EditorFor method that ends up being a textbox, the Name property of the element has ".textbox" appended to it.
This breaks the bindings, so that when I receive my model all of the text values are null. Here is an example of what I'm seeing, Razor code:
<div class="form-group" ng-class="{ 'has-error': validate && accountForm.FirstName.$invalid }">
#Html.LabelFor(m => m.FirstName, new { #class = #ViewBag.LabelCssRequired })
<div class="#ViewBag.TextboxCss">
#Html.EditorFor(m => m.FirstName, new { htmlAttributes = new { ng_model = "firstName" } })
</div>
</div>
and here is the rendered output:
<input class="text-box single-line form-control ng-valid-maxlength ng-not-empty ng-dirty ng-valid-parse ng-valid ng-valid-required ng-touched" id="FirstName_textbox" maxlength="100" name="FirstName.textbox" ng-model="firstName" required="required" type="text" value="">
It is also adding a "_textbox" to the id, but I'm not as concerned about that at the moment. For some reason, this only seems to be happening to input elements where the type is "text". I have another input generated with.EditorFor which has the type of email and it doesn't have any modifications to the name.
This behavior also seems to be restricted to Html.EditorFor, if I use.TextboxFor, it works fine.
I have been able to make the bindings work by explicitly setting the #Name property in Razor, but this only masks the symptom, and I would like to avoid having to do this for every text input on the site.
Has anyone seen this behavior before, or know of a fix?
By default, the TextBoxFor helper generates HTML using a built-in template. You can override the defaults by creating files in the project root\views\shared\editortemplates folder.
Therefore the problem can be caused by some custom template being present there. Normally, you need to check for files whose name Match either the datatype (such as string) or the control type (such as TextArea). If the corresponding model property has a UIHint attribute on it, a custom file specified in it can also come into play.
I have a textbox set-up, like so:
#Html.TextBoxFor(m => m.NewVehCode, new {data_id = "" })
When a user selects a vehicle, the description of the vehicle becomes the value of the textbox and the vehicle code (which is what I want to send to DB) becomes the data-id (not full code but I set the values like this):
$("#NewVehCode").val(vehDescription);
$("#NewVehCode").attr("data-id", vehCode);
This all works fine, except for the fact that when I submit, MVC grabs the value of the textbox.
Is there a way I can on submit, get the data-id of that textbox instead of the value?
Note that I'm not using .js to gather the data. Form calls a controller action that sends the model directly to the controller.
There is no way to submit values of "data" properties. I am not sure why you wanted to use data property (is that a requirement or not, not sure). But you can have a hidden property in the form so, when user selects a vehicle, along with data property of text box , update this hidden value. This hidden value will be submitted back to form.
Stephen Muecke's comment helped me trigger this solution:
I've added a hidden textbox to use for the vehicle code, and made the textbox that shows the description a standard textbox with no data binding.
<input id="newVehInput" readonly="readonly" class="longInput" type="text">
#Html.TextBoxFor(m => m.NewVehCode, new { style = "display: none"})
This way I can use the value with no issues:
$("#newVehInput").val(vehDescription);
$("#NewVehCode").val(vehCode);
(Will mark as answer in 2 days)
Add a hidden textbox to your html which you post back on submit
In your model:
public string Id {get;set;}
Render it in the view with a hidden class
And with js.
$("#vehicleList").on("change",function(){
$("#Id").val($vehCode)
})
When you submit the value of id should be set in your model
You can have a hidden field hold the Updated value.
HTML:
<input type="hidden" id="HiddenVehCode" name="HiddenVehCode" Value="0"/>
JS
$("#HiddenVehCode").val(vehDescription);
Note: you should have the model set up so that HiddenVehCode will reach the actionMethod on Form Submit.Something Like,
public int HiddenVehCode {get;set;}
I have a parent view model (Let's call it ParentViewModel) which has a list of children view models (Let's call them ChildViewModel). Each child view model can be edited independently and I have a separate form which I display in a loop. This works brilliantly but I cannot work out how to post just the child model and ignore the parent.
This is my form:
#model ParentViewModel
...
#foreach (var child in Model.Children)
{
#using (Html.BeginForm("_EditChild", "Admin", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.EditorFor(model => child.Content, new {htmlAttributes = new {#class = "form-control"}})
#Html.ValidationMessageFor(model => child.Content, "", new {#class = "text-danger"})
</div>
<div class="form-group">
<div class="col-md-12">
<input type="submit" value="Create" class="btn btn-default new-post" />
</div>
</div>
}
}
And this is the signature of my controller. It is expecting a type ChildViewModel which exists in ParentViewModel as a list.
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult _EditPost([Bind(Include = "")] ChildViewModel childViewModel)
{
}
The form works and submits BUT ChildViewModel is null when it reaches the submit controller. This is certainly because binding between the Form Post and the Action is not happening.
I am afraid it is not possible to post the child model only , since the page can define one and only one model that is the parent model you have defined .
But you can solve your problem simply by posting the parent model and extracting the child model in the controller .
It is possible, just not intended by ASP.NET MVC. All you would have to do is remove the parent prefix from the name of your submitted inputs on the client side. Your input would probably look something like:
<input name="Children[0].SomeProperty" ../>
If your AdminController._EditChild action expects a ChildViewModel, then you'd just have to use javascript to rename the input to:
<input name="SomeProperty" ../>
and the model binder should build the ChildViewModel. Alternatively, you might also be able to solve it by creating a custom ValueProvider or ModelBinder that maps the inputs to a ChildViewModel even though it has the wrong prefix...although that would seem like an uglier hack to me than changing the input names. On that note, I would probably also update the IDs with javascript when updating the names just to keep them in sync, even though only the name is used for binding.
Note also, if you're not looping but simply want to submit a single child ViewModel of your model, you can just assign it to a variable:
#var childVM = Model.ChildProp;
#Html.HiddenFor(m => childVM.ID)
Notice m is disregarded in the property expression of HiddenFor. I think last time I did this the variable name had to match the action's parameter name so you would submit this to:
public ActionResult SomeAction(ChildViewModel childVM){ ... }
I'm currently trying to understand why this technique can't be combined with looping.
How do you access the model in an ASP.NET MVC Editor Template? For example if the parent view contains say:
#Html.EditorFor(x => x.Surname)
Then inside Views/Shared/EditorTemplates/String.cshtml we have:
#model String
<div class="field">
#Html.LabelFor(x => x)
#Html.TextBoxFor(x => x)
#Model.Length
</div>
This fails at #Model.Length because Model is null, although the LabelFor and TextBoxFor render the correct Surname properties.
When Html.DisplayFor with the equivalent template file is used, Model does contain the given string value.
Creating a Editor Template for something as broad as String could have unintended consequences, since you will very likely have many EditorFor(x => x.StringValue)s in your code that you don't want this Editor Template to apply to.
It's likely that you have another EditorFor that is causing the nullref exception here.
I'm having a bit of pain trying to solve how can I create a new object when I'm passing to my view a IEnumerable.I'm having the creation of the new item in a modal jquery-ui window on the same page.
<div class="form-group">
#Html.LabelFor(model => model.Title)
<div class="col-md-10">
#Html.EditorFor( model =>model.Title)
#Html.ValidationMessageFor(model => model.Title, "Please choose a title that is not empty or less than 3 symbols")
</div>
</div>
So this is the code snippet that I'm trying to implement in the main view from where my js gets the information,but since my model is a collection I cannot access the editor options in my razor view.
This is a part of my js file where I get the values of the items inside the input boxes listed above which do NOT work becase I'm operating on a collection.I cannot change the model so I need a shortcut through it.
$.ajax({
url: "/Course/Create/",
type: "POST",
data:{
Title: $("#itemTitle").val(),
CourseDescription: $("#itemDescription").val(),
CourseCategory: $("#itemCategory").val()
},
You should pass a View Model that will contain both the IEnumerable collection + an object that you would like to submit (with Title, Description and Category properties).
In case you cannot change the model that you are passing to the view you should not use Html.EditorFor but simply Html.Editor (the same applies to Html.LabelFor and other Html extensions that you use there).
Of course you then need to pass appropriate name parameter ( e.g. "itemTitle") to these extensions that will comply with the ones used in the JS code.