Search code examples
c#asp.netasp.net-coreroutesrazor-pages

Add static Routes without visible Identifiers in Url in Razor Pages


We want to migrate a Website from Asp.Net Webforms to Asp.Net Core Webapplication (Razor Pages). We now have readable Urls without visible identifiers (for readability, SEO and to prevent changing Urls after possible database migrations...).

For that i generated dynamically Routes in the Global.Asax at Startup:

RouteTable.Routes.MapPageRoute("myroute1",
    "banana/",
     "~/content.aspx", false, new RouteValueDictionary { { "id", "4711" }, , { "otherid", "4812" }
     
RouteTable.Routes.MapPageRoute("myroute2",
    "apple/",
     "~/content.aspx", false, new RouteValueDictionary { { "id", "4913" }, , { "otherid", "5014" }  

That way users could call the content.aspx like this:

https://example.com/banana
https://example.com/apple

In the content.aspx i got the mapped "id" and "otherid" from the RouteValues.

How can i achieve this in Razor Pages?

Now i have a "Content.cshtml" and there i need access to id and otherid. I added this at the top:

@page "/content/{id:int}/{otherid:int}/{title:string}"

The above code allows mit to call Urls like this:

https://example.com/content/4711/4812/banana // still have the ids in it and prefix "content"

Is there a possibility to add all Routes at Startup with fixed Parameters? I have not been able to find anything similar.

Greetings cpt.oneeye


Solution

  • I found a solution thanks to this thread:

    Is there a way to do dynamic routing with Razor Pages?

    Another full example can be found here:

    https://github.com/dotnet/AspNetCore.Docs/issues/12997

    According to my example at the top you first make your own DynamicRouteValueTransformer (see Namespace Microsoft.AspNetCore.Mvc.Routing):

    public class NavigationTransformer : DynamicRouteValueTransformer
    {
        private readonly MyDatabaseContext _repository;
    
        public NavigationTransformer(MyDatabaseContext repository)
        {
            _repository = repository;
        }
    
        public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
        {
            // all requests which are incoming and fit a specific pattern will go through this method
            
            var slug = values["slug"] as string;
    
            // just get your needed info according to the "slug"
            // in my case i have to map according to NavigationNodes that are stored in the Database
            var myNavigationNode = _repository.NavigationNodes.FirstOrDefault(nn => nn.Name == slug);
    
            if (node == null)
            {
                // User will get a 404
                return null;
            }
            
            // set Path to Page according to Type (or whatever fits your logic)
            string pagename = "";
            
            if(node.Type == "ContentPage")
            {
                pagename = "/Content"  // Pages/Content.cshtml
            }
            else if(node.Type == "ContactForm")
            {
                pagename = "/Contact" // Pages/Contact.cshtml
            } 
            else 
            {
                pagename = "/Someotherpage"
            } 
    
            // return all RouteValues you need on your Page
            return new RouteValueDictionary()
            {
                { "page", pagename },
                { "id", node.Id },
                { "otherid", node.OtherId }
            };
        }
    }
    

    In the Startup.ConfigureServices() you register the new Service:

    services.AddScoped<NavigationTransformer>();
    

    In the Startup.Configure() you add the NavigationTransformer to Endpoints:

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapDynamicPageRoute<NavigationTransformer>("{slug}"); // just "slug" so that every request will be handled by NavigationTransformer, you can also add a prefix-folder
    });
    

    Now when you call a url like the following you will came through the Transformer and you are able to reroute on the fly:

    https://example.com/banana
    https://example.com/apple
    

    Be aware the Routings of existing Pages are stronger. So if we have a Apple.cshtml the second Url will still be routed to Apple.cshtml and not to Content.cshtml