How to extend html.textboxfor to remove the name attribute? - asp.net-mvc

I want to extend the helper to make it like this:
#html.TextBoxFor(x=>x.CustomerId).ReadOnly()
and output the input element without the name attribute, so that it will not be posted to the server.

This should do the trick:
public static class MyInputExtensions
{
public static MvcHtmlString NameLessTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
var textBox = htmlHelper.TextBoxFor(expression);
string pattern = #"name=""([^""]*)""";
string fixedHtml = Regex.Replace(textBox.ToHtmlString(), pattern, "");
return new MvcHtmlString(fixedHtml);
}
}
Usage:
#Html.NameLessTextBoxFor(x=> x.CustomerId)

You can't do it.
(at least without some ugly workarounds with processing string value returned from helper)
Html helpers were written to help you generate form fields for your model with intention that they will sent data to server. For strongly-typed helper (like Html.TextBoxFor(x => x.CustomerId)) the name is taken from passed expression and for not strongly-typed helpers (like Html.TextBoxFor("CustomerId", Model.CustomerId)) there is a check that throws exception when name is null or empty.
If you want to generate input without "name" attribute then simply do not use html helper methods.
For example, if you want to change you html helper usage to generate same output but without "name" attribute then:
instead of this:
#Html.TextBoxFor(x => x.BetAmount)
write this:
<input type="text" value="#Model.BetAmount" />
instead of this:
#Html.TextBoxFor(x => x.BetAmount, new { #class = "red", placeholder = "Type Stuff", data_maximum_value = Model.MaximumBetAmount })
write this:
<input type="text" value="#Model.BetAmount" class="red" placeholder="Type Stuff" data_maximum_value="#Model.MaximumBetAmount" />
instead of this (you use overload with "format" argument):
#Html.TextBoxFor(x => x.BetAmount, "{0:0}", new { #class = "blue" })
write this:
<input type="text" value="#Html.FormatValue(Model.BetAmount,"{0:0}")" class="red" />
because Html.TextBoxFor uses Html.FormatValue when you pass "format" argument.
This is not exactly the same what html helper do because html helpers first tries to get data from ModelState for validation purpose (it's a common gotcha). But for 99% of times this is probably good enough
I recommend checking actual source code of ASP.NET MVC if you want to know what helper methods are actually doing. It's not black magic.

Related

Build list of data validation attributes for a given element

When using any of the Input Extension Helper Methods, like #Html.TextboxFor, any Validation Attributes from your model are automatically generated by the Razor engine (via ClientValidationEnabled/UnobtrusiveJavaScriptEnabled).
For example, take the following case which works fine
Model:
[Required]
public string QuestionOne { get; set; }
View:
#Html.TextBoxFor(model => model.QuestionOne)
#Html.ValidationMessageFor(model => model.QuestionOne)
Generated Markup:
<input type="text" id="QuestionOne" name="QuestionOne" value=""
data-val="true" data-val-required="The QuestionOne field is required." >
<span class="field-validation-valid" data-valmsg-for="QuestionOne" data-valmsg-replace="true"></span>
In this case the attributes data-val="true" & data-val-required="The QuestionOne field is required." are picked up by Unobtrusive validation and the form element is successfully validated.
However, for extensibility reasons, I want to be able to generate the <input> element myself instead of using TextBoxFor. So my view would now look like this:
<input type="textbox"
id="#Html.IdFor(m => m.QuestionTwo)"
name="#Html.NameFor(m => m.QuestionTwo)"
value="#Model.QuestionTwo"
data-val="true" data-val-required="Selection is Required" />
#Html.ValidationMessageFor(model => model.QuestionTwo)
In this case, I'm faking the validation attribute output by just re-writing data-val="true" (etc) by hand, but this would have to be expanded to cover every single case.
Here's a running Demo in .NET Fiddle
Q: Can I build /return a list of data-val-* attributes for a given element?
You can use the GetUnobtrusiveValidationAttributes() method of HtmlHelper to get the validation attributes associated with a specific property.
For example in the view
#{ var attributes = Html.GetUnobtrusiveValidationAttributes("QuestionTwo"); }
<input
type="textbox"
#foreach(var attr in attributes)
{
#:#attr.Key="#attr.Value"
}
id="#Html.IdFor(m => m.QuestionTwo)"
....
/>
Note the #:#attr.Key="#attr.Value" line will give a warning (Missing attribute name) but will run correctly
Alternatively, you could use javaScript/jQuery to add the attributes
<script type="text/javascript">
var attributes = #Html.Raw(Json.Encode(attributes));
var input = $('#QuestionTwo');
for(var i in attributes) {
input.attr(i, attributes[i]);
}
</script>
I have forked the DotNetFiddle here to show the working code for both options.
While the above code shows how it can be done, you should not be doing that. The HtmlHelper methods execute a lot of code your ignoring to ensure correct 2-way model binding, for example, the value attribute is determined by first checking for a value in ModelState, then in the ViewDataDictionary, and only if the previous values do not exist, does it use the value of the property (the second part of TextBoxFor displaying initial value, not the value updated from code explains the behavior).
Except for the incorrect value attribute, the code you have shown for the <input> is the same as will be generated by simply using #Html.TextBoxFor(m => m.Question2). I assume your real case is different, but if you cannot make use of TextBoxFor() and using an overload that accepts htmlAttributes to generate the html you need, then the correct approach is to create your own HtmlHelper method (and making use of existing methods in the HtmlHelper class and System.Web.Mvc.Html namespace)

Reusing Custom Validation Across Views

I have an ASP.NET MVC 5 project. I have some custom validation needs. I know that in the end, a possible result for my HTML looks like this:
<div class="form-group has-error">
<label for="field">Field Label</label>
<div id="field">
<input name="field" class="form-control" type="text" autocomplete="off">
</div>
<div>
Field label is invalid. You need to do something else.
</div>
</div>
The validation errors are stored in a Dictionary<string, string> in the ViewBag. I was thinking of doing something similar to the following:
#string fieldErrorMessage = getFieldError('field', ViewBag.ValidationErrors)
<div class="form-group #if(fieldErrorMessage.length >0) { 'has-error' } ">
<label for="field">Field Label</label>
<div id="field">
<input name="field" class="form-control" type="text" autocomplete="off">
</div>
#if (fieldErrorMessage.length > 0) {
<div>
#fieldErrorMessage
</div>
}
</div>
My problem is, I do not know where to define getFieldError. I would like to use this function in multiple views. So I'm not sure a. where to define it. or b. how to call it. I'm not even sure if my approach to applying 'has-error' is correct. I feel like I have pseudocode more than mvc code.
Embedded functions in the page
For including functions in the page, you have two options.
Extending HtmlHelper:
You can extend HtmlHelper so that you can call Html.getFieldError("field"). Because ViewBag is in HtmlHelper, you won't need to pass that into the function. The best way to demonstrate this is by showing an example.
public static class ErrorValidation
{
public static MvcHtmlString getFieldError(this HtmlHelper<TModel> h, string f)
{
if(h.ViewBag.ValidationErrors.ContainsKey(f))
{
return MvcHtmlString.Create(h.ViewBag.ValidationErrors[f]);
}
return MvcHtmlString.Empty;
}
}
Adding a namespace in your views:
You can include a namespace in your views by adding a line to the Views\Web.config file. Then, you could use static methods like you planned. This is done by adding a line of something like <add namespace="MyProj.Validation" /> inside of <configuration><system.web.webPages.razor><pages><namespaces>. In addition, you can leave this out by calling the full reference to your function each time with MyProj.Validation.getFieldError(...).
Relying on MVC error handling
You can also use API's already built into MVC, which do allow for customized validation.
Doing error checks through model attributes:
The most simple way to do validation is by adding attributes to your model. If your model had a required field, you can simply add [Required] above the field in the class that defines your model. A plethora of validation attributes are provided by System.ComponentModel.DataAnnotations.
If you wanted to do a custom check, you could create your own attribute by implementing abstract class ValidationAttribute. Simply define the IsValid function with your custom logic.
If you have validation checks that need to happen on multiple fields in your model at the same time, you can have your model implement IValidatableObject. And to make this simpler, in the Validate function you don't need to create a list and add all your errors to that; simply return each ValidationResult object by putting the keyword yield at the beginning of the line. This would look like...
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
// Duplicate checks
List<String> fields = new List<String>();
for (var i=0; i<PhoneNumbers.Count; i++)
{
var item = PhoneNumbers[i];
if (PhoneNumbers.IndexOf(item) != PhoneNumbers.LastIndexOf(item))
{
fields.Add("PhoneNumbers["+i+"]");
}
}
if(fields.Count > 0)
{
yield return new ValidationResult("You cannot include duplicate phone numbers.", fields.ToArray());
}
// More validation checks
}
Doing error checks in the controller:
You can also do error checks in the controller, which allows for validation to vary depending on the action. This also lets you use the validation that already happened in the model with the ModelState object. In order to add errors to ModelState, simply call ModelState.AddModelError(). In order to check if a model is valid after all checks are done, you can check ModelState.IsValid.
Displaying errors in the view:
With validation happening in the ModelState object, you can display errors in your view by using the Html object. This allows you to generate a list of errors by calling Html.ValidationSummary() or display errors for individual properties with Html.ValidationMessageFor(...). Here's an extensive example...
for (var x = 0; x < Model.PhoneNumbers.Count(); x++ )
{
<tr>
<td>#Html.EditorFor(m => m.PhoneNumbers.ElementAt(x))</td>
#if(ViewData.ModelState["PhoneNumbers[" + x + "]"].Errors.Any())
{
<td>#Html.ValidationMessageFor(m => m.PhoneNumbers.ElementAt(x))</td>
}
else
{
<td>Ok!</td>
}
</tr>
}

