2

I'm working on an established site. Although small (in terms of pages), there are some big money landing pages as well as the usual stock pages.

Because the site was relatively small, the page structure was flat.

https://example.com/contact
https://example.com/big-money-page

We plan on introducing lots more pages with different page designs. This means we'll either use master pages and/or aspx templated pages and create our own database driven CMS.

Here's the problem I can see is with url routing:

Template type 1

Route url: /{Name} - e.g. /big-money-page
Physica path: ~/template1.aspx

Template type 2

Route url: /{Name} - e.g. /new-supporting-page
Physical path: ~/template2.aspx

I would like to make this work without disruption to the existing money pages and, if possible, keep the familiar website structure, as to visitors, template1 and template2 are similar pages and don't naturally reside in different folders - they just differ in design.

Also, fixed deep routed folder structures make it difficult to make changes in the future.

I've been using WF routing for some time but always in simple ways. Anyone know how I can make the changes work with limited consequences?

UPDATE --------------------------------------------------------------------

Okay, in the absence of any feedback, I've come up with an idea to put on the table. I'd appreciate feedback on the fesibility and any downsides that can be thought of.

My idea is to have a dummy route/page.

The route would take the form http://example.com/{name}.

The dummy page retrieves data from the database for the target page using the placeholder {name}.

We then server.transfer to the correct target page, using the data we retrieved from the database.

I think this will work but I'm concerned about the things I don't know:

  • Browser compatibility for server.transfer
  • Performance overhead
  • Impact on output caching
  • Other things that haven't even crossed my mind

Of course this is not an ideal solution but I'm also open to any other ideas.

1 Answer 1

2
+50

In a WebForm project the task can be implemented using a custom HTTPModule. The implementation includes a few steps. A simplified version is as following:
1. SQL

create table dbo.UrlMap (
  publicUrl varchar(255) not null primary key,
  PhysUrl varchar(255) not null
)

Fill the table with some data like

publicUrl        PhysUrl 
big-money-page   template1.aspx?id=1
huge-money-page  template1.aspx?id=2
no-money-page    template2.aspx?id=3
other-page       template1.aspx?id=4

2. Create a class in the App_code folder

using System;
using System.Web;

/// <summary>
/// Implements IHttpModule  with custom URLs
/// </summary>
public class UrlMap:IHttpModule
{
    /// <summary>
    /// Initialize the module 
    /// </summary>
    /// <param name="context"></param>
    void IHttpModule.Init(HttpApplication context)
    {
        context.BeginRequest += Context_BeginRequest;
        context.PostMapRequestHandler += Context_PostMapRequestHandler;
    }

    private void Context_BeginRequest(object sender, EventArgs e)
    {
        var app = (HttpApplication)sender;
        Url2PhysPath(app.Request.Path, app);
    }

    private void Context_PostMapRequestHandler(object sender, EventArgs e)
    {
        var app = (HttpApplication)sender;
        var pg = app.Context.Handler as System.Web.UI.Page;
        if (pg != null)
        {
            pg.PreRenderComplete += Pg_PreRenderComplete;
        }
    }

    private void Pg_PreRenderComplete(object sender, EventArgs e)
    {
        ProcessPageTree((System.Web.UI.Control)sender);
    }

    /// <summary>
    /// Replaces physical URLs on the page with "beutified" version
    /// </summary>
    /// <param name="control"></param>
    private void ProcessPageTree(System.Web.UI.Control control)
    {
        var form = control as System.Web.UI.HtmlControls.HtmlForm;
        if (form != null)
        {
            form.Action = BeautifyUrl(form.Page.Request.Url.PathAndQuery);
        }
        //other types in a similar way
        if (control.HasControls())
        {
            foreach(System.Web.UI.Control c in control.Controls)
            {
                ProcessPageTree(c);
            }
        }
    }
    /// <summary>
    /// Helper function. Can be inlined. 
    /// Searches "beautified" url in a DB and rewrites path
    /// </summary>
    /// <param name="url"></param>
    /// <param name="app"></param>
    private static void Url2PhysPath(string url, HttpApplication app)
    {
        using (var cnn = new System.Data.SqlClient.SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["SiteCnn"].ConnectionString))
        {
            var cmd = new System.Data.SqlClient.SqlCommand("select physPath from dbo.urlMap where publicUrl=@url", cnn);
            cmd.CommandType = System.Data.CommandType.Text;
            cmd.Parameters.Add("@url", System.Data.SqlDbType.VarChar, 255).Value = url;
            cnn.Open();
            string physPath = null;
            using(var r = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
            {
                if (r.Read())
                {
                    physPath = (string)r["physPath"];
                }
                r.Close();
            }
            if (!string.IsNullOrEmpty(physPath))
            {
                app.Context.RewritePath("/" + physPath);
            }
        }
    }

    /// <summary>
    /// Helper function 
    /// </summary>
    /// <param name="physUrl"></param>
    /// <returns>returns original url when nothing is found</returns>
    private static string BeautifyUrl(string physUrl)
    {
        using (var cnn = new System.Data.SqlClient.SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["SiteCnn"].ConnectionString))
        {
            var cmd = new System.Data.SqlClient.SqlCommand("select publicUrl from dbo.urlMap where physPath=@url", cnn);
            cmd.CommandType = System.Data.CommandType.Text;
            cmd.Parameters.Add("@url", System.Data.SqlDbType.VarChar, 255).Value = physUrl;
            cnn.Open();
            string pubUrl = physUrl;//to return original url when nothing is found
            using(var r = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
            {
                if (r.Read())
                {
                    pubUrl = (string)r["publicUrl"];
                }
                r.Close();
            }
            return pubUrl;
        }

    }
    /// <summary>
    /// Required by interface
    /// </summary>
    void IHttpModule.Dispose()
    {
     //   throw new NotImplementedException();
    }
}

3. Modify Web.config
Register the module. Add following line to configuration \ system.webServer \ modules

  <add name="UrlRewriter" type="UrlMap" preCondition="managedHandler"/>

Follow up
Browser compatibility for server.transfer not a issue. Browser receives HTML only
Performance overhead Not much
Impact on output caching Cached better then template.aspx?id=123
Other things that haven't even crossed my mind Both publicUrl and physUrl must be unique. In practice you can cache direct and inverted key lookups in static Dictionary<string, string> variables.

1
  • Thanks for a superb explanation Alex.
    – John Ohara
    Commented Sep 12, 2017 at 10:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.