Monday, June 15, 2009

ASP.NET MVC Strongly-Typed ActionLink with Images

Today I had the desire to change some of the ActionLinks we have been using from boring text to exciting images (which meant using my image collection as I have no talent whatsoever in drawing anything). Unfortunately I was unable to locate a respective method on the existing HtmlHelper.

I am a big fan of the strongly-typed variety of these methods, which are, as of today, only available in the MVC Futures (available at CodePlex).

In my hunt for an efficient (lazy) copy-and-paste solution I came across a thread on Stack Overflow with a comment by eu-ge-ne. His code does a nice job of getting an image produced, but did not get me the desired strongly-typed implementation I was seeking.

His version was this (with a minor fix and my personal dislike of var replaced):

public static string ActionLinkWithImage(this HtmlHelper html, string imgSrc, string actionName)
{
UrlHelper urlHelper = new UrlHelper(html.ViewContext.RequestContext);
string imgUrl = urlHelper.Content(imgSrc);
TagBuilder imgTagBuilder = new TagBuilder("img");
imgTagBuilder.MergeAttribute("src", imgUrl);
string img = imgTagBuilder.ToString(TagRenderMode.Normal);
string url = urlHelper.Action(actionName);

TagBuilder tagBuilder = new TagBuilder("a")
{
InnerHtml = img
};

tagBuilder.MergeAttribute("href", url);
return tagBuilder.ToString(TagRenderMode.Normal);
}


The following does almost the same, but strongly-typed.
public static class HtmlHelperExtensions
{
public static string ActionLinkWithImage<TController>(this HtmlHelper html, Expression<Action<TController>> action, string imgSrc) where TController : Controller
{
UrlHelper urlHelper = new UrlHelper(html.ViewContext.RequestContext);
string imgUrl = urlHelper.Content(imgSrc);
TagBuilder imgTagBuilder = new TagBuilder("img");
imgTagBuilder.MergeAttribute("src", imgUrl);
string img = imgTagBuilder.ToString(TagRenderMode.Normal);
TagBuilder tagBuilder = new TagBuilder("a")
{
InnerHtml = img
};
tagBuilder.MergeAttribute("href", LinkBuilder.BuildUrlFromExpression(html.ViewContext.RequestContext, html.RouteCollection, action));
return tagBuilder.ToString(TagRenderMode.Normal);
}

public static string ActionLinkWithImage<TController>(this HtmlHelper html, Expression<Action<TController>> action, string imgSrc, object imageAttributes, object linkAttributes) where TController : Controller
{
UrlHelper urlHelper = new UrlHelper(html.ViewContext.RequestContext);
string imgUrl = urlHelper.Content(imgSrc);
TagBuilder imgTagBuilder = new TagBuilder("img");
imgTagBuilder.MergeAttribute("src", imgUrl);

imgTagBuilder.MergeAttributes(new RouteValueDictionary(imageAttributes));

string img = imgTagBuilder.ToString(TagRenderMode.Normal);

TagBuilder tagBuilder = new TagBuilder("a")
{
InnerHtml = img
};

tagBuilder.MergeAttributes(new RouteValueDictionary(linkAttributes));
tagBuilder.MergeAttribute("href", LinkBuilder.BuildUrlFromExpression(html.ViewContext.RequestContext, html.RouteCollection, action));
return tagBuilder.ToString(TagRenderMode.Normal);
}
}


An example of consumption on your page would then be:
<%= Html.ActionLinkWithImage<FooController>(fc => fc.Edit(deal.ID), "~/Content/Images/edit_16.png", new { style = "border-style: none;", alt = "Edit Foo", title = "Edit Deal" }, null) %>


Hope this helps someone with the same predicament (Someone feel free to incorporate into the Futures assembly :))

Saturday, June 13, 2009

ASP.NET MVC Form Validation Error with Decimal Parsing

Today I tried to put a relatively simple form together and was hoping to be done in a few minutes. Included on the form is an optional field asking for a monetary value. The type in the database is money and using LINQ-to-SQL the type is represented as a decimal.

All was well until the user entered a value that decimals generally don’t like to be converted into. Let’s assume ‘aaa’ for the time being. The result I was greeted with was this:

The model of type 'Foo' was not successfully updated.

My desire for ASP.NET MVC to handle this for me and put a nice validation message into the ModelState was just not happening.

My original code did this:

Foo foo = _modelRepository.FindFoo(id);
UpdateModel(foo);
The relatively simple fix is:
Foo foo = _modelRepository.FindFoo(id);
UpdateModel(foo, "SomeStringField", "SomeOtherStringField");

if (!TryUpdateModel(foo, "TheDecimalField"))
{
ModelState.AddModelError("TheDecimalField", "Does that look like a number to you?");
}