问题
I'm trying to deal with plugins using Razor Pages application.
Solution consists of 3 projects: one Razor Pages application and two Razor Class Libraries (RCL). Application must not refer RCL projects statically, they must be loaded as plugins:
There's nothing special inside pages. Feature pages just produce simple HTML. Index page builds a sort of menu.
Index page model:
public class IndexModel : PageModel
{
public IEnumerable<MenuItem> MenuItems { get; private set; }
public void OnGet()
{
MenuItems = new List<MenuItem>
{
new MenuItem { Route = "FeatureA", Title = "Feature A" },
new MenuItem { Route = "FeatureB", Title = "Feature B" }
};
}
}
Index page:
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
@foreach (var item in Model.MenuItems)
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/@item.Route">@item.Title</a>
</li>
}
</ul>
</div>
</div>
When I run the app, there are menu items, but their href
s are empty:
<div class="text-center">
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" href="">Feature A</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="">Feature B</a>
</li>
</ul>
</div>
</div>
Of course, all assemblies (app and feature ones) are in the same directory.
Menu works in two following cases:
- either if I refer RCL projects in App project, which kills plugins idea;
- or if I put
App.deps.json
withFeatureLib_A
andFeatureLib_B
as dependecies (just save deps file from first case, remove references, rebuild all, copy saved deps file).
Also, I've tried to eagerly load RCL assemblies in Startup
class.
Assemblies are being loaded, but Index
page behaves the same.
Is there any way to tell ASP infrastructure to use RCL assemblies without modifying deps file? What am I missing?
回答1:
I've figured it out.
The basic idea is to give ApplicationPartManager
appropriate application parts.
It's important to note that:
- "code" assemblies (e.g.
FeatureLib_A.dll
) must be added asAssemblyPart
; - "view" assemblies (e.g.
FeatureLib_A.Views.dll
) must be added asCompiledRazorAssemblyPart
.
Sample code:
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
var assemblyLoader = new DotNetCoreAssemblyLoader(searchPattern: "FeatureLib*.dll");
services.AddMvc()
.ConfigureApplicationPartManager(_ =>
{
foreach (var assembly in assemblyLoader.Assemblies)
{
if (assembly.FullName.Contains("Views"))
{
_.ApplicationParts.Add(new CompiledRazorAssemblyPart(assembly));
}
else
{
_.ApplicationParts.Add(new AssemblyPart(assembly));
}
}
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
// ...
}
DotNetCoreAssemblyLoader
is a custom class, which looks for assembly files using given search pattern, and loads assemblies via AssemblyLoadContext.Default.LoadFromAssemblyPath
.
回答2:
public class Startup
{
public Startup( IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
private readonly IHostingEnvironment _hostingEnvironment;
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.ConfigureApplicationPartManager(ConfigureApplicationParts); ;
}
private void ConfigureApplicationParts(ApplicationPartManager apm)
{
string rootPath = _hostingEnvironment.ContentRootPath;
var pluginsPath = Path.Combine(rootPath, "Plugins");
var assemblyFiles = Directory.GetFiles(pluginsPath, "Plugin*.dll", SearchOption.AllDirectories);
foreach (var assemblyFile in assemblyFiles)
{
try
{
var assembly = Assembly.LoadFrom(assemblyFile);
if (assemblyFile.EndsWith(".Views.dll"))
apm.ApplicationParts.Add(new
CompiledRazorAssemblyPart(assembly));
else
apm.ApplicationParts.Add(new AssemblyPart(assembly));
}
catch (Exception e) { }
}
}
}
来源:https://stackoverflow.com/questions/53954850/how-to-dynamically-load-pages-from-plugins-in-razor-pages-application