Is there an existing MVC HtmlHelper like #Html.Label that generates a <span>? - asp.net-mvc

I'm wondering if I need to create a custom HtmlHelper to simply display a value as a span.
Currently I'm using:
#model string
#{
var htmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(ViewData["htmlAttributes"]);
}
<span class="#htmlAttributes["class"]">#Model</string>
I'm just wondering if there already exists something like #Html.Label so I could do:
#model string
#Html.SomethingLikeSpan("", Model, #ViewData["htmlAttributes"])

Not yet, but you can extend the Helpers object with any tag combination you wish.
Add a class file named HtmlHelperExtensions.cs with the following code:
using System;
using System.Web;
using System.Web.Mvc;
namespace WebApplication1
{
public static class HtmlHelperExtensions
{
public static IHtmlString Span(this HtmlHelper Helper, string Content, string Class = "")
{
string classstring = Class == "" ? "" : string.Format(" class=\"{0}\" ", Class);
string htmlString = String.Format("<span{1}>{0}</span>", Content, classstring);
return new HtmlString(htmlString);
}
}
}
Then in your view, use the following to format anything with the new helper extension:
#using WebApplication1
#Html.Span("Test Content")
#Html.Span("Test with class", "btn btn-primary")

I know this post is quite old but I'm going to post what I used to accomplish a very basic version of the #Html.Span and #Html.SpanFor helpers. I'll post for both .netcore and MVC5. I hope this helps people out!
MVC 5 implementation
using System;
using System.Linq.Expressions;
using System.Web.Mvc;
public static class HMTLHelperExtensions
{
public static MvcHtmlString Span(this HtmlHelper Helper, string Name, string Content, object HtmlAttributes)
{
var htmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(HtmlAttributes);
TagBuilder tag = new TagBuilder("Span");
tag.MergeAttribute("name", TagBuilder.CreateSanitizedId(Name));
tag.GenerateId(Name);
tag.InnerHtml = Content;
foreach(var i in htmlAttributes )
{
tag.MergeAttribute(i.Key, i.Value.ToString());
}
return MvcHtmlString.Create(tag.ToString());
}
public static MvcHtmlString Span(this HtmlHelper Helper, string Name, string Content)
{
TagBuilder tag = new TagBuilder("Span");
tag.MergeAttribute("name", TagBuilder.CreateSanitizedId(Name));
tag.GenerateId(Name);
tag.InnerHtml = Content;
return MvcHtmlString.Create(tag.ToString());
}
public static MvcHtmlString SpanFor<TModel, TProperty>(this HtmlHelper<TModel> Helper, Expression<Func<TModel, TProperty>> expression, string Content, object HtmlAttributes)
{
var name = ExpressionHelper.GetExpressionText(expression);
var metaData = ModelMetadata.FromLambdaExpression(expression, Helper.ViewData);
return Span(Helper, name, metaData.Model as string, HtmlAttributes);
}
public static MvcHtmlString SpanFor<TModel, TProperty>(this HtmlHelper<TModel> Helper, Expression<Func<TModel, TProperty>> expression, string Content)
{
var name = ExpressionHelper.GetExpressionText(expression);
var metaData = ModelMetadata.FromLambdaExpression(expression, Helper.ViewData);
return Span(Helper, name, metaData.Model as string);
}
}
and here is the .netcore implementation
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using System;
using System.Linq.Expressions;
public static class HtmlHelperExtensions
{
public static IHtmlContent Span(this IHtmlHelper htmlHelper, string name, string Content, object htmlAttributes)
{
TagBuilder tag = new TagBuilder("Span");
tag.MergeAttribute("name", TagBuilder.CreateSanitizedId(name,""));
tag.GenerateId(name, "");
tag.InnerHtml.Append(Content);
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
foreach (var i in attributes)
{
tag.MergeAttribute(i.Key, i.Value.ToString());
}
return tag;
}
public static IHtmlContent Span(this IHtmlHelper htmlHelper, string name, string Content)
{
TagBuilder tag = new TagBuilder("Span");
tag.MergeAttribute("name", TagBuilder.CreateSanitizedId(name, ""));
tag.GenerateId(name, "");
tag.InnerHtml.Append(Content);
return tag;
}
public static IHtmlContent SpanFor<TModel, TProperty>(this IHtmlHelper<TModel> htmlHelper, Expression<Func<TModel,TProperty>> expression, string content, object htmlAttributes)
{
var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, htmlHelper.ViewData, htmlHelper.MetadataProvider);
var name = ExpressionHelper.GetExpressionText(expression);
var metaData = modelExplorer.Metadata;
return Span(htmlHelper, name, modelExplorer.Model as string, htmlAttributes);
}
public static IHtmlContent SpanFor<TModel, TProperty>(this IHtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string content)
{
var modelExplorer = ExpressionMetadataProvider.FromLambdaExpression(expression, htmlHelper.ViewData, htmlHelper.MetadataProvider);
var name = ExpressionHelper.GetExpressionText(expression);
var metaData = modelExplorer.Metadata;
return Span(htmlHelper, name, modelExplorer.Model as string);
}
}
I want to point out, these will work with html attributes only if used in this format.
#Html.Span("Test", "Testing Content", new { #class = "test-class", style = "text-align:right" }
#Html.SpanFor(x=>x.ModelProperty, Model.ModelProperty, new { #class = "test-class", style = "text-align:right" }

