简体   繁体   English

ASP.Net WebForms路由多个目标的单个路由

[英]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: 我一直在关注以下有关利用数据库中的friendlyUrl的教程:

http://www.asp.net/web-forms/tutorials/aspnet-45/getting-started-with-aspnet-45-web-forms/url-routing 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 … mysite.com/{PlayerName}进入player.aspx mysite.com/{TeamName}进入team.aspx……依此类推……

Could somebody point in the right direction of achieving this with asp.net. 有人可以指出使用asp.net实现此目标的正确方向。 Is it possible using the built in routing engine, or should I be looking to code my own HTTPModule for this? 是否可以使用内置的路由引擎,还是应该为此编写自己的HTTPModule?

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. 我不确定为什么有这么多人说这不能通过路由来完成-也许我没有得到什么,但是显然使接受的答案成为有效选项的相同逻辑应该完全适用于自定义路由处理程序,例如IRouteHandler或派生自System.Web.Routing.RouteBase的内容。

You can add "managers" to your RouteCollection (RouteTable.Routes) in the manner of: 您可以通过以下方式将“管理器”添加到RouteCollection(RouteTable.Routes):

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

... Or: ... 要么:

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

... Etcetera, depending on your needs. ... Etcetera,取决于您的需求。

If you go with the RouteBase alternative for example, override GetRouteData() , GetVirtualPath() and whatnot. 例如,如果使用RouteBase替代方法,则重写GetRouteData()GetVirtualPath()和其他方法。 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. 编辑: 在我撰写以上内容时,“可接受的答案”是Tasos K发布的有关URL重写的内容,赏金也得到了奖励。 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. 但是,实现此目标的一种方法是改用URL重写。 The whole process has a few steps and it is rather simple to make. 整个过程只有几个步骤,而且制作起来很简单。

  • Applying the URL rewriting 应用URL重写

You add at the Global.asax the following function. 您在Global.asax添加以下功能。

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. 因此,如果URL具有/some-title-here ,则可以使用Request.Url.Segments数组获得some-title-here部分。

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(...) . 无论如何,都可以通过调用Context.RewritePath(...)在内部更改URL。

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. 另外,在您的代码中, Request.Url将知道重写的URL,而不是原始的URL。

A quick way to test it is to implement the IsTeam(...) and IsPlayer(...) function as below. 一种测试它的快速方法是实现IsTeam(...)IsPlayer(...)函数,如下所示。 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. 当点击/ player-tasos时,仅使用此代码, ~/player.aspx?player=player-tasos页面将被加载;当点击/ team-stackoverflow时~/team.aspx?team=team-stackoverflow页面将被加载。

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(...) 当存在PostBack时,URL更改为您在Context.RewritePath(...)设置的URL Context.RewritePath(...)

  • Avoiding PostBack issue 避免回发问题

To avoid this issue you need to add to your projects two ASP.NET folders 为避免此问题,您需要向项目中添加两个ASP.NET文件夹

  1. App_Browsers App_Browsers文件
  2. App_Code 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 ) 在App_Code文件夹中,创建一个文件FormRewriter.cs并添加以下代码(在我的演示中,根名称空间为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. 在App_Browsers文件夹中,创建一个文件Form.browser并添加以下代码段。 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. 添加这两个文件将处理PostBack问题。 If you put the FormRewriter.cs outside the App_Code folder it will not work. 如果将FormRewriter.cs放在App_Code文件夹之外,它将无法正常工作。 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. 我已经在.NET 3.5和.NET 4.0中使用这种方法多年,没有任何问题。 Today I also tested it in a .NET 4.5 Web Forms project and it works with no issues. 今天,我还在.NET 4.5 Web窗体项目中对其进行了测试,并且可以正常使用。

All of the above are based on a ScottGu's article about the subject 以上所有内容均基于ScottGu关于该主题的文章

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. 我没有在ASP.Net表单中尝试过此操作,但我的应用程序运行时在ASP.Net MVC中开发了约束。 Both type of application (Forms and MVC) shared common routing logic. 两种类型的应用程序(窗体和MVC)都共享通用的路由逻辑。

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/player/{PlayerName}

mysite.com/team/{TeamName} 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. 这样,您就可以轻松便捷地将所有“玩家”流量驱动到Player.aspx,并将“团队”流量驱动到Team.aspx。

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. 但是...如果确实需要支持一条路由,建议您将其添加为第三个选项,并使用301重定向到上述两条路由之一。

mysite.com/{PlayerOrTeamName} -> Route.aspx Let Route.aspx handle requests that don't map to physical files. mysite.com/{PlayerOrTeamName}-> Route.aspx让Route.aspx处理未映射到物理文件的请求。

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. 然后,您的Route.aspx代码需要充当404错误处理程序,但要注意一个问题。它将检查Players数据和Teams数据是否完全匹配。 If it finds one it should do a 301 permanent redirect to the correct /player/ or /team/ route. 如果找到一个,则应执行301永久重定向到正确的/ player /或/ team /路线。

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. 您可以完全跳过RouteTable并将此代码放入默认的404处理程序中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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