在 ASP.Net 5 中呈现 RazorPage 时无法设置模型

Can't set model when rendering RazorPage in ASP.Net 5

我正在使用新的 ASP.Net 5/MVC 6 库构建一个项目,我已经到了要将剃刀页面动态呈现为字符串的部分。

这是针对动态小部件系统的,因此我们的想法是在生成模型之后但在渲染主视图之前 ActionFilter 中执行此操作。

它的设置相当大,但它主要需要服务和拼凑(如果有人知道更简单的方法,请告诉!)。

public override void OnActionExecuted(ActionExecutedContext context) {
    var pageFactory = (IRazorPageFactory)context.HttpContext.ApplicationServices.GetService(typeof(IRazorPageFactory));
    var viewEngine = (IRazorViewEngine)context.HttpContext.ApplicationServices.GetService(typeof(IRazorViewEngine));
    var viewFactory = (IRazorViewFactory)context.HttpContext.ApplicationServices.GetService(typeof(IRazorViewFactory));
    var modelMetadataProvider = (IModelMetadataProvider)context.HttpContext.ApplicationServices.GetService(typeof(IModelMetadataProvider));

    var page = pageFactory.CreateInstance($"~/Views/Widgets/{widgetInfo.Type}.cshtml");
    var view = viewFactory.GetView(viewEngine, page, false);

    var sb = new StringBuilder();
    var sw = new StringWriter(sb);

    page.ViewContext = new ViewContext(new ActionContext(context.HttpContext, context.RouteData, context.ActionDescriptor), view, new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary()), sw);

设置完所有这些后,我尝试在我的 ViewData 上设置 Model-属性,我可以看到我设置了正确的对象,并且它的类型正确页面。

    page.ViewContext.ViewData.Model = widgetInfo.Setting;

然后我执行页面,当我没有在我的模板中使用任何模型时效果很好。

    page.ExecuteAsync().GetAwaiter().GetResult();
    sw.Flush();

    var renderedWidget = sb.ToString();

我可以看到我的模板已加载(甚至在其中放置了断点)但模型始终为空。目前我使用这个简单的模板。

@model DataObjects.CodeWidgetModel
@Html.Raw(Model.Code)

我一直在 GitHub 搜索 MVC6-sources 以查看我可能遗漏了什么,但我找不到任何有用的东西。

我是 运行 ASP.Net MVC 6.0.0-beta3-12628KRE 1.0.0-beta3-11014,但很可能会在可用时尽快升级到 beta4。

任何人都知道为什么在 ViewData 上设置 Model 是不够的,或者我还需要做什么才能让它工作?

为了如此简单的事情,您正在深入研究渲染管道。尝试使用小部件集合创建模型。在您的主视图中执行以下操作:

foreach(var widget in Model.Widgets) { Html.DisplayFor(widget) }

然后在 /views/displaytemplates/widget.cshtml 中创建一个视图,然后将小部件转换为更具体类型的小部件,然后用它调用 Html.DisplayFor,从而将小部件分解为正确的模板。

** 伪代码 **

@model Widget
if (Widget is Widget1Type) Html.DisplayFor((Widget1Type)Widget)
if (Widget is Widget2Type) Html.DisplayFor((Widget2Type)Widget)
if (Widget is Widget3Type) Html.DisplayFor((Widget3Type)Widget)
if (Widget is Widget4Type) Html.DisplayFor((Widget4Type)Widget)

然后为这些小部件创建视图 /views/displaytemplates/widget1类型:

@model Widget1Type
...Stuff here...

经过更多研究(老实说,进行了大量的试验和错误)后,RazorPage.ExecuteAsync() 似乎不是正确的方法。使用正确键入的 ViewDataDictionary<> 调用 RazorView.RenderAsync() 就成功了!

下面是最终代码。

public override void OnActionExecuted(ActionExecutedContext context) {
    var pageFactory = (IRazorPageFactory)context.HttpContext.ApplicationServices.GetService(typeof(IRazorPageFactory));
    var viewEngine = (IRazorViewEngine)context.HttpContext.ApplicationServices.GetService(typeof(IRazorViewEngine));
    var viewFactory = (IRazorViewFactory)context.HttpContext.ApplicationServices.GetService(typeof(IRazorViewFactory));

    var sb = new StringBuilder();
    var sw = new StringWriter(sb);

    var page = pageFactory.CreateInstance($"~/Areas/Widgets/DefaultViews/Widgets/{widgetInfo.Type}.cshtml");
    var view = (RazorView)viewFactory.GetView(viewEngine, page, true);

    var vddType = typeof(ViewDataDictionary<>);
    var vddGeneric = vddType.MakeGenericType(widgetInfo.Model.GetType());
    dynamic viewDataDictionary = Activator.CreateInstance(vddGeneric, new EmptyModelMetadataProvider(), new ModelStateDictionary());

    var actionContext = new ActionContext(context.HttpContext, context.RouteData, context.ActionDescriptor);
    var viewContext = new ViewContext(actionContext, view, viewDataDictionary, sw);
    viewContext.ViewData.Model = widgetInfo.Model;

    view.RenderAsync(viewContext).GetAwaiter().GetResult();
    sw.Flush();

    var renderedWidget = sb.ToString();
}

我对 ViewDataDictionarydynamic 不是特别满意,但它确实有效。可能还有其他更简单的方法可以做到这一点,但现在已经足够了。

ASP.Net MVC 6.0.0-beta3-12628KRE 1.0.0-beta3-11014 上测试。