Jx.Cms第六篇开发笔记:如何重写编译器?

2026-05-19 15:291阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1186个文字,预计阅读时间需要5分钟。

Jx.Cms第六篇开发笔记:如何重写编译器?

在Jx.Cms开发笔记(三)中,探讨了如何动态切换Views主题。然而,切换主题时遇到了一个问题:错误提示。这是由于在asp.net core处理Views信息时,处理数据的时间点是在构造函数中。

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


这是由于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:

Jx.Cms第六篇开发笔记:如何重写编译器?

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();即可。

本文共计1186个文字,预计阅读时间需要5分钟。

Jx.Cms第六篇开发笔记:如何重写编译器?

在Jx.Cms开发笔记(三)中,探讨了如何动态切换Views主题。然而,切换主题时遇到了一个问题:错误提示。这是由于在asp.net core处理Views信息时,处理数据的时间点是在构造函数中。

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


这是由于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:

Jx.Cms第六篇开发笔记:如何重写编译器?

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();即可。