You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ASP.NET Razor页面中动态设置Model类型?

问题描述

我有如下数据库上下文类:

public partial class MainDB_Context : DbContext
{
    public MainDB_Context()
    {
    }

    public MainDB_Context(DbContextOptions<MainDB_Context> options)
        : base(options)
    {
    }

    public virtual DbSet<Chosen> Chosens { get; set; }

    public virtual DbSet<BtnMsg> BtnMsgs { get; set; }

    public virtual DbSet<GameRoster> GameRosters { get; set; }
    ...
}

还有泛型页面模型:

public class Admin_IndexModel<T> : PageModel where T : class
{
    readonly MainDB_Context dbContext;

    readonly List<string> DbSetNames;

    readonly DbSet<T> DbSet;
    readonly List<PropertyInfo> DbSetPropInfo;
    readonly List<string> DbSetPKeys;

    public List<T> AsyncDbSetItems;
    ...
    
}

我想通过单个Razor页面展示数据库中的表,对应URL如下:

/Admin/Chosen
/Admin/BtnMsg
/Admin/GameRoster

希望每个URL对应的@model类型分别是:

Admin_IndexModel<Chosen>
Admin_IndexModel<BtnMsg>
Admin_IndexModel<GameRoster>

当前我的Razor页面代码如下:

@page "{DbSetName?}"

@using System.Reflection;

@using asp_net_sql.Models
@using asp_net_sql.Pages
@model Admin_IndexModel<Chosen>

@{
    var DbSetNames = ViewData["DbSetNames"] as List<string>;
    var DbSetPropInfo = ViewData["DbSetPropInfo"] as List<PropertyInfo>;
    ...
}

<menu id="adminMenu">
    @foreach (var menuItem in DbSetNames)
    {
        <div class="menu">@menuItem</div>
    }
</menu>

<form method="post">
    <table class="table">
        <thead>
            <tr>
                @foreach (var pInf in DbSetPropInfo)
                {
                    <th>@pInf.Name</th>
                }
                <th>&nbsp;</th>
            </tr>
        </thead>
        <tbody>
            @{
                foreach (var item in Model.AsyncDbSetItems)
                {
                    row++;
                    var btnId = $"btn_{row}";
                    <tr>
                        @foreach (var pInf in DbSetPropInfo)
                    {
                        var inpId = $"inp_{pInf.Name}_{row}";
                        <td>
                            <input id="@inpId" type="text" value="@pInf.GetValue(item)" />
                        </td>
                    }
                        <td>
                            <button id="@btnId"
                                    type="submit"
                                    asp-page-handler="Update">
                                Change (Post)
                            </button>
                        </td>
                    </tr>
                }
            }
        </tbody>
    </table>
</form>

请问如何通过DbSetName动态生成@model的表达式?我的项目没有Views或Controllers。

解决方案

Razor页面的@model是编译时确定的,无法直接动态指定泛型类型,但可以通过以下两种方式实现需求:

方式一:非泛型基类+反射适配

1. 抽离非泛型基类页面模型

把通用逻辑和属性放到非泛型基类中,让泛型模型继承它:

public abstract class Admin_BaseIndexModel : PageModel
{
    protected readonly MainDB_Context _dbContext;
    public List<string> DbSetNames { get; protected set; }
    public List<PropertyInfo> DbSetPropInfo { get; protected set; }
    public List<object> DbSetItems { get; protected set; }
    public List<string> DbSetPKeys { get; protected set; }

    protected Admin_BaseIndexModel(MainDB_Context context)
    {
        _dbContext = context;
        // 初始化所有DbSet名称列表
        DbSetNames = _dbContext.GetType()
            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
            .Select(p => p.PropertyType.GetGenericArguments()[0].Name)
            .ToList();
    }

    // 抽象方法,由子类实现数据加载逻辑
    public abstract Task OnGetAsync(string dbSetName);
}

2. 修改泛型页面模型

让泛型模型继承非泛型基类,实现数据加载:

public class Admin_IndexModel<T> : Admin_BaseIndexModel where T : class
{
    public new List<T> DbSetItems => base.DbSetItems.Cast<T>().ToList();

    public Admin_IndexModel(MainDB_Context context) : base(context)
    {
    }

    public override async Task OnGetAsync(string dbSetName)
    {
        var dbSet = _dbContext.Set<T>();
        DbSetItems = await dbSet.ToListAsync();
        // 获取当前实体的属性信息
        DbSetPropInfo = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
        // 获取主键字段
        DbSetPKeys = typeof(T).GetProperties()
            .Where(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Any())
            .Select(p => p.Name)
            .ToList();
    }
}

3. 修改Razor页面

将@model指定为非泛型基类,通过反射动态实例化对应泛型模型:

@page "{DbSetName?}"
@model Admin_BaseIndexModel
@using System.Reflection;
@using asp_net_sql.Models
@using asp_net_sql.Pages

@{
    var targetDbSetName = RouteData.Values["DbSetName"]?.ToString();
    List<PropertyInfo> currentPropInfo = null;
    List<object> currentItems = null;

    if (!string.IsNullOrEmpty(targetDbSetName))
    {
        // 反射获取实体类型
        var entityType = typeof(MainDB_Context).Assembly.GetType($"asp_net_sql.Models.{targetDbSetName}");
        if (entityType != null)
        {
            // 动态创建泛型模型实例
            var genericModelType = typeof(Admin_IndexModel<>).MakeGenericType(entityType);
            var modelInstance = Activator.CreateInstance(genericModelType, 
                Context.RequestServices.GetService(typeof(MainDB_Context)));
            
            // 调用OnGetAsync加载数据
            var onGetMethod = genericModelType.GetMethod("OnGetAsync");
            await onGetMethod.Invoke(modelInstance, new object[] { targetDbSetName });
            
            // 从基类获取数据
            var baseModel = (Admin_BaseIndexModel)modelInstance;
            currentPropInfo = baseModel.DbSetPropInfo;
            currentItems = baseModel.DbSetItems;
        }
    }
}

