Search code examples
c#asp.net-mvcroutesrazorrazor-pages

ASP.NET Core 8 Razor Pages Binding Multiple Page Models to Single Route


I'm working with ASP.NET Core 8.0.8. My razor page application has a dynamic Table.cshtml page with route @page "/table/{table}" that can take any model and display the associated database information in a searchable, filterable, and sortable table. This Table.cshtml page also shows the associated actions available to the user based on the model. I have a TableModel.cs class which handles all the base OnGet and OnPost requests but certain models will extend this class to include specific OnPost handlers (OnPostCreate, OnPostUpdate, OnPostDelete, etc...) or set different [Authorize(Roles = ...)] roles. This is where my problem lies. I just don't know of a way to attach these derived TableModel classes to my Table.cshtml page without creating a separate/duplicate page for each. I have numerous models so I would really prefer to keep the rendering and base logic on one page.

For example:

  • /table/products - Accessible to everyone, uses class TableModel.cs
  • /table/items - Accessible to authenticated users, uses class ItemsTableModel.cs
  • /table/users - Accessible to admins, uses class UsersTableModel.cs

I tried to manually set the page routing in Program.cs but to no avail.

options.Conventions.AddPageRouteModelConvention("/table/items", model => { new MyApp.Pages.ItemsTableModel(builder.Configuration); });
options.Conventions.AddPageRouteModelConvention("/table/users", model => { new MyApp.Pages.UsersTableModel(builder.Configuration); });

I also tried using partial views but ran into trouble with the needed IConfiguration. I know setting the derived class as a property is also an option but I'm not sure how that would then work with the specific OnPost handlers. Open to any suggestions (if this is even possible).


Solution

  • Here's how I was able to accomplish this:

    1. Switched from an ASP.NET Core Web App (Razor Pages) to an ASP.Net Core Web App (Model-View-Controller) which made my solution possible.

    2. Kept the default controller route mapping in my Program.cs file:

    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}"
    );
    
    1. Added a base TableController.cs class to my Controllers that I would then extend with my specific table classes. The specific action calls can then be overwritten by the children classes as needed. I also have a ModelTable.cs class that stores the data associated with each table:
    public class TableController : Controller {
        public ModelTable Model { get; set; } = new ModelTable("");
    
        [HttpGet]
        public virtual IActionResult Index() {
            return View("~/Views/Table.cshtml", Model);
        }
    
        [HttpPost]
        public virtual IActionResult Index(string? returnURL) {
            return Redirect(returnURL ?? $"/{Model.TableName}");
        }
    }
    
    1. Here's an example of a child table class. The important thing to remember is that whatever you name the controller (in this case Users), will be the URL route. So /Users will call this controller:
    public class UsersController : TableController {
        public UsersController() : base() {
            Model = new UsersTable();
        }
    
        [HttpGet]
        public override IActionResult Index() {
            // Users-specific database load code here
            return base.Index();
        }
    }
    
    1. My Table.cshtml view will then use the provided and populated ModelTable.cs class to render out the appropriate data in an HTML table:
    @model ModelTable
    <table id="@Model.TableName">
        @foreach (var column in Model.TableColumns) { ... }
    </table>
    

    *. For those who may stumble across this and ask then how to route to different controllers while maintaining the same URL route, here's how I was able to accomplish that. Continuing with Users here, I added a UsersFormController.cs and updated my Program.cs to include the route mapping of:

    app.MapControllerRoute("usersForm", "/Users/{action}/{id?}", new { controller="UsersForm", action="Index" });
    

    So now I can do /Users/Add/{id} and /Users/Edit/{id} and such using that UsersFormController.cs.