Wednesday, September 8, 2010

URL Routing on IIS7 and ASP.NET 3.5 with SP1

Purpose : To allow users browse your site using extensionless and user-friendly urls.
Step1: Add a Reference to System.Web.Routing to Your Project
Step2: 
Add UrlRoutingModule to 'httpModules' under <system.web> and to 'modules' under  <system.webServer>;
Add UrlRoutingHandler to 'handlers' section under <system.webServer>

<configuration>
   ...

   <system.web>
      ...
      <httpModules>
         ...
         <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </httpModules>

   </system.web>

   <system.webServer>
      <validation validateIntegratedModeConfiguration="false"/>
      <modules>
         ...
         <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </modules>


      <handlers>
         ...
         <add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
      </handlers>
      ...
   </system.webServer>

</configuration>

Step 3: Define the Routes in Global.asax

Define one to many routes in Application_Start event of global.asax.

Here in this example defined a route to 
SchoolType/SchoolName - used to view school detail page. (where actually you have to go to this page by virtual path 'ViewSchool.aspx?schoolid = 14')


<%@ Import Namespace="System.Web.Routing" %>
<script runat="server">
void Application_Start(object sender, EventArgs e) 
{
     RegisterRoutes(RouteTable.Routes);
}

void RegisterRoutes(RouteCollection routes)
{
    routes.Add("ViewSchool", new Route("{SchoolType}/{*SchoolName}", new   SchoolRouteHandler()));
}
</script>


Note: If you are using ASP.NET AJAX along with URL Re-Writing, avoid rewriting paths to '.axd' files. Otherwise, it will rewrite WebResource.axd and ScriptResource.axd file paths also and that is the reason AJAX will not work on your site (ex: update panel, update progress etc.).


For this reason I changed my RegisterRoutes method to be as below.


void RegisterRoutes(RouteCollection routes)
{
    string url = HttpContext.Current.Request.Url.AbsolutePath;
if (! url.Contains(".axd"))
{ routes.Add("ViewSchool", new Route("{SchoolType}/{*SchoolName}", new     SchoolRouteHandler()));
    }
}

Step 4: Create the Route Handler Classes
 A route handler class is a class that is passed information about the incoming request and must return an HTTP Handler to process the request. It must implement the IRouteHandler  interface and, as a consequence, must provide at least one method - GetHttpHandler - which is responsible for returning the HTTP Handler (or ASP.NET page) that will process the request.
Typically, a route handler performs the following steps:
  1. Parse the URL as needed
  2. Load any information from the URL that needs to be passed to the ASP.NET page or HTTP Handler that will handle this request. One way to convey such information is to place it in the HttpContext.Items collection, which serves as a repository for storing data that persists the length of the request.
  3. Return an instance of the ASP.NET page or HTTP Handler that does the processing

public class SchoolRouteHandler : IRouteHandler
{
     public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        string schoolType = requestContext.RouteData.Values["SchoolType"] as string;
        string schoolName = requestContext.RouteData.Values["SchoolName"] as string;
      
        if (string.IsNullOrEmpty(schoolName) || string.IsNullOrEmpty(schoolType))
            return BuildManager.CreateInstanceFromVirtualPath("~/schoolnotfound.aspx", typeof(Page)) as   
            Page;
        else
        {
            //Code here to get school id from database 
           //store values, whichever you want to pass them to .aspx page

            HttpContext.Current.Items["SchoolType"] = schoolType;
            HttpContext.Current.Items["SchoolID"] = schoolid;

            return BuildManager.CreateInstanceFromVirtualPath("~/viewschool.aspx", typeof(Page)) as Page;
        }
            return BuildManager.CreateInstanceFromVirtualPath("~/schoolnotfound.aspx", typeof(Page)) as         Page;
    }
}

Step 5: Create the ASP.NET Pages That Process the Requests

protected void Page_Load(object sender, EventArgs e)
   //Retrieve values back from httpcontext
   Int64 schoolid = Convert.ToInt64(
HttpContext.Current.Items["schoolid"]);

If you do till here it will work fine on your local system using development server.  But, when you host the website on a shared server you get HTTP 404 page. This means IIS receives the request and it serves the page configured for HTTP 404. Generally this is a 404.html file that resides in IIS directory. IIS does not send the request to ASP.NET handler and thus you cannot intercept 404 requests. So, if you can forward all HTTP 404 to ASP.NET, you can serve such extensionless URL. In order to forward HTTP 404 to ASP.NET ISAPI, all you need to do is configure IIS to redirect to some .aspx extension when a missing extensionless url is hit.

Step6:
Add below section to your web.config , under <system.webserver>
<httpErrors errorMode="Custom">
     <remove statusCode="404" subStatusCode="-1" />
     <error statusCode="404" prefixLanguageFilePath="" path="/404.aspx" responseMode="ExecuteURL" />
</httpErrors>

When you will request an URL like "www.findaschool.in/preschools/drskids" it will redirect to "www.findaschool.in/404.aspx?404;http://www.findaschool.in/preschools/drskids". From Global.asax BeginRequest event, capture this URL and see whether it's an extensionless URL request. If it is, do your URL rewriting stuff for such extensionless URL.


protected void Application_BeginRequest(object sender, EventArgs e)
{
      string url = HttpContext.Current.Request.Url.AbsolutePath;
   
      // HTTP 404 redirection for extensionless URL or some missing file
      if (url.Contains("404.aspx"))
      {
          // On 404 redirection, query string contains the original URL in this format:
          // 404;http://www.findaschool.in/preschools/drskids    
          string[] urlInfo404 = Request.Url.Query.ToString().Split(';');
          if (urlInfo404.Length > 1)
          {
              string originalUrl = urlInfo404[1];
System.Uri rwUri = new Uri(originalUrl);
              HttpContext.Current.RewritePath(rwUri.PathAndQuery); 
}
}
}

More detailed information at below links:.

No comments:

Post a Comment