Adding Your Own HTMLhelper in ASP.NET MVC 3

Adding your own HtmlHelper in ASP.NET MVC 3

To use the custom helper method in your Razor views you will need to bring it into scope. There are two possible ways to do this:

  1. Add a @using SomeNamespace in the top of your view with the namespace where the static class containing the helper is defined
  2. In ~/Views/web.config, add:

    <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
    <namespaces>
    <add namespace="System.Web.Mvc" />
    <add namespace="System.Web.Mvc.Ajax" />
    <add namespace="System.Web.Mvc.Html" />
    <add namespace="System.Web.Routing" />
    <add namespace="SomeNamspace" />
    </namespaces>
    </pages>
    </system.web.webPages.razor>

Once the custom helper is brought into scope in the view, Intellisense should be able to pick it and you could use it:

@Html.StateDropDownList()

Now you helper method needs to do something useful. You could either call existing helpers:

public static class ExtensionMethods
{
public static MvcHtmlString StateDropDownList(this HtmlHelper html)
{
return html.TextBox("foo")
}
}

or return some custom data:

public static class ExtensionMethods
{
public static MvcHtmlString StateDropDownList(this HtmlHelper html)
{
return MvcHtmlString.Create("Hello world");
}
}

If you have a strongly typed view and you wanted to use an expression:

using System.Web.Mvc;
using System.Web.Mvc.Html;

public static class ExtensionMethods
{
public static MvcHtmlString StateDropDownList(
this HtmlHelper<MyViewModel> html
)
{
var stateList = new SelectList(new[]
{
new { Key = "Alabama", Value = "Alabama" },
new { Key = "Idaho", Value = "Idaho" },
new { Key = "California", Value = "California" }
}, "Key", "Value");
return Html.DropDownListFor(
x => x.State, stateList, "-- Select a state --"
);
}
}

and then:

@Html.StateDropDownList()

Adding a custom HTML Helper to MVC Project

You should include the namespace in your view:

@using MyWebApp

Or you can import this namespace for all of the views from web.config.

<system.web.webPages.razor>
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization" />
<add namespace="System.Web.Routing" />
<add namespace="MyWebApp" />
</namespaces>
</pages>
</system.web.webPages.razor>

Using custom html helpers in an ASP.NET MVC 3 Application

Like someone pointed out in the comments, there was something different about my setup. In my solution, I had changed the Output path for my projects - including the MVC project - from the default bin\ to something like ..\Library\Build\. No crime there since that setup has worked fine so far.

But, that is what got me into trouble. After I restored the default output path and rebuilt my project the Html helper worked. It continued to work even after I restored back my preferred output path - obviously because the dll in the bin folder got updated.

This would mean that the statement @using MVCUI.Extensibility; in my .cshtml file and <add namespace="MVCUI.Extensibility" /> in the Web.config were referencing an old outdated dll in the bin folder that didn't have the HtmlHelper defined. This bothers still. How would I have them reference the dll in my desired output path?

Anyway, I just thought I should post about the experience and the lessons just in case other people find themselves in similar trouble. Thanks people.

How create Custom HTML Helper in Asp.Net MVC

If I were you, I would not name it as the original name from the Html Helper. I would try something like:

public static string CustomLabel(this HtmlHelper helper, string target, string text)
{
return String.Format("<label for='{0}'>{1}</label>", target, text);
}

After it, just include the namespace (not the entire path to the class), for sample:

@using MvcPractise.Extension

And as the extension method, use it on Html property:

@Html.CustomLabel("firstName", "First Name:")

create a html helper for mvc

Seems like an excellent scenario for a custom html helper:

public class RoundedCorner : IDisposable
{
private readonly ViewContext _viewContext;
private bool _disposed = false;

public RoundedCorner(ViewContext viewContext)
{
_viewContext = viewContext;
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
_viewContext.Writer.Write(
@"<div class=""Clear"">
</div>
</div>
</div>
<div class=""bottom"">
<div class=""right"">
</div>
</div>
</div>
</div>"
);
}
}
}

public static class HtmlExtensions
{
public static RoundedCorner RoundedCorner(this HtmlHelper htmlHelper)
{
htmlHelper.ViewContext.Writer.Write(
@"<div class=""rounded"">
<div class=""top"">
<div class=""right"">
</div>
</div>
<div class=""middle"">
<div class=""right"">
<div class=""content"">"
);
return new RoundedCorner(htmlHelper.ViewContext);
}
}

