Using TextBoxFor to set HTML attributes that have a namespace prefix - asp.net-mvc

I am converting some existing HTML to work with ASP.NET MVC, and have some forms containing input fields that have additional attributes (that, for now, I need to preserve) that contain a namespace prefix:
<input type="text" foo:required="true" foo:message="Please enter a username" />
I would like to use the TextBoxFor helper method overload that allows htmlAttributes to be specified using collection initializer syntax:
#Html.TextBoxFor(
model => model.Username,
new { foo:required="true", foo:message="Please enter a username" })
However, this syntax is not valid, due to the colon in foo:required (etc).
Instead, I am having to use this more verbose dictionary initializer syntax:
#Html.TextBoxFor(
model => model.Username,
new Dictionary<string, object>
{
{ "foo:required", "true" },
{ "foo:message", "Please enter a username" }
})
Is it possible to use a variation on the first syntax, somehow escaping (for want of a better word) the colons?
Or
Is this a scenario where the TextBoxFor helper method does not really help, and would it be simpler to just keep the existing raw HTML, and add value="#Model.UserName" to the input element?

The first syntax is using an anonymous object, for which the same rules regarding how to create identifiers in C# apply:
You can use any unicode character of the classes Lu, Ll, Lt, Lm, Lo, or Nl
You can scape C# keywords using "#" as in new { #class = "foo" };
Since the colon belongs to the Po unicode class it cannot be used in an identifier. (In C# you can use the static method char.GetUnicodeCategory in order to check the class of any character)
Additionally, and only in MVC, when using an anonymous object for the html attributes in the helper, any attribute name with underscores will be replaced by hyphens. This is due to the method HtmlHelper.AnonymousObjectToHtmlAttributes
Back to your case and regarding your options, if those are not too widely used (like in a couple of views) I would consider staying with the Dictionary syntax of the TextBoxFor helper. You will still get the automatic generation of the id\name properties in sync with the model binding, and you will get any other attributes from the model metadata like the unobtrusive validation attributes. (although looking at the attributes you want to preserve, it seems you won´t need the unobtrusive validation ones :) )
However if the id\name will be as simple as the name of the property and you don´t need any other html attributes that would be generated automatically using the helper, then going for the raw HTML makes sense. (as the dictionary syntax is quite ugly)
In case you use it widely across the application, then in my opinion the most sensible approach may be creating your own helper, like #Html.LegacyTextBoxFor(...). That helper will render those legacy attributes you want to kepp, and you can incorporate additionaly logic similar to the standard helpers for id\name attribute creation.
Something like this:
public class FooAttributes
{
public bool Required { get; set; }
public string Message { get; set; }
}
public static class FooHelper
{
public static MvcHtmlString LegacyTextboxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, FooAttributes fooAttributes)
{
var fieldName = ExpressionHelper.GetExpressionText(expression);
var fullBindingName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var value = metadata.Model;
TagBuilder tag = new TagBuilder("input");
tag.Attributes.Add("name", fullBindingName);
tag.Attributes.Add("id", fieldId);
tag.Attributes.Add("type", "text");
tag.Attributes.Add("value", value == null ? "" : value.ToString());
if (fooAttributes != null)
{
if (fooAttributes.Required) tag.Attributes.Add("foo:required", "true");
if (!String.IsNullOrEmpty(fooAttributes.Message)) tag.Attributes.Add("foo:message", fooAttributes.Message);
}
return new MvcHtmlString(tag.ToString(TagRenderMode.SelfClosing));
}
}
That can be used as:
#Html.LegacyTextboxFor(model => model.UserName, new FooAttributes {Required=true, Message="Please enter a value." })
And will generate this html:
<input foo:message="Please enter a value." foo:required="true" id="UserName" name="UserName" type="text" value="">
And once you have your own helper, you could add additionaly logic, for example logic that will generate those attributes from the model metadata and its data annotation attributes...
I have extended my answer more than intended, but I hope it helps!

Related

using Variable property Name with Razor MVC

