PluginCore | 开发笔记

引言

在 ASP.NET Core 中使用承载启动程序集

参考:

通过jsDelivr引用GitHub资源

发布的版本号Releasetag

注意:例如 tagv0.1.0,则 发布的版本号0.1.0,不要加 v

1
https://cdn.jsdelivr.net/gh/你的用户名/你的仓库名@发布的版本号/文件路径

PluginCore.Registry

参考

GitHub Packages Registry

1
https://github.com/search?package_type=nuget&q=PluginCore&type=RegistryPackages

Nuget Registry

搜索 tag 含有PluginCore.IPlugin 的包

1
https://www.nuget.org/packages?q=Tags:"PluginCore.IPlugin"
1
https://www.nuget.org/packages?q=Tags:%22PluginCore.IPlugin%22
1
https://www.nuget.org/packages?page=2&q=Tags%3A%22PluginCore.IPlugin%22&sortBy=relevance
1
https://www.nuget.org/packages?page=2&q=Tags:"PluginCore.IPlugin"&sortBy=relevance

xxxx.0.1.0.nupkg 实际上就是一个 zip,可以改名后解压

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
F:.
├─content
│ └─SimCaptcha
│ ├─bgImages
│ └─fonts
├─contentFiles
│ └─any
│ ├─netcoreapp3.0
│ │ └─SimCaptcha
│ │ ├─bgImages
│ │ └─fonts
│ └─netcoreapp3.1
│ └─SimCaptcha
│ ├─bgImages
│ └─fonts
├─lib
│ ├─netcoreapp3.0
│ └─netcoreapp3.1
├─package
│ └─services
│ └─metadata
│ └─core-properties
└─_rels

EmbeddedResource

将 html,css,js等资源文件打包入 dll - EmbeddedResource

参考:

Swashbuckle.AspNetCore.SwaggerUI.csproj

Swashbuckle.AspNetCore.SwaggerUI.csproj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<Project Sdk="Microsoft.NET.Sdk">

<!-- Use Visual Studio npm package if it is installed. -->
<PropertyGroup Condition="Exists('$(VsInstallRoot)\Web\External\npm.cmd')">
<Path>$(Path)$(VsInstallRoot)\Web\External\;</Path>
</PropertyGroup>

<ItemGroup>
<EmbeddedResource Include="index.html" />
<EmbeddedResource Include="node_modules/swagger-ui-dist/**/*" Exclude="**/*/*.map;**/*/*.json;**/*/*.md" />
</ItemGroup>

<Target Name="NpmInstall" BeforeTargets="Build">
<Exec Command="npm install" EnvironmentVariables="PATH=$(Path.Replace(';', '%3B'))" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js/npm is required to build this project. To continue, please install Node.js from https://nodejs.org/ or Visual Studio Installer, and then restart your command prompt or IDE." />
</Target>

</Project>

结果:

使用 EmbeddedResource

参考:

SwaggerUIMiddleware.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
namespace Swashbuckle.AspNetCore.SwaggerUI
{
public class SwaggerUIMiddleware
{
// 重要: 注意
private const string EmbeddedFileNamespace = "Swashbuckle.AspNetCore.SwaggerUI.node_modules.swagger_ui_dist";

private readonly SwaggerUIOptions _options;
private readonly StaticFileMiddleware _staticFileMiddleware;
private readonly JsonSerializerOptions _jsonSerializerOptions;

public SwaggerUIMiddleware(
RequestDelegate next,
IWebHostEnvironment hostingEnv,
ILoggerFactory loggerFactory,
SwaggerUIOptions options)
{
_options = options ?? new SwaggerUIOptions();

_staticFileMiddleware = CreateStaticFileMiddleware(next, hostingEnv, loggerFactory, options);

_jsonSerializerOptions = new JsonSerializerOptions();
_jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
_jsonSerializerOptions.IgnoreNullValues = true;
_jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, false));
}

