gpt4 book ai didi

razor - 如何在运行时动态加载 ASP.NET Core Razor View

转载 作者:行者123 更新时间:2023-12-03 13:37:55 26 4
gpt4 key购买 nike

是否可以在运行时从单独的程序集中引用 ASP.NET Core Razor View ?
我知道如何使用 IActionDescriptorChangeProvider 动态加载 Controller 但找不到意见的方法。
我想创建一个简单的插件系统并在不重新启动应用程序的情况下管理插件。

最佳答案

我正在创建一个动态且完全模块化(基于插件)的应用程序,其中用户可以在运行时将插件程序集放在文件监视目录中以添加 Controller 和编译 View 。
我遇到的问题和你一样。起初,即使我通过 正确添加了程序集,MVC 也没有“检测到” Controller 和 View 。 ApplicationPartManager 服务。
我解决了 Controller 问题,正如你所说,可以用 处理。 IActionDescriptorChangeProvider .
但是,对于 View 问题,似乎没有内置类似的机制。我在谷歌上爬了几个小时,找到了你的帖子(以及许多其他帖子),但没有人回答。我几乎放弃了。几乎。
我开始爬取 ASP.NET Core 源代码并实现了所有我认为与查找已编译 View 相关的服务。我晚上的大部分时间都在拉扯我的头发,然后…… Eureka 。
我发现负责提供这些编译 View 的服务是默认的 IViewCompiler(又名 DefaultViewCompiler),它又由 IViewCompilerProvider(又名 DefaultViewCompilerProvider)提供。
您实际上需要同时实现这两个功能以使其按预期工作。
IViewCompilerProvider:

 public class ModuleViewCompilerProvider