and in your view simply:

@using (Html.RoundedCorner())
{
<div>Some how allow me to insert data into here</div>
}

which would generate (I know, what an ugly formatting but perfectly valid HTML, I am too lazy to fix it at the moment):

<div class="rounded">
<div class="top">
<div class="right">
</div>
</div>
<div class="middle">
<div class="right">
<div class="content"> <div>Some how allow me to insert data into here</div>

<div class="Clear">
</div>
</div>
</div>
<div class="bottom">
<div class="right">
</div>
</div>
</div>
</div>

Making my own HtmlHelper extension for input that works with model binding

You are right, those attributes (and specially the name attribute) are critical for the model binding.

Say you want to create a custom helper like

public static MvcHtmlString CustomHelperFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)

First you can use var fieldName = ExpressionHelper.GetExpressionText(expression); to get the field name.

Then use var fullBindingName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName); in order to get the full name, taking care of nested views.

Finally you can transform this into an id attribute using var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);.

So a simple custom helper that creates a textbox could be written as:

public static MvcHtmlString CustomHelperFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
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());

var validationAttributes = html.GetUnobtrusiveValidationAttributes(fullBindingName, metadata);
foreach (var key in validationAttributes.Keys)
{
tag.Attributes.Add(key, validationAttributes[key].ToString());
}

return new MvcHtmlString(tag.ToString(TagRenderMode.SelfClosing));
}

You can use it in a view like:

@Html.CustomHelperFor(model => model.ParentDropDown.SelectedValue)

And it will produce the following html:

<input id="ParentDropDown_SelectedValue" name="ParentDropDown.SelectedValue" type="text" value="4">

Hope it helps!

ASP.NET MVC 3 Custom HTML Helpers- Best Practices/Uses

Well in the case of formatting the DisplayFormat attribute could be a nice solution:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")]
public DateTime Date { get; set; }

and then simply:

@Html.DisplayFor(x => x.Date)

As far as truncating string is concerned a custom HTML helper is a good solution.


UPDATE:

Concerning your EDIT, a custom HTML helper might work in this situation but there's also an alternative approach which I like very much: view models. So if in this particular view you are always going to show the concatenation of the names then you could define a view model:

public class PersonViewModel
{
public string FullName { get; set; }
}

Now the controller is going to query the repository to fetch the model and then map this model to a view model which will be passed to the view so that the view could simply @Html.DisplayFor(x => x.FullName). The mapping between models and view models could be simplified with frameworks like AutoMapper.

How to create HtmlHelper extensions with Razor?

Helpers create normal page methods, not extension methods:

@IncludeJS("")

If you want to create an Html extension method, you'll need to create a normal extension method (in a .cs file) for the HtmlHelper class.

If you do that, you can use the TagBuilder class.


EDIT: The Views\Helpers feature was dropped before RTM.

MVC 3 Razor, Helpers with custom markup/section

Well, here's a "standard" way of doing something close to it, using an HTML helper extension. A very simple version of what Html.BeginForm() does.

Approach: Simply return an IDisposable, and let the using statement take care of the rest.

This is just an example of the concept (although it works). Not intended for immediate reuse. Written quickly with lots of shortcuts, not production code, plenty of opportunities for improvement and optimization, may have silly mistakes, could use TagBuilder etc. etc. Could easily be modified to reuse the Wrapper class for different... wrappings (there may even be a generic one already in ASP.NET MVC - haven't had a need for one).

public static class WrapHelper
{
private class Wrapper : IDisposable
{
private bool disposed;
private readonly TextWriter writer;

public Wrapper(HtmlHelper html)
{
this.writer = html.ViewContext.Writer;
}

public void Dispose()
{
if (disposed) return;

disposed = true;

writer.WriteLine(" </div>");
writer.WriteLine("</div>");
}
}

public static IDisposable Wrap(this HtmlHelper html, string title)
{
TextWriter writer = html.ViewContext.Writer;

writer.WriteLine("<div class=\"module\">");
writer.WriteLine(" <h3>" + html.Encode(title) + "</h3>");
writer.WriteLine(" <div>");

return new Wrapper(html);
}
}

Usage:

@using (Html.Wrap("Title"))
{
<p>My very own markup.</p>
}


Related Topics



Leave a reply



Submit