你如何覆盖Html.ActionLink?

时间:2013-04-11 15:02:25

标签: asp.net-mvc

通常我会想要使用数据库中的内容呈现ActionLink(想象一下Id / Name组合),所以我将把以下内容放在我的视图中:

        @foreach (var row in Model)
        {
            <li>@Html.ActionLink(row.Name, "Action", "Controller", new { id = row.Id })</li>
        }

但是,如果Name是空字符串,则会引发异常。有什么办法可以防止这种情况发生。我想覆盖ActionLink,但它是一个扩展,所以我无法覆盖。

有什么建议吗?

修改:

首先,我不控制数据,或者我会确保名称字段是必需的并且始终填充。不幸的是,这种情况并非如此。

其次,我意识到用户没有任何东西可以点击,但我相信渲染空链接比给他们YSOD更好。

3 个答案:

答案 0 :(得分:9)

事实证明,实例方法总是优先于具有相同签名的扩展方法。

我能够加载CustomHtmlHelper并将实例方法ActionLink方法放在新类中:

public abstract class CustomWebViewPage<T> : WebViewPage<T>
{
    public new CustomHtmlHelper<T> Html { get; set; }

    public override void InitHelpers()
    {
        Ajax = new AjaxHelper<T>(ViewContext, this);
        Url = new UrlHelper(ViewContext.RequestContext);

        //Load Custom Html Helper instead of Default
        Html = new CustomHtmlHelper<T>(ViewContext, this);
    }
}

