5

Consider the following ASP.NET MVC razor view snippet which defines a helper

@helper FieldFor<TModel>(Expression<Func<TModel, string>> expression)
{
    <div class="form-group">
        @Html.LabelFor(expression,
                       htmlAttributes:
                           new {@class = "control-label col-md-2"})

        <div class="col-md-10">
            @Html.EditorFor(expression,
                            new
                            {
                                htmlAttributes =
                                new {@class = "form-control"}
                            })
            @Html.ValidationMessageFor(expression, "",
                                       new {@class = "text-danger"})

        </div>
    </div>
}

What is the pattern to convert it to a method of a class similiar to:

public static MvcHtmlString FieldFor(this HtmlHelper helper, FieldFor<TModel>(Expression<Func<TModel, string>> expression))
{
   /* Method */
}

All the examples I have found focus on generating markup which doesn't rely on other HTML helpers.

bloudraak
  • 5,902
  • 5
  • 37
  • 52
  • I think declaring it as an extension method is the way to do what you want (just like you did above). What is the problem with that? You do not want the extension method to return markup? If you are looking for regular functions to use in Razor scripts, you can code using @functions. This way you declare regular methods, without the need to return markup. I just do not know if you can create an external file with code declaring using "@functions" to be used in your Razor code anywhere. – Veverke Nov 16 '14 at 08:29
  • There is no problem. I'm looking for a method to create a @helper method to an extension method while having the exact same functionality. In essence, I'd like to move those extension methods to a class library where they can be reused between different ASP.NET MVC applications. – bloudraak Nov 16 '14 at 21:11
  • Seems you already have an answer. I believe all there is to do is to build extension methods upon MvcHtmlString and use TagBuilder class to build the markup, instead of returning them directly. Then your extension methods should return new MvcHtmlString(yourTag.toString()). – Veverke Nov 17 '14 at 08:38
  • 1
    But that may not work when there isn't a tag involved, right? – bloudraak Nov 17 '14 at 22:24

2 Answers2

6

The signature would need to be

public static MvcHtmlString FieldFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)

Then you use a combination of TagBuilder and the inbuilt html helper methods to generate the html

using System;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace YourAssembly.Html
{
  public static class FieldHelper
  {
    public static MvcHtmlString FieldFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
    {
      MvcHtmlString label = LabelExtensions.LabelFor(helper, expression, new { @class = "control-label col-md-2" });
      MvcHtmlString editor = EditorExtensions.EditorFor(helper, expression, new { htmlAttributes = new {@class = "form-control"}})
       MvcHtmlString validation = ValidationExtensions.ValidationMessageFor(helper, expression, null, new { @class = "text-danger" });

       StringBuilder html = new StringBuilder();
       html.Append(editor);
       html.Append(validation);
       TagBuilder innerDiv = new TagBuilder("div");
       innerDiv.AddCssClass("col-md-10");
       innerDiv.InnerHtml = html.ToString();
       html = new StringBuilder();
       html.Append(label);
       html.Append(innerDiv.ToString());
       TagBuilder outerDiv = new TagBuilder("div");
       outerDiv.AddCssClass("form-group");
       outerDiv.InnerHtml = html.ToString();
       return MvcHtmlString.Create(outerDiv.ToString());
    }
  }
}

Then your can make it available in all you views by adding it to web.config

<system.web.webPages.razor>
  <pages pageBaseType="System.Web.Mvc.WebViewPage">
    <namespaces>
      <add namespace="System.Web.Mvc" />
      ....
      <add namespace="YourAssembly.Html" />
    </namespaces>
0

It should be quite easy. You need only write this as extension method of HtmlHelper and use TagBuilder to build your html:

namespace Sample.Extensions
{
    using System;
    using System.Linq.Expressions;
    using System.Web.Mvc;
    using System.Web.Mvc.Html;

    public static class TestExtensions
    {
        public static MvcHtmlString FieldFor<TModel>(
                                         this HtmlHelper<TModel> helper,
                                         Expression<Func<TModel, string>> expression)
        {
            var mainDiv = new TagBuilder("div");
            mainDiv.AddCssClass("form-group");

            mainDiv.InnerHtml += helper.LabelFor(expression);

            var colDiv = new TagBuilder("div");
            colDiv.AddCssClass("col-md-10");
            colDiv.InnerHtml += helper.EditorFor(expression, 
                                                 new
                                                 {
                                                    htmlAttributes =
                                                        new {@class = "form-control"}
                                                 })
            colDiv.InnerHtml += helper.ValidationMessageFor(
                                        expression, 
                                        "", 
                                        new {@class = "text-danger"});

            mainDiv.InnerHtml += colDiv;

            return new MvcHtmlString(mainDiv.ToString(TagRenderMode.Normal));
        }
    }
}

Usage in view:

@using Sample.Extensions
@model ...
...
@Html.FieldFor(m => m.MyField)