How can I add inline html elements inside a label with Html.Label?
Looks like a good scenario for a custom helper:
public static class LabelExtensions
{
public static MvcHtmlString LabelFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> ex,
Func<object, HelperResult> template
)
{
var htmlFieldName = ExpressionHelper.GetExpressionText(ex);
var for = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName);
var label = new TagBuilder("label");
label.Attributes["for"] = TagBuilder.CreateSanitizedId(for);
label.InnerHtml = template(null).ToHtmlString();
return MvcHtmlString.Create(label.ToString());
}
}
and then:
#Html.LabelFor(
x => x.Name,
#<span>Hello World</span>
)
UPDATE:
To achieve what you asked in the comments section you may try the following:
public static class HtmlHelperExtensions
{
public static MvcHtmlString LabelFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> ex, Func<object, HelperResult> template)
{
var htmlFieldName = ExpressionHelper.GetExpressionText(ex);
var propertyName = htmlFieldName.Split('.').Last();
var label = new TagBuilder("label");
label.Attributes["for"] = TagBuilder.CreateSanitizedId(htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName));
label.InnerHtml = string.Format(
"{0} {1}",
propertyName,
template(null).ToHtmlString()
);
return MvcHtmlString.Create(label.ToString());
}
}
and then:
#Html.LabelFor(
x => x.Name,
#<em>mandatory</em>
)
Rather then writing an extension method you could use the following razor code:
#{ MvcHtmlString label = Html.LabelFor(m => m.ModelProperty, "<span class='cssClass'>Label HTML</span>", new { #class = "clabel"}); }
#Html.Raw(HttpUtility.HtmlDecode(label.ToString()))
It's not as clean but if you need something quick it works.
I borrowed upon Darin's answer, and added to it. I added in capability for Html before the label text and html after the label text. I also added a bunch of overload methods and comments.
I also got some information from this post: How can I override the #Html.LabelFor template?
Hope if helps folks.
namespace System.Web.Mvc.Html
{
public static class LabelExtensions
{
/// <summary>Creates a Label with custom Html before the label text. Only starting Html is provided.</summary>
/// <param name="startHtml">Html to preempt the label text.</param>
/// <returns>MVC Html for the Label</returns>
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, Func<object, HelperResult> startHtml)
{
return LabelFor(html, expression, startHtml, null, new RouteValueDictionary("new {}"));
}
/// <summary>Creates a Label with custom Html before the label text. Starting Html and a single Html attribute is provided.</summary>
/// <param name="startHtml">Html to preempt the label text.</param>
/// <param name="htmlAttributes">A single Html attribute to include.</param>
/// <returns>MVC Html for the Label</returns>
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, Func<object, HelperResult> startHtml, object htmlAttributes)
{
return LabelFor(html, expression, startHtml, null, new RouteValueDictionary(htmlAttributes));
}
/// <summary>Creates a Label with custom Html before the label text. Starting Html and a collection of Html attributes are provided.</summary>
/// <param name="startHtml">Html to preempt the label text.</param>
/// <param name="htmlAttributes">A collection of Html attributes to include.</param>
/// <returns>MVC Html for the Label</returns>
public static MvcHtmlString LabelFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, Func<object, HelperResult> startHtml, IDictionary<string, object> htmlAttributes)
{
return LabelFor(html, expression, startHtml, null, htmlAttributes);
}
/// <summary>Creates a Label with custom Html before and after the label text. Starting Html and ending Html are provided.</summary>
/// <param name="startHtml">Html to preempt the label text.</param>
/// <param name="endHtml">Html to follow the label text.</param>
/// <returns>MVC Html for the Label</returns>
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, Func<object, HelperResult> startHtml, Func<object, HelperResult> endHtml)
{
return LabelFor(html, expression, startHtml, endHtml, new RouteValueDictionary("new {}"));
}
/// <summary>Creates a Label with custom Html before and after the label text. Starting Html, ending Html, and a single Html attribute are provided.</summary>
/// <param name="startHtml">Html to preempt the label text.</param>
/// <param name="endHtml">Html to follow the label text.</param>
/// <param name="htmlAttributes">A single Html attribute to include.</param>
/// <returns>MVC Html for the Label</returns>
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, Func<object, HelperResult> startHtml, Func<object, HelperResult> endHtml, object htmlAttributes)
{
return LabelFor(html, expression, startHtml, endHtml, new RouteValueDictionary(htmlAttributes));
}
/// <summary>Creates a Label with custom Html before and after the label text. Starting Html, ending Html, and a collection of Html attributes are provided.</summary>
/// <param name="startHtml">Html to preempt the label text.</param>
/// <param name="endHtml">Html to follow the label text.</param>
/// <param name="htmlAttributes">A collection of Html attributes to include.</param>
/// <returns>MVC Html for the Label</returns>
public static MvcHtmlString LabelFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, Func<object, HelperResult> startHtml, Func<object, HelperResult> endHtml, IDictionary<string, object> htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
//Use the DisplayName or PropertyName for the metadata if available. Otherwise default to the htmlFieldName provided by the user.
string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
if (String.IsNullOrEmpty(labelText))
{
return MvcHtmlString.Empty;
}
//Create the new label.
TagBuilder tag = new TagBuilder("label");
//Add the specified Html attributes
tag.MergeAttributes(htmlAttributes);
//Specify what property the label is tied to.
tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
//Run through the various iterations of null starting or ending Html text.
if (startHtml == null && endHtml == null) tag.InnerHtml = labelText;
else if (startHtml != null && endHtml == null) tag.InnerHtml = string.Format("{0}{1}", startHtml(null).ToHtmlString(), labelText);
else if (startHtml == null && endHtml != null) tag.InnerHtml = string.Format("{0}{1}", labelText, endHtml(null).ToHtmlString());
else tag.InnerHtml = string.Format("{0}{1}{2}", startHtml(null).ToHtmlString(), labelText, endHtml(null).ToHtmlString());
return MvcHtmlString.Create(tag.ToString());
}
}
}
You will have to write your own helper. The built-in Html.Label helper automatically HTML-encodes the labelText parameter.
In order to meet the SOC and Solid principles, the code can be enhanced to the following code:
public static MvcHtmlString LabelFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> ex,bool applyStylingHtml)
{
var metadata = ModelMetadata.FromLambdaExpression(ex, htmlHelper.ViewData);
string displayName = metadata.DisplayName;
string description= metadata.Description;
if (String.IsNullOrEmpty(displayName))
{
return MvcHtmlString.Empty;
}
var sb = new StringBuilder();
sb.Append(displayName);
var htmlFieldName = ExpressionHelper.GetExpressionText(ex);
var propertyName = htmlFieldName.Split('.').Last();
var tag = new TagBuilder("label");
tag.Attributes["for"] = TagBuilder.CreateSanitizedId(htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName));
tag.SetInnerText(sb.ToString());
//Func<object, HelperResult> template='<em>';
HtmlString nestedHtml=new HtmlString("<em>"+description+"</em>");
tag.InnerHtml = string.Format(
"{0} {1}",
tag.InnerHtml,
nestedHtml
);
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
Then use it in the Razor code:
#Html.LabelFor(m => m.Phone,true)
To make everything more dynamic, description attribute should be applied on the Model class then HtmlHelper will grab the Description as a text to be applied "em" Html tag:
[Display(Name ="Phone",Description = "should be included extention")]
public string Phone { get; set; }
Just a heads up that you need to import the your customized HtmlHelper namespace to the view by adding:
#using yourNamespace
Related
I want to grab object (Ex:Lead) and have it map to search filters based on their order. Instead of the if/then statements and casting the search parameters below. So lead_ID would map to nvc["search_0"], lead_number would map to nvc["search_1"], etc...
Lead:
public class Fre_LeadSummaryDto
{
[DataMember]
public string Lead_ID { get; set; }
[DataMember]
public string Lead_Number { get; set; }
[DataMember]
public string LeadSource { get; set; }
[DataMember]
public string MethodRecvd { get; set; }
[DataMember]
public string TotalLeadRecvd { get; set; }
[DataMember]
public string TotalBalDueReturns { get; set; }
[DataMember]
public string DateLeadRecvd { get; set; }
}
This is the search filters and predicate setup:
var predicate = PredicateBuilder.True<Fre_LeadSummaryDto>();
if (!String.IsNullOrEmpty(nvc["sSearch_0"]))
{
leadId = nvc["sSearch_0"];
predicate = predicate.And(i => i.Lead_ID.Equals(leadId));
}
if (!String.IsNullOrEmpty(nvc["sSearch_1"]))
{
leadNumber = nvc["sSearch_1"];
predicate = predicate.And(i => i.Lead_Number.StartsWith(leadNumber));
}
Where clause:
leads = leads.Where(predicate);
The predicate builder:
/// <summary>
/// Enables the efficient, dynamic composition of query predicates.
/// </summary>
public static class PredicateBuilder
{
/// <summary>
/// Creates a predicate that evaluates to true.
/// </summary>
public static Expression<Func<T, bool>> True<T>() { return param => true; }
/// <summary>
/// Creates a predicate that evaluates to false.
/// </summary>
public static Expression<Func<T, bool>> False<T>() { return param => false; }
/// <summary>
/// Creates a predicate expression from the specified lambda expression.
/// </summary>
public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }
/// <summary>
/// Combines the first predicate with the second using the logical "and".
/// </summary>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.AndAlso);
}
/// <summary>
/// Combines the first predicate with the second using the logical "or".
/// </summary>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.OrElse);
}
/// <summary>
/// Negates the predicate.
/// </summary>
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
{
var negated = Expression.Not(expression.Body);
return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
}
/// <summary>
/// Combines the first expression with the second using the specified merge function.
/// </summary>
static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
{
// zip parameters (map from parameters of second to parameters of first)
var map = first.Parameters
.Select((f, i) => new { f, s = second.Parameters[i] })
.ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with the parameters in the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// create a merged lambda expression with parameters from the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
class ParameterRebinder : ExpressionVisitor
{
readonly Dictionary<ParameterExpression, ParameterExpression> map;
ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p)
{
ParameterExpression replacement;
if (map.TryGetValue(p, out replacement))
{
p = replacement;
}
return base.VisitParameter(p);
}
}
}
I want to use Html.LabelFor() extension of Asp. Net MVC 4 to add html attributes to a Label. I'm using MVC 3. My question is: Can I simple upgrade my system.web.mvc dll?(any problem with this?...) OR it will be a problem in deploy?
this is mvc3 source code
internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string labelText = null) {
string resolvedLabelText = labelText ?? metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
if (String.IsNullOrEmpty(resolvedLabelText)) {
return MvcHtmlString.Empty;
}
TagBuilder tag = new TagBuilder("label");
tag.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
tag.SetInnerText(resolvedLabelText);
return tag.ToMvcHtmlString(TagRenderMode.Normal);}
and this is in mvc4
internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string labelText = null, IDictionary<string, object> htmlAttributes = null){
string resolvedLabelText = labelText ?? metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
if (String.IsNullOrEmpty(resolvedLabelText))
{
return MvcHtmlString.Empty;
}
TagBuilder tag = new TagBuilder("label");
tag.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
tag.SetInnerText(resolvedLabelText);
tag.MergeAttributes(htmlAttributes, replaceExisting: true);
return tag.ToMvcHtmlString(TagRenderMode.Normal);}
there is only one line diffrenct tag.MergeAttributes(htmlAttributes, replaceExisting: true);
in mvc3 InputExtensions.cs you can find the TagBuilder.MergeAttributes function has used.
so you can write a extension method by youself,not need to upgrad you project.
below is the all code in mvc4,maybe it will help you
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
namespace System.Web.Mvc.Html
{
public static class LabelExtensions
{
public static MvcHtmlString Label(this HtmlHelper html, string expression)
{
return Label(html,
expression,
labelText: null);
}
public static MvcHtmlString Label(this HtmlHelper html, string expression, string labelText)
{
return Label(html, expression, labelText, htmlAttributes: null, metadataProvider: null);
}
public static MvcHtmlString Label(this HtmlHelper html, string expression, object htmlAttributes)
{
return Label(html, expression, labelText: null, htmlAttributes: htmlAttributes, metadataProvider: null);
}
public static MvcHtmlString Label(this HtmlHelper html, string expression, IDictionary<string, object> htmlAttributes)
{
return Label(html, expression, labelText: null, htmlAttributes: htmlAttributes, metadataProvider: null);
}
public static MvcHtmlString Label(this HtmlHelper html, string expression, string labelText, object htmlAttributes)
{
return Label(html, expression, labelText, htmlAttributes, metadataProvider: null);
}
public static MvcHtmlString Label(this HtmlHelper html, string expression, string labelText, IDictionary<string, object> htmlAttributes)
{
return Label(html, expression, labelText, htmlAttributes, metadataProvider: null);
}
internal static MvcHtmlString Label(this HtmlHelper html, string expression, string labelText, object htmlAttributes, ModelMetadataProvider metadataProvider)
{
return Label(html,
expression,
labelText,
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes),
metadataProvider);
}
internal static MvcHtmlString Label(this HtmlHelper html, string expression, string labelText, IDictionary<string, object> htmlAttributes, ModelMetadataProvider metadataProvider)
{
return LabelHelper(html,
ModelMetadata.FromStringExpression(expression, html.ViewData, metadataProvider),
expression,
labelText,
htmlAttributes);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
return LabelFor<TModel, TValue>(html, expression, labelText: null);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText)
{
return LabelFor(html, expression, labelText, htmlAttributes: null, metadataProvider: null);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
return LabelFor(html, expression, labelText: null, htmlAttributes: htmlAttributes, metadataProvider: null);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
return LabelFor(html, expression, labelText: null, htmlAttributes: htmlAttributes, metadataProvider: null);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, object htmlAttributes)
{
return LabelFor(html, expression, labelText, htmlAttributes, metadataProvider: null);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, IDictionary<string, object> htmlAttributes)
{
return LabelFor(html, expression, labelText, htmlAttributes, metadataProvider: null);
}
internal static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, object htmlAttributes, ModelMetadataProvider metadataProvider)
{
return LabelFor(html,
expression,
labelText,
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes),
metadataProvider);
}
internal static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, IDictionary<string, object> htmlAttributes, ModelMetadataProvider metadataProvider)
{
return LabelHelper(html,
ModelMetadata.FromLambdaExpression(expression, html.ViewData, metadataProvider),
ExpressionHelper.GetExpressionText(expression),
labelText,
htmlAttributes);
}
public static MvcHtmlString LabelForModel(this HtmlHelper html)
{
return LabelForModel(html, labelText: null);
}
public static MvcHtmlString LabelForModel(this HtmlHelper html, string labelText)
{
return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty, labelText);
}
public static MvcHtmlString LabelForModel(this HtmlHelper html, object htmlAttributes)
{
return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty, labelText: null, htmlAttributes: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString LabelForModel(this HtmlHelper html, IDictionary<string, object> htmlAttributes)
{
return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty, labelText: null, htmlAttributes: htmlAttributes);
}
public static MvcHtmlString LabelForModel(this HtmlHelper html, string labelText, object htmlAttributes)
{
return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty, labelText, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString LabelForModel(this HtmlHelper html, string labelText, IDictionary<string, object> htmlAttributes)
{
return LabelHelper(html, html.ViewData.ModelMetadata, String.Empty, labelText, htmlAttributes);
}
internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string labelText = null, IDictionary<string, object> htmlAttributes = null)
{
string resolvedLabelText = labelText ?? metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
if (String.IsNullOrEmpty(resolvedLabelText))
{
return MvcHtmlString.Empty;
}
TagBuilder tag = new TagBuilder("label");
tag.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
tag.SetInnerText(resolvedLabelText);
tag.MergeAttributes(htmlAttributes, replaceExisting: true);
return tag.ToMvcHtmlString(TagRenderMode.Normal);
}
}
}
I am having two datepickers fields on my form, and i would like to validate that the to date is greater than from date.
Is there any validation attribute in MVC5 which I can use to achieve this?
I would also like this to work on client side, can some body please help in enabling client side validation in MVC?
Many Thanks
Edit: Created the custom attribute, but client side validation not working.
public class ValidateToDateAttribute : ValidationAttribute, IClientValidatable
{
public string errorMessageKey { get; private set; }
public ValidateToDateAttribute(string errorMessageKey)
{
this.errorMessageKey = errorMessageKey;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var viewModel = (TransactionViewModel)validationContext.ObjectInstance;
if (viewModel.ToDate.CompareTo(viewModel.FromDate) < 0)
{
return new ValidationResult(new ResourceManager(typeof(ValidationErrorMessages)).GetString(errorMessageKey));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "validatetodate",
ErrorMessage = new ResourceManager(typeof(ValidationErrorMessages)).GetString(errorMessageKey)
};
yield return modelClientValidationRule;
}
}
}
Bundle.Config
bundles.Add(new ScriptBundle("~/bundles/jqueryvalidation").Include(
"~/Scripts/jquery.validate.unobtrusive.min.js",
"~/Scripts/jquery.unobtrusive-ajax.min.js"));
View Model
[Display(ResourceType = typeof(DisplayLabelText), Name = "FromDate")]
public DateTime FromDate { get; set; }
[Display(ResourceType = typeof(DisplayLabelText), Name = "ToDate")]
[ValidateToDate("ToDateMustBeGreater")]
public DateTime ToDate { get; set; }
in View:
<div class="col-sm-7 margin-top-10">
<div class="col-sm-12">
#Html.LabelFor(m => m.FromDate, new { #class = "col-sm-3 form-group control-label" })
<div class="col-sm-8">
#Html.TextBoxFor(m => m.FromDate, "{0:MMM dd yyyy}", new { #class = "datepicker", disabled = "disabled" })
</div>
</div>
<div class="col-sm-12">
#Html.LabelFor(m => m.ToDate, new { #class = "col-sm-3 form-group control-label" })
<div class="col-sm-8">
#Html.TextBoxFor(m => m.ToDate, "{0:MMM dd yyyy}", new { #class = "datepicker", disabled = "disabled" })
#Html.ValidationMessageFor(m => m.ToDate)
</div>
</div>
<button type="submit" class="apply-filter-button">Apply Filter</button>
</div>
I figured it out, I was missing the unobtrusive java script code:
please see the below, it might be helpful for some one.
public class ValidateToDateAttribute : ValidationAttribute, IClientValidatable
{
/// <summary>
/// Initializes a new instance of the <see cref="ValidateToDateAttribute"/> class.
/// </summary>
/// <param name="errorMessageKey">The error message key.</param>
public ValidateToDateAttribute(string errorMessageKey, string otherProperty)
{
this.ErrorMessageKey = errorMessageKey;
this.FromDate = otherProperty;
}
/// <summary>
/// Gets from date.
/// </summary>
/// <value>
/// From date.
/// </value>
public string FromDate { get; private set; }
/// <summary>
/// Gets the error message key.
/// </summary>
/// <value>
/// The error message key.
/// </value>
public string ErrorMessageKey { get; private set; }
/// <summary>
/// When implemented in a class, returns client validation rules for that class.
/// </summary>
/// <param name="metadata">The model metadata.</param>
/// <param name="context">The controller context.</param>
/// <returns>
/// The client validation rules for this validator.
/// </returns>
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "validatetodate",
ErrorMessage = new ResourceManager(typeof(ValidationErrorMessages)).GetString(this.ErrorMessageKey),
};
modelClientValidationRule.ValidationParameters.Add("other", this.FromDate);
yield return modelClientValidationRule;
}
/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var viewModel = (TransactionViewModel)validationContext.ObjectInstance;
if (viewModel.ToDate.CompareTo(viewModel.FromDate) < 0)
{
return new ValidationResult(new ResourceManager(typeof(ValidationErrorMessages)).GetString(this.ErrorMessageKey));
}
}
return ValidationResult.Success;
}
}
JavaScript:
<script type="text/javascript">
jQuery.validator.addMethod('greaterThan', function (value, element, params) {
if (!/Invalid|NaN/.test(new Date(value))) {
//return new Date(value) > new Date($("input[name='FromDate']").val());
return Date.parse(value) > Date.parse($(params).val());
}
return isNaN(value) && isNaN($(fromDate)) || (parseFloat(value) > parseFloat($("input[name='FromDate']").val()));
}, '');
// and an unobtrusive adapter
jQuery.validator.unobtrusive.adapters.add('validatetodate', ["other"], function (options) {
options.rules['greaterThan'] = "#" + options.params.other;
options.messages['greaterThan'] = options.message;
});
This Html.DropDownListFor helper should produce the data- attributes but fails to if there is any nesting. So this fails to produce them:
#(
Html.DropDownListFor(
m => m.P[0].CId,
new SelectList(
Model.Cs.Values,
"Id", "DisplayFields", Model.Cs.StartValue),
Model.Cs.Message
)
)
However, this produces them just fine:
#(
Html.DropDownListFor(
m => m.CId,
new SelectList(
Model.Cs.Values,
"Id", "DisplayFields", Model.Cs.StartValue),
Model.Cs.Message
)
)
How can I avoid having to go back and define the missing data- attributes manually with some script?
There is no way but to retroactively assign these missing data- attributes when using the #Html.DropDownListFor helper. The important ones to keep are data-val = "true", data-val-required="This value is required". Furthermore, a span for the validation with data-valmsg-replace="true", data-valmsg-for="ID OF SELECT ELEMENT", class="field-validation-valid".
However, if you choose to use a custom helper then this can be avoided. This can be seen here: http://forums.asp.net/t/1649193.aspx/1/10 where the answer details how to extend the DropDownListFor helper. This is the code they use:
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString DdUovFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList)
{
return DdUovFor(htmlHelper, expression, selectList, null /* optionLabel */, null /* htmlAttributes */);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString DdUovFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes)
{
return DdUovFor(htmlHelper, expression, selectList, null /* optionLabel */, new RouteValueDictionary(htmlAttributes));
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString DdUovFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, IDictionary<string, object> htmlAttributes)
{
return DdUovFor(htmlHelper, expression, selectList, null /* optionLabel */, htmlAttributes);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString DdUovFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel)
{
return DdUovFor(htmlHelper, expression, selectList, optionLabel, null /* htmlAttributes */);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString DdUovFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, object htmlAttributes)
{
return DdUovFor(htmlHelper, expression, selectList, optionLabel, new RouteValueDictionary(htmlAttributes));
}
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString DdUovFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
IDictionary<string, object> validationAttributes = htmlHelper
.GetUnobtrusiveValidationAttributes(ExpressionHelper.GetExpressionText(expression), metadata);
if (htmlAttributes == null)
htmlAttributes = validationAttributes;
else
htmlAttributes = htmlAttributes.Concat(validationAttributes).ToDictionary(k => k.Key, v => v.Value);
return SelectExtensions.DropDownListFor(htmlHelper, expression, selectList, optionLabel, htmlAttributes);
}
I want to create a new attribute TipAttribute that I can use to display helpful tips to my users.
public class EditPersonModel {
[Display(Name = "Full Name")]
[Tip(Message = "Enter the person's full name.")]
public string Name { get; set; }
}
So from my View I would do
<div class="editor-field">
#Html.InputFor(m => m.Name)
#Html.TipFor(m => m.Name)
</div>
and it would render
<div class="editor-field">
<input id="Name" name="Name" type="text" value="">
<div class="tip">Enter the person's full name.</div>
</div>
I know that I would need to write an Extension method for HtmlHelper, but I have no idea what should go inside!
public static MvcHtmlString TipFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) {
???
}
I've started looking at the implementation of DisplayAttribute within Mvc3 Source Code, but it's very complex and doesn't look like providing my own custom attribute is very easy/pluggable. Is there a standard MVC3-way to do this?
Thank you!
Assuming your tip attribute was made metadata aware:
public class TipAttribute: Attribute, IMetadataAware
{
public string Message { get; set; }
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["tip"] = Message;
}
}
you could:
public static class HtmlExtensions
{
public static IHtmlString TipFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var tip = metaData.AdditionalValues["tip"] as string;
var div = new TagBuilder("div");
div.AddCssClass("tip");
div.SetInnerText(tip ?? string.Empty);
return new HtmlString(div.ToString());
}
}