ASP.Net 捕获所有 *.aspx 请求的 MVC 路由

ASP.Net MVC route to catch all *.aspx requests

这个必须之前已经被问过,但是在阅读here, here, here and here之后我无法推断相关部分以使其工作。我正在将一个旧的 Web 表单网站改造成 MVC,并希望捕获特定的传入 HTTP 请求,以便我可以发出 RedirectPermanent(以保护我们的 Google 排名并避免用户因 404's 而离开)。

而不是拦截 所有 传入请求,或解析某些 id 值,我需要拦截所有以 [=31 结尾(或包含)的请求=].aspx 文件扩展名,例如

www.sample.com/default.aspx
www.sample.com/somedir/file.aspx
www.sample.com/somedir/file.aspx?foo=bar

应该忽略对 MVC 路由的请求(只是正常处理)。

这是我目前的情况,除了 ASPXFiles 路线从未被击中。

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

        // never generates a match
        routes.MapRoute(
            name: "ASPXFiles",
            url: "*.aspx",
            defaults: new { controller = "ASPXFiles", action = "Index" }
        );

        // Used to process all other requests (works fine)
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

}

是否可以在 MVC 中设置这种类型的路由?

尝试这样的事情:

 routes.MapRoute(
            name: "ASPXFilesWithFolderPath",
            url: "{folder}/{page}.aspx",
            defaults: new { controller = "ASPXFiles", action = "Index", folder=UrlParameter.Optional, page = UrlParameter.Optional }
        );
    routes.MapRoute(
            name: "ASPXFiles",
            url: "{page}.aspx",
            defaults: new { controller = "ASPXFiles", action = "Index", page = UrlParameter.Optional }
        );

最初我打算建议使用 HTTPHandler,但默认情况下 aspx 扩展映射到 IIS 中,因此无法使用。这是 link 到 Jon Galloway's blog

我正在展示在 MVC 中进行 301 重定向的正确 方法,因为并非所有浏览器都能正确响应 301 重定向请求,您需要为用户提供一个选项继续而不是 ASP.NET.

生成的默认“对象已移动”页面

RedirectAspxPermanentRoute

我们构建了一个自定义 RouteBase 子类,用于检测 URL 何时以 .aspx 结尾并路由到我们的 SystemController 以设置 301 重定向。它要求您将 URL 的映射(要匹配的 URL )传递给路由值(用于生成 MVC URL)。

public class RedirectAspxPermanentRoute : RouteBase
{
    private readonly IDictionary<string, object> urlMap;

    public RedirectAspxPermanentRoute(IDictionary<string, object> urlMap)
    {
        this.urlMap = urlMap ?? throw new ArgumentNullException(nameof(urlMap));
    }

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var path = httpContext.Request.Path;
        if (path.EndsWith(".aspx"))
        {
            if (!urlMap.ContainsKey(path))
                return null;

            var routeValues = urlMap[path];
            var routeData = new RouteData(this, new MvcRouteHandler());

            routeData.Values["controller"] = "System";
            routeData.Values["action"] = "Status301";
            routeData.DataTokens["routeValues"] = routeValues;

            return routeData;
        }

        return null;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        return null;
    }
}

请注意,第一个检查是针对 .aspx 扩展名,因此如果扩展名不匹配,将完全跳过其余逻辑。这将为您的方案提供最佳性能。

系统控制器

我们像往常一样设置 SystemController 到 return 视图。如果浏览器因为 301 不重定向,用户将看到该视图。

using System;    
using System.Net;
using System.Web;
using System.Web.Mvc;

public class SystemController : Controller
{
    //
    // GET: /System/Status301/

    public ActionResult Status301()
    {
        var routeValues = this.Request.RequestContext.RouteData.DataTokens["routeValues"];
        var url = this.GetAbsoluteUrl(routeValues);

        Response.CacheControl = "no-cache";
        Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
        Response.RedirectLocation = url;

        ViewBag.DestinationUrl = url;
        return View();
    }

    private string GetAbsoluteUrl(object routeValues)
    {
        var urlBuilder = new UriBuilder(Request.Url.AbsoluteUri)
        {
            Path = Url.RouteUrl(routeValues)
        };

        var encodedAbsoluteUrl = urlBuilder.Uri.ToString();
        return HttpUtility.UrlDecode(encodedAbsoluteUrl);
    }
}

Status301.cshtml

遵循 MVC 的约定,确保将其放在 /Views/System/ 文件夹中。

因为它是您的 301 响应的视图,所以您可以使其与站点其余部分的主题相匹配。所以,如果用户最终来到这里,这仍然是一个不错的体验。

视图将尝试通过 Meta-Refresh JavaScript 自动重定向用户。这两个都可以在浏览器中关闭,但用户很可能会把它带到他们应该去的地方。如果没有,你应该告诉用户:

  1. 该页面有一个新位置。
  2. 如果没有自动重定向,他们需要点击 link。
  3. 他们应该更新书签。

@{
    ViewBag.Title = "Page Moved";
}
@section MetaRefresh {
    <meta http-equiv="refresh" content="5;@ViewBag.DestinationUrl" />
}

<h2 class="error">Page Moved</h2>
<p>
    The page has moved. Click on the following URL if you are 
    not redirected automatically in 5 seconds. Be sure to update your bookmarks.
</p>
<a href="@ViewBag.DestinationUrl">@ViewBag.DestinationUrl</a>.

<script>
    //<!--
    setTimeout(function () {
        window.location = "@ViewBag.DestinationUrl";
    }, 5000);
    //-->
</script>

用法

首先,您需要向 _Layout.cshtml 添加一个部分,以便可以将元刷新添加到页面的头部部分。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>@ViewBag.Title - My ASP.NET MVC Application</title>
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <!-- Add this so the view can update this section -->
        @RenderSection("MetaRefresh", required: false)
        <meta name="viewport" content="width=device-width" />
        @Styles.Render("~/Content/css")
        @Scripts.Render("~/bundles/modernizr")
    </head>
    
    <!-- layout code omitted -->
    
</html>

然后将 RedirectAspxRoute 添加到您的路由配置中。

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

        routes.Add(new RedirectAspxPermanentRoute(
            new Dictionary<string, object>() 
            {
                // Old URL on the left, new route values on the right.
                { @"/about-us.aspx", new { controller = "Home", action = "About" } },
                { @"/contact-us.aspx", new { controller = "Home", action = "Contact" }  }
            })
        );

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

鉴于我的情况,我只有几个主要页面,使用 cockamamy 规则,我发现这更容易.. 创建一个 "oldaspxcontroller"。所以我可以确定一切都正确映射。

//https://www.oldsite.com/topics/travel/page9.aspx
[HttpGet]
[Route("/topics/{topic}/page{oldpagenum}.aspx")]
public LocalRedirectResult TopicWithPage(string topic, string oldpagenum)
{

    return LocalRedirectPermanent($"/topics/{topic}?p={oldpagenum}");
}

您可能会注意到我仍然在查询字符串中使用 pagenum..我只是觉得它看起来更好..就像 mysite.com/topics/travel?p=9 我比 mysite.com/topics/travel/page/9 更喜欢。我在 .Net 核心 3.1 中,它工作得很好,甚至可以识别模式和页码..