简体   繁体   中英

ASP.Net WebForms Routing Single Route for Multiple Destinations

I am looking into setting database routing up for a new website I plan to create. I have been looking at the following tutorial with regards to utilising friendlyUrls from a database:

http://www.asp.net/web-forms/tutorials/aspnet-45/getting-started-with-aspnet-45-web-forms/url-routing

However, I would like to use the same route structure for multiple entities. Meaning:

mysite.com/{PlayerName} goes to player.aspx mysite.com/{TeamName} goes to team.aspx … and so on …

Could somebody point in the right direction of achieving this with asp.net. Is it possible using the built in routing engine, or should I be looking to code my own HTTPModule for this?

Thanks David

I'm not sure why so many people say that this cannot be done with routing - maybe I'm not getting something, but the same logic that apparently makes the accepted answer a valid option should be perfectly applicable to a custom route handler, eg IRouteHandler or something derived from System.Web.Routing.RouteBase.

You can add "managers" to your RouteCollection (RouteTable.Routes) in the manner of:

routes.Add("MyRoutName", new MyCustomRouteBaseThing())

... Or:

routes.Add(new Route("whatever/{possiblySomething}", new RouteValueDictionary {
        {"whatever", null}
    }, new MyImplementationOfIRouteHandler()));

... Etcetera, depending on your needs.

If you go with the RouteBase alternative for example, override GetRouteData() , GetVirtualPath() and whatnot. I'm not saying it's necessarily a better option than the accepted answer, I just don't see why routing should be deemed not viable. (What am I missing?)

EDIT: At the time I wrote the above, the "accepted answer" was the one about URL rewriting posted by Tasos K, to whom the bounty was also rewarded. The accepted answer has since been reassigned.

I also don't know how this can be done using routing. But one way to achieve this is using URL rewriting instead. The whole process has a few steps and it is rather simple to make.

  • Applying the URL rewriting

You add at the Global.asax the following function.

void Application_BeginRequest(object sender, EventArgs e)
{
    //Here you will get exception 'Index was outside the bounds of the array' when loading home page, handle accordingly
    string currentsegment = Request.Url.Segments[1]; 
    string RewritePath = "";

    if (IsTeam(currentsegment))
    {
        RewritePath = "~/team.aspx?team=" + currentsegment;
    }

    if (IsPlayer(currentsegment))
    {
        RewritePath = "~/player.aspx?player=" + currentsegment;
    }

    if (RewritePath != "") {
        // Adding all query string items to the new URL
        for (int I = 0; I <= Request.QueryString.Count - 1; I++)
        {
            RewritePath = RewritePath + "&" + Request.QueryString.Keys[I] + "=" + Request.QueryString[I];
        }
        Context.RewritePath(RewritePath);
    }
}

So if the URL has is /some-title-here you can get the some-title-here part using the Request.Url.Segments array.

Then based on that your code detects if this title is a team or a player. In any case you change internally the URL by calling the Context.RewritePath(...) .

One important thing is that you need to add all the query string items manually in order to pass them to your pages.

Also, inside your code the Request.Url will know the rewritten URL, not the original.

A quick way to test it is to implement the IsTeam(...) and IsPlayer(...) function as below. With only this code when hitting /player-tasos the ~/player.aspx?player=player-tasos page loads and when hitting /team-stackoverflow the ~/team.aspx?team=team-stackoverflow page loads.

private bool IsTeam(string segment)
{
    return segment.StartsWith("team");
}

private bool IsPlayer(string segment)
{
    return segment.StartsWith("player");
}

So far this approach works but it has one main issue. When there is a PostBack the URL changes to the one you have set in the Context.RewritePath(...)

  • Avoiding PostBack issue

To avoid this issue you need to add to your projects two ASP.NET folders

  1. App_Browsers
  2. App_Code

In the App_Code folder you create a file FormRewriter.cs and add the following code (In my demo the root namespace is WebFormsRewriting )

using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.UI;

namespace WebFormsRewriting
{
    public class FormRewriterControlAdapter : System.Web.UI.Adapters.ControlAdapter
    {
        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {
            base.Render(new RewriteFormHtmlTextWriter(writer));
        }
    }

    public class RewriteFormHtmlTextWriter : System.Web.UI.HtmlTextWriter
    {

        public RewriteFormHtmlTextWriter(HtmlTextWriter writer)
            : base(writer)
        {
            this.InnerWriter = writer.InnerWriter;
        }

        public RewriteFormHtmlTextWriter(System.IO.TextWriter writer)
            : base(writer)
        {
            base.InnerWriter = writer;
        }

