4

I’m trying to set up some routes for my ASP.NET MVC 5 project.

  • I defined custom routes to get nice blog post permalinks – those seem to be working fine
  • I added a XmlRpc Handler (similar to how it’s done in Mads' Miniblog and Scott’s post)

Now I have some strange behavior:

  • /Home/About is routed correctly
  • /Home/Index gets routed to /XmlRpc?action=Index&controller=Blog
  • /HOme/Index works (yes I discovered that due to a typo) – I always thought routes are case insensitive?
  • Using Url.Action("Foo","Bar") also creates /XmlRpc?action=Foo&controller=Bar

This is my RouteConfig file:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.Add("XmlRpc", new Route("XmlRpc", new MetaWeblogRouteHandler()));

    routes.MapRoute("Post", "Post/{year}/{month}/{day}/{id}", new {controller = "Blog", action = "Post"}, new {year = @"\d{4,4}", month = @"\d{1,2}", day = @"\d{1,2}", id = @"(\w+-?)*"});
    routes.MapRoute("Posts on Day", "Post/{year}/{month}/{day}", new {controller = "Blog", action = "PostsOnDay"}, new {year = @"\d{4,4}", month = @"\d{1,2}", day = @"\d{1,2}"});
    routes.MapRoute("Posts in Month", "Post/{year}/{month}", new {controller = "Blog", action = "PostsInMonth"}, new {year = @"\d{4,4}", month = @"\d{1,2"});
    routes.MapRoute("Posts in Year", "Post/{year}", new {controller = "Blog", action = "PostsInYear"}, new {year = @"\d{4,4}"});
    routes.MapRoute("Post List Pages", "Page/{page}", new {controller = "Blog", action = "Index"}, new {page = @"\d{1,6}"});
    routes.MapRoute("Posts by Tag", "Tag/{tag}", new {controller = "Blog", action = "PostsByTag"}, new {id = @"(\w+-?)*"});
    routes.MapRoute("Posts by Category", "Category/{category}", new {controller = "Blog", action = "PostsByCategory"}, new {id = @"(\w+-?)*"});

    routes.MapRoute("Default", "{controller}/{action}/{id}", new {controller = "Blog", action = "Index", id = UrlParameter.Optional});            
}

And that’s the definition of MetaWeblogRouteHandler:

public class MetaWeblogRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new MetaWeblog();
    }
}

Basically I’d like to have the usual ASP.NET MVC routing behavior (/controller/action) + my defined custom routes for permalinks + XML-RPC handling via the XmlRpc handler only at /XmlRpc.

Since the parameters are the same that are defined in the Default route I tried to remove the route, but without success.
Any ideas?

Update:
When calling /Home/Index the AppRelativeCurrentExecutionFilePath is set to "~/XmlRpc" so the XmlRpc route is legally chosen. Something seems to be messing around with the request?

Update2: The problem fixed itself in every but one case: when starting IE via Visual Studio for Debug it still fails. In every other case it now works (yes I checked browser cache and even tried it on a different machine to be sure; IE started from VS = fail, all other combinations are fine). Anyway, since it will now work for the end user I'm satisfied for the moment ;)

marce
  • 781
  • 1
  • 10
  • 20

1 Answers1

5

When you execute Url.Action("Foo","Bar"), MVC will create a collection of route values from your inputs (In that case action=Foo, controller=Bar) and it will then look at your routes, trying to match one that matches based on its segments and default values.

Your XmlRpc route has no segments and no default values, and is the first one defined. This means it will always be the first match when generating urls using @Url.Action, @Html.ActionLink etc.

A quick way to prevent that route from being matched when generating urls would be adding a default controller parameter (using a controller name that you are sure you will never use). For example:

routes.Add("XmlRpc", new Route("XmlRpc", new RouteValueDictionary() { { "controller", "XmlRpc" } }, new MetaWeblogRouteHandler())); 

Now when you execute Url.Action("Foo","Bar"), you will get the expected /Bar/Foo url, as "Bar" doesn´t match the default controller value in the route definition, "XmlRpc".

However that seems a bit hacky.

A better option would be creating your own RouteBase class. This willonly care for the url /XmlRpc, which will then be served using MetaWeblogRouteHandler and will be ignored when generating links using the Html and Url helpers:

