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