asp.net-coreroutes

How do I reuse pages and controllers with application parts but use a different URL route?


I am trying to reuse a set of controllers, razor pages and content (wwwroot) from another assembly, however, I want these pages and controllers to run in a sub route of the existing pages.

The pages and controllers I want to reuse have pages/controllers like Index, _Layout, and others that clash with the names of the existing application in my web application, and the wwwroot folder likewise has folders and files that clash.

I know that I can use AddApplicationPart to add these resources to my existing web application but how do I add them in a self-contained fashion so that the application part I'm bringing in runs entirely in its own route. So the existing app will continue to operate at the default URL but the referenced assembly will run at say /legacy?

I've tried calling AddApplicationPart which works but it overwrites my existing routes and I can't find an option to use a different route.


Solution

  • So the existing app will continue to operate at the default URL but the referenced assembly will run at say /legacy?

    For the static file in the external assembly (Razor Class Library), when consuming the static file, we need to use the prefix _content/{PACKAGE ID}/. So, there is no need to add the /legacy prefix in the request URL for the static file.

    In ASP.NET Core MVC, to add a route prefix to Razor Pages and controllers in an external referenced assembly, for example, adding "/legacy/" to the request URL when accessing pages from the assembly, you could use the customize route conventions. Refer to the following sample:

    1. Create a .NET 9 Razor Class Library (LegacyRCL) which Support pages and views. Then add the Razor page, Controller and static file.

      rcl content

      In the rclstyle.css file, I just add a class to set the background color:

       .rclcolor{
           background-color:aquamarine;
       }
      

      Then in the razor page and MVC view, we could add use it use the following code:

       <link rel="stylesheet" href="~/_content/LegacyRCL/css/rclstyle.css" asp-append-version="true" />
       <h2 class="rclcolor">Razor Product Index</h2>
      
    2. Create a Asp.NET 9 MVC application, then add the LegacyRCL project reference. After registering the Razor Page and MVC service use the following code:

       builder.Services.AddControllersWithViews();
       builder.Services.AddRazorPages();
       ...
       app.UseRouting();
      
       app.UseAuthorization();
      
       app.MapStaticAssets();
       //app.UseStaticFiles();
       app.MapControllerRoute(
           name: "default",
           pattern: "{controller=Home}/{action=Index}/{id?}")
           .WithStaticAssets();
       app.MapRazorPages().WithStaticAssets();
      

      Runing the application, we can access the RCL razor page, controller and static file like this:

      default request

    3. Add customize razor page route convention. Create a LegacyPageRouteModelConvention class with the following code:

       using Microsoft.AspNetCore.Mvc.ApplicationModels;
       using System.Reflection;
      
       namespace WebApplication8.Services
       {
           public class LegacyPageRouteModelConvention : IPageRouteModelConvention
           {
               private readonly Assembly _externalAssembly;
      
               public LegacyPageRouteModelConvention(Assembly externalAssembly)
               {
                   _externalAssembly = externalAssembly;
               }
      
               public void Apply(PageRouteModel model)
               {
                   // Get the PageModel type using reflection
                   var pageType = GetPageModelType(model);
                   // Check if the PageModel type belongs to the external assembly
                   if (pageType != null && pageType.Assembly == _externalAssembly)
                   {
                       // Customize the route for pages in the external assembly
                       foreach (var selector in model.Selectors)
                       {
                           selector.AttributeRouteModel.Template = $"legacy/{selector.AttributeRouteModel.Template}";
                       }
                   }
               }
      
               private Type GetPageModelType(PageRouteModel model)
               {
                   // The PageModel type is typically named after the Razor Page file
                   // For example, for "/Pages/Index.cshtml", the PageModel type is typically "IndexModel"
                   var pageName = model.ViewEnginePath.Trim('/').Replace("/", ".");
                   var pageModelTypeName = $"{pageName}Model";
      
                   // Search for the PageModel type in the external assembly
                   return _externalAssembly.GetTypes()
                                          .FirstOrDefault(t => t.Name == pageModelTypeName);
               }
           }
       }
      
    4. Create customize controller route convention: create a LegacyControllerRouteConvention class:

       using Microsoft.AspNetCore.Mvc;
       using Microsoft.AspNetCore.Mvc.ApplicationModels;
       using System.Reflection;
      
       namespace WebApplication8.Services
       {
           public class LegacyControllerRouteConvention : IControllerModelConvention
           {
               private readonly Assembly _externalAssembly;
      
               public LegacyControllerRouteConvention(Assembly externalAssembly)
               {
                   _externalAssembly = externalAssembly;
               }
      
               public void Apply(ControllerModel controller)
               {
                   if (controller.ControllerType.Assembly == _externalAssembly)
                   {
                       foreach (var selector in controller.Selectors)
                       {
                           var template = selector.AttributeRouteModel?.Template;
                           if (template == null)
                           {
                               selector.AttributeRouteModel = new AttributeRouteModel(
                                   new RouteAttribute("legacy/[controller]/[action]"));
                           } 
                       }
                   }
               }
           }
       }
      
    5. Register the conventions in Program.cs file:

       // Add Razor Pages from external assembly with route prefix
       builder.Services.AddRazorPages()
           .AddApplicationPart(externalAssembly)
           .AddRazorPagesOptions(options =>
           {
               options.Conventions.Add(new LegacyPageRouteModelConvention(externalAssembly));
           });
      
       // Add MVC Controllers from external assembly with route prefix
       builder.Services.AddControllersWithViews()
           .AddApplicationPart(externalAssembly)
           .AddMvcOptions(options =>
           {
               options.Conventions.Add(new LegacyControllerRouteConvention(externalAssembly));
           });
      

      Running the application, we can see that it required to add the /legacy/ in the request URL to access the pages/views in the external assembly.

      legacy url