Custom MVC HTML extension for DateTime property

Is it possible to create a custom html helper for asp.net mvc that would take a DateTime as TProperty, create 3 textboxes for day, month and year and allow me to get the full DateTime back when posting the form ?
In other words, I want something that is written like this in Razor :
#Html.TripleTextboxFor(userModel => userModel.Birthdate)
That outputs something like :
<input type="text" name="BirthdateDay" />
<input type="text" name="BirthdateMonth" />
<input type="text" name="BirthdateYear" />
And finally, is it possible to create such an extension without doing extra model custom binding to retrieve the DateTime posted ?
Here is what I have been using so far. It is ok to display the value, but I have extra work to get the DateTime back + validation.
public static MvcHtmlString TripleBirthDateTextBoxes<TModel>(this HtmlHelper<TModel> htmlHelper,Expression<Func<TModel, DateTime>> expression)
{
StringBuilder output = new StringBuilder();
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
DateTime dt = (DateTime)metaData.Model;
output.Append(htmlHelper.TextBoxFor(m => dt.Day));
output.Append(htmlHelper.TextBoxFor(m => dt.Month));
output.Append(htmlHelper.TextBoxFor(m => dt.Year));
return new MvcHtmlString(output.ToString());
}
Thanks !

Set html attributes with data annotation

