Ric*_*ahl 5 c# razor-pages asp.net-core-3.0
我正在尝试构建一个动态 Web 界面,在其中我可以动态指向文件夹并使用 ASP.NET Core 提供该文件夹中的 Web 内容。通过使用 ASP.NET Core 中的 FileProviders 重新路由 Web 根文件夹,可以非常轻松地实现此目的。这适用于 StaticFiles 和 RazorPages。
但是,对于 RazorPages 来说,问题是一旦执行此操作,您就无法动态添加其他类型的引用。我希望能够选择添加一个文件夹 (PrivateBin),在启动时我可以循环遍历该文件夹、加载程序集,然后让这些程序集在 Razor 中可见。
不幸的是,它不起作用,因为即使使用运行时编译,Razor 似乎也看不到加载的程序集。
我在启动期间使用以下内容来加载程序集。请注意,加载这些文件的文件夹不在默认的 ContentRoot 或 WebRoot 中,而是在新的重定向的 WebRoot 中。
// WebRoot is a user chosen Path here specified via command line --WebRoot c:\temp\web
private void LoadPrivateBinAssemblies()
{
var binPath = Path.Combine(WebRoot, "PrivateBin");
if (Directory.Exists(binPath))
{
var files = Directory.GetFiles(binPath);
foreach (var file in files)
{
if (!file.EndsWith(".dll", StringComparison.CurrentCultureIgnoreCase) &&
!file.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
continue;
try
{
var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
Console.WriteLine("Additional Assembly: " + file);
}
catch (Exception ex)
{
Console.WriteLine("Failed to load private assembly: " + file);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
程序集加载到 AssemblyLoadContext() 中,我可以使用反射并Type.GetType("namespace.class,assembly")访问该类型。
但是,当我尝试访问 RazorPages 中的类型时 - 即使启用了运行时编译 - 这些类型也不可用。我收到以下错误:
为了确保该类型确实可用,我检查了是否可以在 Razor 内部执行以下操作:
@{
var md = Type.GetType("Westwind.AspNetCore.Markdown.Markdown,Westwind.AspNetCore.Markdown");
var mdText = md.InvokeMember("Parse", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null,
null, new object[] { "**asdasd**", false, false, false });
}
@mdText
Run Code Online (Sandbox Code Playgroud)
效果很好。因此,程序集已加载且类型可访问,但 Razor 似乎没有意识到这一点。
所以问题是:
是否可以在运行时加载程序集,并通过运行时编译将它们提供给 Razor,并像通常通过直接声明性访问使用类型一样使用它?
快速查看 ASP.NET Core 源代码可以发现:
所有 Razor 视图编译开始于:
RuntimeViewCompiler.CreateCompilation (..)
它使用: CSharpCompiler.Create(..,..,引用:..)
它使用: RazorReferenceManager.CompilationReferences
// simplyfied
var referencePaths = ApplicationPartManager.ApplicationParts
.OfType<ICompilationReferencesProvider>()
.SelectMany(_ => _.GetReferencePaths())
Run Code Online (Sandbox Code Playgroud)
它使用: ApplicationPartManager.ApplicationParts
所以我们需要以某种方式注册我们自己的ICompilationReferencesProvider,这就是......
在搜索应用程序部分时,会执行ApplicationPartManager以下操作:
[assembly: ApplicationPartAttribute(assemblyName:"..")] // Specifies an assembly to be added as an ApplicationPart
[assembly: RelatedAssemblyAttribute(assemblyFileName:"..")] // Specifies a assembly to load as part of MVC's assembly discovery mechanism.
// plus `Assembly.GetEntryAssembly()` gets added automaticly behind the scenes.
Run Code Online (Sandbox Code Playgroud)
然后它循环遍历所有找到的程序集并使用ApplicationPartFactory.GetApplicationPartFactory (程序集)(如第 69 行所示)来查找扩展的类型ApplicationPartFactory。
GetApplicationParts(assembly)然后它对所有找到的s调用该方法ApplicationPartFactory。
所有没有ApplicationPartFactory得到DefaultApplicationPartFactory返回的程序new AssemblyPart(assembly)集GetApplicationParts。
public abstract IEnumerable<ApplicationPart> GetApplicationParts(Assembly assembly);
Run Code Online (Sandbox Code Playgroud)
GetApplicationPartFactory 搜索[assembly: ProvideApplicationPartFactory(typeof(SomeType))]然后将其用作SomeType工厂。
public abstract class ApplicationPartFactory {
public abstract IEnumerable<ApplicationPart> GetApplicationParts(Assembly assembly);
public static ApplicationPartFactory GetApplicationPartFactory(Assembly assembly)
{
// ...
var provideAttribute = assembly.GetCustomAttribute<ProvideApplicationPartFactoryAttribute>();
if (provideAttribute == null)
{
return DefaultApplicationPartFactory.Instance; // this registers `assembly` as `new AssemblyPart(assembly)`
}
var type = provideAttribute.GetFactoryType();
// ...
return (ApplicationPartFactory)Activator.CreateInstance(type);
}
}
Run Code Online (Sandbox Code Playgroud)
这意味着我们可以创建并注册(使用ProvideApplicationPartFactoryAttribute)我们自己的ApplicationPartFactory,它返回一个自定义ApplicationPart实现,该实现实现ICompilationReferencesProvider然后返回我们在GetReferencePaths.
[assembly: ProvideApplicationPartFactory(typeof(MyApplicationPartFactory))]
namespace WebApplication1 {
public class MyApplicationPartFactory : ApplicationPartFactory {
public override IEnumerable<ApplicationPart> GetApplicationParts(Assembly assembly)
{
yield return new CompilationReferencesProviderAssemblyPart(assembly);
}
}
public class CompilationReferencesProviderAssemblyPart : AssemblyPart, ICompilationReferencesProvider {
private readonly Assembly _assembly;
public CompilationReferencesProviderAssemblyPart(Assembly assembly) : base(assembly)
{
_assembly = assembly;
}
public IEnumerable<string> GetReferencePaths()
{
// your `LoadPrivateBinAssemblies()` method needs to be called before the next line executes!
// So you should load all private bin's before the first RazorPage gets requested.
return AssemblyLoadContext.GetLoadContext(_assembly).Assemblies
.Where(_ => !_.IsDynamic)
.Select(_ => new Uri(_.CodeBase).LocalPath);
}
}
}
Run Code Online (Sandbox Code Playgroud)
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Content Remove="Pages\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.0.0" />
</ItemGroup>
</Project>
Run Code Online (Sandbox Code Playgroud)
services
.AddRazorPages()
.AddRazorRuntimeCompilation();
AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\path\to\ClassLibrary1.dll");
// plus the MyApplicationPartFactory and attribute from above.
Run Code Online (Sandbox Code Playgroud)
〜/页面/Index.cshtml
@page
<pre>
output: [
@(
new ClassLibrary1.Class1().Method1()
)
]
</pre>
Run Code Online (Sandbox Code Playgroud)
它显示了预期的输出:
output: [
Hallo, World!
]
Run Code Online (Sandbox Code Playgroud)
祝你今天过得愉快。
| 归档时间: |
|
| 查看次数: |
2418 次 |
| 最近记录: |