【问题标题】:Render View to String caching issue将视图渲染到字符串缓存问题
【发布时间】:2018-07-27 14:00:55
【问题描述】:

对于我的项目,我目前为我的 asp.net 核心应用程序实现了 ViewRender。它在没有控制器的情况下生成 html 视图,使用以下代码可以正常工作:

public class ViewRenderService : IViewRenderService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;

    public ViewRenderService(IRazorViewEngine razorViewEngine,
        ITempDataProvider tempDataProvider,
        IServiceProvider serviceProvider)
    {
        _razorViewEngine = razorViewEngine;
        _tempDataProvider = tempDataProvider;
        _serviceProvider = serviceProvider;
    }

    public async Task<string> RenderToStringAsync(string viewName, object model)
    {
        var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };

        var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
        string viewgerendered = "";
        try
        {
            using (var sw = new StringWriter())
            {
                var viewResult = _razorViewEngine.GetView(viewName, viewName, false);

                if (viewResult.View == null)
                {
                    throw new ArgumentNullException($"{viewName} does not match any available view");
                }

                var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                {
                    Model = model
                };        

                var viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewDictionary,
                    new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                    sw,
                    new HtmlHelperOptions()
                );

                await viewResult.View.RenderAsync(viewContext);

                viewgerendered = sw.ToString();
                return viewgerendered;
            }
        }
        catch (Exception e)
        {
            object temp = e.Message + " - " + e.StackTrace;
            return temp.ToString();
        }
    }

    public Task RenderToStringAsync(string v)
    {
        throw new NotImplementedException();
    }
}

来源:https://ppolyzos.com/2016/09/09/asp-net-core-render-view-to-string/

如果不重新启动应用程序本身,则不会更新使用此渲染器的视图所做的更改。深入研究,视图被缓存。源中的评论提到使用_razorViewEngine.GetView 方法应该可以解决我的缓存问题。但是这不起作用。

我得到了什么,试图找出一种方法来注册一个新的 ViewRender,对 ViewRenderService 稍作修改。

//Seems not to be available on asp.net core 2.0...
services.AddMvc().Configure<MvcViewOptions>(options =>
            {
                options.ViewEngines.Clear();
                options.ViewEngines.Add(typeof(CustomViewEngine));
            });

并重载 RazorViewEngine 以公开 ViewLookupCache,据推测视图缓存所在的位置。

  public class CustomViewEngine : RazorViewEngine
    {
        public CustomViewEngine(
            IRazorPageFactoryProvider pageFactory, 
            IRazorPageActivator pageActivator,
            HtmlEncoder htmlEncoder, 
            IOptions<RazorViewEngineOptions> optionsAccessor, 
            Microsoft.AspNetCore.Razor.Language.RazorProject razorProject, 
            ILoggerFactory loggerFactory, 
            System.Diagnostics.DiagnosticSource diagnosticSource) : 
            base(pageFactory, pageActivator, htmlEncoder, optionsAccessor,razorProject,loggerFactory, diagnosticSource){ }

        public void RemoveCachedView(string view)
        { 
            this.ViewLookupCache.Remove(view);
        }
    }

关于如何在 asp.net core 2.0 中为视图完成缓存和清除特定视图/集合的内容并不多。基本上,出于性能原因,我想找到一种方法如何将整个缓存视图选择刷新为命令。

编辑 13-04-2018

按照 K Finley 的建议,我尝试按照建议清空 ViewLookupCache。简而言之就是代码;

在我的 startup.cs ConfigureServices 中(不完全确定这是否是自定义视图引擎的注册方式)。

    services.AddSingleton<IRazorViewEngine, CustomViewEngine>();
    services.AddSingleton<IViewRenderService, ViewRenderService>();

自定义视图引擎:

public class CustomViewEngine : RazorViewEngine
{
    public CustomViewEngine(
        IRazorPageFactoryProvider pageFactory,
        IRazorPageActivator pageActivator,
        HtmlEncoder htmlEncoder,
        IOptions<RazorViewEngineOptions> optionsAccessor,
        Microsoft.AspNetCore.Razor.Language.RazorProject razorProject,
        ILoggerFactory loggerFactory,
        System.Diagnostics.DiagnosticSource diagnosticSource) :
        base(pageFactory, pageActivator, htmlEncoder, optionsAccessor, razorProject, loggerFactory, diagnosticSource)
    { }


    public void RemoveViewFromCache(string viewName, string controller, bool isLayout, bool isPartial = false, string pageName = null, string areaName = null)
    {   
        var key = new ViewLocationCacheKey(viewName, controller, areaName, pageName, !isLayout | !isPartial, isLayout ? null : new Dictionary<string, string>(StringComparer.Ordinal));        
        base.ViewLookupCache.Remove(key);
    }

    public void RemoveViewFromCache(string viewName, bool isLayout)
    {
        //Code uses this one
        var key = new ViewLocationCacheKey(viewName, isLayout);  
        base.ViewLookupCache.Remove(key);    
    }
}