        public override void WriteAttribute(string name, string value, bool fEncode)
        {
            // If the attribute we are writing is the "action" attribute, and we are not on a sub-control, 
            // then replace the value to write with the raw URL of the request - which ensures that we'll
            // preserve the PathInfo value on postback scenarios
            if ((name == "action"))
            {
                HttpContext Context = default(HttpContext);
                Context = HttpContext.Current;

                if (Context.Items["ActionAlreadyWritten"] == null)
                {
                    // Because we are using the UrlRewriting.net HttpModule, we will use the 
                    // Request.RawUrl property within ASP.NET to retrieve the origional URL
                    // before it was re-written.  You'll want to change the line of code below
                    // if you use a different URL rewriting implementation.

                    value = Context.Request.RawUrl;

                    // Indicate that we've already rewritten the <form>'s action attribute to prevent
                    // us from rewriting a sub-control under the <form> control

                    Context.Items["ActionAlreadyWritten"] = true;
                }
            }

            base.WriteAttribute(name, value, fEncode);
        }
    }
}

In the App_Browsers folder you create a file Form.browser and add the following snippet. Note here to put the class name of the Adapter with its namespace.

<browsers>
    <browser refID="Default">
        <controlAdapters>
            <adapter controlType="System.Web.UI.HtmlControls.HtmlForm"
                     adapterType="WebFormsRewriting.FormRewriterControlAdapter" />
        </controlAdapters>
    </browser>
</browsers>

And that's it. Adding those two files will handle the PostBack issue. If you put the FormRewriter.cs outside the App_Code folder it will not work. Also those two folders must be uploaded to the production server.

I have used this approach for years in .NET 3.5 and .NET 4.0 without any problems. Today I also tested it in a .NET 4.5 Web Forms project and it works with no issues.

All of the above are based on a ScottGu's article about the subject

Write two constraints which return boolean whether segment is a team or not / a player or not.

public class IsTeamConstraint : IRouteConstraint
{
    public bool Match
        (
            HttpContextBase httpContext, 
            Route route, 
            string parameterName, 
            RouteValueDictionary values, 
            RouteDirection routeDirection
        )
    {
        return SomeService.IsTeam(values["teamName"]);
    }
}

public class IsPlayerConstraint : IRouteConstraint
{
    public bool Match
        (
            HttpContextBase httpContext, 
            Route route, 
            string parameterName, 
            RouteValueDictionary values, 
            RouteDirection routeDirection
        )
    {
        return SomeService.IsPlayer(values["playerName"]);
    }
}

Set constraint in page route.

void RegisterCustomRoutes(RouteCollection routes)
{
    routes.MapPageRoute(
        "Team",
        "{teamName}",
        "~/Team.aspx",
        false,
        null,
        new RouteValueDictionary { { "isTeam", new IsTeamConstraint() } }
    );
    routes.MapPageRoute(
        "Player",
        "{playerName}",
        "~/Player.aspx",
        false,
        null,
        new RouteValueDictionary { { "isPlayer", new IsPlayerConstraint() } }
    );
}

Now when a page is requested registered page routes will use constraint to check that the route is valid and execute page if it is.

I haven't tried this in ASP.Net Forms but I've applications running with constraints developed in ASP.Net MVC. Both type of application (Forms and MVC) shared common routing logic.

As others have pointed out... it would be much better NOT to use this route for both Players and Teams.

It would be preferable to setup two routes...

mysite.com/player/{PlayerName}

mysite.com/team/{TeamName}

In this way you can drive all "player" traffic to Player.aspx, and "team" traffic to Team.aspx, nice and easy.

However... If you really have to support a single route, I recommend that you add it as a third option, and use a 301 Redirect to one of the two above routes.

mysite.com/{PlayerOrTeamName} -> Route.aspx Let Route.aspx handle requests that don't map to physical files.

Then your Route.aspx code needs to function as a 404 Error handler, but with a catch.. It will check the Players data and the Teams data for an exact match. If it finds one it should do a 301 permanent redirect to the correct /player/ or /team/ route.

Using...

string strCorrectURL = RouteTable.Routes.GetVirtualPath(null, "player", new RouteValueDictionary { { "Name", strValue }});

    Response.StatusCode = 301;
    Response.Status = "301 Moved Permanently";
    Response.AddHeader("Location", strCorrectURL);
    Response.End();

This will give you the functionality of a single path, but tell search engines to index the more precise path.

You could skip the RouteTable altogether and just put this code into your default 404 handler.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM