Wednesday, September 24, 2014

ASP.NET Web Forms Routing for web services

I have written up a few notes on this as I hit a few issues with routing and I can see on sites like StackOverflow others have had issues as well.

Let’s start with the requirement:

You want to provide a web site that allows you to provide URLs that are meaningful to the end user and that can be resolved and routed to one or more selected pages. Your user might have a URL like


As the owner of website.com, you want to route that request to (say) an aspx page called customer.aspx and pass the string “particularvariant” to that page to use in deciding on the content to display or services to offer. This part of the requirement is well documented.


Using Visual Studio you can add a “Global Application Class” Global.asax if there is not one already in your project. Then you just need to add the following code to the Global.asax.cs code file in the ApplicationStart method:


protected void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes(RouteTable.Routes);
        }

Then add the RegisterRoutes method to the same file. It should contain the line required to add the routing like the one below.
        void RegisterRoutes(RouteCollection routes)
        {
           
            routes.MapPageRoute("", "specialfunction/{userneed}", "~/customer.aspx");
        }

A check on the MSDN documentation and/or a quick Google will supply plenty of alternate patterns for registering more complex URLs with routes to specific pages and with varying numbers of parameters but this example will suffice for this post. A request for the URL we wanted will now be routed to customer.aspx and the code page for that page can access the second element of the request (particularvariant) in the Page_Load method by interrogating the Page.RouteData.Values dictionary using the key(s) specified in the MapPageRoute method thus:

string userWants = (string)Page.RouteData.Values["userneed"];

That works fine – the required page will be served to the browser in exchange for our example request URL. The first snag you will probably hit will be that when the customer.aspx page loads in the browser any specified JavaScript and CSS files will have failed to load. If you use the browser developer tools (in Chrome maybe) you will see that the browser attempted to load these resources from the “wrong” URL. Maybe looking for a JavaScript file at ~/specialfunction/jscript/ rather than just ~/jscript/ or a css file at specialfunction/css/ rather than in the css subfolder from the root.
The css file is the simplest to solve – just specify the whole path. Something like:

<link href="~/css/bootstrap.min.css" rel="stylesheet" />

And it will be correctly resolved. This does not seem to work with JavaScript files and the best approach here is to add a scriptmanager <tag> to the web page (inside the <form> tags) and add the required scripts like:

    <asp:scriptmanager runat="server">
        <Scripts>
            <asp:ScriptReference Path="jscript/jquery-2.1.1.min.js"/>
            <asp:ScriptReference Path="jscript/standard.js"/>
        </Scripts>
    </asp:scriptmanager>
The JavaScript files will then be correctly loaded into the browser.

Now we get to the fun bit. Suppose you have web services served from an asmx object and you want to call web services using the jQuery $.ajax() method. You might want to make a call to a notional GetCust(Int32 CustID) web method served by OurAPI.asmx with a call like this:

$.ajax({
        type: 'POST',
        url: 'OurAPI.asmx/GetCust',
        data: '{CustID: 1}',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        processData: true,
        success: getCustBack,
        error: onError
    });

As you will probably new expect, you will get an error as the “/specialfunction/OurAPI.asmx” service can’t be found. So you pop back to the Global.asax.cs file to add a new routing.

But while the routes class has a MapPageRoute() method it hast no MapServiceRoute method – which is surprising in a mature environment like .NET 4.5. However there is an Add() method but we need to create something to be added. To do this we need to create a new class that inherits from the .NET IRouteHandler class. I based mine upon a StackOverflow post (http://stackoverflow.com/questions/2764925/mapping-to-an-asmx-service-using-routing-in-asp-net-mvc) and that goes like this:

public class ServiceRouteHandler : IRouteHandler
    {
        private readonly string _virtualPath;
        private readonly WebServiceHandlerFactory _handlerFactory = new WebServiceHandlerFactory();

        public ServiceRouteHandler(string virtualPath)
        {
            _virtualPath = virtualPath;
        }

        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            return _handlerFactory.GetHandler(HttpContext.Current, requestContext.HttpContext.Request.HttpMethod, _virtualPath, requestContext.HttpContext.Server.MapPath(_virtualPath));
        }
}

Although you might want to add some guard code for the virtualPath parameter.

Now you can add an additional line to the RegisterRoutes method. Something like:

void RegisterRoutes(RouteCollection routes)
        {
            routes.Add("reroutapi", new Route("specialfunction/OurAPI.asmx", new RouteValueDictionary() { { "controller", null }, { "action", null } }, new ServiceRouteHandler("~/OurAPI.asmx")));
            routes.MapPageRoute("", "specialfunction/{userneed}", "~/customer.aspx");
        }

And feeling justly proud you will give your code a spin to call the web service method and it still will not work. The fix is simple but subtle and took me far too long to figure out (read, make random changes until I got it).

Make a small change to the url parameter in the object passed to $.ajax(). A forward slash in front of OurAPI.asmx thus:

$.ajax({
        type: 'POST',
        url: '/OurAPI.asmx/GetCust',

Then the routing will work and the call will be serviced.

So that is how you get asmx web service routing to work from jQuery $.ajax() with Web Forms in ASP.NET

5 comments:

Anonymous said...

Do you mind if I quote a couple of your articles as long as I
provide credit and sources back to your blog? My blog site
is in the very same area of interest as yours and
my users would really benefit from some of the information you present here.
Please let me know if this okay with you.
Appreciate it!

Mike Griffiths said...

Quotations with attribution are always welcome.
Please feel free. Also, post a link back to your blog in the comments here.

Anonymous said...

What i do not understood is if truth be told how you're not actually much more
neatly-preferred than you may be right now. You are so intelligent.
You recognize thus significantly on the subject of this topic, produced me personally
consider it from so many various angles. Its like men and women aren't fascinated unless it's one thing to do with Girl gaga!
Your personal stuffs great. At all times maintain it up!

Anonymous said...

I know this if off topic but I'm looking into starting my own blog and was curious
what all is needed to get setup? I'm assuming having a blog like yours would cost a pretty penny?
I'm not very internet savvy so I'm not 100% certain. Any tips or advice would be greatly appreciated.
Many thanks

Mike Griffiths said...

Step by step instructions to create a free blog on this platform are easy to find. One example is New blog on Blogger