I'd like to be able to force Url.Action to always add one of my route values to the URL. Basically, I need to be able to add a customer ID in the URL. My routes look like this:
app.MapControllerRoute(
name: "Areas",
pattern: "{customerid:int}/{area:exists}/{controller}/{action=Index}");
app.MapControllerRoute(
name: "default",
pattern: "{customerid:int}/{controller=Home}/{action=Index}/{id?}");
I'm not concerned with exposing the Customer ID since it will always be protected against the authenticated user. These routes will allow a user to view/manage different customers across multiple tabs.
If i navigate to /99/Admin/User, I can correctly read RouteData.Values["customerid"]
. If I use @Url.Action("Index", "Role")
in the view, it correctly renders the URL "/99/Admin/Role". Since I'm not jumping across areas, it's smart enough to pass along the customerid route value. However, what if I want to leave to an empty area? For example, using @Url.Action("Index", "Home", new { area = "" })
. Of course, this technically correctly renders /Home/Index
, but is it possible to have it "remember" and use the customerid route value across areas to render /99/Home/Index
?
I know I can use @Url.Action("Index", "Home", new { area = "", customerid = ViewContext.RouteData.Values["customerid"] })
to get the proper path. Or even just create an extension/overload to UrlHelper to sniff the customerid route value. I'm fine with either. Unfortunately, this is an improvement to a large existing application, so making sure to hit every single Url.Action
or Html.ActionLink
is going to be a daunting task. Albeit, one I think I might have to endure. I was just hoping somebody may know of a way to create a "sticky" route of {domain}/{customerid}/...
.
You can implement this feature by using custom IActionFilter
.
Test Result
Here is the sample code for you.
AppendCustomerIdFilter
using Microsoft.AspNetCore.Mvc.Filters;
namespace WebApplication4
{
public class AppendCustomerIdFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
//throw new NotImplementedException();
}
public void OnActionExecuting(ActionExecutingContext context)
{
var routeValues = context.RouteData.Values;
if (!routeValues.ContainsKey("customerid"))
{
var customerid = context.HttpContext.Request.RouteValues["customerid"];
if (customerid != null)
{
context.RouteData.Values["customerid"] = customerid;
}
}
}
}
}
Register it
using WebApplication4;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Register it
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add<AppendCustomerIdFilter>();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
//app.MapControllerRoute(
// name: "default",
// pattern: "{controller=Home}/{action=Index}/{id?}");
// Change code like below
app.MapControllerRoute(
name: "default",
pattern: "{customerid:int}/{controller=Home}/{action=Index}/{id?}");
app.Run();
My HomeController.cs
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using WebApplication4.Models;
namespace WebApplication4.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
ViewBag.CustomerId = RouteData.Values["customerid"];
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
Privacy .cshtml
@{
ViewData["Title"] = "Privacy Page";
}
<h2>Privacy Page</h2>
<a href="@Url.Action("Index", "Home")">Back to Home</a>
Index.cshtml
@{
ViewData["Title"] = "Home Page";
}
<h2>Customer ID: @ViewBag.CustomerId</h2>
<a href="@Url.Action("Privacy", "Home")">Privacy Page</a>