2

I am trying to register a Custom MVC HtmlHelper extension.

I have created the appropriate static class and static method for my extension method but how do I register/import/use that namespace in my view so it shows up as an extension method for some given method, for @Html.SomeMethod

In Asp.Net WebForms I can simply add:

<%@ Import Namespace="MyExtensionNamespace.MyExtensionClassName" %>

How do I do the same with MVC so my extension method for my Html Helper will resolve correctly and my method will show up in IntelliSense?

To be clear, I am only trying to add extensions to existing methods so that they will accept different arguments as parameters, such as System.Reflection.PropertyInfo

For example, I want to an an extension method for System.Reflection.PropertyInfo to @Html.Label

    @{
    System.Reflection.PropertyInfo[] props = typeof(MyClaimDto).GetProperties();
    foreach (var prop in props)
    {
        if (prop.PropertyType != typeof(MyNamespace.DynamicDictionary))
        {
            <div class="form-group">
                @Html.LabelFor(prop, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.Editor(prop.Name, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.Hidden("Utility." + prop.PropertyType.FullName, new { @class = "form-control" } )
                    @Html.Hidden("PlaceHolder." + prop.Name, new { @class = "form-control" } )
                    @Html.ValidationMessage(prop.Name, "", new { @class = "text-danger" })
                </div>
            </div>
        }
    }


}

But VS tells me I still have an error under my call that says the Type Arguments could not be inferred:

enter image description here

Here's the code for my extension:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Reflection;
using System.Linq.Expressions;
namespace EFWCF.Extensions
{
    public static class LabelExtensions
    {
        public static MvcHtmlString LabelFor<PropertyInfo, TValue>(this HtmlHelper<PropertyInfo> html, Expression<Func<PropertyInfo, TValue>> expression)
        {
            var type = expression.Type;
            MvcHtmlString result = new MvcHtmlString(type.FullName);
            return result;
        }
    }
}

If I give the method a different name it will resolve:

public static MvcHtmlString PropertyLabelFor<PropertyInfo, TValue>(this HtmlHelper<PropertyInfo> html)
{
    var type = expression.Type;
    MvcHtmlString result = new MvcHtmlString(type.FullName);
    return result;
}

And:

 @Html.PropertyLabelFor(prop)

But I want to be able to call existing @Html methods for additional types.

Alexander Higgins
  • 6,765
  • 1
  • 23
  • 41
  • 1
    In Razor you can use `@using MyExtensionNamespace.MyExtensionClassName`. Note that your extension method needs to be a static `MvcHtmlString` method. – Tetsuya Yamamoto Jul 10 '17 at 01:26
  • It is, let me try that. – Alexander Higgins Jul 10 '17 at 01:30
  • No go, I will update my question. – Alexander Higgins Jul 10 '17 at 01:31
  • 1
    What kind of error text you get on reddish underline in `@using EFWCF.Extensions.LabelExtensions` from your picture? – Tetsuya Yamamoto Jul 10 '17 at 01:36
  • Thanks @TetsuyaYamamoto First error fixed, Was using `Namespace.ClassName` when it should have been just `Namespace` for the import. +1 for that answer. But I have updated question again. Its complaining type couldn't be inferred and should be explicitly defined. – Alexander Higgins Jul 10 '17 at 01:42
  • You need to give other method name for custom extension method since `LabelFor` is already reserved for `System.Web.Mvc.HtmlHelper.LabelFor`. Your custom method trying to override standard `LabelFor` defined with `TModel` and it simply doesn't work. Read https://stackoverflow.com/questions/5196290/how-can-i-override-the-html-labelfor-template to override standard `LabelFor`. – Tetsuya Yamamoto Jul 10 '17 at 01:52
  • @TetsuyaYamamoto Please add your comments as an answer so I can mark it correct. – Alexander Higgins Jul 10 '17 at 01:56

2 Answers2

3

You can refer the namespace in two ways like below:

Directly in the View:

@using YourProject.HtmlHelpers;

or you could add a reference to that namespace within your Views/web.config file so that there is no need to add the using to the top of the view :

<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" />
      <!-- Reference here -->
      <add namespace="YourProject.HtmlHelpers" />
    </namespaces>
  </pages>
</system.web.webPages.razor>
Nkosi
  • 235,767
  • 35
  • 427
  • 472
Karthik Elumalai
  • 1,574
  • 1
  • 11
  • 12
1

The type arguments cannot be inferred from the usage means that there was generic type mismatch occurred during assigning an identifier as a method type argument. Here your custom extension method defines System.Reflection.PropertyInfo as substitution of TModel:

public static MvcHtmlString LabelFor<PropertyInfo, TValue>(this HtmlHelper<PropertyInfo> html, Expression<Func<PropertyInfo, TValue>> expression)
{
    var type = expression.Type;
    MvcHtmlString result = new MvcHtmlString(type.FullName);
    return result;
}

And this is a standard HtmlHelper.LabelFor method:

public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
   ...
}

Based from my observation, Razor seems to use HtmlHelper.LabelFor instead of using its custom extension method overload, hence there in view page you're tried to assign PropertyInfo as expression argument on standard LabelFor which expecting a lambda expression, resulting CS0411 error.

To solve this, either change custom extension method name to another valid name (avoiding naming conflict) or create an overload for standard LabelFor with type checking against System.Reflection.PropertyInfo like this example:

// note that you still require to insert PropertyInfo as a lambda expression here
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
    var modelType = typeof(TModel);
    var propertyInfo = typeof(System.Reflection.PropertyInfo);
    if (modelType.IsAssignableFrom(propertyInfo))
    {
        var type = expression.Type;
        var tag = new TagHelper("label");
        var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
        tag.MergeAttributes(attributes);
        tag.SetInnerText(type.FullName); // set inner text between label tag
        return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
    }
    else
    {
        // return another value, e.g. return MvcHtmlString.Empty
    }
}

NB: If you taking first option by using different method name than LabelFor & don't want to use lambda expression as PropertyInfo assignment, simply remove Expression<Func<PropertyInfo, TValue>> and use PropertyInfo directly as given below:

public static MvcHtmlString PropertyLabelFor<TModel>(this HtmlHelper<TModel> html, PropertyInfo propertyInfo, object htmlAttributes)
{
    var type = propertyInfo.GetType();
    var tag = new TagHelper("label");
    var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
    tag.MergeAttributes(attributes);
    tag.SetInnerText(type.FullName); // set inner text between label tag
    return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}

Afterwards, use reference of your helper namespace at web.config inside Views directory so that you don't need to use @using directive in view page:

<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.Routing" />
        <add namespace="System.Web.UI.WebControls" />
        ...
        <add namespace="EFWCF.Extensions.LabelExtensions" /> <-- add this line
      </namespaces>
    </pages>
</system.web.webPages.razor>

Additional references:

How can I override the @Html.LabelFor template?

How to extend MVC3 Label and LabelFor HTML helpers?

Tetsuya Yamamoto
  • 24,297
  • 8
  • 39
  • 61