Monday, March 12, 2012

Refactoring with LINQ

So my little brother sent over the code below last week and asked me if there was a better way to do it. When I first read through it I was thinking…I can’t believe people still use the good ole to/from listbox style UI. I don’t remember the last time I had to write this code, but I’m pretty sure it was prior to LINQ.

So here’s what I came up with in my response using LINQ.

Basically I was able to get rid of all the for loops, sorting, adding items, and most importantly, the new version is more readable.

Shout it

kick it on DotNetKicks.com

Sunday, March 04, 2012

Create an ActionLink without Magic Strings

Updated: 3/7/2011

So it’s been a while. I have a new job working for Headspring in Austin, which is one of the reasons I haven’t blogged in a while. It’s tough moving and re-adjusting to a new area and job, but Austin is great and Headspring is terrific. I started blogging on the Headspring site too. I’m trying to alternate weeks for posting, so we’ll see how that goes. (also fyi, Headspring is hiring)

On to the good stuff, I started playing with an HtmlHelper extension to rid a project of magic strings for action links. It was definitely more tricky than I originally thought, but I have a first attempt ready to share. Here’s the code:

@(Html.ActionLinkFor<HomeController>(a=>a.Contact(Model), "Contact"
, new Dictionary<string, object>{{"style","color: #f90;background:#000"}})) <br />

@(
Html.ActionLinkFor<MainControllerController>(a=>a.Index(), "Main Home"))<br/>

@(Html.ActionLinkFor<HomeController>(a=>a.Index(), "Home")) <br />

@(Html.ActionLinkFor<HomeController>(a=>a.AnotherTest(new SampleModel{Id=5555
, Name =
"Just a test", Address = "123 Main"}), "Another Test"))<br/>

@(Html.ActionLinkFor<HomeController>(a=>a.TestYetAnother(new SampleModel{Id=12345678
, Name =
"Just another test yet"}), "Yet Another Test"))

The code above is what I wanted for the end result. The code below is what makes the code above work.

    public static class HtmlHelperExtensions
{
public static IHtmlString ActionLinkFor<TController>(this HtmlHelper htmlHelper
,
Expression<Func<TController, object>> expression,
string displayText) where TController : Controller
{
return ActionLinkFor(htmlHelper, expression, displayText, null);
}

public static IHtmlString ActionLinkFor<TController>(this HtmlHelper htmlHelper
,
Expression<Func<TController, object>> expression, string displayText
,
IDictionary<string, object> htmlAttributes)
where TController : Controller
{
var method = ((MethodCallExpression)expression.Body).Method;
var actionName = method.Name;
var parameters = method.GetParameters();
var arguments = ((MethodCallExpression)expression.Body).Arguments;

var routeValue = new RouteValueDictionary();
for (int i = 0; i < parameters.Length; i++)
{
if (arguments[i] as MemberExpression != null)
{
var memberExpression = (MemberExpression)arguments[i];
if (memberExpression.Expression as ConstantExpression == null)
AddRouteValuesForMemberExpression((
MemberExpression)arguments[i]
, parameters[i].Name, routeValue);

if (memberExpression.Expression as ConstantExpression != null)
AddRouteValuesForConstantExpression(((
MemberExpression)arguments[i])
, parameters[i].Name, routeValue);
}
if (arguments[i] as MemberInitExpression != null)
AddRouteValuesForMemberInitExpression((
MemberInitExpression)arguments[i]
, routeValue);
if (arguments[i] as MethodCallExpression != null)
AddRouteValuesForMethodCallExpression((
MethodCallExpression)arguments[i]
, parameters[i].Name, routeValue);
}

var controller = typeof(TController).Name;
var controllerName = controller.Remove(controller.LastIndexOf("Controller"));
return htmlHelper.ActionLink(displayText, actionName, controllerName, routeValue, htmlAttributes);
}

private static void AddRouteValuesForMethodCallExpression(MethodCallExpression methodCallExpression
,
string parameterName, RouteValueDictionary routeValue)
{
if (methodCallExpression.Object != null)
{
var value = ((PropertyInfo)((MemberExpression)methodCallExpression.Object).Member)
.GetValue(((
ConstantExpression)((MemberExpression)methodCallExpression.Object)
.Expression).Value,
null);
routeValue.Add(parameterName, value);
}
}

private static void AddRouteValuesForMemberExpression(MemberExpression memberExpression
,
string parameterName, IDictionary<string, object> routeValue)
{
object model;
if (((MemberExpression)memberExpression.Expression).Member is PropertyInfo)
model = ((
PropertyInfo)((MemberExpression)memberExpression.Expression).Member)
.GetValue(((
ConstantExpression)((MemberExpression)memberExpression.Expression)
.Expression).Value,
null);
else
model = ((FieldInfo)((MemberExpression)memberExpression.Expression).Member)
.GetValue(((
ConstantExpression)((MemberExpression)memberExpression.Expression)
.Expression).Value);

foreach (var p in model.GetType().GetProperties())
{
if (p.Name == memberExpression.Member.Name)
{
var value = p.GetValue(model, null);
if (value != null && !routeValue.ContainsKey(p.Name))
routeValue.Add(parameterName, value.ToString());
}
}
}

private static void AddRouteValuesForConstantExpression(MemberExpression memberExpression
,
string parameterName, IDictionary<string, object> routeValue)
{
var properties = ((PropertyInfo)memberExpression.Member)
.GetValue(((
ConstantExpression)memberExpression.Expression).Value, null)
.GetType()
.GetProperties();

foreach (var property in properties)
{
var value = property.GetValue(((PropertyInfo)memberExpression.Member)
.GetValue(((
ConstantExpression)memberExpression.Expression).Value, null), null);

if (value != null)
routeValue.Add(property.Name, value.ToString());
}

if (properties.Length == 0)
{
var value = ((PropertyInfo)memberExpression.Member)
.GetValue(((
ConstantExpression)memberExpression.Expression).Value, null);

if (value != null)
routeValue.Add(parameterName, value.ToString());
}
}

private static void AddRouteValuesForMemberInitExpression(MemberInitExpression memberInitExpression
,
IDictionary<string, object> routeValue)
{
foreach (var p in memberInitExpression.Bindings.Cast<MemberAssignment>())
{
if (p.Expression is ConstantExpression)
{
var value = ((ConstantExpression)p.Expression).Value;
var name = p.Member.Name;
if (value != null)
routeValue.Add(name, value.ToString());
}
if (p.Expression is UnaryExpression)
{
throw new NotImplementedException("Not able to handle UnaryExpressions yet.");
}
}
}
}

Basically the way this thing works is off the Expression<Func<TController, object>> parameter being passed into the ActionLinkFor method. From the expression, you can determine the route values and action name…by doing a lot of manipulation. So, if you would, try this helper out and let me know of any bugs or improvements that can be made.


Shout it

kick it on DotNetKicks.com

Related Posts Plugin for WordPress, Blogger...