Question

From a previous question How to route web pages on a mixed MVC and Web Forms, I am trying to further extend my idea of routing all Web Forms (*.aspx) to a specific sub-folder on the web site. The basic idea is that all requests are checked to see if they map to an existing .aspx page in the designated 'web form pages folder. For example, if all .aspx pages exist in a folder structure from '~/WebPages'...

/MyPage.aspx => /WebPages/MyPage.aspx

/SubFolder/MyotherPage.aspx => /WebPages/SubFolder/MyOtherPage.aspx

In addition, I would like to simplify the URLs by dropping the .aspx extension, so

/MyPage => /WebPages/MyPage

/SubFolder/MyotherPage.aspx => /WebPages/SubFolder/MyotherPage.aspx

To do this, I need to consider every request, as I don't think it is possible to define a specific route or constraint. To do this, I implemented the following route configuration.

Route Configuration

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

      routes.Add(
         "rootforms",
         new Route(
            "{*pathInfo}",
            new DirectoryRouteHandler(virtualDir: "~/WebPages")
         )
      );

      routes.MapRoute(
         name: "Default",
         url: "{controller}/{action}/{id}",
         defaults: new
         {
               controller = "Home",
               action = "Index",
               id = UrlParameter.Optional
         },
         namespaces: new[] {
               "MultiApp.Web.Controllers"
         }
      );

   }
}

And defined the custom route handler as follows.

IRouteHandler

public class DirectoryRouteHandler : IRouteHandler
{
   private readonly string _virtualDir;

   public DirectoryRouteHandler(string virtualDir)
   {
      _virtualDir = virtualDir;
   }

   public IHttpHandler GetHttpHandler(RequestContext requestContext)
   {
      var routeValues = requestContext.RouteData.Values;

      if (!routeValues.ContainsKey("pathInfo"))
      {
         return null; /* this doesn't work - must be a RouteHandler */;
      }

      var path = routeValues["pathInfo"] as string;

      if (String.IsNullOrWhiteSpace(path))
      {
         path = "Default.aspx";
      }

      // add the .aspx extension if required    
      if (!path.EndsWith(".aspx")) { path += ".aspx"; }

      // build the test path    
      var pageVirtualPath = string.Format("{0}/{1}", _virtualDir, path);

      string filePath = requestContext.HttpContext.Server.MapPath(pageVirtualPath);

      // check to see if the physical .aspx file exists, if not exit this handler
      if (!File.Exists(filePath))
      {
         return null; /* this doesn't work - must be a RouteHandler */;
      }

      return new PageRouteHandler(pageVirtualPath, true).GetHttpHandler(requestContext);
   }
}

What I was hoping to be able to do was to either return a PageRouteHandler object (as suggested in my previous question), or exit this handler, passing the responsibility back to the default routing mechanism.

Is this possible, or am I barking up the wrong tree entirely? Would I be better off just registering a simple IHttpHandler and leaving the routing alone?

Était-ce utile?

La solution

I think I have found the solution to this. Rather than try and achieve this functionality by managing routes, it is better to use a custom HttpModule. Here is the code.

HttpModule

public class WebFormsHttpHandler
   : IHttpModule
{

   // All Web Form (*.aspx) pages will exist in this folder hierarchy
   public const string WebFormsRootFolder = "~/WebPages";


   #region Constructor
   public WebFormsHttpHandler()
   {
   }
   #endregion Constructor


   #region IHttpModule Methods
   public void Dispose()
   {
      /* Nothing to dispose */
   }
   #endregion Constructor


   // Wire up the interesting events
   public void Init(HttpApplication context)
   {
      context.BeginRequest += context_BeginRequest;
      context.AuthorizeRequest += context_AuthorizeRequest;
      context.PreRequestHandlerExecute += context_PreRequestHandlerExecute;
   }


   #region BeginRequest
   // Capture the original request path so we can restore it to the browser
   // in the PreRequestHandlerExecute event if required
   void context_BeginRequest(object sender, EventArgs e)
   {
      var context = ((HttpApplication)sender).Context;

      var path = context.Request.Url.AbsolutePath;

      context.Items["originalUrl"] = path;
   }
   #endregion BeginRequest


   #region AuthorizeRequest
   void context_AuthorizeRequest(object sender, EventArgs e)
   {
      var context = ((HttpApplication)sender).Context;

      string requestPath = context.Request.Url.AbsolutePath ?? "";
      string filePath = context.Server.MapPath(requestPath);

      // Check for physical existence
      if (File.Exists(filePath))
      {
         return;
      }

      var virtualPath = WebFormsRootFolder;
      if (!requestPath.StartsWith("/")) { virtualPath += "/"; }
      virtualPath += requestPath;

      filePath = context.Server.MapPath(virtualPath);

      // Is the virtualPath a directory?
      if (Directory.Exists(filePath))
      {
         if (!virtualPath.EndsWith("/")) { virtualPath += "/"; }
         virtualPath += "Default.aspx";
      }
      else if (!virtualPath.EndsWith(".aspx")) {
         virtualPath += ".aspx";
      }

      filePath = context.Server.MapPath(virtualPath);

      if (!File.Exists(filePath))
      {
         return;
      }

      /*
      ** Should I set a context.Response.StatusCode here?
      */

      context.RewritePath(virtualPath);
   }
   #endregion AuthorizeRequest


   #region PreRequestHandlerExecute
   // Restore the original request path on the browser - the user doesn't
   // need to know what's happened
   void context_PreRequestHandlerExecute(object sender, EventArgs e)
   {
      var context = ((HttpApplication)sender).Context;

      string originalUrl = context.Items["originalUrl"] as string;

      if (originalUrl != null)
      {
         context.RewritePath(originalUrl);
      }
   }
   #endregion PreRequestHandlerExecute

}

Register HttpModule in Web.config

<system.webServer>
    <modules>
        <add name="WebFormsHttpHandler" type="MultiApp.Web.WebFormsHttpHandler" />
    </modules>
</system.webServer>

Sample/Test Route Configuration

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

   // WebForm Route Definitions
   routes.MapPageRoute(
      "product-details",
      "product/{productid}",
      "~/WebPages/SubFolder/Product.aspx"
   );

   // MVC Route Definitions
   routes.MapRoute(
      name: "Default",
      url: "{controller}/{action}/{id}",
      defaults: new
      {
         controller = "Home",
         action = "Index",
         id = UrlParameter.Optional
      },
      namespaces: new[] {
         "MultiApp.Web.Controllers"
      }
   );

}

I have tested this with a wide format of URL constructs and it appears to work as required. If anyone has any suggestions for improvement, please let me know.

I would like to thank Rahul Rajat Singh's project Implementing HTTPHandler and HTTPModule in ASP.NET which was a big help.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top