HtmlHelper如下(从Reflector复制的ActionLink方法没有LinkText错误检查:

public class CustomHtmlHelper<T> : HtmlHelper<T>
{
    public CustomHtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer) :
        base(viewContext, viewDataContainer)
    {
    }

    //Instance methods will always be called instead of extension methods when both exist with the same signature...

    public MvcHtmlString ActionLink(string linkText, string actionName)
    {
        return ActionLink(linkText, actionName, null, new RouteValueDictionary(), new RouteValueDictionary());
    }

    public MvcHtmlString ActionLink(string linkText, string actionName, object routeValues)
    {
        return ActionLink(linkText, actionName, null, new RouteValueDictionary(routeValues), new RouteValueDictionary());
    }

    public MvcHtmlString ActionLink(string linkText, string actionName, string controllerName)
    {
        return ActionLink(linkText, actionName, controllerName, new RouteValueDictionary(), new RouteValueDictionary());
    }

    public MvcHtmlString ActionLink(string linkText, string actionName, RouteValueDictionary routeValues)
    {
        return ActionLink(linkText, actionName, null, routeValues, new RouteValueDictionary());
    }

    public MvcHtmlString ActionLink(string linkText, string actionName, object routeValues, object htmlAttributes)
    {
        return ActionLink(linkText, actionName, null, new RouteValueDictionary(routeValues), AnonymousObjectToHtmlAttributes(htmlAttributes));
    }

    public MvcHtmlString ActionLink(string linkText, string actionName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
    {
        return ActionLink(linkText, actionName, null, routeValues, htmlAttributes);
    }

    public MvcHtmlString ActionLink(string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
    {
        return ActionLink(linkText, actionName, controllerName, new RouteValueDictionary(routeValues), AnonymousObjectToHtmlAttributes(htmlAttributes));
    }

    public MvcHtmlString ActionLink(string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
    {
        return MvcHtmlString.Create(GenerateLink(ViewContext.RequestContext, RouteCollection, linkText, null, actionName, controllerName, routeValues, htmlAttributes));
    }

    public MvcHtmlString ActionLink(string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, object routeValues, object htmlAttributes)
    {
        return ActionLink(linkText, actionName, controllerName, protocol, hostName, fragment, new RouteValueDictionary(routeValues), AnonymousObjectToHtmlAttributes(htmlAttributes));
    }

    public MvcHtmlString ActionLink(string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
    {
        return MvcHtmlString.Create(GenerateLink(ViewContext.RequestContext, RouteCollection, linkText, null, actionName, controllerName, protocol, hostName, fragment, routeValues, htmlAttributes));
    }
}

最后,将pageBaseType设置为Views/Web.config文件以使用新的自定义WebViewPage:

  <system.web.webPages.razor>
    ...
    <pages pageBaseType="Fully.Qualified.Namespace.CustomWebViewPage">
    ...
    </pages>
  </system.web.webPages.razor>

希望这有助于其他人。

答案 1 :(得分:4)

您将遇到的问题是,如果名称为空白,则无需在UI中为用户单击任何内容。示例:<a href="someUrl"></a>没有给出可点击的对象。

如果您可以为那些空字符串的行提供默认的“No Name”字符串,那可能会更好。

@foreach (var row in Model)
        {
            <li>@Html.ActionLink(string.isNullOrEmpty(row.Name) ? "No Name" : row.Name, "Action", "Controller", new { id = row.Id })</li>
        }

修改

如果您不想在每个@ Html.ActionLink中添加条件,您可以创建自己的@ Html.Helper

 public static IHtmlString ActionLinkCheckNull(this HtmlHelper htmlHelper, string linkText, string action, string controller, object routeValues, object htmlAttributes)
        {
            var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
            var anchor = new TagBuilder("a") { InnerHtml = string.IsNullOrEmpty(linkText) ? "No Name", linktext };
            anchor.Attributes["href"] = urlHelper.Action(action, controller, routeValues);
            anchor.MergeAttributes(new RouteValueDictionary(htmlAttributes));
            return MvcHtmlString.Create(anchor.ToString());
        }

然后在您的Views文件夹web.config中,添加对您放置此扩展名的命名空间的引用,这样您就不必在每个视图的顶部都有一个using语句。例如:

<add namespace="Core.Extensions"/>

然后在您的观看中,使用

 @Html.ActionLinkCheckNull(row.Name, "Action", "Controller", new { id = row.Id })

答案 2 :(得分:0)

为了进一步具体说明汤米所说的话,我想包括最终被称为受保护的方法:

private static string GenerateLinkInternal(RequestContext requestContext, RouteCollection routeCollection, string linkText, string routeName, string actionName, string controllerName, string protocol, string hostName, string fragment, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes, bool includeImplicitMvcValues)
{
    string value = UrlHelper.GenerateUrl(routeName, actionName, controllerName, protocol, hostName, fragment, routeValues, routeCollection, requestContext, includeImplicitMvcValues);
    TagBuilder tagBuilder = new TagBuilder("a")
    {
        InnerHtml = (!string.IsNullOrEmpty(linkText)) ? HttpUtility.HtmlEncode(linkText) : string.Empty
    };
    tagBuilder.MergeAttributes<string, object>(htmlAttributes);
    tagBuilder.MergeAttribute("href", value);
    return tagBuilder.ToString(TagRenderMode.Normal);
}

所以,既然你正在构建一个链接,那么外部方法会抛出异常,因为它不会在屏幕上显示任何文本。该代码如下所示:

public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
{
    if (string.IsNullOrEmpty(linkText))
    {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "linkText");
    }
    return MvcHtmlString.Create(HtmlHelper.GenerateLink(htmlHelper.ViewContext.RequestContext, htmlHelper.RouteCollection, linkText, null, actionName, controllerName, routeValues, htmlAttributes));
}

从技术上讲,代码不会必须受保护,但它是功能


以下是针对您的解决方法,但您需要构建自己的ActionLink扩展方法,并将其放在项目中的某个代码文件中:

using System;
using System.Collections.Generic;
using System.Web.Mvc.Resources;
using System.Web.Routing;
namespace System.Web.Mvc.Html
{
    public static class MyLinkExtensions
    {
        public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, int identifier)
        {
            return htmlHelper.ActionLink(string.IsNullOrEmpty(linkText) ? "some default text" : linkText, actionName, controllerName, new { id = identifier });
        }
    }
}

所以现在你的标记看起来像这样:

@foreach (var row in Model)
{
    <li>@Html.ActionLink(row.Name, "Action", "Controller", row.Id)</li>
}

并且您已封装了该转发以确定linkText的状态,但仍然将您的链接呈现在一行中。