public class XmlRpcRoute : RouteBase
{
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        //The route will only be a match when requesting the url ~/XmlRpc, and in that case the MetaWeblogRouteHandler will handle the request
        if (httpContext.Request.AppRelativeCurrentExecutionFilePath.Equals("~/XmlRpc", StringComparison.CurrentCultureIgnoreCase))
            return new RouteData(this, new MetaWeblogRouteHandler());

        //If url is other than /XmlRpc, return null so MVC keeps looking at the other routes
        return null;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {            
        //return null, so this route is skipped by MVC when generating outgoing Urls (as in @Url.Action and @Html.ActionLink)
        return null;
    }
}

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    //Add the route using our custom XmlRpcRoute class
    routes.Add("XmlRpc", new XmlRpcRoute());

    ... your other routes ...
}

However, in the end you are creating a route just to run an IHttpHandler outside the MVC flow, for a single url. You are even struggling to keep that route from interferring with the rest of the MVC components, like when generating urls using helpers.

You could then just add directly a handler for that module in the web.config file, also adding an ignore rule for /XmlRpc in your MVC routes:

<configuration>
  ...
  <system.webServer>
    <handlers>
      <!-- Make sure to update the namespace "WebApplication1.Blog" to whatever your namespace is-->
      <add name="MetaWebLogHandler" verb="POST,GET" type="WebApplication1.Blog.MetaWeblogHandler" path="/XmlRpc" />
    </handlers>
  </system.webServer>
</configuration>

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    //Make sure MVC ignores /XmlRpc, which will be directly handled by MetaWeblogHandler
    routes.IgnoreRoute("XmlRpc");

    ... your other routes ...         
}

Using either of these 3 approaches, this is what I get:

  • /Home/Index renders the Index view of the HomeController

  • / renders the Index view of the BlogController

  • @Url.Action("Foo","Bar") generates the url /Bar/Foo

  • @Html.ActionLink("MyLink","Foo","Bar") renders the following html: <a href="/Bar/Foo">MyLink</a>

  • /XmlRcp renders the a view describing the MetaWeblogHandler and its available methods, where there is a single method available (blog.index, taking no parameters and returning a string)


In order for testing this, I have created a new empty MVC 5 application, adding the NuGet package xmlrpcnet-server.

I have created a HomeController and a BlogController, both with an index action, and I have created the following MetaWeblog classes:

public interface IMetaWeblog
{
    [XmlRpcMethod("blog.index")]
    string Index();        
}

public class MetaWeblogHandler : XmlRpcService, IMetaWeblog
{
    string IMetaWeblog.Index()
    {
        return "Hello World";
    }        
}

public class MetaWeblogRouteHandler : IRouteHandler
{
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new MetaWeblogHandler();
    }
}
Daniel J.G.
  • 34,266
  • 9
  • 112
  • 112
  • Thanks for the extensive answer! I’m trying to implement your last version since that’s what I really want to achieve after all. And there is still the same problem with `/Home/Index` (`/Home` works). I quickly started a second project and created the same minimal version you described and your solution does work just fine. So there has to be a bad setting anywhere in my main project. Do you have any idea where the flaw could be or how I can debug the routing process? – marce Apr 24 '14 at 21:52
  • The most obvious option would be to start adding more functionality to the basic project, specially things like global filters, additional httpHandlers, NuGet packages... If you try the second approach (creating your custom RouteBase) do you also have the issue with `/Home/Index`? In that case you could set a breakpoint in its `GetRouteData` method and inspect the request object inside the httpContext, just in case you see anything unusual there... – Daniel J.G. Apr 24 '14 at 22:09
  • Nice idea utilizing the RouteBase! I now found the problem, but no solution: when calling `/Home/Index` the `AppRelativeCurrentExecutionFilePath` is set to `"~/XmlRpc"` so the XmlRpc route is _legally_ chosen. At least I can now try to track down why the request gets messed up. – marce Apr 24 '14 at 22:35
  • Alright, I finally tracked it down to a strange problem which only occurs when IE is started from Visual Studio. The end users won't do that and I can work around it. Thanks again! – marce Apr 24 '14 at 23:10