ASP.NET MVC Route to Catch All *.Aspx Requests

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

I am showing the right way to make a 301 redirect in MVC, since not all browsers respond to 301 redirect requests properly, and you need to give the user an option to continue rather than the default "Object Moved" page that is generated by ASP.NET.

RedirectAspxPermanentRoute

We build a custom RouteBase subclass that detects when a URL ends with .aspx and routes to our SystemController to setup the 301 redirect. It requires you to pass in a map of URL (the URL to match) to route values (which are used to generate the 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;
}
}

Note that the first check is for the .aspx extension, so the rest of the logic will be entirely skipped if the extension doesn't match. This will provide the best performance for your scenario.

SystemController

We setup the SystemController to return a view as we normally would. If the browser doesn't redirect because of the 301, the user will see the view.

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

Follow the conventions of MVC and be sure to place this in the /Views/System/ folder.

Because it is a view for your 301 response, you can make it match the theme of the rest of your site. So, if the user ends up here, it is still not a bad experience.

The view will attempt to redirect the user automatically via JavaScript and via Meta-Refresh. Both of these can be turned off in the browser, but chances are the user will make it where they are supposed to go. If not, you should tell the user:

  1. The page has a new location.
  2. They need to click the link if not automatically redirected.
  3. They should update their bookmark.


@{
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>

Usage

First you need to add a section to your _Layout.cshtml so the Meta-refresh can be added to the head section of your page.

<!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>

Then add the RedirectAspxRoute to your routing configuration.

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 }
);
}
}

Route '0' requires Hosts or Path specified. Set the Path to '/{**catchall}' to match all requests

There was a breaking changes in v1.0.0-preview11 for the way Routes are configured. You need to update your setting.

"ReverseProxy": {
"Routes": {
"SampleService": {
"ClusterId": "SampleService-cluster1",
"Match": {
"Host": "localhost",
"Path": "sample/{**catchall}"
}
},
"NotificationService": {
"ClusterId": "NotificationService-cluster",
"Match": {
"Host": "localhost",
"Path": "api/NotificationService/{**catchall}"
}
}
},
"Clusters": {
"SampleService-cluster1": {
"Destinations": { "SampleService-cluster1/destination1": { "Address": "http://127.0.0.1:6500" } }
},
"NotificationService-cluster": {
"Destinations": { "NotificationService-cluster/destination1": { "Address": "http://*:6020" } }
}
}
}

Url.Action generates old route vs new route

You could fix this by creating a route constraint that only matches incoming requests.

public class IncomingOnlyConstraint : IRouteConstraint
{
public bool Match(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection)
{
return routeDirection == RouteDirection.IncomingRequest;
}
}

Usage

routes.MapRoute("SiteDefault", "Default.aspx", 
new { controller = "Home", action = "Index" },
new { controller = new IncomingOnlyConstraint() });

NOTE: The above solution is not the best one. It is generally bad for SEO to have more than one URL serving the same content. If you use the above solution, you should ensure your response has a canonical tag or header set.

A far better solution would be to do a 301 redirect from the old URL to the new URL. You could either handle that directly through MVC (which will ensure all browsers that don't respect the 301 response have a better user experience), or you can use the IIS URL rewrite module to issue the 301 redirect, which keeps the legacy code neatly outside of your application.

Map legecy url to MVC Controller and action

The problem is that you are returning null from your route, which makes MVC never consider it a matching route. But a route cannot issue a 301 redirect on its own - for that, you need a controller. So, the solution is to route to a controller and do the 301 from there.

asp mvc 2 route all requests to files in specific folder to given controller/action

IIS will, by default, serve all .txt files directly without even going to the ASP.NET handler, which handles routing and MVC.

To change that behaviour, you will have to change your IIS settings or change the routing rules at any firewall/load balancer you have in front of it.



Related Topics



Leave a reply



Submit