Related

Rendering special characters in readonly input field

In ASP.NET MVC application i'm using TagBuilder to create readonly input field.
public class DisplayTextFieldBuilder<TModel, TProperty>
{
private HtmlHelper<TModel> _helper;
private Expression<Func<TModel, TProperty>> _expression;
public DisplayTextFieldBuilder(HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
_helper = helper;
_expression = expression;
}
public override string ToString()
{
var id = _helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(_expression));
var name = _helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(_expression));
var value = HttpUtility.HtmlEncode(_helper.DisplayFor(_expression).ToString());
TagBuilder builder = new TagBuilder("input");
builder.MergeAttribute("value", value);
builder.MergeAttribute("id", id);
builder.MergeAttribute("name", name);
builder.MergeAttribute("readonly", "readonly");
var html = builder.ToString(TagRenderMode.SelfClosing);
return html;
}
However certain string does not render properly. For example, lets say the model property value is Foe`s BBQ (note it has apostrophe)
then
HttpUtility.HtmlEncode(_helper.DisplayFor(_expression).ToString())
renders this value as Foe&#39;s, BBQ
if i remove html encoding then
_helper.DisplayFor(_expression).ToString()
renders this value as Foe's, BBQ
what i am missing?

Create CheckboxFor MVC helper with title attribute from model description

I've created a text box helper to add a title (tooltip) taken from the description attribute for the field in a model:
public static MvcHtmlString TextBoxForWithTitle<Tmodel, TProperty>(this HtmlHelper<Tmodel> htmlHelper, Expression<Func<Tmodel, TProperty>> expression, object htmlAttributes = null)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
string textboxText = metaData.DisplayName ?? metaData.PropertyName ?? htmlFieldName.Split('.').Last();
if (string.IsNullOrEmpty(textboxText))
return MvcHtmlString.Empty;
var textbox = new TagBuilder("input");
textbox.MergeAttributes(new RouteValueDictionary(htmlAttributes));
if (!string.IsNullOrEmpty(metaData.Description))
textbox.Attributes.Add("title", metaData.Description);
return MvcHtmlString.Create(textbox.ToString());
}
I know the checkbox is also an 'input' type element but I have no idea how to construct a helper to use the description as a title.
public static MvcHtmlString CheckBoxForWithTitle<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
string chkboxText = metaData.DisplayName ?? metaData.PropertyName ?? htmlFieldName.Split('.').Last();
MemberExpression memberExpression = expression.Body as MemberExpression;
string parameterName = memberExpression.Member.Name;
if (string.IsNullOrEmpty(chkboxText))
return MvcHtmlString.Empty;
var chkbox = new MvcHtmlString(
string.Format(
"<input type=\"checkbox\" name=\"{0}\" id=\"{0}\" value=\"{1}\" {2} />",
parameterName,
chkbox.MergeAttributes(new RouteValueDictionary(htmlAttributes));
if (!string.IsNullOrEmpty(metaData.Description))
chkbox.Attributes.Add("title", metaData.Description);
return MvcHtmlString.Create(chkbox.ToString());
}
You current implementations are not taking into account model binding and ModelState, do not generate the attributes necessary for unobtrusive validation and can generate incorrect id attributes.
Make use of the existing html helper methods in your own helpers so you generate the correct html. Your TextBoxForWithTitle() method need only be
public static MvcHtmlString TextBoxForWithTitle<Tmodel, TProperty>(this HtmlHelper<Tmodel> htmlHelper, Expression<Func<Tmodel, TProperty>> expression, object htmlAttributes = null)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
IDictionary<string, object> attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (!string.IsNullOrEmpty(metaData.Description))
{
attributes.Add("title", metaData.Description);
}
return htmlHelper.TextBoxFor(expression, attributes);
}
and similarly the CheckBoxForWithTitle() would be the same except
return htmlHelper.CheckBoxFor(expression, attributes);
Side note: To see how the existing helpers actually work, you can view the source code here
I tried and it seems to work so far - still have to try a few examples where I need the ID of the element:
public static MvcHtmlString CheckBoxForWithTitle<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
string chkboxText = metaData.DisplayName ?? metaData.PropertyName ?? htmlFieldName.Split('.').Last();
MemberExpression memberExpression = expression.Body as MemberExpression;
string parameterName = memberExpression.Member.Name;
if (string.IsNullOrEmpty(chkboxText))
return MvcHtmlString.Empty;
var chkbox = new TagBuilder("input");
chkbox.Attributes.Add("type", "checkbox");
chkbox.MergeAttributes(new RouteValueDictionary(htmlAttributes));
if (!string.IsNullOrEmpty(metaData.Description))
chkbox.Attributes.Add("title", metaData.Description);
return MvcHtmlString.Create(chkbox.ToString());
}

Stronglytyped html helper with different model for get and post

If a Get Action returns a View with a "Car" model. The view displays info from the object and takes input to post within a form to another action that takes an object of type "Payment"
The Model on the view is of type Car and gives me stronglytyped html support and some other features like displaytext. But for posting I there is no Htmlhelper support like TextBox(x => x.amount I need to make it like #Html.TextBox("Amount"...
Its possible, but is this the only option?
You can do this:
#{
var paymentHtml = Html.HtmlHelperFor<Payment>();
}
#paymentHtml.EditorFor(p => p.Amount)
with this extension method:
public static class HtmlHelperFactoryExtensions {
public static HtmlHelper<TModel> HtmlHelperFor<TModel>(this HtmlHelper htmlHelper) {
return HtmlHelperFor(htmlHelper, default(TModel));
}
public static HtmlHelper<TModel> HtmlHelperFor<TModel>(this HtmlHelper htmlHelper, TModel model) {
return HtmlHelperFor(htmlHelper, model, null);
}
public static HtmlHelper<TModel> HtmlHelperFor<TModel>(this HtmlHelper htmlHelper, TModel model, string htmlFieldPrefix) {
var viewDataContainer = CreateViewDataContainer(htmlHelper.ViewData, model);
TemplateInfo templateInfo = viewDataContainer.ViewData.TemplateInfo;
if (!String.IsNullOrEmpty(htmlFieldPrefix))
templateInfo.HtmlFieldPrefix = templateInfo.GetFullHtmlFieldName(htmlFieldPrefix);
ViewContext viewContext = htmlHelper.ViewContext;
ViewContext newViewContext = new ViewContext(viewContext.Controller.ControllerContext, viewContext.View, viewDataContainer.ViewData, viewContext.TempData, viewContext.Writer);
return new HtmlHelper<TModel>(newViewContext, viewDataContainer, htmlHelper.RouteCollection);
}
static IViewDataContainer CreateViewDataContainer(ViewDataDictionary viewData, object model) {
var newViewData = new ViewDataDictionary(viewData) {
Model = model
};
newViewData.TemplateInfo = new TemplateInfo {
HtmlFieldPrefix = newViewData.TemplateInfo.HtmlFieldPrefix
};
return new ViewDataContainer {
ViewData = newViewData
};
}
class ViewDataContainer : IViewDataContainer {
public ViewDataDictionary ViewData { get; set; }
}
}
If I understand your question correctly, here's some code I just wrote for one of my projects to do something similar. It doesn't require anything special like what was suggested by Max Toro.
#{
var teamHelper = new HtmlHelper<Team>(ViewContext, this);
}
#using (teamHelper.BeginForm())
{
#teamHelper.LabelFor(p => p.Name)
#teamHelper.EditorFor(p => p.Name)
}
Adding to the implementation by Max Toro, here are a couple more for when you have a non-null model but don't have static type information (these two methods need to be embedded into the implementation Max provides).
These methods work well when you have dynamically retrieved property names for a model and need to call the non-generic HtmlHelper methods that take a name instead of an expression:
#Html.TextBox(propertyName)
for example.
public static HtmlHelper HtmlHelperFor( this HtmlHelper htmlHelper, object model )
{
return HtmlHelperFor( htmlHelper, model, null );
}
public static HtmlHelper HtmlHelperFor( this HtmlHelper htmlHelper, object model, string htmlFieldPrefix )
{
var t = model.GetType();
var viewDataContainer = CreateViewDataContainer( htmlHelper.ViewData, model );
TemplateInfo templateInfo = viewDataContainer.ViewData.TemplateInfo;
if( !String.IsNullOrEmpty( htmlFieldPrefix ) )
templateInfo.HtmlFieldPrefix = templateInfo.GetFullHtmlFieldName( htmlFieldPrefix );
ViewContext viewContext = htmlHelper.ViewContext;
ViewContext newViewContext = new ViewContext( viewContext.Controller.ControllerContext, viewContext.View, viewDataContainer.ViewData, viewContext.TempData, viewContext.Writer );
var gt = typeof( HtmlHelper<> ).MakeGenericType( t );
return Activator.CreateInstance( gt, newViewContext, viewDataContainer, htmlHelper.RouteCollection ) as HtmlHelper;
}
For ASP.NET Core 2
public static class HtmlHelperFactoryExtensions
{
public static IHtmlHelper<TModel> HtmlHelperFor<TModel>(this IHtmlHelper htmlHelper)
{
return HtmlHelperFor(htmlHelper, default(TModel));
}
public static IHtmlHelper<TModel> HtmlHelperFor<TModel>(this IHtmlHelper htmlHelper, TModel model)
{
return HtmlHelperFor(htmlHelper, model, null);
}
public static IHtmlHelper<TModel> HtmlHelperFor<TModel>(this IHtmlHelper htmlHelper, TModel model, string htmlFieldPrefix)
{
ViewDataDictionary<TModel> newViewData;
var runtimeType = htmlHelper.ViewData.ModelMetadata.ModelType;
if (runtimeType != null && typeof(TModel) != runtimeType && typeof(TModel).IsAssignableFrom(runtimeType))
{
newViewData = new ViewDataDictionary<TModel>(htmlHelper.ViewData, model);
}
else
{
newViewData = new ViewDataDictionary<TModel>(htmlHelper.MetadataProvider, new ModelStateDictionary())
{
Model = model
};
}
if (!String.IsNullOrEmpty(htmlFieldPrefix))
newViewData.TemplateInfo.HtmlFieldPrefix = newViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldPrefix);
ViewContext newViewContext = new ViewContext(htmlHelper.ViewContext, htmlHelper.ViewContext.View, newViewData, htmlHelper.ViewContext.Writer);
var newHtmlHelper = htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService<IHtmlHelper<TModel>>();
((HtmlHelper<TModel>)newHtmlHelper).Contextualize(newViewContext);
return newHtmlHelper;
}
}
If I understand your problem correctly, try:
#Html.EditorFor(x => x.Amount)
You could also create an editor template for Payment. See this page for details on doing this.
If I'm misunderstanding, some sample code might help.

How do I prevent MVC3 html escaping my validation messages?

I'm trying to output a validation message in MVC3 which contains a link.
I'm outputting the error message place holder like this
#Html.ValidationMessageFor(model => model.Email)
The problem is, the error message is html escaped which is fine most times, but I'd like a link to be in the middle.
<span class="field-validation-error" data-valmsg-for="Email" data-valmsg-replace="true">This e-mail address is already registed. <a href="%url_token%">Click here to reset.</a></span>
How do I prevent this from happening?
This works but is not a solution, rather, a temporary work around.
#{
string s = Html.ValidationMessageFor(model => model.Email).ToString();
}
#Html.Raw(HttpUtility.HtmlDecode(s))
Looking at the code with reflector it doesn't seem to have a method for that, the more complete overload is:
public static MvcHtmlString ValidationMessageFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
string validationMessage,
object htmlAttributes)
{
return htmlHelper.ValidationMessageFor<TModel, TProperty>(expression, validationMessage, ((IDictionary<string, object>) HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)));
}
What you can do however is to create an extension method that returns the string as you want, like this:
public static class ValidationExtender
{
public static IHtmlString HtmlValidationMessageFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
return MvcHtmlString.Create(htmlHelper.ValidationMessageFor(expression).ToHtmlString());
}
}
You can use a MvcHtmlString. See MvcHtmlString.Create(). It will output the html without escaping.
Taking #BrunoLM's lead, The following extension methods should give you what you need. I've only done basic testing on this, but it does work.
public static class HtmlHelperExtensions
{
private static readonly string htmlErrorPlaceholder = "##__html#Error#Placeholder__##";
public static IHtmlString HtmlValidationMessageFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes)
{
var name = expression.GetMemberName();
var isInError = htmlHelper.ViewContext.ViewData.ModelState.ContainsKey(name);
var message = htmlHelper.ValidationMessageFor(expression, htmlErrorPlaceholder, htmlAttributes);
if (isInError && !MvcHtmlString.IsNullOrEmpty(message))
{
var realError = htmlHelper.ViewContext.ViewData.ModelState[name].Errors.First().ErrorMessage;
return htmlHelper.Raw(message.ToString().Replace(htmlErrorPlaceholder, realError));
}
return MvcHtmlString.Empty;
}
}
public static class Expression_1Extensions
{
public static string GetMemberName<TModel, TProperty>(this Expression<Func<TModel, TProperty>> expression)
{
switch (expression.Body.NodeType)
{
case ExpressionType.MemberAccess:
MemberExpression memberExpression = (MemberExpression)expression.Body;
return memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
}
throw new NotSupportedException();
}
}

Can I add a CSS class definition to the Html.LabelFor in MVC3?

I hope someone out there has some ideas. I would like to tidy up my code and so I already used the Html.LabelFor. However now I want to assign a CSS class to the labels.
Html.LabelFor(model => model.Customer.Description ????)
Does anyone out there know if this is possible in MVC3. Note it's MVC3 I am using. Already I saw a post that talked about MVC2 and there being no simple solution.
Here you go buddy-o:
namespace System.Web.Mvc.Html
{
using System;
using Collections.Generic;
using Linq;
using Linq.Expressions;
using Mvc;
public static class LabelExtensions
{
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
return html.LabelFor(expression, null, htmlAttributes);
}
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, object htmlAttributes)
{
return html.LabelHelper(
ModelMetadata.FromLambdaExpression(expression, html.ViewData),
ExpressionHelper.GetExpressionText(expression),
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes),
labelText);
}
private static MvcHtmlString LabelHelper(this HtmlHelper html, ModelMetadata metadata, string htmlFieldName, IDictionary<string, object> htmlAttributes, string labelText = null)
{
var str = labelText
?? (metadata.DisplayName
?? (metadata.PropertyName
?? htmlFieldName.Split(new[] { '.' }).Last()));
if (string.IsNullOrEmpty(str))
return MvcHtmlString.Empty;
var tagBuilder = new TagBuilder("label");
tagBuilder.MergeAttributes(htmlAttributes);
tagBuilder.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
tagBuilder.SetInnerText(str);
return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
}
private static MvcHtmlString ToMvcHtmlString(this TagBuilder tagBuilder, TagRenderMode renderMode)
{
return new MvcHtmlString(tagBuilder.ToString(renderMode));
}
}
}
There is no built in way to do this in MVC 3. You will have to write your helper that does this. Take a look at the LabelExtensions class to see how it is done.

Resources