: IViewCompilerProvider
{

public ModuleViewCompilerProvider(ApplicationPartManager applicationPartManager, ILoggerFactory loggerFactory)
{
this.Compiler = new ModuleViewCompiler(applicationPartManager, loggerFactory);
}

protected IViewCompiler Compiler { get; }

public IViewCompiler GetCompiler()
{
return this.Compiler;
}

}
IViewCompiler:
public class ModuleViewCompiler
: IViewCompiler
{

public static ModuleViewCompiler Current;

public ModuleViewCompiler(ApplicationPartManager applicationPartManager, ILoggerFactory loggerFactory)
{
this.ApplicationPartManager = applicationPartManager;
this.Logger = loggerFactory.CreateLogger<ModuleViewCompiler>();
this.CancellationTokenSources = new Dictionary<string, CancellationTokenSource>();
this.NormalizedPathCache = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
this.PopulateCompiledViews();
ModuleViewCompiler.Current = this;
}

protected ApplicationPartManager ApplicationPartManager { get; }

protected ILogger Logger { get; }

protected Dictionary<string, CancellationTokenSource> CancellationTokenSources { get; }

protected ConcurrentDictionary<string, string> NormalizedPathCache { get; }

protected Dictionary<string, CompiledViewDescriptor> CompiledViews { get; private set; }

public void LoadModuleCompiledViews(Assembly moduleAssembly)
{
if (moduleAssembly == null)
throw new ArgumentNullException(nameof(moduleAssembly));
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
this.CancellationTokenSources.Add(moduleAssembly.FullName, cancellationTokenSource);
ViewsFeature feature = new ViewsFeature();
this.ApplicationPartManager.PopulateFeature(feature);
foreach(CompiledViewDescriptor compiledView in feature.ViewDescriptors
.Where(v => v.Type.Assembly == moduleAssembly))
{
if (!this.CompiledViews.ContainsKey(compiledView.RelativePath))
{
compiledView.ExpirationTokens = new List<IChangeToken>() { new CancellationChangeToken(cancellationTokenSource.Token) };
this.CompiledViews.Add(compiledView.RelativePath, compiledView);
}
}
}

public void UnloadModuleCompiledViews(Assembly moduleAssembly)
{
if (moduleAssembly == null)
throw new ArgumentNullException(nameof(moduleAssembly));
foreach (KeyValuePair<string, CompiledViewDescriptor> entry in this.CompiledViews
.Where(kvp => kvp.Value.Type.Assembly == moduleAssembly))
{
this.CompiledViews.Remove(entry.Key);
}
if (this.CancellationTokenSources.TryGetValue(moduleAssembly.FullName, out CancellationTokenSource cancellationTokenSource))
{
cancellationTokenSource.Cancel();
this.CancellationTokenSources.Remove(moduleAssembly.FullName);
}
}

private void PopulateCompiledViews()
{
ViewsFeature feature = new ViewsFeature();
this.ApplicationPartManager.PopulateFeature(feature);
this.CompiledViews = new Dictionary<string, CompiledViewDescriptor>(feature.ViewDescriptors.Count, StringComparer.OrdinalIgnoreCase);
foreach (CompiledViewDescriptor compiledView in feature.ViewDescriptors)
{
if (this.CompiledViews.ContainsKey(compiledView.RelativePath))
continue;
this.CompiledViews.Add(compiledView.RelativePath, compiledView);
};
}

public async Task<CompiledViewDescriptor> CompileAsync(string relativePath)
{
if (relativePath == null)
throw new ArgumentNullException(nameof(relativePath));
if (this.CompiledViews.TryGetValue(relativePath, out CompiledViewDescriptor cachedResult))
return cachedResult;
string normalizedPath = this.GetNormalizedPath(relativePath);
if (this.CompiledViews.TryGetValue(normalizedPath, out cachedResult))
return cachedResult;
return await Task.FromResult(new CompiledViewDescriptor()
{
RelativePath = normalizedPath,
ExpirationTokens = Array.Empty<IChangeToken>(),
});
}

protected string GetNormalizedPath(string relativePath)
{
if (relativePath.Length == 0)
return relativePath;
if (!this.NormalizedPathCache.TryGetValue(relativePath, out var normalizedPath))
{
normalizedPath = this.NormalizePath(relativePath);
this.NormalizedPathCache[relativePath] = normalizedPath;
}
return normalizedPath;
}

protected string NormalizePath(string path)
{
bool addLeadingSlash = path[0] != '\\' && path[0] != '/';
bool transformSlashes = path.IndexOf('\\') != -1;
if (!addLeadingSlash && !transformSlashes)
return path;
int length = path.Length;
if (addLeadingSlash)
length++;
return string.Create(length, (path, addLeadingSlash), (span, tuple) =>
{
var (pathValue, addLeadingSlashValue) = tuple;
int spanIndex = 0;
if (addLeadingSlashValue)
span[spanIndex++] = '/';
foreach (var ch in pathValue)
{
span[spanIndex++] = ch == '\\' ? '/' : ch;
}
});
}

}
现在,您需要找到现有的 IViewCompilerProvider 描述符,并将其替换为您自己的描述符,如下所示:
    public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
ServiceDescriptor descriptor = services.FirstOrDefault(s => s.ServiceType == typeof(IViewCompilerProvider));
services.Remove(descriptor);
services.AddSingleton<IViewCompilerProvider, ModuleViewCompilerProvider>();
}
然后,在加载已编译的 View 插件程序集时,只需进行以下调用:
ModuleViewCompiler.Current.LoadModuleCompiledViews(compiledViewsAssembly);
卸载已编译的 View 插件程序集后,进行该调用:
ModuleViewCompiler.Current.UnloadModuleCompiledViews(compiledViewsAssembly);
这将取消并摆脱我们与插件程序集加载的编译 View 相关联的 IChangeToken。 这很重要如果您打算在运行时加载、卸载然后重新加载特定的插件程序集,因为否则 MVC 会跟踪它,可能会禁止卸载您的 AssemblyLoadContext,并且由于模型类型不匹配(模型 x 来自程序集 z在时间 T 加载的模型 x 与在时间 T+1 加载的组件 z 中的模型 x 不同)
希望有帮助;)

关于razor - 如何在运行时动态加载 ASP.NET Core Razor View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48206993/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com