并且修改了原来的ViewRenderService...

    public class ViewRenderService : IViewRenderService
    {
        private CustomViewEngine _razorViewEngine;
        private ITempDataProvider _tempDataProvider;
        private IServiceProvider _serviceProvider;
        private IHostingEnvironment _hostingEnvironment;


        public ViewRenderService(IRazorViewEngine razorViewEngine,
            ITempDataProvider tempDataProvider,
            IServiceProvider serviceProvider, 
            IHostingEnvironment hostingEnvironment)
        {
            _razorViewEngine = (CustomViewEngine)razorViewEngine;
...

try
            {
                using (var sw = new StringWriter())
                {
                    _razorViewEngine.RemoveViewFromCache(viewName, false);
                    var viewResult = _razorViewEngine.GetView(viewName, viewName, false);

这些修改确实使用第二种方法删除了 ViewLookupCache。但是它仍然没有正确更新我的观点。我必须注意视图没有自己的控制器。

【问题讨论】:

  • 您是否确认该条目实际上已从集合中删除?我怀疑不是。我的经验是您不能使用简单的构造函数 (new ViewLocationCacheKey(viewName, isLayout);),因为它不会填充键中的其他项。 ViewLocationCacheKey 上的 ControllerName、IsMainPage、ViewLocationExpanderValue 等值必须设置为与 RenderViewEngine 加载和缓存视图时添加到缓存的键相匹配。
  • 我可以确认 base.ViewLookupCache 已清空。我没有检查当我在同一个调用中第二次调用该方法时会发生什么。我注册 CustomViewEngine 的方式怎么样,是它应该发生的方式吗?

标签: c# asp.net-mvc caching asp.net-core .net-core


【解决方案1】:

您需要启用文件观察器:
在Dockerfile中添加环境变量DOTNET_USE_POLLING_FILE_WATCHER=trueENV DOTNET_USE_POLLING_FILE_WATCHER=true

【讨论】:

    【解决方案2】:

    如果您查看RazorViewEngine 源代码,您可以看到视图是如何被缓存的。我将解释如何从缓存中删除视图,但您需要一些关于视图的知识才能使其正常工作。

    在您的示例中,您正在缓存中查找仅基于名称的视图(我假设视图名称)。这不起作用,因为您需要使用ViewLocationCacheKey 在缓存中查找视图。 ViewLocationCacheKey 结构上有 2 个构造函数。

        public ViewLocationCacheKey(
            string viewName,
            bool isMainPage)
    
        public ViewLocationCacheKey(
            string viewName,
            string controllerName,
            string areaName,
            string pageName,
            bool isMainPage,
            IReadOnlyDictionary<string, string> values)
    

    RazorViewEngine 中,每一个都根据视图的加载方式(FromPath 或 FromViewLocations)被调用。在我的情况下,我有一个完全支持 Razor 的 CMS,其中任何类型的内容都只是一个 Razor 视图,它通过 RazorViewEngine 呈现并因此缓存在内存中。当对一段内容进行更新时,我会从 ViewLookupCache 中删除该视图,并允许它在下一次加载时重新填充。不幸的是,RazorViewEngine(当前)不允许您将缓存换成分布式缓存选项(Redis、Memcached 等)。

    这是我在自定义视图引擎上的处理方式。

    public void RemoveViewFromCache(string viewName, string controller, bool isLayout, bool isPartial = false, string pageName = null, string areaName = null) {
    
         var key = new ViewLocationCacheKey(viewName, controller, areaName, pageName, !isLayout | !isPartial, isLayout ? null : new Dictionary<string, string>(StringComparer.Ordinal))
    
         base.ViewLookupCache.Remove(key);
    }
    

    我在进行更新时删除视图,让视图在下次请求时自然地重新加载和缓存。

    如果您的应用正在使用 ViewLocationExpanderValues 执行任何操作,则您必须进行更改。只需在应用程序运行时检查 ViewLookupCache 集合,您就会开始了解这里发生了什么。

    【讨论】:

    • 感谢您的回复!我可能需要一些进一步的帮助。我会更新我原来的帖子。我可以确认 ViewLookupCache 正在被清空,但它仍然没有正确更新我的视图。
    • 你的部署环境是什么?这是单个实例/服务器应用程序还是您正在运行应用程序负载平衡的多个实例?
    • 这是我正在测试的单个实例。简而言之,它只是一个小型 MVC 应用程序,它使用提供的代码将附加视图呈现为字符串,然后使用 PhantomJS 将其呈现为 PDF。它运行良好,除了对视图进行了更改,我无法更新它。
    • @KFinley 你好,我在 Net 5 视图刷新时遇到了同样的问题,但部分视图没有,我正在使用 razorruntimecompilation() 方法,你能帮忙吗?
    【解决方案3】:

    在 ASP.NET Core 2.2 及更高版本中,您可以这样做,

    services.Configure<RazorViewEngineOptions>(opts => opts.AllowRecompilingViewsOnFileChange = true);
    

    在 ConfigureServices 中强制视图引擎在视图发生变化时重新编译。

    【讨论】:

      猜你喜欢
      • 2013-10-09
      • 1970-01-01
      • 2022-01-14
      • 1970-01-01
      • 1970-01-01
      • 2019-08-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多