ASMX web services routing in ASP.NET Web Forms
Asked Answered
C

4

9

NOTE: There is no MVC in this code. Pure old Web Forms and .asmx Web Service.

I have inherited a large scale ASP.NET Web Forms & Web Service (.asmx) application at my new company.

Due to some need I am trying to do URL Routing for all Web Forms, which I was successfully able to do.

Now for .asmx, routes.MapPageRoute does not work. Based on the below article, I created an IRouteHandler class. Here's how the code looks:

using System;
using System.Web;
using System.Web.Routing;
using System.Web.Services.Protocols;
using System.Collections.Generic;
public class ServiceRouteHandler : IRouteHandler
{
private readonly string _virtualPath;
private readonly WebServiceHandlerFactory _handlerFactory = new WebServiceHandlerFactory();

public ServiceRouteHandler(string virtualPath)
{
    if (virtualPath == null)
        throw new ArgumentNullException("virtualPath");
    if (!virtualPath.StartsWith("~/"))
        throw new ArgumentException("Virtual path must start with ~/", "virtualPath");
    _virtualPath = virtualPath;
}

public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
    // Note: can't pass requestContext.HttpContext as the first parameter because that's
    // type HttpContextBase, while GetHandler wants HttpContext.
    return _handlerFactory.GetHandler(HttpContext.Current, requestContext.HttpContext.Request.HttpMethod, _virtualPath, requestContext.HttpContext.Server.MapPath(_virtualPath));
}

}

http://mikeoncode.blogspot.in/2014/09/aspnet-web-forms-routing-for-web.html

Now when I do routing via Global.asax, it work for the root documentation file but does not work with the Web Methods inside my .asmx files.

 routes.Add("myservice", new System.Web.Routing.Route("service/sDxcdfG3SC", new System.Web.Routing.RouteValueDictionary() { { "controller", null }, { "action", null } }, new ServiceRouteHandler("~/service/myoriginal.asmx")));

    routes.MapPageRoute("", "service/sDxcdfG3SC", "~/service/myoriginal.asmx");

Goal

I would like to map an .asmx Web Method URL such as www.website.com/service/myservice.asmx/fetchdata to a URL with obscured names in it like www.website.com/service/ldfdsfsdf/dsd3dfd3d using .NET Routing.

How can this be done?

Commutable answered 2/2, 2018 at 8:15 Comment(4)
Please explain what URL you would like to use to access the web methods inside of your web service. Also, how are you calling the web methods, HTTP POST or HTTP GET? Is your client using JavaScript, a Win Forms app, or other? Your example shows a configuration that doesn't work, but it doesn't explain what your goal is or why that configuration is not adequate for your needs.Antecede
@NightOwl888, I am using HTTP Post. Client is using JavaScript. www.website.com/service/myservice.asmx/fetchdata I want it to be like www.website.com/service/ldfdsfsdf/dsd3dfd3dCommutable
And what URL(s) do you want to use for the web service? It only makes sense to use routing if the URL is different from what it would normally be without routing. Please edit your question to include that info. Thanks.Antecede
My original url is like : www.website.com/service/myservice.asmx/fetchdata After routing the new url should hide the actual name and should be some unreadable name like : www.website.com/service/ldfdsfsdf/dsd3dfd3dCommutable
A
4

It is slightly more tricky to do this with routing than in the article you posted because you don't want the incoming URL to have a query string parameter and it looks like the WebServiceHandler won't call the method without an ?op=Method parameter.

So, there are a few parts to this:

  1. A custom route (ServiceRoute) to do URL rewriting to add the ?op=Method parameter
  2. An IRouteHandler to wrap the WebServiceHandlerFactory that calls the web service.
  3. A set of extension methods to make registration easy.

ServiceRoute

public class ServiceRoute : Route
{
    public ServiceRoute(string url, string virtualPath, RouteValueDictionary defaults, RouteValueDictionary constraints)
        : base(url, defaults, constraints, new ServiceRouteHandler(virtualPath))
    {
        this.VirtualPath = virtualPath;
    }