public async Task Invoke(HttpContext httpContext)
{
var httpMethod = httpContext.Request.Method;
var path = httpContext.Request.Path.Value;

// If the RoutePrefix is requested (with or without trailing slash), redirect to index URL
if (httpMethod == "GET" && Regex.IsMatch(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?$", RegexOptions.IgnoreCase))
{
// Use relative redirect to support proxy environments
var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/")
? "index.html"
: $"{path.Split('/').Last()}/index.html";

RespondWithRedirect(httpContext.Response, relativeIndexUrl);
return;
}

if (httpMethod == "GET" && Regex.IsMatch(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase))
{
await RespondWithIndexHtml(httpContext.Response);
return;
}

await _staticFileMiddleware.Invoke(httpContext);
}

private StaticFileMiddleware CreateStaticFileMiddleware(
RequestDelegate next,
IWebHostEnvironment hostingEnv,
ILoggerFactory loggerFactory,
SwaggerUIOptions options)
{
var staticFileOptions = new StaticFileOptions
{
RequestPath = string.IsNullOrEmpty(options.RoutePrefix) ? string.Empty : $"/{options.RoutePrefix}",

// 重要: 注意
FileProvider = new EmbeddedFileProvider(typeof(SwaggerUIMiddleware).GetTypeInfo().Assembly, EmbeddedFileNamespace),
};

return new StaticFileMiddleware(next, hostingEnv, Options.Create(staticFileOptions), loggerFactory);
}

private void RespondWithRedirect(HttpResponse response, string location)
{
response.StatusCode = 301;
response.Headers["Location"] = location;
}

private async Task RespondWithIndexHtml(HttpResponse response)
{
response.StatusCode = 200;
response.ContentType = "text/html;charset=utf-8";

using (var stream = _options.IndexStream())
{
// Inject arguments before writing to response
var htmlBuilder = new StringBuilder(new StreamReader(stream).ReadToEnd());
foreach (var entry in GetIndexArguments())
{
htmlBuilder.Replace(entry.Key, entry.Value);
}

await response.WriteAsync(htmlBuilder.ToString(), Encoding.UTF8);
}
}

private IDictionary<string, string> GetIndexArguments()
{
return new Dictionary<string, string>()
{
{ "%(DocumentTitle)", _options.DocumentTitle },
{ "%(HeadContent)", _options.HeadContent },
{ "%(ConfigObject)", JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions) },
{ "%(OAuthConfigObject)", JsonSerializer.Serialize(_options.OAuthConfigObject, _jsonSerializerOptions) },
{ "%(Interceptors)", JsonSerializer.Serialize(_options.Interceptors) },
};
}
}
}

插件系统

参考:

监听文件夹 - 加载/卸载插件

参考:

重启站点

参考:

读取、修改 Request.Body, Reponse.Body

参考:

Q&A

补充

System.InvalidOperationException: 'Unable to resolve service for type 'Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager' while attempting to activate 'PluginCore.PluginControllerManager'.'

获取运行时 .NET 版本

参考

1
Environment.Version

注意:

.NET Core 2.1.0 及之前,这是硬编码

1
2
3
4
// Previously this represented the File version of mscorlib.dll.  Many other libraries in the framework and outside took dependencies on the first three parts of this version 
// remaining constant throughout 4.x. From 4.0 to 4.5.2 this was fine since the file version only incremented the last part. Starting with 4.6 we switched to a file versioning
// scheme that matched the product version. In order to preserve compatibility with existing libraries, this needs to be hard-coded.
public static Version Version => new Version(4, 0, 30319, 42000);

现在获取 运行时 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Version ver = Environment.Version;
Console.WriteLine($"Environment.Version = {ver}");

if (ver < new Version("3.0"))
{
Console.WriteLine("ERROR: Version less than 3.0.");
return -1;
}

// Verify that we are not returning hardcoded version from .NET Framework.
if (ver == new Version("4.0.30319.42000"))
{
Console.WriteLine("ERROR: Version is hardcoded .NET Framework version.");
return -1;
}

// .NET Core assemblies use 4.6+ as file version. Verify that we have not used
// the file version as product version by accident.
if (ver.Major == 4 && (ver.Minor >= 6))
{
Console.WriteLine("ERROR: Version is 4.6+.");
return -1;
}

实际内部核心就是下方代码

1
System.AppDomain.CurrentDomain.GetData("FX_PRODUCT_VERSION") as string;

补充

指定版本运行 .net 程序

1
2
3
4
5
6
7
8
> dotnet run -f net47
.Net Framework 4.7.1: Everything's fine

> dotnet run -f netcoreapp1.0
.Net Core 1.0: Everything's fine

> dotnet run -f netcoreapp2.0
.Net Core 2.0: BUG!

参考

感谢帮助!