You can get whatever behavior you desire by implementing IRouter
as in this answer, including basing your logic on data from an external source (such as a config file or database).
This is much more flexible than a catchall route because it lets you choose the controller on the fly.
public class MyRoute : IRouter
{
private readonly IRouter innerRouter;
public MyRoute(IRouter innerRouter)
{
if (innerRouter == null)
throw new ArgumentNullException("innerRouter");
this.innerRouter = innerRouter;
}
public async Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
{
// Trim the leading slash
requestPath = requestPath.Substring(1);
}
if (!requestPath.StartsWith("abcd"))
{
return;
}
//Invoke MVC controller/action
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
newRouteData.Routers.Add(this.innerRouter);
newRouteData.Values["controller"] = "Item";
newRouteData.Values["action"] = "View";
newRouteData.Values["id"] = 123;
try
{
context.RouteData = newRouteData;
await this.innerRouter.RouteAsync(context);
}
finally
{
// Restore the original values to prevent polluting the route data.
if (!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
}
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
VirtualPathData result = null;
var values = context.Values;
var controller = Convert.ToString(values["controller"]);
var action = Convert.ToString(values["action"]);
var id = Convert.ToString(values["id"]);
if ("Item".Equals(controller) && "View".Equals(action))
{
result = new VirtualPathData(this, "abcd?id=" + id);
context.IsBound = true;
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return result;
}
}
Usage
// Add MVC to the request pipeline.
app.UseMvc(routes =>
{
routes.Routes.Add(new MyRoute(
innerRouter: routes.DefaultHandler)
);
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
// Uncomment the following line to add a route for porting Web API 2 controllers.
// routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");
});
The GetVirtualPath
should mirror what the RouteAsync
does. RouteAsync
converts a URL into route values, and the GetVirtualPath
should convert the same route data back into the same URL.
The easiest way to accomplish this is to use a data structure to create a two-way mapping between these 2 data points (as in the linked answer) so you don't have to continually change the logic within these 2 methods. This data structure should be cached and not do anything too resource intensive, since every request will use it to determine where to send each URL.
Alternatively, you could create a separate route for each of your individual pieces of logic and register them all at application startup. However, you need to ensure they are registered in the correct order and that each route will only match the correct set of URLs and correct set of RouteValues.
NOTE: For a scenario such as this you should almost never need to use RedirectToAction
. Keep in mind redirecting will send an HTTP 302 request to the browser, which tells it to lookup another location on your server. This is unnecessary overhead in most cases because it is much more efficient just to route the initial request to the controller you want.