Update: I've updated the source to be compatible with the Preview 3 bits. Download and execution is the same, but the sample code within the post is no longer valid.
Since the introduction of lambda expressions within the .NET framework, and it's extensive use of them within ASP.NET MVC, I've grown extremely fond of working with compile time errors that lambda expressions gives us. You've seen the ASP.NET MVC team build out a set of ActionLink<T> methods that enable you to specify an expression that will be compiled like the following
<%= Html.ActionLink<UserController>(c => c.Login(), "Login") %>
As the team released their April push and introduced us to ActionResult return types, I've totally fell in love with using these, mainly from a testability standpoint, and it also makes your code extremely readable to determine exactly what is going to be happening at what point. However, what I've found is that when using RedirectToAction, I would constantly be writing code like
1: public class UserController : Controller
2: {
3: public ActionResult Login() { ... }
4: public ActionResult ProcessLogin()
5: {
6: // ... determine if error'd
7: return RedirectToAction("Login");
8: }
9: }
Yup, nothing fancy and pretty straightforward. However, what if I needed to refactor my codebase and change my action method names... the task becomes straight forward when using ActionLink<T> in my views, but all of my controllers continue to compile even when they shouldn't be! Yes, if you're doing true TDD, this task is easy to spot because after refactoring, you can run all of your test cases and see which ones failed.
Great. That's a lot of manual work...mmmmkay. In the age of having refactoring shortcuts built right into the IDE, why couldn't I change my method name using refactoring, and have IT do all the work for me? Enter, the expression based RedirectToAction method. I know, you saw it coming, right? Here's how I want to write my above code
1: public class UserController : Controller
2: {
3: public ActionResult Login() { ... }
4: public ActionResult ProcessLogin()
5: {
6: // ... determine if error'd
7: return this.RedirectToAction(c => c.Login());
8: }
9: }
Ohh, pretty - and hey look, refactoring now works! It even supports parameters, route value dictionaries, anonymous types. Even better, you can specify a completely different controller to route to!
1: // parameters, route dictionaries, anonmyous types
2: this.RedirectToAction(c => c.Login("matt"));
3: this.RedirectToAction(c => c.Login(), new RouteValueDictionary(new { userName = "matt" }));
4: this.RedirectToAction(c => c.Login(), new { userName = "matt" }));
5:
6: // different controller
7: this.RedirectToAction<ProductsController>(c => c.View(101));
All this is super powerful, and I'm sure your dying to get your hands on the source...okay :)
1: using System;
2: using System.Collections.Generic;
3: using System.Linq.Expressions;
4: using System.Web.Mvc;
5: using System.Web.Routing;
6:
7: public static class ControllerExtensions
8: {
9: public static ActionRedirectResult RedirectToAction<T>(this T controller,
10: Expression<Action<T>> action) where T : Controller
11: {
12: return RedirectToAction<T>(controller, action, null);
13: }
14:
15: public static ActionRedirectResult RedirectToAction<T>(this T controller,
16: Expression<Action<T>> action, object values) where T : Controller
17: {
18: return RedirectToAction<T>(controller, action, new RouteValueDictionary(values));
19: }
20:
21: public static ActionRedirectResult RedirectToAction<T>(this T controller,
22: Expression<Action<T>> action, RouteValueDictionary values) where T : Controller
23: {
24: MethodCallExpression body = action.Body as MethodCallExpression;
25: if (body == null)
26: throw new InvalidOperationException("Expression must be a method call.");
27:
28: if (body.Object != action.Parameters[0])
29: throw new InvalidOperationException("Method call must target lambda argument.");
30:
31: string actionName = body.Method.Name;
32: string controllerName = typeof(T).Name;
33:
34: if (controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
35: controllerName = controllerName.Remove(controllerName.Length - 10, 10);
36:
37: RouteValueDictionary parameters = LinkBuilder.BuildParameterValuesFromExpression(body);
38:
39: values = values ?? new RouteValueDictionary();
40: values.Add("controller", controllerName);
41: values.Add("action", actionName);
42:
43: if (parameters != null)
44: {
45: foreach (KeyValuePair<string, object> parameter in parameters)
46: {
47: values.Add(parameter.Key, parameter.Value);
48: }
49: }
50:
51: return new ActionRedirectResult(values);
52: }
53: }
As you can see, it's fairly straight forward. This is something that I think is very useful, and gets more in line with how code should be written. Since this example is a bit lengthy, I've provided the source in downloadable format, so click here to get it. Hope you enjoy and use this in your applications!
b77f0f89-579c-4be6-bf1b-60815976c3f2|5|5.0