<menu id="adminMenu">
    @foreach (var menuItem in Model.DbSetNames)
    {
        <a href="/Admin/@menuItem" class="menu">@menuItem</a>
    }
</menu>

@if (currentPropInfo != null && currentItems != null)
{
    <form method="post">
        <table class="table">
            <thead>
                <tr>
                    @foreach (var pInf in currentPropInfo)
                    {
                        <th>@pInf.Name</th>
                    }
                    <th>&nbsp;</th>
                </tr>
            </thead>
            <tbody>
                @{
                    int row = 0;
                    foreach (var item in currentItems)
                    {
                        row++;
                        var btnId = $"btn_{row}";
                        <tr>
                            @foreach (var pInf in currentPropInfo)
                            {
                                var inpId = $"inp_{pInf.Name}_{row}";
                                <td>
                                    <input id="@inpId" type="text" value="@pInf.GetValue(item)" />
                                </td>
                            }
                            <td>
                                <button id="@btnId"
                                        type="submit"
                                        asp-page-handler="Update"
                                        asp-route-DbSetName="@targetDbSetName">
                                    Change (Post)
                                </button>
                            </td>
                        </tr>
                    }
                }
            </tbody>
        </table>
    </form>
}

4. 处理Post更新请求

在非泛型基类中添加通用更新方法,通过反射处理不同实体的更新:

public async Task<IActionResult> OnPostUpdateAsync(string DbSetName)
{
    var entityType = typeof(MainDB_Context).Assembly.GetType($"asp_net_sql.Models.{DbSetName}");
    if (entityType == null) return NotFound();

    var entity = Activator.CreateInstance(entityType);
    // 绑定表单数据到实体
    await TryUpdateModelAsync(entity);
    if (!ModelState.IsValid) return Page();

    _dbContext.Update(entity);
    await _dbContext.SaveChangesAsync();
    return RedirectToPage(new { DbSetName });
}

方式二:自定义路由模型提供者(更简洁)

1. 创建路由模型提供者

根据DbSet名称动态生成对应泛型页面的路由:

public class AdminPageRouteProvider : IPageRouteModelProvider
{
    public int Order => -100;

    public void OnProvidersExecuting(PageRouteModelProviderContext context)
    {
        // 找到Admin目录下的Index页面
        var adminIndexPage = context.RouteModels.FirstOrDefault(p => p.RelativePath == "/Pages/Admin/Index.cshtml");
        if (adminIndexPage == null) return;

        // 获取所有DbSet对应的实体类型
        var dbContextType = typeof(MainDB_Context);
        var entityTypes = dbContextType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
            .Select(p => p.PropertyType.GetGenericArguments()[0])
            .ToList();

        // 为每个实体生成专属路由
        foreach (var entityType in entityTypes)
        {
            var routeModel = new PageRouteModel(adminIndexPage)
            {
                RouteTemplate = $"Admin/{entityType.Name}",
                PageType = typeof(Admin_IndexModel<>).MakeGenericType(entityType)
            };
            context.RouteModels.Add(routeModel);
        }
    }

    public void OnProvidersExecuted(PageRouteModelProviderContext context)
    {
        // 无需额外处理
    }
}

2. 注册路由提供者

在Program.cs中添加注册代码:

builder.Services.AddSingleton<IPageRouteModelProvider, AdminPageRouteProvider>();

3. 修改Razor页面

将@model改为dynamic,运行时动态解析泛型模型的属性:

@page "{DbSetName?}"
@using System.Reflection;
@using asp_net_sql.Models
@using asp_net_sql.Pages
@model dynamic

@{
    var DbSetNames = Model.DbSetNames;
    var DbSetPropInfo = Model.DbSetPropInfo;
    var AsyncDbSetItems = Model.AsyncDbSetItems;
}

<menu id="adminMenu">
    @foreach (var menuItem in DbSetNames)
    {
        <a href="/Admin/@menuItem" class="menu">@menuItem</a>
    }
</menu>

<form method="post">
    <table class="table">
        <thead>
            <tr>
                @foreach (var pInf in DbSetPropInfo)
                {
                    <th>@pInf.Name</th>
                }
                <th>&nbsp;</th>
            </tr>
        </thead>
        <tbody>
            @{
                int row = 0;
                foreach (var item in AsyncDbSetItems)
                {
                    row++;
                    var btnId = $"btn_{row}";
                    <tr>
                        @foreach (var pInf in DbSetPropInfo)
                    {
                        var inpId = $"inp_{pInf.Name}_{row}";
                        <td>
                            <input id="@inpId" type="text" value="@pInf.GetValue(item)" />
                        </td>
                    }
                        <td>
                            <button id="@btnId"
                                    type="submit"
                                    asp-page-handler="Update"
                                    asp-route-DbSetName="@RouteData.Values["DbSetName"]">
                                Change (Post)
                            </button>
                        </td>
                    </tr>
                }
            }
        </tbody>
    </table>
</form>

注意:使用dynamic会失去编译时类型检查,需确保模型属性名称完全匹配。

内容的提问来源于stack exchange,提问作者user9163823

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 04:59:51