eWorld.UI - Matt Hawley

Ramblings of Matt

ASP.NET MVC: Expression Based RedirectToAction Method

May 14, 2008 21:29 by matthaw

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!

kick it on DotNetKicks.com

Currently rated 5.0 by 3 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

May 20. 2008 18:58

Legend,
I had something similar, extending the RequestContext and returning:

VirtualPathData vpd = req.RouteData.Route.GetVirtualPath(req, values);
return (vpd == null) ? null : vpd.VirtualPath;

Much like the original BuildUrlFromExpression method, but for some reason it was returning null and I've been looking for a solution since. Thanks!

Harry

May 20. 2008 20:03

Pingback from weblogs.asp.net

ASP.NET MVC: Expression Based RedirectToAction Method - eWorld.UI - Matt Hawley

weblogs.asp.net

May 20. 2008 20:10

Is there any reason you need these methods to be extensions on the controller? I removed the controller parameter in the methods so I could use your code to redirect from an ActionFilterAttribute, which has no Controller reference, but allows me to do the following:

UrlHelper.RedirectToAction<FooController>(s => s.Bar()).ExecuteResult(filterContext);
//I added your methods as statics to a custom UrlHelper btw.

Out of Curiosity, do you know how to obtain the virtual path for this redirect? I mentioned some code earlier:

VirtualPathData vpd = req.RouteData.Route.GetVirtualPath(req, values);
return (vpd == null) ? null : vpd.VirtualPath;

which would replace your

return new ActionRedirectResult(values);

Where req is the RequestContext (which ControllerContext extends). For some reason the virtual path approach works when using a controllers context but returns null when using the filterContext (which extends ControllerContext) of an ActionFilterAttribute .

Any ideas why?

(P.S. I think your approach is neater, but there may be cases where you need to create an absolute path for use with assets outside the domain)

Harry

May 20. 2008 21:19

@Harry - The reason I took this approach was to extend what was already there for the April bits of MVC for RedirectToAction. A action filter attribute, while could be used to do this, doesn't make any sense because the purpose of ActionResult's are to do things like redirect to actions, render views, redirect to url's or whatever else.

And when you say virtual path, do you mean ~/views/foo/bar.aspx or "/foo/bar" ? The latter of the two you can get with what your doing, but the former requires you to get the ViewEngine, and get it's associated ViewLocator to map the controller & action to the actual file.

matthaw

May 20. 2008 21:45

@Matt - The action filter redirected to a virtual path, this is why I needed the method. I agree your approach is better as I only need to redirect, not get an actual virtual path, I was just curious why it didn't work. I dug a little deeper and found that when I used my approach from the controllers context it returns a valid virtual path (of the latter type) in actions that have at least one parameter, but returns null in actions with no parameters. So my approach fails at the controller level were it would be useful to get this information and to me this seems like a bug in MVC... To clarify:

In regards to this line: string q = this.BuildUrlFromExpression<FooController>(s => s.Detail(2));

calling it from *inside* a controller action with no params returns null, calling it from *inside* a controller action with one param returns the correct path. My filter that was not working was on a controller method that had no params, which I believe is why it broke.

Does this seem like a bug to you?

Harry

May 21. 2008 02:44

It's quite possible that it didn't have enough information to match the route, copy down the source from CodePlex and debug in to see what's happening. I couldn't tell you why.

matthaw

May 24. 2008 04:32

your download url is broken

bobo

May 24. 2008 04:33

using my powers of deduction the correct url should be www.eworldui.net/.../...rectToActionExtensions.zip

bobo

May 24. 2008 06:04

@bobo - thanks, link is fixed.

matthaw

May 25. 2008 20:19

Hi Matt,
Thanks for this code. It's exactly what I was looking for. I fixed a small bug that occured when I was redirecting from controller A to controller B and took the liberty to merge you class with mine that had exactly the same name and added download ActionResult methods.
I put the code on my new blog (old blog is in french and I doubt it helps a lot of people !) :
weblogs.asp.net/.../MVControllerExtensions.aspx