How can I set attributes when my model property is rendered with any Html helper?
Example:
I have my custom data annotation:
[MyCustomAttribute(AnyHtmlProperty = "100")]
public string Name{get;set;}
So, when this property is rendered, I want something like this:
<input type="text" anyHtmlProperty="100" />
You could write an HtmlHelper extension method which accepts an expression. Here's a non-functioning example of what the razor syntax would look like.
#Html.CustomInputFor(x => x.Name)

How do I set up a check box with ASP MVC in my view?

I have the following class:
public class City {
public string Name { get; set; }
public bool CityValid { get; set; }
}
I know how to set up the name but how can I set up the CityValid field so it acts like a checkbox. I'd like to do this without using HTML helpers.
If you really don't want to use helpers, you would use a normal HTML input tag:
<input type="checkbox" id="CityValid" name="CityValid" value="#Model.CityValid" />
<input type="hidden" id="CityValue_Hidden" name="CityValid" value="false" />
The name attribute has to match your property name so that the model binder will pick it up correctly when you post back to the server.
When you use the helpers, something similar to the above markup will be generated. The hidden field is there so that a value is always sent with the form post data, regardless of whether you check the box or not (if you leave the box unchecked, no value gets sent by default, not even a 'false').
However, unless you're doing something really weird, I'd recommend you stick to using the helpers. Either:
#Html.CheckboxFor(m => m.CityValid)
or
#Html.EditorFor(m => m.CityValid)
In your view you could use the EditorFor helper:
#model City
...
#Html.EditorFor(x => x.CityValid)
The default editor template for a boolean field generates a checkbox.
You could first create it with a HTML-helper, look at the markup that gets created when running the page, and then recreate that markup...

Resources