ASP.NET MVC 4 override emitted html name and id - asp.net-mvc

I'm trying to change the emitted name of the html input created by #Html.HiddenFor.
The code I'm using this:
#Html.HiddenFor(e => e.SomeProperty, new { #id = "some_property", #name = "some_property" }
Now this works for the id, however it doesn't work for the name. Now I don't really care for the id now, I need the name to change, because that's the one that get's posted back to the target server.
Is there
A property I can apply on SomeProperty in my model?
A way in the Html.HiddenFor to override the name property?
Or am I stuck to do a plain <input ...> by hand?

You need to use the Html.Hidden (or write out the <input ...> by hand) instead of the Html.HiddenFor
#Html.Hidden("some_property", Model.SomeProperty, new { #id = "some_property" })
The goal of the strongly typed helpers (e.g the one which the name end "For" like HiddenFor) is to guess the input name for you from the provided expression. So if you want to have a "custom" input name you can always use the regular helpers like Html.Hidden where you can explicitly set the name.
The answer from unjuken is wrong because it generates invalid HTML.
Using that solution generates TWO name attributes:
<input Name="some_property" name="SomeProperty" id="some_property" type="hidden" value="test" />
So you will have Name="some_property" AND name="SomeProperty" which is INVALID HTML because an input can only have ONE name attribute! (although most browers happen to take the first Name="some_property" and don't care about the second one...)

If you use:
#Html.HiddenFor(e => e.SomeProperty, new { #id = "some_property",
#Name = "some_property" });
Notice the capital "N" in #Name. It´ll work.

I was curious as to why specifically overriding the name attribute wouldn't work. Unless I capitalized it (i.e. new {#Name = 'somename'} ), then it doesn't seem to work. As others have pointed out, this only works because it generates duplicated name attributes and Chrome cleans it up.
I looked at the latest MVC source code to figure out what is going on. Consider the following snippet from the GenerateInput method in DefaultHtmlGenerator.cs:
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
if (string.IsNullOrEmpty(fullName))
{
throw new ArgumentException(
...
}
var inputTypeString = GetInputTypeString(inputType);
var tagBuilder = new TagBuilder("input");
tagBuilder.TagRenderMode = TagRenderMode.SelfClosing;
tagBuilder.MergeAttributes(htmlAttributes);
tagBuilder.MergeAttribute("type", inputTypeString);
tagBuilder.MergeAttribute("name", fullName, replaceExisting: true);
We can see here, the problem is that, regardless of whatever name property you provide, it will be overridden by the last call to MergeAttribute, which will use whatever logic it is that assigns to the variable fullName from the GetFullHtmlFieldName method.
I sort of understand why they enforce this behavior, guessing it has something to do with controlling the names used in the postback to guarantee it works with the model binder.
In any case, to make this happen, I say just manually construct the input element and don't use the razor view helper.

never worked for me (aspnet.core)
I used plain
<input type="hidden" id="#myid" name="#myname" value="#Model.prop" />
and worked like a charm. No need for HtmlHelper HiddenForModel.

Related

Set HtmlFieldPrefix for id but not name

Setting ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix will prepend all name and id attributes.
I'm wondering if there is a way to prefix just the id and leave the name alone.
The scenario is this: A partial view is being loaded by AJAX multiple times on a page. A JavaScript library we are using requires the use of ids but we also want to be able to post the forms (so the names need to match the server model).
Our current solution is to generate a unique number for each time the partial view is loaded, set the HtmlFieldPrefix to items[n] (where n is the generated number) and for our action to recieve an array of items (where we only need to receive one). This gives us a globally unique id and a name which can be parsed by the model binder.
This feels ugly, though. Any suggestions?
I had the same issue as yourself.
#{ ViewData.TemplateInfo.HtmlFieldPrefix = "Prefix"; }
#using(Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.TextBoxFor(m => m.MerchantAccount.MerchantName, new { maxlength = 29, size = 35 })<br />
#Html.ValidationMessageFor(m => m.MerchantAccount.MerchantName)
}
Firstly, my bad idea: Use jQuery on page load to remove undesired prefix to all names.
$('[id*="Prefix_']).each(function(i, e) {...
What we actually ended up doing was suggested here: https://stackoverflow.com/a/1318342/4946681
Basically, on the controller you state what prefix to strip (by specifying a bind prefix in the method signature) and it's done for you automatically.
Now we can have our ID prefixes cake, and eat (bind) them, too!

MVC #Html.Display()

I have something like:
<input type="text" name="TerrMng" id="TerrMng"/>
in HTML. What is the equivalent of the above using #Html.Display?
I tried using: #Html.Display("TerrMng", TerrMng)
but was not successful. Note that I like to use #Html.Display but not sure how to translate the ID value so that it shows up.
The Display method is not for creating input boxes. You'd want to use:
#Html.TextBoxFor(m => m.TerrMng);
or the templated helper method:
#Html.EditorFor(m => m.TerrMng);
I'm assuming that you want to use modelbinding. If not, if you really just want to use a helper to simply make an input tag, use:
#Html.TextBox("TerrMng");
This would be sent to the client:
<input id="TerrMng" type="text" value="" name="TerrMng">
The first 2 methods above would result in the exact same html, if model.TerrMng was "" or String.Empty. If for some reason you don't want the value attribute, you'll need to type it out yourself.
This should do the trick if you are just wanting to display the data and not allow the user to edit the information.
#Html.DisplayFor(m => m.TerrMng);
Edit:
what-is-the-html-displayfor-syntax-for is another question on stackoverflow that may give you some more guidance.
Edit:
TerrMng does not exist on PageLoad so you cannot use the Html.Display in that way. You need to create it and fill its value with the value received from the jQuery. In this case where you would have to do the following:
HTML
#Html.Display("TerrMng"); // This creates the label with an id of TerrMng
jQuery
$("#TerrMng").val(TerrMng); // This puts the value of the javascript variable into the label
You could try something based on this. This is not exact but you could get some idea.
#Html.TextBoxFor(yourmodel => model.yourModelFieldname, null)
#Html.Display() is used instead of #Html.DisplayFor() when your model is not known at compile time, or if you prefer to work with strings, rather than with strong types. For example, these 2 are equivalents (given that your model is some class):
#Html.DisplayFor(m => m.MyProperty)
and
#Html.Display("MyProperty")
But the additional cool feature of the Display() method is that it can also do the lookup in the ViewData, and not just in your Model class. For example, here is a way to display the HTML for the property on a random object, given that we know it has a property named "Blah" (the type of the object doesn't really matter):
#{ ViewData["itsawonderfullife"] = SomeObject; }
<div>#Html.Display("itsawonderfullife.Blah")</div>
This way, we are telling HtmlHelper to look into the ViewData, instead of our Model, and to display the property Blah of a given SomeObject.

LabelFor and TextBoxFor don't generate the same id's

When using the following code, the id's of the field and the id in the for attribute of the label isn't identical.
<%: Html.LabelFor(x => x.Localizations["en"]) %> => Localizations[en]
<%: Html.TextBoxFor(x=> x.Localizations["en"]) %> => Localizations_en_
<%: Html.LabelFor(x => x.Localizations["en"].Property) %>
=> Localizations[en]_Property
<%: Html.TextBoxFor(x=> x.Localizations["en"].Property) %>
=> Localizations_en__Property
I traced the code in reflector and saw that the way the values are generated are different. Not using the same helper method.
LabelFor uses HtmlHelper.GenerateIdFromName and TextBoxFor uses TagBuilder#GenerateId.
Does anyone know the reason for this, or a workaround (except writing your own entire set of input/textarea/select helpers)? Or is it a bug?
UPDATE:
Since I anyway use a html helper for the label with a second parameter for the label text, I did modify it to use the same id generation code as the form field helpers.
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string labelText)
{
// The main part of this code is taken from the internal code for Html.LabelFor<TModel, TValue>(...).
var metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
var fieldName = ExpressionHelper.GetExpressionText(expression);
TagBuilder builder = new TagBuilder("label");
// Generate the id as for the form fields (adds it to self).
builder.GenerateId(fieldName);
// Use the generated id for the 'for' attribute.
builder.Attributes.Add("for", builder.Attributes["id"]);
// Remove the id again.
builder.Attributes.Remove("id");
builder.SetInnerText(labelText);
return MvcHtmlString.Create(builder.ToString());
}
This solves my immediate problem, but it doesn't answer the question as to why the implementation looks like it does in MVC2. If there's an reason for it.
By the way: There's no need to actually modify the id/for attribute in HTML5, since it's perfectly legal to have an id looking like ^~[] if you'd like to. All major browsers support it. This is nicely explained by Mathias Bynens.
UPDATE 2:
This does not solve the problem at all actually, since the DefaultModelBinder can't bind to it anyway. Using nested objects in dictionaries doesn't seem to be supported by the field name generator in MVC 2, since it generates:
<input type="text" name="Dict[en]" value="(VALUE)">
Instead of what the model binder wants:
<input type="hidden" name="Dict[0].Key" value="en">
<input type="text" name="Dict[0].Value" value="(VALUE)">
Strange that it comes out of the box this way.
I've tried create a custom model binder for it, but I can't get MVC2 to use it whatever I try to use it on:
ModelBinders.Binders.Add(typeof(IDictionary<string,object>), new DictionaryModelBinder());
ModelBinders.Binders.Add(typeof(IDictionary<string,string>), new DictionaryModelBinder());
ModelBinders.Binders.Add(typeof(IDictionary), new DictionaryModelBinder());
ModelBinders.Binders.Add(typeof(Dictionary), new DictionaryModelBinder());
So right now it looks like it's back to manually creating the name attribute values with hidden .Key fields.
This is a bug in MVC3 that we are planning on fixing for the next release (MVC 3 RTM). LabelFor will go through the tagbuilder to generate the 'for' attribute using the same logic that is used to generate ids so they will line up for arrays and nested types.
We are currently using the html 4.01 spec to generate ids so you cannot use ids that begin with non-letters. We will think about what the best approach should be now that the standards have changed.
MVC intentionally mungs ids with special characters which are significant in jQuery/CSS3 selectors. This is because the selector syntax becomes complicated when there are "reserved" (by jQuery/CSS3) characters in the ID.
It does not do this with name because it isn't necessary there and gets in the way of binding.
It's certainly a bug if LabelFor doesn't actually point to the corresponding TextBoxFor. But I'd argue the bug is in LabelFor, not in TextBoxFor.

Disabled Form Elements ASP.NET MVC Update

I'm wanting to update a record in my database which has two values, one is the ID, and one is the "description". The ID can never be changed, however I'm relying on the use of strongly-typed features to do it. So, I have the following:
Inherits="System.Web.Mvc.ViewPage<Business>"
Which is fine as it allows me to get everything back. The problem is when I use the following line:
<%= Html.TextBox("BusinessID", ViewData.Model.BusinessID, new { disabled = "disabled", style = "width:50px;", #class = "uppercase", maxlength = "4" })%>
With the disabled = "disabled" option it doesn't recognise the BusinessID and therefore doesn't pass it back to the controller which, in turn has problems binding the object up.
Not that you'll need it, but here's the controller action:
[HttpPost]
public ActionResult EditBusiness(Business business)
{
if (!ModelState.IsValid)
return View(business);
// update business here
_contractsControlRepository.UpdateBusiness(business);
return RedirectToAction("Businesses");
}
Any ideas why this is happening? I didn't realise form elements were completely hidden on postback when they're disabled. I don't want the users editing that particular field. I've also tried Html.DisplayFor(b=>b.BusinessID) without any luck.
display the id like this
Html.Hidden("BusinessID", ViewData.Model.BusinessID)
<%=Model.BussinessID %>
this way you will have the id for the binding in the hidden tag
and you will display the value in the label
or you can use anything else that you want yo can do like this also
<input type="text" value="<%=Model.BussinessID %>" contentEditable="false">
and put the hidden somewhere in the form
instead of Html.Textbox you can use Html.Hidden("BusinessID", ViewData.Model.BusinessID)
You always have the option of either "hard-coding" the html element, or writing your own html helper method to do it.
public static string DisabledTextBox(this HtmlHelper helper, string name, object value)
{
return String.Format(#"<input type="text" name="{0}" id="{0}" disabled="disabled" value="{1}" />", name, value);
}
Is there a particular reason you are displaying the id? if not then leave it out and on your controller simply use TryUpdateModel() instead.
or is that not what your asking?
edit
<%= Html.TextBox("name","value", null, new { style="readonly"}) %>
Edit 2
You might think about doing a route like //site/controller/yourbusinessid
then you can use the id as it's passed to your controller and you can then, in your view, simply use <%= Model.BusinessId %> as a string.

How to map form values to an object for ASP.NET MVC HTTP-Post scenario?

i have a simple form for an ASP.NET MVC application. I have a form property that is named differently (for whatever reason) to the real property name.
I know there's [Bind(Exlcude="", Include="")] attribute, but that doesn't help me in this case.
I also don't want to have a (FormsCollection formsCollection) argument in the Action method signature.
is there another way I can define the mapping?
eg.
<%= Html.ValidationMessage("GameServer", "*")%>
results in ..
<select id="GameServer" name="GameServer">
<option value="2">PewPew</option>
</select>
this needs to map to..
myGameServer.GameServerId = 2; // PewPew.
cheers!
i believe you will need to define it in your controller arguments or else it wouldnt have any clue what to accept.
public ActionResult GameServer(string GameServer){
GServer myGameServer = new GServer();
myGameServer.GameServerId.ToString() = GameServer;
return View("GameServer");
}
you can pass in the name/id of the parameter your trying to go for on your view page, it will automagically know the value to recieve based on the id on your view.
What's wrong with having a FormCollection passed as argument? I had to do the same thing as you and I just excluded the GameServer property in the BindAttribute.
Another thing you have to note is that Html.ValidationMessage("GameServer", "*") won't work because the underlying model doesn't contain a GameServer property. You have to add it to the model. I don't know if there is a better way to do it, I didn't find but it was required to make ValidationMessage works
You can create you own ModelBinder (see how to) to do the custom mapping.
An overkill IMO, but you might have your reasons...

Resources