    public string VirtualPath { get; private set; }

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        // Run a test to see if the URL and constraints don't match
        // (will be null) and reject the request if they don't.
        if (base.GetRouteData(httpContext) == null)
            return null;

        // Use URL rewriting to fake the query string for the ASMX
        httpContext.RewritePath(this.VirtualPath);

        return base.GetRouteData(httpContext);
    }
}

ServiceHandler

public class ServiceRouteHandler : IRouteHandler
{
    private readonly string virtualPath;
    private readonly WebServiceHandlerFactory handlerFactory = new WebServiceHandlerFactory();

    public ServiceRouteHandler(string virtualPath)
    {
        if (virtualPath == null)
            throw new ArgumentNullException(nameof(virtualPath));
        if (!virtualPath.StartsWith("~/"))
            throw new ArgumentException("Virtual path must start with ~/", "virtualPath");
        this.virtualPath = virtualPath;
    }

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        // Strip the query string (if any) off of the file path
        string filePath = virtualPath;
        int qIndex = filePath.IndexOf('?');
        if (qIndex >= 0)
            filePath = filePath.Substring(0, qIndex);

        // Note: can't pass requestContext.HttpContext as the first 
        // parameter because that's type HttpContextBase, while 
        // GetHandler expects HttpContext.
        return handlerFactory.GetHandler(
            HttpContext.Current, 
            requestContext.HttpContext.Request.HttpMethod,
            virtualPath, 
            requestContext.HttpContext.Server.MapPath(filePath));
    }
}

RouteCollectionExtensions

public static class RouteCollectionExtensions
{
    public static void MapServiceRoutes(
        this RouteCollection routes,
        Dictionary<string, string> urlToVirtualPathMap,
        object defaults = null,
        object constraints = null)
    {
        foreach (var kvp in urlToVirtualPathMap)
            MapServiceRoute(routes, null, kvp.Key, kvp.Value, defaults, constraints);
    }

    public static Route MapServiceRoute(
        this RouteCollection routes, 
        string url, 
        string virtualPath, 
        object defaults = null, 
        object constraints = null)
    {
        return MapServiceRoute(routes, null, url, virtualPath, defaults, constraints);
    }

    public static Route MapServiceRoute(
        this RouteCollection routes, 
        string routeName, 
        string url, 
        string virtualPath, 
        object defaults = null, 
        object constraints = null)
    {
        if (routes == null)
            throw new ArgumentNullException("routes");

        Route route = new ServiceRoute(
            url: url,
            virtualPath: virtualPath,
            defaults: new RouteValueDictionary(defaults) { { "controller", null }, { "action", null } },
            constraints: new RouteValueDictionary(constraints)
        );
        routes.Add(routeName, route);
        return route;
    }
}

Usage

You can either use MapServiceRoute to add the routes one at a time (with an optional name):

public static class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        var settings = new FriendlyUrlSettings();
        settings.AutoRedirectMode = RedirectMode.Permanent;
        routes.EnableFriendlyUrls(settings);

        routes.MapServiceRoute("AddRoute", "service/ldfdsfsdf/dsd3dfd3d", "~/service/myoriginal.asmx?op=Add");
        routes.MapServiceRoute("SubtractRoute", "service/ldfdsfsdf/dsd3dfd3g", "~/service/myoriginal.asmx?op=Subtract");
        routes.MapServiceRoute("MultiplyRoute", "service/ldfdsfsdf/dsd3dfd3k", "~/service/myoriginal.asmx?op=Multiply");
        routes.MapServiceRoute("DivideRoute", "service/ldfdsfsdf/dsd3dfd3v", "~/service/myoriginal.asmx?op=Divide");
    }
}

Alternatively, you can call MapServiceRoutes to map a batch of your web service routes at once:

