Question

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 ;)

Was it helpful?

Solution

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();
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top