使用嵌套托管将 MVC5 视图作为字符串获取

Getting MVC5 Views as string, with nested hosting

我有以下由 Hangfire 执行的代码(没有 HttpContext),当我在本地 运行 时它运行完美:

class FakeController : ControllerBase
{
    protected override void ExecuteCore() { }

    public static string RenderViewToString(string controllerName, string viewName, object viewData)
    {
        using (var writer = new StringWriter())
        {
            var routeData = new RouteData();
            routeData.Values.Add("controller", controllerName);
            var fakeControllerContext = new ControllerContext(
                                                              new HttpContextWrapper(
                                                                                     new HttpContext(new HttpRequest(null, "http://nopage.no", null)
                                                                                                   , new HttpResponse(null))
                                                                                     ), 
                                                              routeData, 
                                                              new FakeController()
                                                            );
            var razorViewEngine = new RazorViewEngine();
            var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);

            var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);
            razorViewResult.View.Render(viewContext, writer);
            return writer.ToString();

        }
    }
}

然而,我们设置应用程序的方式如下:

https://application.net <- 一份申请

https://application.net/admin <- 此代码 运行 所在的其他应用程序。

当我 运行 https://application.net/admin 上的代码时,出现以下异常:

System.ArgumentException: The virtual path '/' maps to another application, which is not allowed.

它出现在这一行:razorViewEngine.FindView(fakeControllerContext, viewName, "", false)

我尝试创建自己的 RazorViewEngine 并覆盖了一些方法来查找视图,但无济于事。

class MyViewEngine : RazorViewEngine
{
    protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
    {
        if (!base.FileExists(controllerContext, virtualPath))
            return base.FileExists(controllerContext, "~/../admin" + virtualPath.TrimStart('~'));

        return true;
    }
    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
    {
        var newViewPath = viewPath;
        if (!base.FileExists(controllerContext, viewPath))
            newViewPath = "~/../admin/" + viewPath.TrimStart('~');

        return base.CreateView(controllerContext, newViewPath, masterPath);
    }
}

这失败了,因为它不想让我离开“目录树的最上面的部分”。

是否有解决此问题的简单方法,或从剃刀视图创建字符串的替代方法? - 目的是创建电子邮件模板。将创建很多电子邮件,我不想使用 HttpClient 请求我自己的端点来创建它。

我能够复制问题。 httpContext 设置不正确。以这种方式更改代码,无需覆盖 RazorViewEngine:

static string GetUrlRoot()
 {
    var httpContext = HttpContext.Current;
    if (httpContext == null)
     {
        return "http://localhost";
     }

    return httpContext.Request.Url.GetLeftPart(UriPartial.Authority) +
               httpContext.Request.ApplicationPath;
}

public static string RenderViewToString(string controllerName, string viewName, object viewData)
 {
    using (var writer = new StringWriter())
        {
            var routeData = new RouteData();
            routeData.Values.Add("controller", controllerName);
            var fakeControllerContext = new ControllerContext(
                                                              new HttpContextWrapper(
                                                                                     new HttpContext(new HttpRequest(null, GetUrlRoot(), null)
                                                                                                   , new HttpResponse(null))
                                                                                     ),
                                                              routeData,
                                                              new FakeController()
                                                            );
            var razorViewEngine = new RazorViewEngine();
            var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);

            var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View, new ViewDataDictionary(viewData), new TempDataDictionary(), writer);
            razorViewResult.View.Render(viewContext, writer);
            return writer.ToString();

        }
}

经过大量尝试和失败后,我最终放弃了我的方法,改用 Rick Strahl's WestWind.RazorHosting 库。

这是我使用的 documentation 中最重要的部分。

下面是我的代码最后的样子。

class FakeController : IDisposable
{
    private readonly RazorFolderHostContainer _host;
    public FakeController()
    {
        _host = new RazorFolderHostContainer
               {
                   TemplatePath = $@"{HostingEnvironment.ApplicationPhysicalPath}Views\EmailTemplate\"
               };

        _host.AddAssemblyFromType(typeof(Controller));
        _host.AddAssemblyFromType(typeof(EmailTemplateController.TestViewModel));
        _host.Start();
    }

    public string RenderViewToString(string viewName, object viewData)
    {
        return _host.RenderTemplate($@"~/{viewName}.cshtml", viewData);
    }

    public void Dispose()
    {
        _host.Stop();
    }
}

这会在我的 EmailTemplate 文件夹中查找位于 Views 中的视图,找到我想要使用的 *.cshtml 文件。因为它是从 Views 文件夹中获取它们的,所以我仍然能够使用与普通控制器相同的视图,因此当我也在我的端点中使用视图时它也会起作用。漂亮。