public static class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        var settings = new FriendlyUrlSettings();
        settings.AutoRedirectMode = RedirectMode.Permanent;
        routes.EnableFriendlyUrls(settings);

        routes.MapServiceRoutes(new Dictionary<string, string>
        {
            { "service/ldfdsfsdf/dsd3dfd3d", "~/service/myoriginal.asmx?op=Add" },
            { "service/ldfdsfsdf/dsd3dfd3g", "~/service/myoriginal.asmx?op=Subtract" },
            { "service/ldfdsfsdf/dsd3dfd3k", "~/service/myoriginal.asmx?op=Multiply" },
            { "service/ldfdsfsdf/dsd3dfd3v", "~/service/myoriginal.asmx?op=Divide" },
        });
    }
}

NOTE: If you were to have MVC in the application, you should generally register your MVC routes after these routes.

References:

Antecede answered 10/2, 2018 at 23:4 Comment(0)
A
2

Not a direct answer but something worth considering.

You could possibly upgrade your ASMX service to a WCF service with compatible contract so that you don't have to upgrade your clients at all.

With that, you could use a known technique to dynamically route WCF services. Since this known technique involves an arbitrary address for your service, you can bind the WCF service to a .......foo.asmx endpoint address so that your clients not only don't upgrade their client proxies but also they have exactly the same endpoint address.

In other words, to a client, your dynamically routed WCF service looks 1-1 identical as your old ASMX service.

We've succesfully used this technique over couple of last years to upgrade most of all old ASMXes to WCFs, preserving client proxies in many cases.

All technical details are documented in my blog entry

http://www.wiktorzychla.com/2014/08/dynamic-wcf-routing-and-easy-upgrading.html

Aftereffect answered 7/2, 2018 at 7:58 Comment(0)
G
2

The article you are referencing is not to provide extensionless routing to asmx WS, is to provide routing from server/whateverYouAre/ws.asmx to server/ws.asmx (the real resource location). This allows JS use local path (current location) to invoque the asmx without worry abot where the browser are.

Anyway, maybe, just maybe, you can use the article as starting point. I never do this so it just a guess:

There are 2 modes to consume your WS. If the client is using SOAP the request URL will be:

/server/service/myoriginal.asmx

with SOAPAction http header and the SOAP XML in the POST body. Your current routing solution should work. BUT if you are consuming the WS though raw HTTP GET/POST (i.e. from a webBrowser) the url of each webMethod is:

/server/service/myoriginal.asmx/webMethod

So I think you could to provide some url routing in the form of:

routes.Add("myservice", new System.Web.Routing.Route("service/sDxcdfG3SC/{webMethod}", new System.Web.Routing.RouteValueDictionary() { { "controller", null }, { "action", null } }, new ServiceRouteHandler("~/service/myoriginal.asmx")));

//Delete routes.MapPageRoute("", "service/sDxcdfG3SC", "~/service/myoriginal.asmx"); from your code, it is wrong even in your actual solution

and modify GetHttpHandler:

public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
    return _handlerFactory.GetHandler(HttpContext.Current, requestContext.HttpContext.Request.HttpMethod, _virtualPath + "\" + requestContext.RouteData.Values["webMethod"], requestContext.HttpContext.Server.MapPath(_virtualPath));
}

to provide the raw URL of the requested resource in the form of /server/service/myoriginal.asmx/webMethod.

My code is write on the fly from the top of my head so just make sure _virtualPath + "/" + requestContext.RouteData.Values["webMethod"] create the right URL before a early rage quit ;-) Modify it if needed.

With some luck; WebServiceHandlerFactory should be able to locate the physical resource and, inspecting the raw URL, execute the webMethod by its name.

Gautama answered 7/2, 2018 at 10:37 Comment(2)
If works for POST you have still work to make it work for GET with URL parameters. You have to forge the rawURL in the form of: /server/service/myoriginal.asmx/webMethod?p1=v1&p2=v2Gautama
I tried doing this for last 2 days. But it didn't work.Commutable
A
2

If the site is hosted in IIS you could use the IIS URL Rewrite to create a friendly url and redirect it to your internal path as per creating-rewrite-rules-for-the-url-rewrite-module. Each of these rules are stored in the web.config so can be managed within your development environment

The drawback (or benefit depending upon your usage) is that the original path would still be accessible

Artiodactyl answered 13/2, 2018 at 1:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.