使用嵌套托管将 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
文件夹中获取它们的,所以我仍然能够使用与普通控制器相同的视图,因此当我也在我的端点中使用视图时它也会起作用。漂亮。
我有以下由 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
文件夹中获取它们的,所以我仍然能够使用与普通控制器相同的视图,因此当我也在我的端点中使用视图时它也会起作用。漂亮。