Often I will want to render an ActionLink with content from the database (imagine an Id/Name combination) so I will put the following in my view:
@foreach (var row in Model)
{
<li>@Html.ActionLink(row.Name, "Action", "Controller", new { id = row.Id })</li>
}
However, this throws an exception if the Name is an empty string. Is there any way to prevent this. I would like to override ActionLink, but it is an extension so I cannot override.
Any suggestions?
Edit:
First, I do not control the data, or I would ensure that the Name field is required and always populated. Unfortunately this is not the case.
Second, I realize the user will have nothing to click on, but I believe that rendering an empty link is a better alternative than giving them a YSOD.
It turns out that an instance method will always be preferred over an extension method with the same signature.
I was able to load a CustomHtmlHelper and put instance method ActionLink methods in the new class:
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);
}
}
And the HtmlHelper is as follows (ActionLink methods copied from Reflector without the LinkText error check:
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));
}
}
And finally, setting the pageBaseType int the Views/Web.config
file to use the new custom WebViewPage:
<system.web.webPages.razor>
...
<pages pageBaseType="Fully.Qualified.Namespace.CustomWebViewPage">
...
</pages>
</system.web.webPages.razor>
Hope this helps someone else.
The issue you are going to have is if the name is blank, there is nothing to click on for the user in the UI. Example: <a href="someUrl"></a>
gives no clickable object.
What may be better is if you could provide a default "No Name" string to those rows that have an empty string.
@foreach (var row in Model)
{
<li>@Html.ActionLink(string.isNullOrEmpty(row.Name) ? "No Name" : row.Name, "Action", "Controller", new { id = row.Id })</li>
}
EDIT
If you do not want to put a condition in every @Html.ActionLink, you can create your own @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());
}
Then in your Views folder web.config, add a reference to the namespace that you place this extension so you don't have to have a using statement at the top of every view. Example:
<add namespace="Core.Extensions"/>
Then in your views, use
@Html.ActionLinkCheckNull(row.Name, "Action", "Controller", new { id = row.Id })
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With