Damien

July 11. 2008 13:03

Pingback from jason.whitehorn.ws

Jason Whitehorn - ASP.NET MVC - Decoupling RedirectToAction

jason.whitehorn.ws

March 20. 2009 21:10

Nice one actually

Bayram Çelik

March 24. 2009 05:38

Funny, I wrote almost the exact some code today in my project. Smile Glad to see that someone else thought the same way.

Jon Kruger

May 29. 2009 12:56

nices article.

nakliyat

June 15. 2009 17:34

<a href="http://www.ftkcambalkon.com"" rel="nofollow">http://www.ftkcambalkon.com" title="cam balkon">cam balkon</a>
<a href="http://www.ftkcambalkon.com"" rel="nofollow">http://www.ftkcambalkon.com" title="cambalkon">cambalkon</a>

cam balkon

June 22. 2009 14:50

anadolu cam balkon

cam balkon

June 22. 2009 14:50

anadolu cam balkon

cam balkon

June 22. 2009 14:51

anadolu cam balkon

cam balkon

June 22. 2009 14:52

marmara cam balkon

cam balkon

June 22. 2009 14:52

marmara cam balkon

cam balkon

June 24. 2009 07:18

thanks for the post

Nation High School

June 24. 2009 08:33


Thank you very much very nice article
Great information! Very useful for me. Thanks a lot.
The idea is awesome. Congrats.
<a href="http://gogusestetik.com/">Gogus Estetigi</a>
<a href="http://burunestetik.de/">Burun Estetigi</a>
<a href="liposuction.gen.tr/">Liposuction</a>
<a href="http://Fuesacekimi.com/">Sac" rel="nofollow">http://Fuesacekimi.com/">Sac Ekimi</a>
<a href="http://Fuesacekimi.com/">Sac" rel="nofollow">http://Fuesacekimi.com/">Sac Ekimi</a>
<a href="http://tedavi.com.tr/">Tedavi</a>
<a href="Gogusestetik.com/">Estetik</a>
<a href="http://memekucultme.com/">meme estetigi</a>
<a href="http://dugun-salonu.com/">dugun salonu</a>
<a href="Dorahospital.com/.../a>

İstanbul Özel Hastaneler

June 24. 2009 19:14

thankss cam balkon

cam balkon

June 24. 2009 19:15

cam balkon sistemleri

cam balkon

June 24. 2009 19:15

efe cam balkon sistemleri

cam balkon

June 24. 2009 19:16

thankss cam balkon

cam balkon

June 24. 2009 19:16

gunes cam balkon

cam balkon

June 24. 2009 19:16

efe camlama balkon

cam balkon

June 24. 2009 19:17

cam balkon izmir

cam balkon

June 24. 2009 19:17

thankss cam balkon

cam balkon

June 24. 2009 19:17

net cam balkon

cam balkon

June 24. 2009 19:17

cam lamam

cam balkon

June 24. 2009 19:18

cam balkon

cam balkon

June 24. 2009 19:18

es cam balkon

cam balkon

June 24. 2009 19:18

es cam

cam balkon

June 24. 2009 19:19

ılgazlı

ılgaz

June 24. 2009 19:19

web tasarım

web tasarım

June 24. 2009 19:19

koç web

web tasarım

June 24. 2009 19:19

soğutma

soğutma

June 25. 2009 06:56

<a href="http://www.movies.gen.tr" title="movies">movies</a>

movies

June 25. 2009 06:56

<a href="http://www.cinema.gen.tr" title="cinema">cinema</a>

cinema

June 26. 2009 10:37

bambu

bambu

July 1. 2009 01:40

thank you

cam balkon

Add comment


 

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

July 3. 2009 21:27



Copyright © 2000 - 2009 , Excentrics World