• Jx.Cms开发笔记(六)-重写Compiler


    我们在Jx.Cms开发笔记(三)-Views主题动态切换中说了如何切换主题。但是这里有一个问题,就是主题切换时,会报错

    theme.png

    这是由于asp.net core在处理Views的信息的时候是在构造函数中处理的,没有任何方法可以刷新这个处理结果。

    这里放最新版的DefaultViewCompiler代码,在Jx.Cms编写的时候代码有少许区别,但是基本逻辑是一样的。

    public DefaultViewCompiler(
            ApplicationPartManager applicationPartManager,
            ILogger<DefaultViewCompiler> logger)
        {
            _applicationPartManager = applicationPartManager;
            _logger = logger;
            _normalizedPathCache = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
    
            EnsureCompiledViews(logger);
        }
    
        [MemberNotNull(nameof(_compiledViews))]
        private void EnsureCompiledViews(ILogger logger)
        {
            if (_compiledViews is not null)
            {
                return;
            }
    
            var viewsFeature = new ViewsFeature();
            _applicationPartManager.PopulateFeature(viewsFeature);
    
            // We need to validate that the all compiled views are unique by path (case-insensitive).
            // We do this because there's no good way to canonicalize paths on windows, and it will create
            // problems when deploying to linux. Rather than deal with these issues, we just don't support
            // views that differ only by case.
            var compiledViews = new Dictionary<string, Task<CompiledViewDescriptor>>(
                viewsFeature.ViewDescriptors.Count,
                StringComparer.OrdinalIgnoreCase);
    
            foreach (var compiledView in viewsFeature.ViewDescriptors)
            {
                logger.ViewCompilerLocatedCompiledView(compiledView.RelativePath);
    
                if (!compiledViews.ContainsKey(compiledView.RelativePath))
                {
                    // View ordering has precedence semantics, a view with a higher precedence was not
                    // already added to the list.
                    compiledViews.TryAdd(compiledView.RelativePath, Task.FromResult(compiledView));
                }
            }
    
            if (compiledViews.Count == 0)
            {
                logger.ViewCompilerNoCompiledViewsFound();
            }
    
            // Safe races should be ok. We would end up logging multiple times
            // if this is invoked concurrently, but since this is primarily a dev-scenario, we don't think
            // this will happen often. We could always re-consider the logging if we get feedback.
            _compiledViews = compiledViews;
        }

    所以程序只能获取到第一次的_compiledViews,切换后的Views由于没有放在_compiledViews中,所以无法被找到,就出现了第一图的那种错误。

    这里的解决方法很简单,我们只需要重写一个自己的ViewCompiler就可以了,由于官方源码全部都是internal的,所以我们只能把这部分内容全部重写。

    我们创建自己的MyViewCompilerProviderMyViewCompiler。由于.Net6在这里有部分修改,我们的程序是在.Net5时编写的,所以这里我们的源码是.Net5修改的,与目前最新的代码有些许差距,但是不影响正常使用.

    MyViewCompiler:

    public class MyViewCompiler : IViewCompiler
        {
            private readonly Dictionary<string, Task<CompiledViewDescriptor>> _compiledViews;
            private readonly ConcurrentDictionary<string, string> _normalizedPathCache;
            private readonly ILogger _logger;
    
            public MyViewCompiler(
                IList<CompiledViewDescriptor> compiledViews,
                ILogger logger)
            {
                if (compiledViews == null)
                {
                    throw new ArgumentNullException(nameof(compiledViews));
                }
    
                if (logger == null)
                {
                    throw new ArgumentNullException(nameof(logger));
                }
    
                _logger = logger;
                _normalizedPathCache = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
    
                // We need to validate that the all of the precompiled views are unique by path (case-insensitive).
                // We do this because there's no good way to canonicalize paths on windows, and it will create
                // problems when deploying to linux. Rather than deal with these issues, we just don't support
                // views that differ only by case.
                _compiledViews = new Dictionary<string, Task<CompiledViewDescriptor>>(
                    compiledViews.Count,
                    StringComparer.OrdinalIgnoreCase);
    
                foreach (var compiledView in compiledViews)
                {
    
                    if (!_compiledViews.ContainsKey(compiledView.RelativePath))
                    {
                        // View ordering has precedence semantics, a view with a higher precedence was not
                        // already added to the list.
                        _compiledViews.Add(compiledView.RelativePath, Task.FromResult(compiledView));
                    }
                }
    
                if (_compiledViews.Count == 0)
                {
                    
                }
            }
    
            /// <inheritdoc />
            public Task<CompiledViewDescriptor> CompileAsync(string relativePath)
            {
                if (relativePath == null)
                {
                    throw new ArgumentNullException(nameof(relativePath));
                }
    
                // Attempt to lookup the cache entry using the passed in path. This will succeed if the path is already
                // normalized and a cache entry exists.
                if (_compiledViews.TryGetValue(relativePath, out var cachedResult))
                {
                    
                    return cachedResult;
                }
    
                var normalizedPath = GetNormalizedPath(relativePath);
                if (_compiledViews.TryGetValue(normalizedPath, out cachedResult))
                {
                    
                    return cachedResult;
                }
    
                // Entry does not exist. Attempt to create one.
                
                return Task.FromResult(new CompiledViewDescriptor
                {
                    RelativePath = normalizedPath,
                    ExpirationTokens = Array.Empty<IChangeToken>(),
                });
            }
    
            private string GetNormalizedPath(string relativePath)
            {
                Debug.Assert(relativePath != null);
                if (relativePath.Length == 0)
                {
                    return relativePath;
                }
    
                if (!_normalizedPathCache.TryGetValue(relativePath, out var normalizedPath))
                {
                    normalizedPath = NormalizePath(relativePath);
                    _normalizedPathCache[relativePath] = normalizedPath;
                }
    
                return normalizedPath;
            }
    
            public static string NormalizePath(string path)
            {
                var addLeadingSlash = path[0] != '\\' && path[0] != '/';
                var transformSlashes = path.IndexOf('\\') != -1;
    
                if (!addLeadingSlash && !transformSlashes)
                {
                    return path;
                }
    
                var length = path.Length;
                if (addLeadingSlash)
                {
                    length++;
                }
    
                return string.Create(length, (path, addLeadingSlash), (span, tuple) =>
                {
                    var (pathValue, addLeadingSlashValue) = tuple;
                    var spanIndex = 0;
    
                    if (addLeadingSlashValue)
                    {
                        span[spanIndex++] = '/';
                    }
    
                    foreach (var ch in pathValue)
                    {
                        span[spanIndex++] = ch == '\\' ? '/' : ch;
                    }
                });
            }
        }

    这个类完全复制了.Net5的源码,只是删除了部分编译不过去的日志内容。

    MyViewCompilerProvider:

    public class MyViewCompilerProvider : IViewCompilerProvider
        {
            private MyViewCompiler _compiler;
            private readonly ApplicationPartManager _applicationPartManager;
            private readonly ILoggerFactory _loggerFactory;
    
            public MyViewCompilerProvider(
                ApplicationPartManager applicationPartManager,
                ILoggerFactory loggerFactory)
            {
                _applicationPartManager = applicationPartManager;
                _loggerFactory = loggerFactory;
                Modify();
            }
    
            public void Modify()
            {
                var feature = new ViewsFeature();
                _applicationPartManager.PopulateFeature(feature);
    
                _compiler = new MyViewCompiler(feature.ViewDescriptors, _loggerFactory.CreateLogger<MyViewCompiler>());
            }
    
            public IViewCompiler GetCompiler() => _compiler;
        }

    这个类我们只是把.Net5源码里的构造函数拆分了,拆出了一个PublicModify方法。

    然后我们需要用自己的MyViewCompilerProvider替换自带的,所以我们需要在Startup.csConfigureServices方法中添加services.Replace<IViewCompilerProvider, MyViewCompilerProvider>();

    最后我们只需要在需要重新获取所有Views的地方调用viewCompilerProvider?.Modify();即可。

  • 相关阅读:
    机器学习-感知机模型
    KES服务管理和环境变量配置(Kylin)
    【深度学习】StabelDiffusion,Lora训练过程,秋叶包,Linux,SDXL Lora训练
    Docker 入门指南
    开源AI模型:未来发展的领航者!
    日本IT Week秋季展丨美格智能以技术创新共建美好数字生活
    1.SpringSecurity认证核心机制
    Knife4j_接口概述、常用注解详解、搭建swagger项目、功能概述
    初识ROS
    总算给女盆友讲明白了,如何使用stream流的filter()操作
  • 原文地址:https://www.cnblogs.com/j4587698/p/16171954.html