I am trying to have all the models used for specific searches into one Generic search view.
I need to render only some of the fields in the model, either with something similar to this : (Psudo code)
foreach (string textBoxFieldName in TextBoxFieldNames)
{
Html.Toolbox_TextBoxFor(m => m.textBoxFieldName)
}
or having attributes on the model and checking when that attribute applies e.g.
in the model I'll have something like this :
[AppliedCases("Case1","Case4","Case77")]
[ControlToUse("TextBoxFor")]
public string LastName { get; set; }
And some how in the view will be able to go trough all the properties check that if the CurrentCase is one of the AppliedCases of the Model property and if so then use the Razor magic display it accordingly
My question is this correct approach and if so how do I implement it, looking for some examples with dynamic rendering (if that is the name of this topic)
You could use the overload of Html.Editor that takes the name (as string) of the property you want to render:
var fieldNames = new string[] {"LastName"};
foreach (string fieldName in fieldNames) {
#Html.Editor(fieldName)
}
You can use the UIHint attribute in the ViewModel to influence which editor template shall be used:
[UIHint("MySpecialStringEditor")]
public string LastName { get; set; }
At the shop where I work, we do not use this approach. Rather, we use different explicit Views of the same ViewModel. This allows for more flexibility and is easier to understand and maintain.

Custom attribute to change HTML form element's "name" attribute

I need to add a prefix to the name of form elements that are rendered within a form. I've created a custom attribute to decorate a property that accepts the name of another property whose value will be used for the name prefix.
public class HtmlElementNamePrefixPropertyAttribute : Attribute {
public string PropertyName { get; set; }
public HtmlElementNamePrefixPropertyAttribute(string propertyName) {
PropertyName = propertyName;
}
}
And my custom ModelMetadataProvider:
public class AddressModelMetadataProvider : DataAnnotationsModelMetadataProvider {
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) {
ModelMetadata metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
HtmlElementNamePrefixPropertyAttribute nameAttribute = attributes.OfType<HtmlElementNamePrefixPropertyAttribute>().FirstOrDefault();
if (nameAttribute != null) {
ModelMetadata prefixMetadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor, metadata.ContainerType, nameAttribute.PropertyName);
metadata.PropertyName = string.Format("{0}{1}", prefixMetadata.Model, metadata.PropertyName);
}
return metadata;
}
}
As an example, if I decorate Address1 with HtmlElementNamePrefixAttribute:
[HtmlElementNamePrefix("AddressType")]
public string Address1 { get; set; }
public AddressType AddressType { get; set; }
And in my view render a textbox for Address1:
#Html.TextBoxFor(m => m.Address1)
It should render as (assuming that AddressType == AddressType.Home)
<input name="HomeAddress1" value="123 Street way"/>
I have a few problems:
I'm not sure how to effect the rendered name HTML attribute from AddressModelMetadataProvider or what property in ModelMetadata would allow me to do that. My current attempt was to change the PropertyName property of ModelMetadata, but that doesn't have a setter.
If possible, I don't want to create a new HtmlHelper as this attribute could apply to any type of form element that would be rendered in an Address. I also don't want to create a string EditorTemplate since this scenario only applies to an Address object and its properties.
To give a better understanding of what I'm trying to accomplish and why, let me give a brief explanation of the project and its purpose.
Our application allows users to create "fields". From the end users perspective, "fields" can be a single line textbox, multi line textbox (textarea), chooseMany (which is a group of checkboxes), chooseOne (which is a dropdown), address (which consists of more than one form element and can be of type home, business or other), contact (email, phone, and fax), and others. I've simplified a great deal, but I think this gets the point across.
All this information is stored in the database ("field" values, "field" metadata, which "fields" are on the requested "form", etc.), and at runtime used to configure the "form" the user is requesting (i.e., /forms/123). So the form may have a textbox and an address "field", or maybe a home address "field" and a business address "field". Each "form" is created and configured by an administrative user. These "fields" or rather models, inherit from IDataItem and have their own views (templates) that describe how they should be rendered to the UI.
Because of the dynamic nature of a "form" custom model binding and validation was needed (i.e., custom ValidationAttributes, ValidatorProviders, ModelBinders, etc.). Validation rules and logic are applied dynamically at runtime using custom and standard ValidationAttributes (i.e., RequiredAttribute is used for simple "fields" like a single line textbox or chooseOne). This is done at runtime, because the administrator building the "form" can mark a "field" as required or not (as well as other validation constraints).
Required validation for an address "field" is different, because an address is not considered complete unless all parts of the address are filled out (with the exception of address2 and address3).
For client side validation we're using the standard MVC client validation library, jQuery validate. Here in lies my problem... Error messages are applied by jQuery validate based on the name of the form element. So if address1 is invalid, but there is a home address "field" and a business address "field" on the form then a distinction needs to be made between each form element name, so that the error message can be applied to the correct element. This is the biggest reason why I need to prefix the address form element names with AddressType.
This is a very long-winded simplified explanation, but I think it relays the purpose of the application and why my problem exists.

