14

My skills are failing me, and I know I've seen the code around for this but I can't find it.

What's the quickest way to take any arbitrary URL, run it through your asp.net mvc routing system, and come out with a reference to a controller instance on the other end?

For example, code execution is inside some arbitrary controller method. I want to do something like this:

...
string myURL = "http://mysite/mycontroller/myaction/myparameters";

RouteData fakeRouteData = new RouteData(Route???, IRouteHandler???)
RequestContext ctxt = new RequestContext(this.ControllerContext.HttpContext,
                                         fakeRouteData);

ControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
Controller result = factory.CreateController(ctxt, controllername???)

I'm trying to get an instance of a controller just like the routing system does, regardless of where the code is executing. I'm unclear as to how to fit the pieces together at this point. While I will eventually discover it, I thought I could save time by asking here ;)

womp
  • 115,835
  • 26
  • 236
  • 269
  • Yes, I've been doing that with Reflector and various articles. I was hoping to save some time though, I know I've seen a thread somewhere with two or three ways of doing exactly this, all with a fairly succinct amount of code. – womp Jan 14 '10 at 19:28
  • 1
    I wrote a full sample that doesn't involve mock [here][1]. [1]: http://stackoverflow.com/a/19382567/126574 – VitalyB Oct 28 '13 at 09:16

3 Answers3

13

Hmm... I don't know if this is the best solution because it requires mocking, but maybe this will help. You're on the right track and the controller factory part is simple once you know what controller to instantiate, so the question is what's the fastest way to get a RouteData object from an arbitrary url.

And the only way I know how would be like so, with Moq:

string url = "~/Account/LogOn";  //trying to create Account controller in default MVC app

RouteCollection rc = new RouteCollection();
MvcApplication.RegisterRoutes(rc);
System.Web.Routing.RouteData rd = new RouteData();
var mockHttpContext = new Moq.Mock<HttpContextBase>();
var mockRequest = new Moq.Mock<HttpRequestBase>();
mockHttpContext.Setup(x => x.Request).Returns(mockRequest.Object);
mockRequest.Setup(x => x.AppRelativeCurrentExecutionFilePath).Returns(url);

RouteData routeData = rc.GetRouteData(mockHttpContext.Object);

string controllerName = routeData.Values["controller"].ToString();

IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controller = factory.CreateController(this.ControllerContext.RequestContext, controllerName);

I did quite a bit of googling and couldn't find much that didn't pertain to unit testing/mocking. I don't know if there is a quick and easy to do this, but would certainly like to know if someone has a better solution!

Kurt Schindler
  • 21,037
  • 4
  • 43
  • 48
  • Kurt - fantastic! I was just getting there when you posted this, I was about two lines off with the RouteData stuff. I think I may have a way to avoid mocks as well, using the current HttpContext - if you call this.HttpContext.RewritePath("~/Account/LogOn") it correctly finds the controller after that. I'm not sure if that is entirely safe though, since downstream consumers of the HttpContext might get confused, so I have to test it some more and may yet fall back to trusty Moq... many +1's to you. – womp Jan 14 '10 at 21:03
  • Cool! Please post whatever you come up with. Clever thinking w/ RewritePath, but indeed that may be problematic. – Kurt Schindler Jan 14 '10 at 21:11
  • I posted what I ended up with. The RewritePath() approach seems to work and lets me avoid the mocks for now. Thanks again for your help ;) – womp Jan 29 '10 at 00:38
10

Okay, after working with this for a couple weeks, this is what I ended up with. It works like a charm and avoids dependencies on mocks, but still feels hacky to me.

// Get the application's route collection.
UrlRoutingModule module = new UrlRoutingModule();
RouteCollection col = module.RouteCollection;

// Fake a request to the supplied URL into the routing system
string originalPath = this.HttpContext.Request.Path;
this.HttpContext.RewritePath(urlToGetControllerFor);
RouteData fakeRouteData = col.GetRouteData(this.HttpContext);

// Get an instance of the controller that would handle this route
string controllername = fakeRouteData.Values["controller"].ToString();
var ctxt = new RequestContext(this.ControllerContext.HttpContext, fakeRouteData);
IController controller = ControllerBuilder.Current.GetControllerFactory()
                          .CreateController(ctxt, controllername);

// Reset our request path.
this.HttpContext.RewritePath(originalPath.ToString());

So far there have been absolutely no downstream side effects of the RewritePath() calls. Thanks to Kurt for his code, and if anyone can come up with something better, please don't hesitate to post.

womp
  • 115,835
  • 26
  • 236
  • 269
-1

Obviously you're meaning any string of the post-domain URL on an MVC site, right? So, in http://yourdomain/something/thatSomeone/might/type/here you're meaning the something/thatSomeone/might/type/here part.

Go to your Global.asax.ca file in the RegisterRoutes method and add something like the following:

routes.MapRoute("foo", "foo/bar/in/a/jar", new { controller = "Home", action = "Index", id = "" });

Now when someone types in http://yourdoamin/foo/bar/in/a/jar they will get routed to the Home/Index controller action.

Hope that helps.

Cheers, -jc

MisterJames
  • 3,306
  • 1
  • 30
  • 48
  • Thanks for the response. I actaully want to leverage the routing system anywhere in my code. I'll edit the question to make it a bit clearer. – womp Jan 14 '10 at 19:55