How to get raw validationmessages in ValidationMessageFor?

I got an asp.net mvc4 application where I'd like to retrieve only the exact error string of validationmessagefor and not the span tag, classes etc.
Currently the following is being returned from ValidationMessageFor:
<span class="field-validation-error" data-valmsg-replace="true" data-valmsg-for="Password">
<span class="" for="Password" generated="true">The Password field is required.</span>
</span>
I would like to only have returned: The Password field is required.
It's because I need to insert the exact value into another element attribute (so I can't use the HTML tags).
Essentially I need a custom ValidationMessageFor html helper that only returns the error message itself.
How would I do this?
Any error messages are a part of your ModelState.
For example, if you have a required field FirstName you can get the error message like this:
ModelState["FirstName"].Errors[0].ErrorMessage
In this case, you expect that there is only one error. This property returns 'The FirstName field is required'.
You can access ModelState from your controller or from your view by using ViewDate.ModelState.
ModelState is of type ModelStateDictionary which is an IDictionary<string, ModelState>. It only has an indexer property of type string. Because of this you can't access it by using ModelState[p => p.Property]. However, you can easily create an extension method that helps you with this:
public static class ModelStateExtension
{
public static ModelState For<TModel>(this ModelStateDictionary dictionary,
Expression<Func<TModel, object>> expression)
{
string propertyName = ExpressionHelper.GetExpressionText(expression);
return dictionary[propertyName];
}
}
You can use it like this:
string errorMessage = ModelState.For<Person>(p => p.FirstName).Errors[0].ErrorMessage;
If you want, you can let the extension method return the ErrorMessage for the first error directly instead of the ModelState.
To access errors you should use ModelState.Errors property. It contains
ModelErrorCollection
whith is a collection of
ModelError
Refer here http://msdn.microsoft.com/en-us/library/system.web.mvc.modelstate.errors%28v=vs.108%29.aspx
You can create your own helper then, and access those messagess on client-side.
For a specific property you can use
ViewData.ModelState["Password"].Errors

ASP.Net MVC Html.HiddenFor with wrong value

I'm using MVC 3 in my project, and I'm seeing a very strange behavior.
I'm trying to create a hidden field for a particular value on my Model, the problem is that for some reason the value set on the field does not correspond to the value in the Model.
e.g.
I have this code, just as a test:
<%:Html.Hidden("Step2", Model.Step) %>
<%:Html.HiddenFor(m => m.Step) %>
I would think that both hidden fields would have the same value.
What I do is, set the value to 1 the first time I display the View, and then after the submission I increase the value of the Model field by 1.
So, the first time I render the page both controls have the value 1, but the second time the values rendered are these:
<input id="Step2" name="Step2" type="hidden" value="2" />
<input id="Step" name="Step" type="hidden" value="1" />
As you can see, the first value is correct, but the second value seems to be the same as the first time I display the View.
What am I missing? Are the *For Html helpers caching the values in some way? If so, how can I disable this caching?.
Thanks for your help.
That's normal and it is how HTML helpers work. They first use the value of the POST request and after that the value in the model. This means that even if you modify the value of the model in your controller action if there is the same variable in the POST request your modification will be ignored and the POSTed value will be used.
One possible workaround is to remove this value from the model state in the controller action which is trying to modify the value:
// remove the Step variable from the model state
// if you want the changes in the model to be
// taken into account
ModelState.Remove("Step");
model.Step = 2;
Another possibility is to write a custom HTML helper which will always use the value of the model and ignore POST values.
And yet another possibility:
<input type="hidden" name="Step" value="<%: Model.Step %>" />
I encountered the same problem when writing a Wizard that shows different parts of a larger model at every step.
Data and/or Errors from "Step 1" would become mixed up with "Step 2", etc, until I finally realized that ModelState was to 'blame'.
This was my simple solution:
if (oldPageIndex != newPageIndex)
{
ModelState.Clear(); // <-- solution
}
return View(model[newPageIndex]);
This code will not work
// remove the Step variable from the model state
// if you want the changes in the model to be
// taken into account
ModelState.Remove("Step");
model.Step = 2;
...because HiddenFor always (!) reads from ModelState not the model itself. And if it doesn't find the "Step" key it will produce the default for that variable type which will be 0 in this case
Here is the solution. I wrote it for myself but don't mind sharing it cause I see many people are struggling with this naughty HiddenFor helper.
public static class CustomExtensions
{
public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
ReplacePropertyState(htmlHelper, expression);
return htmlHelper.HiddenFor(expression);
}
public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
ReplacePropertyState(htmlHelper, expression);
return htmlHelper.HiddenFor(expression, htmlAttributes);
}
public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
{
ReplacePropertyState(htmlHelper, expression);
return htmlHelper.HiddenFor(expression, htmlAttributes);
}
private static void ReplacePropertyState<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
string text = ExpressionHelper.GetExpressionText(expression);
string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(text);
ModelStateDictionary modelState = htmlHelper.ViewContext.ViewData.ModelState;
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (modelState.ContainsKey(fullName))
{
ValueProviderResult currentValue = modelState[fullName].Value;
modelState[fullName].Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), currentValue.Culture);
}
else
{
modelState[fullName] = new ModelState
{
Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), CultureInfo.CurrentUICulture)
};
}
}
}
Then you just use it as usual from within you view:
#Html.HiddenFor2(m => m.Id)
It worth to mention it works with collections too.
I am too struggling with the same situation I think, where I use the same model state between calls, and when I alter a model property on backend. Though, it does not matter for me, if I use textboxfor or hiddenfor.
I just bypass the situation by using page scripts to store the model value as a js variable, because I need the hiddenfield for that purpose in the beginning.
Not sure if this helps but just consider..

mvc.net how to edit member list variables

Given the following model which has a name, url, and an arbitrary list of keywords (I want the user to add a series of keywords) ...
public class Picture
{
public Picture()
{
keywords = new List<string>();
}
public string name {get;set:}
public string url {get;set;}
public List<string> keywords{get;set;}
}
... and the following action in my controller ...
[HttpPost]
public ActionResult Edit(FormCollection fc)
{
if (ModelState.IsValid)
{
// do stuff
}
return View(ModelManager.Picture);
}
In the FormCollection I have the following field
fc["keywords"] = "keyword1,keyword2,keyword3"
And I then create a Picture object based on the form collection.
However, I would prefer to use a strongly-typed action such as
[HttpPost]
public ActionResult Edit(Picture p)
But in this approach, my p.keywords property is always empty. Is there some way to help the framework recreate my p.keywords property before it hits my controller's action method?
I thought an Editor Template might work here, but I don't think there is a way to model bind a nested IEnumerable view model member. Your fastest bet may be handling it directly with FormCollection and some string parsing magic. Otherwise, if you have to strongly-type this, maybe a custom model binder like this could help if you can control your keyword element id's:
public class PictureKeywordBinder : IModelBinder
{
public object GetValue(ControllerContext controllerContext,
string modelName, Type modelType,
ModelStateDictionary modelState)
{
Picture picture = new Picture();
//set name, url, other paramaters here
foreach(var item in Request.Form.Keys)
{
if (item.StartsWith("keyword"))
{
picture.keywords.Add(Request.Form[item]);
}
}
//add any errors to model here
return picture;
}
}
Maybe the keyword id's could be setup in a partial view passed the sub model from your parent view:
<% Html.RenderPartial("PictureKeywords", Model.keywords);
Are your keywords seperate text boxes? If so, create an inputs like this and they will be populated by the model binder.
<input name="keywords[0]" type="text">
<input name="keywords[1]" type="text">
<input name="keywords[2]" type="text">
The way I got around this, is to use a hidden input to store the csv string of items, in your case, keywords.
I then hooked into the form submit event (using jQuery) and appended the inputs to form the csv string, which is then stored in the hidden input. This hidden input was strongly typed to a property on my model.
It's a little clunky, but if you have a dynamic number of possible keywords then this works quite well (except if JS is disabled of course)
In what way you are expecting the user to add more keywords? In the form comma separated values(CSV) or by dynamically adding textboxes?
Based on your requirement, i have two solutions with me.

Resources