diff --git a/.gitignore b/.gitignore index 27e33882..107d2c52 100644 --- a/.gitignore +++ b/.gitignore @@ -355,6 +355,12 @@ examples/AspNetCore6/PluginCoreAdmin/ examples/AspNetCore6/Plugins/ examples/AspNetCore6/Plugins_wwwroot/ +examples/AspNetCore7/App_Data/ +examples/AspNetCore7/PluginCoreAdmin/ +examples/AspNetCore7/Plugins/ +examples/AspNetCore7/Plugins_wwwroot/ + + # PluginCore src/PluginCore/package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29b..c38c6f5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +[README.md](README.md) \ No newline at end of file diff --git a/PluginCore.sln b/PluginCore.sln index 4eb2e7dc..ca6d4c5d 100644 --- a/PluginCore.sln +++ b/PluginCore.sln @@ -9,10 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginCore", "src\PluginCor EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{F7651716-CAC1-42B3-9D95-2C7C835F17CB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore3_1", "examples\AspNetCore3_1\AspNetCore3_1.csproj", "{A28AFD35-06C0-4CEE-BB40-45B080DF5A7F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore5", "examples\AspNetCore5\AspNetCore5.csproj", "{5B797C83-F308-4455-A599-E0CC771AE059}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginCore.IPlugins", "src\PluginCore.IPlugins\PluginCore.IPlugins.csproj", "{9675260E-82D8-40CF-B681-898FE0DA839F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{D8255E7B-33DD-40B1-95A7-2B4F45BFE0F4}" @@ -29,7 +25,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestTimeJobPlugin", "plugin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoModePlugin", "plugins\DemoModePlugin\DemoModePlugin.csproj", "{386ED921-054C-4BB3-A5FF-39011FDD574B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore6", "examples\AspNetCore6\AspNetCore6.csproj", "{601F78E5-D68D-4B79-BE56-A3F731E37511}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore7", "examples\AspNetCore7\AspNetCore7.csproj", "{601F78E5-D68D-4B79-BE56-A3F731E37511}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HexoPlugin", "plugins\HexoPlugin\HexoPlugin.csproj", "{93F2ABF9-64E2-4093-B23A-F22FF1981394}" EndProject @@ -42,7 +38,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginCore.AspNetCore", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginCore.IPlugins.AspNetCore", "src\PluginCore.IPlugins.AspNetCore\PluginCore.IPlugins.AspNetCore.csproj", "{003457E8-D13F-47C0-B07F-0CE5E6AD66F0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore6WithNatasha", "examples\AspNetCore6WithNatasha\AspNetCore6WithNatasha.csproj", "{73810C6C-4049-4382-8A96-D7B67B71CDDC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCore7WithNatasha", "examples\AspNetCore7WithNatasha\AspNetCore7WithNatasha.csproj", "{73810C6C-4049-4382-8A96-D7B67B71CDDC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -54,14 +50,6 @@ Global {8F29F5E4-F306-42E1-BA5D-E0ACAF0A7ED1}.Debug|Any CPU.Build.0 = Debug|Any CPU {8F29F5E4-F306-42E1-BA5D-E0ACAF0A7ED1}.Release|Any CPU.ActiveCfg = Release|Any CPU {8F29F5E4-F306-42E1-BA5D-E0ACAF0A7ED1}.Release|Any CPU.Build.0 = Release|Any CPU - {A28AFD35-06C0-4CEE-BB40-45B080DF5A7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A28AFD35-06C0-4CEE-BB40-45B080DF5A7F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A28AFD35-06C0-4CEE-BB40-45B080DF5A7F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A28AFD35-06C0-4CEE-BB40-45B080DF5A7F}.Release|Any CPU.Build.0 = Release|Any CPU - {5B797C83-F308-4455-A599-E0CC771AE059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B797C83-F308-4455-A599-E0CC771AE059}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B797C83-F308-4455-A599-E0CC771AE059}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B797C83-F308-4455-A599-E0CC771AE059}.Release|Any CPU.Build.0 = Release|Any CPU {9675260E-82D8-40CF-B681-898FE0DA839F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9675260E-82D8-40CF-B681-898FE0DA839F}.Debug|Any CPU.Build.0 = Debug|Any CPU {9675260E-82D8-40CF-B681-898FE0DA839F}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -116,8 +104,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {8F29F5E4-F306-42E1-BA5D-E0ACAF0A7ED1} = {F4038C30-CEF3-406D-AAC6-1158BE61617D} - {A28AFD35-06C0-4CEE-BB40-45B080DF5A7F} = {F7651716-CAC1-42B3-9D95-2C7C835F17CB} - {5B797C83-F308-4455-A599-E0CC771AE059} = {F7651716-CAC1-42B3-9D95-2C7C835F17CB} {9675260E-82D8-40CF-B681-898FE0DA839F} = {F4038C30-CEF3-406D-AAC6-1158BE61617D} {C000158C-1690-41E8-8D44-41AC01A8E81B} = {D8255E7B-33DD-40B1-95A7-2B4F45BFE0F4} {42648CA6-5BC7-4C5C-8A23-1F384D18309F} = {F4038C30-CEF3-406D-AAC6-1158BE61617D} diff --git a/examples/AspNetCore6WithNatasha/Natasha/NatashaPluginContext.cs b/examples/AspNetCore6WithNatasha/Natasha/NatashaPluginContext.cs deleted file mode 100644 index 8cedd28e..00000000 --- a/examples/AspNetCore6WithNatasha/Natasha/NatashaPluginContext.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Reflection; -using PluginCore; -using PluginCore.Interfaces; - -namespace AspNetCore6WithNatasha.Natasha -{ - public class NatashaPluginContext : IPluginContext - { - - public NatashaReferenceDomain Domain - { - get; set; - } - - public NatashaPluginContext(string pluginId) - { - this.Domain = DomainManagement.Create(pluginId); - // 插件的主dll, 不包括插件项目引用的dll - string pluginMainDllFilePath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId, $"{pluginId}.dll"); - this.Domain.LoadPluginWithAllDependency(path: pluginMainDllFilePath); - } - - public IEnumerable Assemblies - { - get - { - return this.Domain.Assemblies; - } - } - - public Assembly LoadFromAssemblyName(AssemblyName assemblyName) - { - var ass = this.Domain.LoadFromAssemblyName(assemblyName); - - return ass; - } - - public Assembly LoadFromStream(Stream assembly) - { - throw new NotImplementedException(); - } - - public void Unload() - { - this.Domain.Unload(); - } - } -} diff --git a/examples/AspNetCore6WithNatasha/Natasha/NatashaPluginContextPack.cs b/examples/AspNetCore6WithNatasha/Natasha/NatashaPluginContextPack.cs deleted file mode 100644 index 7c6c6aa9..00000000 --- a/examples/AspNetCore6WithNatasha/Natasha/NatashaPluginContextPack.cs +++ /dev/null @@ -1,14 +0,0 @@ -using PluginCore.Interfaces; - -namespace AspNetCore6WithNatasha.Natasha -{ - public class NatashaPluginContextPack : IPluginContextPack - { - public IPluginContext Pack(string pluginId) - { - var context = new NatashaPluginContext(pluginId); - - return context; - } - } -} diff --git a/examples/AspNetCore6WithNatasha/WeatherForecast.cs b/examples/AspNetCore6WithNatasha/WeatherForecast.cs deleted file mode 100644 index 7500c0c5..00000000 --- a/examples/AspNetCore6WithNatasha/WeatherForecast.cs +++ /dev/null @@ -1,31 +0,0 @@ -//=================================================== -// License: Apache-2.0 -// Contributors: yiyungent@gmail.com -// Project: https://moeci.com/PluginCore -// GitHub: https://github.com/yiyungent/PluginCore -//=================================================== - - - -namespace AspNetCore6WithNatasha -{ - public class WeatherForecast - { - public DateTime Date - { - get; set; - } - - public int TemperatureC - { - get; set; - } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary - { - get; set; - } - } -} \ No newline at end of file diff --git a/examples/AspNetCore7/App_Data/PluginCore.Config.json b/examples/AspNetCore7/App_Data/PluginCore.Config.json new file mode 100644 index 00000000..cc4c80c2 --- /dev/null +++ b/examples/AspNetCore7/App_Data/PluginCore.Config.json @@ -0,0 +1 @@ +{"Admin":{"UserName":"admin","Password":"ABC12345"},"FrontendMode":"LocalEmbedded","RemoteFrontend":"https://cdn.jsdelivr.net/gh/yiyungent/plugincore-admin-frontend@0.3.1/dist-cdn","PluginWidgetDebug":false} \ No newline at end of file diff --git a/examples/AspNetCore7/App_Data/plugin.config.json b/examples/AspNetCore7/App_Data/plugin.config.json new file mode 100644 index 00000000..82704e71 --- /dev/null +++ b/examples/AspNetCore7/App_Data/plugin.config.json @@ -0,0 +1 @@ +{"EnabledPlugins":["HelloWorldPlugin"]} \ No newline at end of file diff --git a/examples/AspNetCore6/AspNetCore6.csproj b/examples/AspNetCore7/AspNetCore7.csproj similarity index 87% rename from examples/AspNetCore6/AspNetCore6.csproj rename to examples/AspNetCore7/AspNetCore7.csproj index 5275c3ac..6507c6b3 100644 --- a/examples/AspNetCore6/AspNetCore6.csproj +++ b/examples/AspNetCore7/AspNetCore7.csproj @@ -1,13 +1,14 @@ - net6.0 + net7.0 enable enable + 11 - + diff --git a/examples/AspNetCore6/Controllers/WeatherForecastController.cs b/examples/AspNetCore7/Controllers/WeatherForecastController.cs similarity index 97% rename from examples/AspNetCore6/Controllers/WeatherForecastController.cs rename to examples/AspNetCore7/Controllers/WeatherForecastController.cs index adbfb0f4..6f0c806c 100644 --- a/examples/AspNetCore6/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore7/Controllers/WeatherForecastController.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Mvc; -namespace AspNetCore6.Controllers +namespace AspNetCore7.Controllers { [ApiController] [Route("[controller]")] diff --git a/examples/AspNetCore7/Plugins/HelloWorldPlugin/HelloWorldPlugin.dll b/examples/AspNetCore7/Plugins/HelloWorldPlugin/HelloWorldPlugin.dll new file mode 100644 index 00000000..d6103abc Binary files /dev/null and b/examples/AspNetCore7/Plugins/HelloWorldPlugin/HelloWorldPlugin.dll differ diff --git a/examples/AspNetCore7/Plugins/HelloWorldPlugin/PluginCore.IPlugins.dll b/examples/AspNetCore7/Plugins/HelloWorldPlugin/PluginCore.IPlugins.dll new file mode 100644 index 00000000..af7d551e Binary files /dev/null and b/examples/AspNetCore7/Plugins/HelloWorldPlugin/PluginCore.IPlugins.dll differ diff --git a/examples/AspNetCore6/Program.cs b/examples/AspNetCore7/Program.cs similarity index 86% rename from examples/AspNetCore6/Program.cs rename to examples/AspNetCore7/Program.cs index 7612839a..bf97f3a8 100644 --- a/examples/AspNetCore6/Program.cs +++ b/examples/AspNetCore7/Program.cs @@ -7,7 +7,7 @@ -// PluginCore ռ +// ���� PluginCore ��������ռ� using PluginCore.AspNetCore.Extensions; @@ -20,7 +20,7 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -// 1. PluginCore +// 1. ��� PluginCore builder.Services.AddPluginCore(); var app = builder.Build(); @@ -32,9 +32,9 @@ app.UseSwaggerUI(); } -app.UseHttpsRedirection(); +//app.UseHttpsRedirection(); -// 2. ʹ PluginCore +// 2. ʹ�� PluginCore app.UsePluginCore(); //app.UseAuthorization(); diff --git a/examples/AspNetCore6/Properties/launchSettings.json b/examples/AspNetCore7/Properties/launchSettings.json similarity index 100% rename from examples/AspNetCore6/Properties/launchSettings.json rename to examples/AspNetCore7/Properties/launchSettings.json diff --git a/examples/AspNetCore6/WeatherForecast.cs b/examples/AspNetCore7/WeatherForecast.cs similarity index 95% rename from examples/AspNetCore6/WeatherForecast.cs rename to examples/AspNetCore7/WeatherForecast.cs index 48a04b06..102797af 100644 --- a/examples/AspNetCore6/WeatherForecast.cs +++ b/examples/AspNetCore7/WeatherForecast.cs @@ -7,7 +7,7 @@ -namespace AspNetCore6 +namespace AspNetCore7 { public class WeatherForecast { diff --git a/examples/AspNetCore6/appsettings.Development.json b/examples/AspNetCore7/appsettings.Development.json similarity index 100% rename from examples/AspNetCore6/appsettings.Development.json rename to examples/AspNetCore7/appsettings.Development.json diff --git a/examples/AspNetCore6/appsettings.json b/examples/AspNetCore7/appsettings.json similarity index 100% rename from examples/AspNetCore6/appsettings.json rename to examples/AspNetCore7/appsettings.json diff --git a/examples/AspNetCore6WithNatasha/App_Data/PluginCore.Config.json b/examples/AspNetCore7WithNatasha/App_Data/PluginCore.Config.json similarity index 100% rename from examples/AspNetCore6WithNatasha/App_Data/PluginCore.Config.json rename to examples/AspNetCore7WithNatasha/App_Data/PluginCore.Config.json diff --git a/examples/AspNetCore6WithNatasha/App_Data/plugin.config.json b/examples/AspNetCore7WithNatasha/App_Data/plugin.config.json similarity index 100% rename from examples/AspNetCore6WithNatasha/App_Data/plugin.config.json rename to examples/AspNetCore7WithNatasha/App_Data/plugin.config.json diff --git a/examples/AspNetCore6WithNatasha/AspNetCore6WithNatasha.csproj b/examples/AspNetCore7WithNatasha/AspNetCore7WithNatasha.csproj similarity index 81% rename from examples/AspNetCore6WithNatasha/AspNetCore6WithNatasha.csproj rename to examples/AspNetCore7WithNatasha/AspNetCore7WithNatasha.csproj index 48be62ba..d1e3e791 100644 --- a/examples/AspNetCore6WithNatasha/AspNetCore6WithNatasha.csproj +++ b/examples/AspNetCore7WithNatasha/AspNetCore7WithNatasha.csproj @@ -1,14 +1,16 @@ - net6.0 + net7.0 enable enable + 11 + AspNetCore6WithNatasha - - + + diff --git a/examples/AspNetCore6WithNatasha/Controllers/WeatherForecastController.cs b/examples/AspNetCore7WithNatasha/Controllers/WeatherForecastController.cs similarity index 96% rename from examples/AspNetCore6WithNatasha/Controllers/WeatherForecastController.cs rename to examples/AspNetCore7WithNatasha/Controllers/WeatherForecastController.cs index 7f809c7c..570e5725 100644 --- a/examples/AspNetCore6WithNatasha/Controllers/WeatherForecastController.cs +++ b/examples/AspNetCore7WithNatasha/Controllers/WeatherForecastController.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Mvc; -namespace AspNetCore6WithNatasha.Controllers +namespace AspNetCore7WithNatasha.Controllers { [ApiController] [Route("[controller]")] @@ -39,4 +39,4 @@ public IEnumerable Get() .ToArray(); } } -} \ No newline at end of file +} diff --git a/examples/AspNetCore7WithNatasha/Natasha/NatashaPluginContext.cs b/examples/AspNetCore7WithNatasha/Natasha/NatashaPluginContext.cs new file mode 100644 index 00000000..56573e59 --- /dev/null +++ b/examples/AspNetCore7WithNatasha/Natasha/NatashaPluginContext.cs @@ -0,0 +1,54 @@ +using System.Reflection; +using PluginCore; +using PluginCore.Interfaces; +using PluginCore.IPlugins.Infrastructure; + +namespace AspNetCore7WithNatasha.Natasha; + +public class NatashaPluginContext : IPluginContext +{ + + public NatashaReferenceDomain Domain + { + get; set; + } + + public NatashaPluginContext(string pluginId) + { + this.Domain = DomainManagement.Create(pluginId); + // 插件的主dll, 不包括插件项目引用的dll + string pluginMainDllFilePath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId, $"{pluginId}.dll"); + this.Domain.LoadPluginWithAllDependency(path: pluginMainDllFilePath); + } + + public string PluginId + { + get; + } + + public IEnumerable Assemblies + { + get + { + return this.Domain.Assemblies; + } + } + + public Assembly LoadFromAssemblyName(AssemblyName assemblyName) + { + var ass = this.Domain.LoadFromAssemblyName(assemblyName); + + return ass; + } + + public Assembly LoadFromStream(Stream assembly) + { + throw new NotImplementedException(); + } + + public void Unload() + { + this.Domain.Unload(); + } +} + diff --git a/examples/AspNetCore7WithNatasha/Natasha/NatashaPluginContextPack.cs b/examples/AspNetCore7WithNatasha/Natasha/NatashaPluginContextPack.cs new file mode 100644 index 00000000..14f31bb4 --- /dev/null +++ b/examples/AspNetCore7WithNatasha/Natasha/NatashaPluginContextPack.cs @@ -0,0 +1,13 @@ +using PluginCore.Interfaces; + +namespace AspNetCore7WithNatasha.Natasha; + +public class NatashaPluginContextPack : IPluginContextPack +{ + public IPluginContext Pack(string pluginId) + { + var context = new NatashaPluginContext(pluginId); + + return context; + } +} diff --git a/examples/AspNetCore6WithNatasha/Program.cs b/examples/AspNetCore7WithNatasha/Program.cs similarity index 84% rename from examples/AspNetCore6WithNatasha/Program.cs rename to examples/AspNetCore7WithNatasha/Program.cs index 45e77f97..507ff5c7 100644 --- a/examples/AspNetCore6WithNatasha/Program.cs +++ b/examples/AspNetCore7WithNatasha/Program.cs @@ -7,7 +7,7 @@ -// PluginCore ռ +// ���� PluginCore ��������ռ� using PluginCore.AspNetCore.Extensions; using PluginCore.Interfaces; @@ -20,8 +20,8 @@ builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -// 1. PluginCore -builder.Services.AddTransient(); +// 1. ��� PluginCore +builder.Services.AddTransient(); builder.Services.AddPluginCore(); var app = builder.Build(); @@ -35,7 +35,7 @@ app.UseHttpsRedirection(); -// 2. ʹ PluginCore +// 2. ʹ�� PluginCore app.UsePluginCore(); //app.UseAuthorization(); diff --git a/examples/AspNetCore6WithNatasha/Properties/launchSettings.json b/examples/AspNetCore7WithNatasha/Properties/launchSettings.json similarity index 100% rename from examples/AspNetCore6WithNatasha/Properties/launchSettings.json rename to examples/AspNetCore7WithNatasha/Properties/launchSettings.json diff --git a/examples/AspNetCore7WithNatasha/WeatherForecast.cs b/examples/AspNetCore7WithNatasha/WeatherForecast.cs new file mode 100644 index 00000000..050e61a1 --- /dev/null +++ b/examples/AspNetCore7WithNatasha/WeatherForecast.cs @@ -0,0 +1,30 @@ +//=================================================== +// License: Apache-2.0 +// Contributors: yiyungent@gmail.com +// Project: https://moeci.com/PluginCore +// GitHub: https://github.com/yiyungent/PluginCore +//=================================================== + + + +namespace AspNetCore7WithNatasha; + +public class WeatherForecast +{ + public DateTime Date + { + get; set; + } + + public int TemperatureC + { + get; set; + } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary + { + get; set; + } +} diff --git a/examples/AspNetCore6WithNatasha/appsettings.Development.json b/examples/AspNetCore7WithNatasha/appsettings.Development.json similarity index 100% rename from examples/AspNetCore6WithNatasha/appsettings.Development.json rename to examples/AspNetCore7WithNatasha/appsettings.Development.json diff --git a/examples/AspNetCore6WithNatasha/appsettings.json b/examples/AspNetCore7WithNatasha/appsettings.json similarity index 100% rename from examples/AspNetCore6WithNatasha/appsettings.json rename to examples/AspNetCore7WithNatasha/appsettings.json diff --git a/plugins/DemoModePlugin/DemoModePlugin.cs b/plugins/DemoModePlugin/DemoModePlugin.cs index b2a4d778..22e41ab6 100644 --- a/plugins/DemoModePlugin/DemoModePlugin.cs +++ b/plugins/DemoModePlugin/DemoModePlugin.cs @@ -5,55 +5,52 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; +using System; using DemoModePlugin.Middlewares; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using PluginCore.IPlugins; -namespace DemoModePlugin +namespace DemoModePlugin; + +public class DemoModePlugin : BasePlugin, IStartupPlugin { - public class DemoModePlugin : BasePlugin, IStartupPlugin + public override (bool IsSuccess, string Message) AfterEnable() { - public override (bool IsSuccess, string Message) AfterEnable() - { - Console.WriteLine($"{nameof(DemoModePlugin)}: {nameof(AfterEnable)}"); - return base.AfterEnable(); - } + Console.WriteLine($"{nameof(DemoModePlugin)}: {nameof(AfterEnable)}"); + return base.AfterEnable(); + } - public override (bool IsSuccess, string Message) BeforeDisable() - { - Console.WriteLine($"{nameof(DemoModePlugin)}: {nameof(BeforeDisable)}"); - return base.BeforeDisable(); - } + public override (bool IsSuccess, string Message) BeforeDisable() + { + Console.WriteLine($"{nameof(DemoModePlugin)}: {nameof(BeforeDisable)}"); + return base.BeforeDisable(); + } - public void ConfigureServices(IServiceCollection services) - { + public void ConfigureServices(IServiceCollection services) + { - } + } - public void Configure(IApplicationBuilder app) - { - app.UseMiddleware(); - } + public void Configure(IApplicationBuilder app) + { + app.UseMiddleware(); + } - public int ConfigureOrder + public int ConfigureOrder + { + get { - get - { - return int.MinValue; - } + return int.MinValue; } + } - public int ConfigureServicesOrder + public int ConfigureServicesOrder + { + get { - get - { - return int.MinValue; - } + return int.MinValue; } } } diff --git a/plugins/DemoModePlugin/DemoModePlugin.csproj b/plugins/DemoModePlugin/DemoModePlugin.csproj index dc09f9b9..34c3a0aa 100644 --- a/plugins/DemoModePlugin/DemoModePlugin.csproj +++ b/plugins/DemoModePlugin/DemoModePlugin.csproj @@ -1,8 +1,8 @@  - netstandard2.0;netcoreapp3.1;net5.0;net6.0 true + net7.0 diff --git a/plugins/DemoModePlugin/Middlewares/DemoModeMiddleware.cs b/plugins/DemoModePlugin/Middlewares/DemoModeMiddleware.cs index e7b1b04a..a58b494e 100644 --- a/plugins/DemoModePlugin/Middlewares/DemoModeMiddleware.cs +++ b/plugins/DemoModePlugin/Middlewares/DemoModeMiddleware.cs @@ -5,9 +5,7 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -15,129 +13,127 @@ using DemoModePlugin.ResponseModel; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; -using PluginCore.Interfaces; using PluginCore.IPlugins; +using PluginCore.IPlugins.Interfaces; -namespace DemoModePlugin.Middlewares -{ - public class DemoModeMiddleware - { - private readonly RequestDelegate _next; - - /// - /// 在 Build 时, 将会 new Middleware(), 最终将所有 Middleware 包装为一个 - /// - /// - public DemoModeMiddleware(RequestDelegate next) - { - _next = next; - } +namespace DemoModePlugin.Middlewares; +public class DemoModeMiddleware +{ + private readonly RequestDelegate _next; - /// - /// - /// - /// - /// 测试,是否运行时添加的Middleware,是否可以依赖注入 - /// - public async Task InvokeAsync(HttpContext httpContext, IPluginFinder pluginFinder) - { - // 测试: 成功 - List plugins = pluginFinder.EnablePlugins()?.ToList(); + /// + /// 在 Build 时, 将会 new Middleware(), 最终将所有 Middleware 包装为一个 + /// + /// + public DemoModeMiddleware(RequestDelegate next) + { + _next = next; + } - bool isMatch = false; - CommonResponseModel responseModel = new CommonResponseModel(); - #region 特殊可用 + /// + /// + /// + /// + /// 测试,是否运行时添加的Middleware,是否可以依赖注入 + /// + public async Task InvokeAsync(HttpContext httpContext, IPluginFinder pluginFinder) + { + // 测试: 成功 + List plugins = pluginFinder.EnablePlugins()?.ToList(); - isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Disable", StringComparison.OrdinalIgnoreCase) - || httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Enable", StringComparison.OrdinalIgnoreCase) - || httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Uninstall", StringComparison.OrdinalIgnoreCase) - || httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Install", StringComparison.OrdinalIgnoreCase); - if (isMatch) - { - KeyValuePair pluginIdPair = httpContext.Request.Query - .FirstOrDefault(m => m.Key.Equals("pluginId", StringComparison.OrdinalIgnoreCase)); - string pluginId = pluginIdPair.Value.FirstOrDefault(); - if (!string.IsNullOrEmpty(pluginId) && pluginId == "HelloWorldPlugin") - { - await _next(httpContext); - return; - } - } + bool isMatch = false; + CommonResponseModel responseModel = new CommonResponseModel(); - #endregion + #region 特殊可用 - // 注意: 忽略大小写 - #region 禁用 - isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Upload", StringComparison.OrdinalIgnoreCase); - if (isMatch) + isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Disable", StringComparison.OrdinalIgnoreCase) + || httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Enable", StringComparison.OrdinalIgnoreCase) + || httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Uninstall", StringComparison.OrdinalIgnoreCase) + || httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Install", StringComparison.OrdinalIgnoreCase); + if (isMatch) + { + KeyValuePair pluginIdPair = httpContext.Request.Query + .FirstOrDefault(m => m.Key.Equals("pluginId", StringComparison.OrdinalIgnoreCase)); + string pluginId = pluginIdPair.Value.FirstOrDefault(); + if (!string.IsNullOrEmpty(pluginId) && pluginId == "HelloWorldPlugin") { - responseModel.code = -1; - responseModel.message = "演示模式: 禁止上传插件"; - string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); - - await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); + await _next(httpContext); return; } - isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Uninstall", StringComparison.OrdinalIgnoreCase); - if (isMatch) - { - responseModel.code = -1; - responseModel.message = "演示模式: 禁止卸载插件"; - string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); + } - await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); - return; - } - isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Disable", StringComparison.OrdinalIgnoreCase); - if (isMatch) - { - responseModel.code = -1; - responseModel.message = "演示模式: 禁止禁用插件"; - string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); + #endregion - await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); - return; - } - isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Delete", StringComparison.OrdinalIgnoreCase); - if (isMatch) - { - responseModel.code = -1; - responseModel.message = "演示模式: 禁止删除插件"; - string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); + // 注意: 忽略大小写 + #region 禁用 + isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Upload", StringComparison.OrdinalIgnoreCase); + if (isMatch) + { + responseModel.code = -1; + responseModel.message = "演示模式: 禁止上传插件"; + string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); - await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); - return; - } - isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Settings", StringComparison.OrdinalIgnoreCase); - string httpMethod = httpContext.Request.Method; - if (isMatch && httpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)) - { - responseModel.code = -1; - responseModel.message = "演示模式: 禁止设置插件"; - string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); + await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); + return; + } + isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Uninstall", StringComparison.OrdinalIgnoreCase); + if (isMatch) + { + responseModel.code = -1; + responseModel.message = "演示模式: 禁止卸载插件"; + string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); - await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); - return; - } - isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/User/Update", StringComparison.OrdinalIgnoreCase); - if (isMatch) - { - responseModel.code = -1; - responseModel.message = "演示模式: 禁止修改用户"; - string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); + await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); + return; + } + isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Disable", StringComparison.OrdinalIgnoreCase); + if (isMatch) + { + responseModel.code = -1; + responseModel.message = "演示模式: 禁止禁用插件"; + string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); - await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); - return; - } - #endregion + await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); + return; + } + isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Delete", StringComparison.OrdinalIgnoreCase); + if (isMatch) + { + responseModel.code = -1; + responseModel.message = "演示模式: 禁止删除插件"; + string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); + await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); + return; + } + isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/Plugins/Settings", StringComparison.OrdinalIgnoreCase); + string httpMethod = httpContext.Request.Method; + if (isMatch && httpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase)) + { + responseModel.code = -1; + responseModel.message = "演示模式: 禁止设置插件"; + string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); - // Call the next delegate/middleware in the pipeline - await _next(httpContext); + await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); + return; } + isMatch = httpContext.Request.Path.Value.StartsWith("/api/plugincore/admin/User/Update", StringComparison.OrdinalIgnoreCase); + if (isMatch) + { + responseModel.code = -1; + responseModel.message = "演示模式: 禁止修改用户"; + string jsonStr = System.Text.Json.JsonSerializer.Serialize(responseModel); + await httpContext.Response.WriteAsync(jsonStr, Encoding.UTF8); + return; + } + #endregion + + + // Call the next delegate/middleware in the pipeline + await _next(httpContext); } -} +} diff --git a/plugins/DemoModePlugin/ResponseModel/CommonResponseModel.cs b/plugins/DemoModePlugin/ResponseModel/CommonResponseModel.cs index a4cb42c7..03820218 100644 --- a/plugins/DemoModePlugin/ResponseModel/CommonResponseModel.cs +++ b/plugins/DemoModePlugin/ResponseModel/CommonResponseModel.cs @@ -5,20 +5,15 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +using System; +namespace DemoModePlugin.ResponseModel; -using System; -using System.Collections.Generic; -using System.Text; - -namespace DemoModePlugin.ResponseModel +public class CommonResponseModel { - public class CommonResponseModel - { - public int code { get; set; } + public int code { get; set; } - public string message { get; set; } + public string message { get; set; } - public Object data { get; set; } - } + public Object data { get; set; } } diff --git a/plugins/DemoModePlugin/SettingsModel.cs b/plugins/DemoModePlugin/SettingsModel.cs index 41b6d4c8..cfb3461a 100644 --- a/plugins/DemoModePlugin/SettingsModel.cs +++ b/plugins/DemoModePlugin/SettingsModel.cs @@ -5,18 +5,11 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +using PluginCore.IPlugins.Models; +namespace DemoModePlugin; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Text; -using PluginCore.Models; - -namespace DemoModePlugin +public class SettingsModel : PluginSettingsModel { - public class SettingsModel : PluginSettingsModel - { - public string Hello { get; set; } - } + public string Hello { get; set; } } diff --git a/plugins/GitHubApiPlugin/Controllers/GitHubController.cs b/plugins/GitHubApiPlugin/Controllers/GitHubController.cs index 4135ff71..14305f89 100644 --- a/plugins/GitHubApiPlugin/Controllers/GitHubController.cs +++ b/plugins/GitHubApiPlugin/Controllers/GitHubController.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Octokit; using PluginCore; + using PluginCore.IPlugins.Infrastructure; namespace GitHubApiPlugin.Controllers { diff --git a/plugins/GitHubApiPlugin/GitHubApiPlugin.csproj b/plugins/GitHubApiPlugin/GitHubApiPlugin.csproj index 10d7df85..46b78e97 100644 --- a/plugins/GitHubApiPlugin/GitHubApiPlugin.csproj +++ b/plugins/GitHubApiPlugin/GitHubApiPlugin.csproj @@ -1,9 +1,9 @@ - netstandard2.0;netcoreapp3.1;net5.0;net6.0 true true + net7.0 @@ -11,7 +11,7 @@ runtime - + diff --git a/plugins/GitHubApiPlugin/SettingsModel.cs b/plugins/GitHubApiPlugin/SettingsModel.cs index 3aaf9f04..37ef3f5e 100644 --- a/plugins/GitHubApiPlugin/SettingsModel.cs +++ b/plugins/GitHubApiPlugin/SettingsModel.cs @@ -11,7 +11,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Text; -using PluginCore.Models; + using PluginCore.IPlugins.Models; namespace GitHubApiPlugin { diff --git a/plugins/HelloWorldPlugin/Controllers/HelloController.cs b/plugins/HelloWorldPlugin/Controllers/HelloController.cs index 56ed4c1f..c1b531ed 100644 --- a/plugins/HelloWorldPlugin/Controllers/HelloController.cs +++ b/plugins/HelloWorldPlugin/Controllers/HelloController.cs @@ -6,29 +6,27 @@ //=================================================== - -using System; -using System.Collections.Generic; -using System.Text; -using HelloWorldPlugin; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; +using PluginCore; +using PluginCore.IPlugins.Infrastructure; -namespace PluginCore.IPlugins.Controllers -{ - [Route("api/plugins/[controller]")] - [ApiController] - public class HelloController : ControllerBase - { +namespace HelloWorldPlugin.Controllers; - public ActionResult Get() - { - SettingsModel settingsModel = PluginSettingsModelFactory.Create("HelloWorldPlugin"); - string str = $"Hello PluginCore ! {settingsModel.Hello}"; +/// +/// Hello World Plugin +/// +[Route("api/plugins/[controller]")] +[ApiController] +public class HelloController : ControllerBase +{ - return Ok(str); - } + [HttpGet] + public ActionResult Get() + { + SettingsModel settingsModel = PluginSettingsModelFactory.Create("HelloWorldPlugin"); + string str = $"Hello PluginCore ! {settingsModel.Hello}"; + return Ok(str); } + } diff --git a/plugins/HelloWorldPlugin/Controllers/HomeController.cs b/plugins/HelloWorldPlugin/Controllers/HomeController.cs index f6a7db3b..46f50414 100644 --- a/plugins/HelloWorldPlugin/Controllers/HomeController.cs +++ b/plugins/HelloWorldPlugin/Controllers/HomeController.cs @@ -5,33 +5,29 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using PluginCore; +using PluginCore.IPlugins.Infrastructure; + +namespace HelloWorldPlugin.Controllers; -namespace HelloWorldPlugin.Controllers +/// +/// 其实也可以不写这个, 直接访问 Plugins/HelloWorldPlugin/index.html +/// +/// 下面的方法, 是去掉 index.html +/// +/// 若 wwwroot 下有其它需要访问的文件, 如何 css, js, 而你又不想每次新增 action 指定返回, 则 Route 必须 Plugins/{PluginId}, +/// 这样访问 Plugins/HelloWorldPlugin/css/main.css 就会访问到你插件下的 wwwroot/css/main.css +/// +[Route("Plugins/HelloWorldPlugin")] +public class HomeController : Controller { - /// - /// 其实也可以不写这个, 直接访问 Plugins/HelloWorldPlugin/index.html - /// - /// 下面的方法, 是去掉 index.html - /// - /// 若 wwwroot 下有其它需要访问的文件, 如何 css, js, 而你又不想每次新增 action 指定返回, 则 Route 必须 Plugins/{PluginId}, - /// 这样访问 Plugins/HelloWorldPlugin/css/main.css 就会访问到你插件下的 wwwroot/css/main.css - /// - [Route("Plugins/HelloWorldPlugin")] - public class HomeController : Controller + [HttpGet] + public async Task Get() { - public async Task Get() - { - string indexFilePath = System.IO.Path.Combine(PluginPathProvider.PluginWwwRootDir("HelloWorldPlugin"), "index.html"); + string indexFilePath = System.IO.Path.Combine(PluginPathProvider.PluginWwwRootDir("HelloWorldPlugin"), "index.html"); - return PhysicalFile(indexFilePath, "text/html"); - } + return PhysicalFile(indexFilePath, "text/html"); } } diff --git a/plugins/HelloWorldPlugin/HelloWorldPlugin.cs b/plugins/HelloWorldPlugin/HelloWorldPlugin.cs index 37b52c3f..52678357 100644 --- a/plugins/HelloWorldPlugin/HelloWorldPlugin.cs +++ b/plugins/HelloWorldPlugin/HelloWorldPlugin.cs @@ -5,75 +5,74 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; +using System; using System.Threading.Tasks; using HelloWorldPlugin.Middlewares; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using PluginCore.IPlugins; +using PluginCore.IPlugins.AspNetCore.IPlugins; +using PluginCore.IPlugins.IPlugins; -namespace HelloWorldPlugin +namespace HelloWorldPlugin; + +public class HelloWorldPlugin : BasePlugin, IStartupXPlugin, IWidgetPlugin { - public class HelloWorldPlugin : BasePlugin, IStartupXPlugin, IWidgetPlugin + public override (bool IsSuccess, string Message) AfterEnable() { - public override (bool IsSuccess, string Message) AfterEnable() - { - Console.WriteLine($"{nameof(HelloWorldPlugin)}: {nameof(AfterEnable)}"); - return base.AfterEnable(); - } + Console.WriteLine($"{nameof(HelloWorldPlugin)}: {nameof(AfterEnable)}"); + return base.AfterEnable(); + } - public override (bool IsSuccess, string Message) BeforeDisable() - { - Console.WriteLine($"{nameof(HelloWorldPlugin)}: {nameof(BeforeDisable)}"); - return base.BeforeDisable(); - } + public override (bool IsSuccess, string Message) BeforeDisable() + { + Console.WriteLine($"{nameof(HelloWorldPlugin)}: {nameof(BeforeDisable)}"); + return base.BeforeDisable(); + } - public void ConfigureServices(IServiceCollection services) - { + public void ConfigureServices(IServiceCollection services) + { - } + } - public void Configure(IApplicationBuilder app) - { - app.UseMiddleware(); - } + public void Configure(IApplicationBuilder app) + { + app.UseMiddleware(); + } - public int ConfigureOrder + public int ConfigureOrder + { + get { - get - { - return 2; - } + return 2; } + } - public int ConfigureServicesOrder + public int ConfigureServicesOrder + { + get { - get - { - return 2; - } + return 2; } + } - public async Task Widget(string widgetKey, params string[] extraPars) + public async Task Widget(string widgetKey, params string[] extraPars) + { + string rtnStr = null; + if (widgetKey == "PluginCore.Admin.Footer") { - string rtnStr = null; - if (widgetKey == "PluginCore.Admin.Footer") + if (extraPars != null) { - if (extraPars != null) - { - Console.WriteLine(string.Join(",", extraPars)); - } - rtnStr = @"
+ Console.WriteLine(string.Join(",", extraPars)); + } + rtnStr = @"

HelloWorldPlugin 注入

HelloWorldPlugin 挂件
"; - } - - return await Task.FromResult(rtnStr); } + + return await Task.FromResult(rtnStr); } } diff --git a/plugins/HelloWorldPlugin/HelloWorldPlugin.csproj b/plugins/HelloWorldPlugin/HelloWorldPlugin.csproj index bd9813c7..325a3fab 100644 --- a/plugins/HelloWorldPlugin/HelloWorldPlugin.csproj +++ b/plugins/HelloWorldPlugin/HelloWorldPlugin.csproj @@ -1,8 +1,8 @@  - netstandard2.0;netcoreapp3.1;net5.0;net6.0 true + net7.0 diff --git a/plugins/HelloWorldPlugin/Middlewares/SayHelloMiddleware.cs b/plugins/HelloWorldPlugin/Middlewares/SayHelloMiddleware.cs index 9846fec0..afd3020c 100644 --- a/plugins/HelloWorldPlugin/Middlewares/SayHelloMiddleware.cs +++ b/plugins/HelloWorldPlugin/Middlewares/SayHelloMiddleware.cs @@ -5,60 +5,57 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using PluginCore.Interfaces; using PluginCore.IPlugins; +using PluginCore.IPlugins.Interfaces; -namespace HelloWorldPlugin.Middlewares +namespace HelloWorldPlugin.Middlewares; + +public class SayHelloMiddleware { - public class SayHelloMiddleware - { - private readonly RequestDelegate _next; + private readonly RequestDelegate _next; - /// - /// 在 Build 时, 将会 new Middleware(), 最终将所有 Middleware 包装为一个 - /// - /// - public SayHelloMiddleware(RequestDelegate next) - { - _next = next; - } + /// + /// 在 Build 时, 将会 new Middleware(), 最终将所有 Middleware 包装为一个 + /// + /// + public SayHelloMiddleware(RequestDelegate next) + { + _next = next; + } - /// - /// - /// - /// - /// 测试,是否运行时添加的Middleware,是否可以依赖注入 - /// - public async Task InvokeAsync(HttpContext httpContext, IPluginFinder pluginFinder) - { - // 测试: 成功 - List plugins = pluginFinder.EnablePlugins()?.ToList(); + /// + /// + /// + /// + /// 测试,是否运行时添加的Middleware,是否可以依赖注入 + /// + public async Task InvokeAsync(HttpContext httpContext,IServiceProvider serviceProvider) + { + //// 测试: 成功 + //List plugins = pluginFinder.EnablePlugins()?.ToList(); - bool isMatch = false; + bool isMatch = false; - isMatch = httpContext.Request.Path.Value.StartsWith("/SayHello"); + isMatch = httpContext.Request.Path.Value.StartsWith("/SayHello"); - if (isMatch) - { - await httpContext.Response.WriteAsync($"Hello World! {DateTime.Now:yyyy-MM-dd HH:mm:ss}
" + - $"{httpContext.Request.Path}
" + - $"{httpContext.Request.QueryString.Value}", Encoding.UTF8); - } - else - { - // Call the next delegate/middleware in the pipeline - await _next(httpContext); - } + if (isMatch) + { + await httpContext.Response.WriteAsync($"Hello World! {DateTime.Now:yyyy-MM-dd HH:mm:ss}
" + + $"{httpContext.Request.Path}
" + + $"{httpContext.Request.QueryString.Value}", Encoding.UTF8); + } + else + { + // Call the next delegate/middleware in the pipeline + await _next(httpContext); } - } + } diff --git a/plugins/HelloWorldPlugin/SettingsModel.cs b/plugins/HelloWorldPlugin/SettingsModel.cs index 8a142aea..4bb53a8c 100644 --- a/plugins/HelloWorldPlugin/SettingsModel.cs +++ b/plugins/HelloWorldPlugin/SettingsModel.cs @@ -5,18 +5,11 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +using PluginCore.IPlugins.Models; +namespace HelloWorldPlugin; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Text; -using PluginCore.Models; - -namespace HelloWorldPlugin +public class SettingsModel : PluginSettingsModel { - public class SettingsModel : PluginSettingsModel - { - public string Hello { get; set; } - } + public string Hello { get; set; } } diff --git a/plugins/HexoPlugin/Controllers/HomeController.cs b/plugins/HexoPlugin/Controllers/HomeController.cs index 3db2e911..d43b0a5a 100644 --- a/plugins/HexoPlugin/Controllers/HomeController.cs +++ b/plugins/HexoPlugin/Controllers/HomeController.cs @@ -5,35 +5,31 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using PluginCore; -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; +using PluginCore.IPlugins.Infrastructure; + +namespace HexoPlugin.Controllers; -namespace HexoPlugin.Controllers +/// +/// ****** 注意: 请将 HexoPlugin 修改为你的 PluginId ************ +/// +/// 其实也可以不写这个, 直接访问 Plugins/HexoPlugin/index.html +/// +/// 下面的方法, 是去掉 index.html +/// +/// 若 wwwroot 下有其它需要访问的文件, 如何 css, js, 而你又不想每次新增 action 指定返回, 则 Route 必须 Plugins/{PluginId}, +/// 这样访问 Plugins/HexoPlugin/css/main.css 就会访问到你插件下的 wwwroot/css/main.css +/// +[Route("Plugins/HexoPlugin")] +public class HomeController : Controller { - /// - /// ****** 注意: 请将 HexoPlugin 修改为你的 PluginId ************ - /// - /// 其实也可以不写这个, 直接访问 Plugins/HexoPlugin/index.html - /// - /// 下面的方法, 是去掉 index.html - /// - /// 若 wwwroot 下有其它需要访问的文件, 如何 css, js, 而你又不想每次新增 action 指定返回, 则 Route 必须 Plugins/{PluginId}, - /// 这样访问 Plugins/HexoPlugin/css/main.css 就会访问到你插件下的 wwwroot/css/main.css - /// - [Route("Plugins/HexoPlugin")] - public class HomeController : Controller + [HttpGet] + public async Task Get() { - public async Task Get() - { - string indexFilePath = System.IO.Path.Combine(PluginPathProvider.PluginWwwRootDir("HexoPlugin"), "index.html"); + string indexFilePath = System.IO.Path.Combine(PluginPathProvider.PluginWwwRootDir("HexoPlugin"), "index.html"); - return PhysicalFile(indexFilePath, "text/html"); - } + return PhysicalFile(indexFilePath, "text/html"); } } diff --git a/plugins/HexoPlugin/HexoPlugin.cs b/plugins/HexoPlugin/HexoPlugin.cs index bbfbc3d5..d7c643c3 100644 --- a/plugins/HexoPlugin/HexoPlugin.cs +++ b/plugins/HexoPlugin/HexoPlugin.cs @@ -7,89 +7,90 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using PluginCore; using PluginCore.IPlugins; using System; using System.Threading.Tasks; using System.Linq; +using PluginCore.IPlugins.Infrastructure; +using PluginCore.IPlugins.IPlugins; -namespace HexoPlugin +namespace HexoPlugin; + +public class HexoPlugin : BasePlugin, IWidgetPlugin { - public class HexoPlugin : BasePlugin, IWidgetPlugin - { - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IHttpContextAccessor _httpContextAccessor; - public HexoPlugin(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } + public HexoPlugin(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } - public override (bool IsSuccess, string Message) AfterEnable() - { - Console.WriteLine($"{nameof(HexoPlugin)}: {nameof(AfterEnable)}"); - return base.AfterEnable(); - } + public override (bool IsSuccess, string Message) AfterEnable() + { + Console.WriteLine($"{nameof(HexoPlugin)}: {nameof(AfterEnable)}"); + return base.AfterEnable(); + } - public override (bool IsSuccess, string Message) BeforeDisable() - { - Console.WriteLine($"{nameof(HexoPlugin)}: {nameof(BeforeDisable)}"); - return base.BeforeDisable(); - } + public override (bool IsSuccess, string Message) BeforeDisable() + { + Console.WriteLine($"{nameof(HexoPlugin)}: {nameof(BeforeDisable)}"); + return base.BeforeDisable(); + } - public Task Widget(string widgetKey, params string[] extraPars) + public Task Widget(string widgetKey, params string[] extraPars) + { + string rtn = string.Empty; + if (_httpContextAccessor?.HttpContext?.Request != null) { - string rtn = string.Empty; - if (_httpContextAccessor?.HttpContext?.Request != null) + _httpContextAccessor.HttpContext.Request.Headers.TryGetValue("origin", out Microsoft.Extensions.Primitives.StringValues originValue); + SettingsModel settingsModel = PluginSettingsModelFactory.Create("HexoPlugin"); + string[] origins = settingsModel.Origins?.Select(m => m.Trim())?.ToArray(); + if (origins == null || origins.Length <= 0) { - _httpContextAccessor.HttpContext.Request.Headers.TryGetValue("origin", out Microsoft.Extensions.Primitives.StringValues originValue); - SettingsModel settingsModel = PluginSettingsModelFactory.Create("HexoPlugin"); - string[] origins = settingsModel.Origins?.Select(m => m.Trim())?.ToArray(); - if (origins == null || origins.Length <= 0) - { - return Task.FromResult(""); - } - if (origins.Contains(originValue.ToString().Trim())) + return Task.FromResult(""); + } + if (origins.Contains(originValue.ToString().Trim())) + { + switch (widgetKey) { - switch (widgetKey) - { - case "Hexo.Head": - // 1. - rtn += settingsModel.Head; + case "Hexo.Head": + // 1. + rtn += settingsModel.Head; - // 2. - string tokenJsonFilePath = System.IO.Path.Combine(PluginPathProvider.PluginsRootPath(), "HexoPlugin", "token.json"); - string tokenJsonStr = System.IO.File.ReadAllText(tokenJsonFilePath, System.Text.Encoding.UTF8); - // 注意: 不要再对 tokenJson 使用 JSON.stringify - rtn += $@""; - // 3. - string headFilePath = System.IO.Path.Combine(PluginPathProvider.PluginsRootPath(), "HexoPlugin", "head.html"); - string headStr = System.IO.File.ReadAllText(headFilePath, System.Text.Encoding.UTF8); - rtn += headStr; + // 3. + string headFilePath = System.IO.Path.Combine(PluginPathProvider.PluginsRootPath(), "HexoPlugin", "head.html"); + string headStr = System.IO.File.ReadAllText(headFilePath, System.Text.Encoding.UTF8); + rtn += headStr; - break; - case "Hexo.Footer": - // 1. - rtn += settingsModel.Footer; + break; + case "Hexo.Footer": + // 1. + rtn += settingsModel.Footer; - // 2. - string footerFilePath = System.IO.Path.Combine(PluginPathProvider.PluginsRootPath(), "HexoPlugin", "footer.html"); - string footerStr = System.IO.File.ReadAllText(footerFilePath, System.Text.Encoding.UTF8); - rtn += footerStr; + // 2. + string footerFilePath = System.IO.Path.Combine(PluginPathProvider.PluginsRootPath(), "HexoPlugin", "footer.html"); + string footerStr = System.IO.File.ReadAllText(footerFilePath, System.Text.Encoding.UTF8); + rtn += footerStr; - break; - default: - break; - } + break; + default: + break; } } - - return Task.FromResult(rtn); } + + return Task.FromResult(rtn); } } diff --git a/plugins/HexoPlugin/HexoPlugin.csproj b/plugins/HexoPlugin/HexoPlugin.csproj index 1315c195..7c9de4b5 100644 --- a/plugins/HexoPlugin/HexoPlugin.csproj +++ b/plugins/HexoPlugin/HexoPlugin.csproj @@ -1,8 +1,8 @@ - netstandard2.0;netcoreapp3.1;net5.0;net6.0 true + net7.0 diff --git a/plugins/HexoPlugin/SettingsModel.cs b/plugins/HexoPlugin/SettingsModel.cs index 947568fa..85e9455f 100644 --- a/plugins/HexoPlugin/SettingsModel.cs +++ b/plugins/HexoPlugin/SettingsModel.cs @@ -6,18 +6,16 @@ //=================================================== +using PluginCore.IPlugins.Models; -using PluginCore.Models; +namespace HexoPlugin; -namespace HexoPlugin +public class SettingsModel : PluginSettingsModel { - public class SettingsModel : PluginSettingsModel - { - public string[] Origins { get; set; } + public string[] Origins { get; set; } - public string Head { get; set; } + public string Head { get; set; } - public string Footer { get; set; } + public string Footer { get; set; } - } } diff --git a/plugins/TestTimeJobPlugin/SettingsModel.cs b/plugins/TestTimeJobPlugin/SettingsModel.cs index 5c1d32d3..432733a4 100644 --- a/plugins/TestTimeJobPlugin/SettingsModel.cs +++ b/plugins/TestTimeJobPlugin/SettingsModel.cs @@ -5,18 +5,11 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +using PluginCore.IPlugins.Models; +namespace TestTimeJobPlugin; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Text; -using PluginCore.Models; - -namespace TestTimeJobPlugin +public class SettingsModel : PluginSettingsModel { - public class SettingsModel : PluginSettingsModel - { - public string Hello { get; set; } - } + public string Hello { get; set; } } diff --git a/plugins/TestTimeJobPlugin/TestTimeJobPlugin.cs b/plugins/TestTimeJobPlugin/TestTimeJobPlugin.cs index 2506c7c4..b9885f5b 100644 --- a/plugins/TestTimeJobPlugin/TestTimeJobPlugin.cs +++ b/plugins/TestTimeJobPlugin/TestTimeJobPlugin.cs @@ -5,52 +5,39 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; +using System; using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; using PluginCore; using PluginCore.IPlugins; +using PluginCore.IPlugins.Infrastructure; +using PluginCore.IPlugins.IPlugins; + +namespace TestTimeJobPlugin; -namespace TestTimeJobPlugin +public class TestTimeJobPlugin : BasePlugin, ITimeJobPlugin { - public class TestTimeJobPlugin : BasePlugin, ITimeJobPlugin + public override (bool IsSuccess, string Message) AfterEnable() { - public override (bool IsSuccess, string Message) AfterEnable() - { - Console.WriteLine($"{nameof(TestTimeJobPlugin)}: {nameof(AfterEnable)}"); - return base.AfterEnable(); - } - - public override (bool IsSuccess, string Message) BeforeDisable() - { - Console.WriteLine($"{nameof(TestTimeJobPlugin)}: {nameof(BeforeDisable)}"); - return base.BeforeDisable(); - } - - public long SecondsPeriod - { - get - { - return 10; - } - } - - public static int Count { get; set; } - - public async Task ExecuteAsync() - { - SettingsModel model = PluginSettingsModelFactory.Create("TestTimeJobPlugin"); - Count++; - model.Hello = $"{model.Hello} - {Count}"; + Console.WriteLine($"{nameof(TestTimeJobPlugin)}: {nameof(AfterEnable)}"); + return base.AfterEnable(); + } - PluginSettingsModelFactory.Save(model, "TestTimeJobPlugin"); - } + public override (bool IsSuccess, string Message) BeforeDisable() + { + Console.WriteLine($"{nameof(TestTimeJobPlugin)}: {nameof(BeforeDisable)}"); + return base.BeforeDisable(); + } + public long SecondsPeriod { get { return 10; } } + public static int Count { get; set; } + public async Task ExecuteAsync() + { + SettingsModel model = PluginSettingsModelFactory.Create("TestTimeJobPlugin"); + Count++; + model.Hello = $"{model.Hello} - {Count}"; + PluginSettingsModelFactory.Save(model, "TestTimeJobPlugin"); } } diff --git a/plugins/TestTimeJobPlugin/TestTimeJobPlugin.csproj b/plugins/TestTimeJobPlugin/TestTimeJobPlugin.csproj index dc09f9b9..34c3a0aa 100644 --- a/plugins/TestTimeJobPlugin/TestTimeJobPlugin.csproj +++ b/plugins/TestTimeJobPlugin/TestTimeJobPlugin.csproj @@ -1,8 +1,8 @@  - netstandard2.0;netcoreapp3.1;net5.0;net6.0 true + net7.0 diff --git a/plugins/WebSocketDemoPlugin/Controllers/WebSocketController.cs b/plugins/WebSocketDemoPlugin/Controllers/WebSocketController.cs index 734db64e..1db11fa9 100644 --- a/plugins/WebSocketDemoPlugin/Controllers/WebSocketController.cs +++ b/plugins/WebSocketDemoPlugin/Controllers/WebSocketController.cs @@ -5,9 +5,7 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; using System.Net; @@ -15,54 +13,47 @@ using System.Threading; using System.Threading.Tasks; +namespace WebSocketDemoPlugin.Controllers; -namespace WebSocketDemoPlugin.Controllers +/// +/// 接受 WebSocket 请求: 方式1: 通过 Controller +/// +public class WebSocketController : ControllerBase { - /// - /// 接受 WebSocket 请求: 方式1: 通过 Controller - /// - public class WebSocketController : ControllerBase + [HttpGet("/plugins/ws")] + public async Task Get() { - [HttpGet("/plugins/ws")] - public async Task Get() + if (HttpContext.WebSockets.IsWebSocketRequest) { - if (HttpContext.WebSockets.IsWebSocketRequest) - { - using WebSocket webSocket = await - HttpContext.WebSockets.AcceptWebSocketAsync(); - await Echo(HttpContext, webSocket); - - //using (WebSocket webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync()) - //{ - // await Echo(HttpContext, webSocket); - //} - } - else - { - HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - } + using WebSocket webSocket = await + HttpContext.WebSockets.AcceptWebSocketAsync(); + await Echo(HttpContext, webSocket); + + //using (WebSocket webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync()) + //{ + // await Echo(HttpContext, webSocket); + //} } - - private async Task Echo(HttpContext httpContext, WebSocket webSocket) + else { - var receiveBuffer = new byte[1024 * 4]; - WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); - while (!result.CloseStatus.HasValue) - { - byte[] myBuffer = System.Text.Encoding.UTF8.GetBytes("哈哈哈哈!"); - await webSocket.SendAsync(new ArraySegment(myBuffer), WebSocketMessageType.Text, false, CancellationToken.None); - - - await webSocket.SendAsync(new ArraySegment(receiveBuffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); - - // 继续接收 - result = await webSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); - } - await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; } + } + private async Task Echo(HttpContext httpContext, WebSocket webSocket) + { + var receiveBuffer = new byte[1024 * 4]; + WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); + while (!result.CloseStatus.HasValue) + { + byte[] myBuffer = System.Text.Encoding.UTF8.GetBytes("哈哈哈哈!"); + await webSocket.SendAsync(new ArraySegment(myBuffer), WebSocketMessageType.Text, false, CancellationToken.None); + await webSocket.SendAsync(new ArraySegment(receiveBuffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); - + // 继续接收 + result = await webSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); + } + await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); } } diff --git a/plugins/WebSocketDemoPlugin/SettingsModel.cs b/plugins/WebSocketDemoPlugin/SettingsModel.cs index dad98f22..609d2a68 100644 --- a/plugins/WebSocketDemoPlugin/SettingsModel.cs +++ b/plugins/WebSocketDemoPlugin/SettingsModel.cs @@ -5,18 +5,11 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +using PluginCore.IPlugins.Models; +namespace WebSocketDemoPlugin; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Text; -using PluginCore.Models; - -namespace WebSocketDemoPlugin +public class SettingsModel : PluginSettingsModel { - public class SettingsModel : PluginSettingsModel - { - public string Hello { get; set; } - } + public string Hello { get; set; } } diff --git a/plugins/WebSocketDemoPlugin/WebSocketDemoPlugin.cs b/plugins/WebSocketDemoPlugin/WebSocketDemoPlugin.cs index 5b772a7d..cb4be611 100644 --- a/plugins/WebSocketDemoPlugin/WebSocketDemoPlugin.cs +++ b/plugins/WebSocketDemoPlugin/WebSocketDemoPlugin.cs @@ -5,10 +5,7 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Net; +using System; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; @@ -17,93 +14,92 @@ using Microsoft.Extensions.DependencyInjection; using PluginCore.IPlugins; -namespace WebSocketDemoPlugin +namespace WebSocketDemoPlugin; + +public class WebSocketDemoPlugin : BasePlugin, /*IStartupXPlugin,*/ IStartupPlugin { - public class WebSocketDemoPlugin : BasePlugin, /*IStartupXPlugin,*/ IStartupPlugin + public override (bool IsSuccess, string Message) AfterEnable() { - public override (bool IsSuccess, string Message) AfterEnable() - { - Console.WriteLine($"{nameof(WebSocketDemoPlugin)}: {nameof(AfterEnable)}"); - return base.AfterEnable(); - } + Console.WriteLine($"{nameof(WebSocketDemoPlugin)}: {nameof(AfterEnable)}"); + return base.AfterEnable(); + } - public override (bool IsSuccess, string Message) BeforeDisable() - { - Console.WriteLine($"{nameof(WebSocketDemoPlugin)}: {nameof(BeforeDisable)}"); - return base.BeforeDisable(); - } + public override (bool IsSuccess, string Message) BeforeDisable() + { + Console.WriteLine($"{nameof(WebSocketDemoPlugin)}: {nameof(BeforeDisable)}"); + return base.BeforeDisable(); + } - public void ConfigureServices(IServiceCollection services) - { + public void ConfigureServices(IServiceCollection services) + { - } + } - public void Configure(IApplicationBuilder app) + public void Configure(IApplicationBuilder app) + { + // 1. 首先必须 添加 WebSocketMiddleware + var webSocketOptions = new WebSocketOptions() { - // 1. 首先必须 添加 WebSocketMiddleware - var webSocketOptions = new WebSocketOptions() - { - // KeepAliveInterval - 向客户端发送 "ping" 帧的频率,以确保代理保持连接处于打开状态。 默认值为 2 分钟。 - KeepAliveInterval = TimeSpan.FromSeconds(120), - }; - app.UseWebSockets(webSocketOptions); + // KeepAliveInterval - 向客户端发送 "ping" 帧的频率,以确保代理保持连接处于打开状态。 默认值为 2 分钟。 + KeepAliveInterval = TimeSpan.FromSeconds(120), + }; + app.UseWebSockets(webSocketOptions); - #region 接受 WebSocket 请求: 方式2: 通过 Middleware - //app.Use(async (context, next) => - // { - // if (context.Request.Path == "/plugins/ws") - // { - // if (context.WebSockets.IsWebSocketRequest) - // { - // using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync()) - // { - // await Echo(context, webSocket); - // } - // } - // else - // { - // context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - // } - // } - // else - // { - // await next(); - // } + #region 接受 WebSocket 请求: 方式2: 通过 Middleware + //app.Use(async (context, next) => + // { + // if (context.Request.Path == "/plugins/ws") + // { + // if (context.WebSockets.IsWebSocketRequest) + // { + // using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync()) + // { + // await Echo(context, webSocket); + // } + // } + // else + // { + // context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + // } + // } + // else + // { + // await next(); + // } - // }); - #endregion - } + // }); + #endregion + } - #region 方式2: 通过 Middleware - private async Task Echo(HttpContext context, WebSocket webSocket) + #region 方式2: 通过 Middleware + private async Task Echo(HttpContext context, WebSocket webSocket) + { + var buffer = new byte[1024 * 4]; + WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + while (!result.CloseStatus.HasValue) { - var buffer = new byte[1024 * 4]; - WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - while (!result.CloseStatus.HasValue) - { - await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + await webSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); - result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - } - await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); } - #endregion + await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + } + #endregion - public int ConfigureOrder + public int ConfigureOrder + { + get { - get - { - return 2; - } + return 2; } + } - public int ConfigureServicesOrder + public int ConfigureServicesOrder + { + get { - get - { - return 2; - } + return 2; } } } diff --git a/plugins/WebSocketDemoPlugin/WebSocketDemoPlugin.csproj b/plugins/WebSocketDemoPlugin/WebSocketDemoPlugin.csproj index 99503b82..68a49055 100644 --- a/plugins/WebSocketDemoPlugin/WebSocketDemoPlugin.csproj +++ b/plugins/WebSocketDemoPlugin/WebSocketDemoPlugin.csproj @@ -1,9 +1,9 @@  - netstandard2.0;netcoreapp3.1;net5.0;net6.0 latest true + net7.0 diff --git a/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIBuilderExtensions.cs b/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIBuilderExtensions.cs index 0e06d5cc..de8a4586 100644 --- a/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIBuilderExtensions.cs +++ b/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIBuilderExtensions.cs @@ -5,89 +5,80 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Options; -namespace PluginCore.AspNetCore.AdminUI +namespace PluginCore.AspNetCore.AdminUI; + +public static class PluginCoreAdminUIBuilderExtensions { - public static class PluginCoreAdminUIBuilderExtensions - { - /// - /// Register the SwaggerUI middleware with provided options - /// - public static IApplicationBuilder UsePluginCoreAdminUI(this IApplicationBuilder app, PluginCoreAdminUIOptions options) - { - #region Old - 区分 - //Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create(); - - //switch (pluginCoreConfig.FrontendMode?.ToLower()) - //{ - // case "localembedded": - // app.UseMiddleware(options); - // break; - // case "localfolder": - - // #region LocalFolder - // //string contentRootPath = Directory.GetCurrentDirectory(); - - // // https://docs.microsoft.com/zh-CN/aspnet/core/fundamentals/static-files?view=aspnetcore-5.0 - // //var options = new DefaultFilesOptions() - // //{ - // // RequestPath = "/PluginCore/Admin", - // //}; - // //// TODO: 404: 无效, 失败, 改为使用 Controller 手动指定 - // ////options.DefaultFileNames.Add("PluginCoreAdmin/index.html"); - // //app.UseDefaultFiles(options); - - // // 注意: 为了无需重启Web,而更新是否本地前端配置, 因此此项保持常驻开启 - // // 因此, 需要保证 PluginCoreAdmin 文件夹存在 - // string pluginCoreAdminDir = PluginPathProvider.PluginCoreAdminDir(); - // app.UseStaticFiles(new StaticFileOptions - // { - // FileProvider = new PhysicalFileProvider( - // pluginCoreAdminDir), - // RequestPath = "/PluginCore/Admin" - // }); - // #endregion - - // break; - // case "remotecdn": - - // break; - // default: - // app.UseMiddleware(options); - // break; - //} - - //return app; - #endregion + /// + /// Register the SwaggerUI middleware with provided options + /// + public static IApplicationBuilder UsePluginCoreAdminUI(this IApplicationBuilder app, PluginCoreAdminUIOptions options) + { + #region Old - 区分 + //Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create(); + + //switch (pluginCoreConfig.FrontendMode?.ToLower()) + //{ + // case "localembedded": + // app.UseMiddleware(options); + // break; + // case "localfolder": + + // #region LocalFolder + // //string contentRootPath = Directory.GetCurrentDirectory(); + + // // https://docs.microsoft.com/zh-CN/aspnet/core/fundamentals/static-files?view=aspnetcore-5.0 + // //var options = new DefaultFilesOptions() + // //{ + // // RequestPath = "/PluginCore/Admin", + // //}; + // //// TODO: 404: 无效, 失败, 改为使用 Controller 手动指定 + // ////options.DefaultFileNames.Add("PluginCoreAdmin/index.html"); + // //app.UseDefaultFiles(options); + + // // 注意: 为了无需重启Web,而更新是否本地前端配置, 因此此项保持常驻开启 + // // 因此, 需要保证 PluginCoreAdmin 文件夹存在 + // string pluginCoreAdminDir = PluginPathProvider.PluginCoreAdminDir(); + // app.UseStaticFiles(new StaticFileOptions + // { + // FileProvider = new PhysicalFileProvider( + // pluginCoreAdminDir), + // RequestPath = "/PluginCore/Admin" + // }); + // #endregion + + // break; + // case "remotecdn": + + // break; + // default: + // app.UseMiddleware(options); + // break; + //} + + //return app; + #endregion - return app.UseMiddleware(options); - } - - /// - /// Register the SwaggerUI middleware with optional setup action for DI-injected options - /// - public static IApplicationBuilder UsePluginCoreAdminUI( - this IApplicationBuilder app) + return app.UseMiddleware(options); + } + + /// + /// Register the SwaggerUI middleware with optional setup action for DI-injected options + /// + public static IApplicationBuilder UsePluginCoreAdminUI( + this IApplicationBuilder app) + { + PluginCoreAdminUIOptions options = new PluginCoreAdminUIOptions() { - PluginCoreAdminUIOptions options = new PluginCoreAdminUIOptions() - { - }; + }; - return app.UsePluginCoreAdminUI(options); - } + return app.UsePluginCoreAdminUI(options); + } - } } diff --git a/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIMiddleware.cs b/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIMiddleware.cs index 3fca3ef0..1d6a20f7 100644 --- a/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIMiddleware.cs +++ b/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIMiddleware.cs @@ -5,9 +5,6 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -22,127 +19,125 @@ using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using System.Text.Json; -using System.Text.Json.Serialization; +using PluginCore.IPlugins.Infrastructure; + +namespace PluginCore.AspNetCore.AdminUI; -namespace PluginCore.AspNetCore.AdminUI +public class PluginCoreAdminUIMiddleware { - public class PluginCoreAdminUIMiddleware + private const string EmbeddedFileNamespace = "PluginCore.AspNetCore.node_modules.plugincore_admin_frontend.dist"; + + private readonly PluginCoreAdminUIOptions _options; + private readonly StaticFileMiddleware _staticFileMiddleware; + + + public PluginCoreAdminUIMiddleware( + RequestDelegate next, + IWebHostEnvironment hostingEnv, + ILoggerFactory loggerFactory, + PluginCoreAdminUIOptions options) { - private const string EmbeddedFileNamespace = "PluginCore.AspNetCore.node_modules.plugincore_admin_frontend.dist"; + _options = options ?? new PluginCoreAdminUIOptions(); - private readonly PluginCoreAdminUIOptions _options; - private readonly StaticFileMiddleware _staticFileMiddleware; + _staticFileMiddleware = CreateStaticFileMiddleware(next, hostingEnv, loggerFactory, options); + } + public async Task Invoke(HttpContext httpContext) + { + var httpMethod = httpContext.Request.Method; + var path = httpContext.Request.Path.Value; - public PluginCoreAdminUIMiddleware( - RequestDelegate next, - IWebHostEnvironment hostingEnv, - ILoggerFactory loggerFactory, - PluginCoreAdminUIOptions options) + // 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)) { - _options = options ?? new PluginCoreAdminUIOptions(); + // Use relative redirect to support proxy environments + var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/") + ? "index.html" + : $"{path.Split('/').Last()}/index.html"; - _staticFileMiddleware = CreateStaticFileMiddleware(next, hostingEnv, loggerFactory, options); + RespondWithRedirect(httpContext.Response, relativeIndexUrl); + return; } - public async Task Invoke(HttpContext httpContext) + if (httpMethod == "GET" && Regex.IsMatch(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase)) { - 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; - } + await RespondWithIndexHtml(httpContext.Response); + return; + } - if (httpMethod == "GET" && Regex.IsMatch(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase)) - { - await RespondWithIndexHtml(httpContext.Response); - return; - } + await _staticFileMiddleware.Invoke(httpContext); + } - await _staticFileMiddleware.Invoke(httpContext); + private StaticFileMiddleware CreateStaticFileMiddleware( + RequestDelegate next, + IWebHostEnvironment hostingEnv, + ILoggerFactory loggerFactory, + PluginCoreAdminUIOptions options) + { + Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create(); + IFileProvider fileProvider = null; + switch (pluginCoreConfig.FrontendMode?.ToLower()) + { + case "localembedded": + fileProvider = new EmbeddedFileProvider(typeof(PluginCoreAdminUIMiddleware).GetTypeInfo().Assembly, + EmbeddedFileNamespace); + break; + case "localfolder": + string absoluteRootPath = PluginPathProvider.PluginCoreAdminDir(); + fileProvider = new PhysicalFileProvider(absoluteRootPath); + break; + case "remotecdn": + fileProvider = new PluginCoreAdminUIRemoteFileProvider(pluginCoreConfig.RemoteFrontend); + break; + default: + fileProvider = new EmbeddedFileProvider(typeof(PluginCoreAdminUIMiddleware).GetTypeInfo().Assembly, + EmbeddedFileNamespace); + break; } - private StaticFileMiddleware CreateStaticFileMiddleware( - RequestDelegate next, - IWebHostEnvironment hostingEnv, - ILoggerFactory loggerFactory, - PluginCoreAdminUIOptions options) + var staticFileOptions = new StaticFileOptions { - Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create(); - IFileProvider fileProvider = null; - switch (pluginCoreConfig.FrontendMode?.ToLower()) - { - case "localembedded": - fileProvider = new EmbeddedFileProvider(typeof(PluginCoreAdminUIMiddleware).GetTypeInfo().Assembly, - EmbeddedFileNamespace); - break; - case "localfolder": - string absoluteRootPath = PluginPathProvider.PluginCoreAdminDir(); - fileProvider = new PhysicalFileProvider(absoluteRootPath); - break; - case "remotecdn": - fileProvider = new PluginCoreAdminUIRemoteFileProvider(pluginCoreConfig.RemoteFrontend); - break; - default: - fileProvider = new EmbeddedFileProvider(typeof(PluginCoreAdminUIMiddleware).GetTypeInfo().Assembly, - EmbeddedFileNamespace); - break; - } + RequestPath = string.IsNullOrEmpty(options.RoutePrefix) ? string.Empty : $"/{options.RoutePrefix}", + FileProvider = fileProvider, + }; - var staticFileOptions = new StaticFileOptions - { - RequestPath = string.IsNullOrEmpty(options.RoutePrefix) ? string.Empty : $"/{options.RoutePrefix}", - FileProvider = fileProvider, - }; + return new StaticFileMiddleware(next, hostingEnv, Options.Create(staticFileOptions), loggerFactory); + } - return new StaticFileMiddleware(next, hostingEnv, Options.Create(staticFileOptions), loggerFactory); - } + private void RespondWithRedirect(HttpResponse response, string location) + { + response.StatusCode = 301; + response.Headers["Location"] = location; + } - 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"; - private async Task RespondWithIndexHtml(HttpResponse response) + using (var stream = _options.IndexStream()) { - 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()) { - // 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); + htmlBuilder.Replace(entry.Key, entry.Value); } - } - private IDictionary GetIndexArguments() - { - return new Dictionary() - { - //{ "%(DocumentTitle)", _options.DocumentTitle }, - //{ "%(HeadContent)", _options.HeadContent }, - //{ "%(ConfigObject)", JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions) }, - //{ "%(OAuthConfigObject)", JsonSerializer.Serialize(_options.OAuthConfigObject, _jsonSerializerOptions) }, - //{ "%(Interceptors)", JsonSerializer.Serialize(_options.Interceptors) }, - }; + await response.WriteAsync(htmlBuilder.ToString(), Encoding.UTF8); } + } + private IDictionary GetIndexArguments() + { + return new Dictionary() + { + //{ "%(DocumentTitle)", _options.DocumentTitle }, + //{ "%(HeadContent)", _options.HeadContent }, + //{ "%(ConfigObject)", JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions) }, + //{ "%(OAuthConfigObject)", JsonSerializer.Serialize(_options.OAuthConfigObject, _jsonSerializerOptions) }, + //{ "%(Interceptors)", JsonSerializer.Serialize(_options.Interceptors) }, + }; } + } diff --git a/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIOptions.cs b/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIOptions.cs index f363fd51..2da10aee 100644 --- a/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIOptions.cs +++ b/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIOptions.cs @@ -5,62 +5,58 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Net.Http; using System.Reflection; -using System.Text; +using PluginCore.IPlugins.Infrastructure; + +namespace PluginCore.AspNetCore.AdminUI; -namespace PluginCore.AspNetCore.AdminUI +public class PluginCoreAdminUIOptions { - public class PluginCoreAdminUIOptions + /// + /// Gets or sets a route prefix for accessing the swagger-ui + /// + public string RoutePrefix { get; set; } = "PluginCore/Admin"; + + /// + /// Gets or sets a Stream function for retrieving the swagger-ui page + /// + public Func IndexStream { - /// - /// Gets or sets a route prefix for accessing the swagger-ui - /// - public string RoutePrefix { get; set; } = "PluginCore/Admin"; - - /// - /// Gets or sets a Stream function for retrieving the swagger-ui page - /// - public Func IndexStream + get { - get + Func funcStream = null; + ; + Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create(); + switch (pluginCoreConfig.FrontendMode?.ToLower()) { - Func funcStream = null; - ; - Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create(); - switch (pluginCoreConfig.FrontendMode?.ToLower()) - { - case "localembedded": - funcStream = () => typeof(PluginCoreAdminUIOptions).GetTypeInfo().Assembly - .GetManifestResourceStream("PluginCore.AspNetCore.node_modules.plugincore_admin_frontend.dist.index.html"); - break; - case "localfolder": - string absoluteRootPath = PluginPathProvider.PluginCoreAdminDir(); - string indexFilePath = Path.Combine(absoluteRootPath, "index.html"); - - funcStream = () => (Stream)new FileStream(indexFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1, FileOptions.Asynchronous | FileOptions.SequentialScan); - break; - case "remotecdn": - string remoteFrontendRootPath = pluginCoreConfig.RemoteFrontend; - string indexFileRemotePath = remoteFrontendRootPath + "/" + "index.html"; - - funcStream = () => new HttpClient().GetStreamAsync(indexFileRemotePath).Result; - break; - default: - funcStream = () => typeof(PluginCoreAdminUIOptions).GetTypeInfo().Assembly - .GetManifestResourceStream("PluginCore.AspNetCore.node_modules.plugincore_admin_frontend.dist.index.html"); - break; - } - - return funcStream; + case "localembedded": + funcStream = () => typeof(PluginCoreAdminUIOptions).GetTypeInfo().Assembly + .GetManifestResourceStream("PluginCore.AspNetCore.node_modules.plugincore_admin_frontend.dist.index.html"); + break; + case "localfolder": + string absoluteRootPath = PluginPathProvider.PluginCoreAdminDir(); + string indexFilePath = Path.Combine(absoluteRootPath, "index.html"); + + funcStream = () => (Stream)new FileStream(indexFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1, FileOptions.Asynchronous | FileOptions.SequentialScan); + break; + case "remotecdn": + string remoteFrontendRootPath = pluginCoreConfig.RemoteFrontend; + string indexFileRemotePath = remoteFrontendRootPath + "/" + "index.html"; + + funcStream = () => new HttpClient().GetStreamAsync(indexFileRemotePath).Result; + break; + default: + funcStream = () => typeof(PluginCoreAdminUIOptions).GetTypeInfo().Assembly + .GetManifestResourceStream("PluginCore.AspNetCore.node_modules.plugincore_admin_frontend.dist.index.html"); + break; } + + return funcStream; } + } - } } diff --git a/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIRemoteFileProvider.cs b/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIRemoteFileProvider.cs index f09b5ddc..77861073 100644 --- a/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIRemoteFileProvider.cs +++ b/src/PluginCore.AspNetCore/AdminUI/PluginCoreAdminUIRemoteFileProvider.cs @@ -5,126 +5,118 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Net.Http; -using System.Text; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; -namespace PluginCore.AspNetCore.AdminUI +namespace PluginCore.AspNetCore.AdminUI; + +public class PluginCoreAdminUIRemoteFileProvider : IFileProvider { - public class PluginCoreAdminUIRemoteFileProvider : IFileProvider - { - protected string RootUrl { get; set; } + protected string RootUrl { get; set; } - public PluginCoreAdminUIRemoteFileProvider(string rootUrl) - { - this.RootUrl = rootUrl; - } + public PluginCoreAdminUIRemoteFileProvider(string rootUrl) + { + this.RootUrl = rootUrl; + } - public IDirectoryContents GetDirectoryContents(string subpath) - { - return (IDirectoryContents)NotFoundDirectoryContents.Singleton; - } + public IDirectoryContents GetDirectoryContents(string subpath) + { + return (IDirectoryContents)NotFoundDirectoryContents.Singleton; + } - public IFileInfo GetFileInfo(string subpath) - { - if (string.IsNullOrEmpty(subpath)) - return (IFileInfo)new NotFoundFileInfo(subpath); + public IFileInfo GetFileInfo(string subpath) + { + if (string.IsNullOrEmpty(subpath)) + return (IFileInfo)new NotFoundFileInfo(subpath); - IFileInfo fileInfo = new PluginCoreAdminUIFileInfo(this.RootUrl, subpath); + IFileInfo fileInfo = new PluginCoreAdminUIFileInfo(this.RootUrl, subpath); - return fileInfo; - } + return fileInfo; + } - public IChangeToken Watch(string filter) - { - throw new NotImplementedException(); - } + public IChangeToken Watch(string filter) + { + throw new NotImplementedException(); + } - public class PluginCoreAdminUIFileInfo : IFileInfo - { - protected string RootUrl { get; set; } + public class PluginCoreAdminUIFileInfo : IFileInfo + { + protected string RootUrl { get; set; } - protected string SubPath { get; set; } + protected string SubPath { get; set; } - private string _name; + private string _name; - public PluginCoreAdminUIFileInfo(string rootUrl, string subpath) - { - this.RootUrl = rootUrl; - this.SubPath = subpath; - this._name = this.SubPath.Substring(this.SubPath.LastIndexOf("/") + 1); - } + public PluginCoreAdminUIFileInfo(string rootUrl, string subpath) + { + this.RootUrl = rootUrl; + this.SubPath = subpath; + this._name = this.SubPath.Substring(this.SubPath.LastIndexOf("/") + 1); + } - public Stream CreateReadStream() - { - HttpClient httpClient = new HttpClient(); + public Stream CreateReadStream() + { + HttpClient httpClient = new HttpClient(); - return httpClient.GetStreamAsync($"{this.RootUrl}/{this.SubPath}").Result; - } + return httpClient.GetStreamAsync($"{this.RootUrl}/{this.SubPath}").Result; + } - public bool Exists + public bool Exists + { + get { - get + bool isExist = false; + if (this.Name == "index.html") { - bool isExist = false; - if (this.Name == "index.html") - { - isExist = true; - } - - return isExist; + isExist = true; } + + return isExist; } - public bool IsDirectory + } + public bool IsDirectory + { + get { - get - { - return false; - } + return false; } + } - public DateTimeOffset LastModified + public DateTimeOffset LastModified + { + get { - get - { - return new DateTimeOffset(DateTime.Now); - } + return new DateTimeOffset(DateTime.Now); } + } - public long Length + public long Length + { + get { - get - { - return 111; - } + return 111; } + } - public string Name + public string Name + { + get { - get - { - return this._name; - } + return this._name; } + } - public string PhysicalPath + public string PhysicalPath + { + get { - get - { - return ""; - } + return ""; } } - } - - } diff --git a/src/PluginCore.AspNetCore/Authentication/PluginCoreAuthenticationHandler.cs b/src/PluginCore.AspNetCore/Authentication/PluginCoreAuthenticationHandler.cs index 2ec6b9c8..7e5e9c92 100644 --- a/src/PluginCore.AspNetCore/Authentication/PluginCoreAuthenticationHandler.cs +++ b/src/PluginCore.AspNetCore/Authentication/PluginCoreAuthenticationHandler.cs @@ -5,12 +5,7 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; using System.Security.Claims; -using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -18,48 +13,47 @@ using Microsoft.Extensions.Options; using PluginCore.AspNetCore.Authorization; -namespace PluginCore.AspNetCore.Authentication +namespace PluginCore.AspNetCore.Authentication; + +/// +/// https://stackoverflow.com/questions/52287542/invalidoperationexception-no-authenticationscheme-was-specified-and-there-was +/// +public class PluginCoreAuthenticationHandler : AuthenticationHandler { - /// - /// https://stackoverflow.com/questions/52287542/invalidoperationexception-no-authenticationscheme-was-specified-and-there-was - /// - public class PluginCoreAuthenticationHandler : AuthenticationHandler + private readonly AccountManager _accountManager; + + public PluginCoreAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, AccountManager accountManager) : base(options, logger, encoder, clock) { - private readonly AccountManager _accountManager; + this._accountManager = accountManager; + } - public PluginCoreAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, AccountManager accountManager) : base(options, logger, encoder, clock) + protected override async Task HandleAuthenticateAsync() + { + string token = this._accountManager.CurrentToken(); + if (string.IsNullOrEmpty(token)) { - this._accountManager = accountManager; + return AuthenticateResult.NoResult(); } - protected override async Task HandleAuthenticateAsync() - { - string token = this._accountManager.CurrentToken(); - if (string.IsNullOrEmpty(token)) - { - return AuthenticateResult.NoResult(); - } + bool isAdmin = this._accountManager.IsAdminToken(token); - bool isAdmin = this._accountManager.IsAdminToken(token); - - if (!isAdmin) - { - return AuthenticateResult.Fail($"token is not admin"); - } - else - { - var id = new ClaimsIdentity( - new Claim[] { new Claim("PluginCore.Token", token) }, // not safe , just as an example , should custom claims on your own - Scheme.Name - ); - ClaimsPrincipal principal = new ClaimsPrincipal(id); - var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name); - - Utils.LogUtil.Info($"通过 Authentication: token: {token}"); + if (!isAdmin) + { + return AuthenticateResult.Fail($"token is not admin"); + } + else + { + var id = new ClaimsIdentity( + new Claim[] { new Claim("PluginCore.Token", token) }, // not safe , just as an example , should custom claims on your own + Scheme.Name + ); + ClaimsPrincipal principal = new ClaimsPrincipal(id); + var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name); - return AuthenticateResult.Success(ticket); - } + Utils.LogUtil.Info($"通过 Authentication: token: {token}"); + return AuthenticateResult.Success(ticket); } + } } diff --git a/src/PluginCore.AspNetCore/Authentication/PluginCoreAuthenticationSchemeOptions.cs b/src/PluginCore.AspNetCore/Authentication/PluginCoreAuthenticationSchemeOptions.cs index 8ab67086..b91acec1 100644 --- a/src/PluginCore.AspNetCore/Authentication/PluginCoreAuthenticationSchemeOptions.cs +++ b/src/PluginCore.AspNetCore/Authentication/PluginCoreAuthenticationSchemeOptions.cs @@ -5,17 +5,11 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; using Microsoft.AspNetCore.Authentication; -namespace PluginCore.AspNetCore.Authentication +namespace PluginCore.AspNetCore.Authentication; + +public class PluginCoreAuthenticationSchemeOptions : AuthenticationSchemeOptions { - public class PluginCoreAuthenticationSchemeOptions : AuthenticationSchemeOptions - { - } } diff --git a/src/PluginCore.AspNetCore/Authorization/AccountManager.cs b/src/PluginCore.AspNetCore/Authorization/AccountManager.cs index a5f7056c..9c6c6f51 100644 --- a/src/PluginCore.AspNetCore/Authorization/AccountManager.cs +++ b/src/PluginCore.AspNetCore/Authorization/AccountManager.cs @@ -5,135 +5,130 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; +using System; using Microsoft.AspNetCore.Http; using PluginCore.Utils; -namespace PluginCore.AspNetCore.Authorization +namespace PluginCore.AspNetCore.Authorization; + +public class AccountManager { - public class AccountManager - { - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IHttpContextAccessor _httpContextAccessor; - public Microsoft.AspNetCore.Http.HttpContext HttpContext + public Microsoft.AspNetCore.Http.HttpContext HttpContext + { + get { - get - { - return this._httpContextAccessor.HttpContext; - } + return this._httpContextAccessor.HttpContext; } + } - public AccountManager(IHttpContextAccessor httpContextAccessor) + public AccountManager(IHttpContextAccessor httpContextAccessor) + { + // Exception: IFeatureCollection has been disposed. Object name: 'Collection'. + // https://stackoverflow.com/questions/59963383/session-setstring-method-throws-exception-ifeaturecollection-has-been-disposed + //HttpContext = ((HttpContextAccessor)httpContextAccessor).HttpContext; + //HttpContext = httpContextAccessor.HttpContext; + // 注意: 不要将 HttpContext 保存起来,应当每次都从 httpContextAccessor 取 + _httpContextAccessor = httpContextAccessor; + } + + public Config.PluginCoreConfig.AdminModel Admin + { + get { - // Exception: IFeatureCollection has been disposed. Object name: 'Collection'. - // https://stackoverflow.com/questions/59963383/session-setstring-method-throws-exception-ifeaturecollection-has-been-disposed - //HttpContext = ((HttpContextAccessor)httpContextAccessor).HttpContext; - //HttpContext = httpContextAccessor.HttpContext; - // 注意: 不要将 HttpContext 保存起来,应当每次都从 httpContextAccessor 取 - _httpContextAccessor = httpContextAccessor; + return Config.PluginCoreConfigFactory.Create().Admin; } - - public Config.PluginCoreConfig.AdminModel Admin + set { - get - { - return Config.PluginCoreConfigFactory.Create().Admin; - } - set - { - var sourceModel = Config.PluginCoreConfigFactory.Create(); - sourceModel.Admin = value; - Config.PluginCoreConfigFactory.Save(sourceModel); - } + var sourceModel = Config.PluginCoreConfigFactory.Create(); + sourceModel.Admin = value; + Config.PluginCoreConfigFactory.Save(sourceModel); } + } - public string CurrentToken() + public string CurrentToken() + { + string token = null; + HttpRequest request = HttpContext.Request; + try { - string token = null; - HttpRequest request = HttpContext.Request; + // header -> cookie try { - // header -> cookie - try + // header 中找 token + if (request.Headers.ContainsKey("Authorization")) { - // header 中找 token - if (request.Headers.ContainsKey("Authorization")) + string authHeader = request.Headers["Authorization"]; + if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer")) { - string authHeader = request.Headers["Authorization"]; - if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer")) - { - token = authHeader.Substring("Bearer ".Length).Trim(); - } + token = authHeader.Substring("Bearer ".Length).Trim(); } } - catch (Exception ex) - { - //throw ex; - } - if (string.IsNullOrEmpty(token)) + } + catch (Exception ex) + { + //throw ex; + } + if (string.IsNullOrEmpty(token)) + { + // cookie 中找 token + //string tokenCookieName = "token"; + string tokenCookieName = "PluginCore.Admin.Token"; + if (request.Cookies.Keys.Contains(tokenCookieName)) { - // cookie 中找 token - //string tokenCookieName = "token"; - string tokenCookieName = "PluginCore.Admin.Token"; - if (request.Cookies.Keys.Contains(tokenCookieName)) + if (request.Cookies[tokenCookieName] != null && string.IsNullOrEmpty(request.Cookies[tokenCookieName]) == false) { - if (request.Cookies[tokenCookieName] != null && string.IsNullOrEmpty(request.Cookies[tokenCookieName]) == false) - { - token = request.Cookies[tokenCookieName]; - } + token = request.Cookies[tokenCookieName]; } } } - catch (Exception ex) - { - throw ex; - } - - - return token; } - - public string CreateToken() + catch (Exception ex) { - return CreateToken(Admin.UserName, Admin.Password); + throw ex; } - public string CreateToken(string userName, string password) - { - string token = $"UserName={userName}&Password={password}"; - token = Md5Helper.MD5Encrypt32(token); - return token; - } + return token; + } - public bool IsAdminToken(string token) - { - bool isAdmin = false; - isAdmin = CreateToken().Equals(token); + public string CreateToken() + { + return CreateToken(Admin.UserName, Admin.Password); + } - return isAdmin; - } + public string CreateToken(string userName, string password) + { + string token = $"UserName={userName}&Password={password}"; + token = Md5Helper.MD5Encrypt32(token); + return token; + } + + public bool IsAdminToken(string token) + { + bool isAdmin = false; + isAdmin = CreateToken().Equals(token); + + return isAdmin; + } - public bool IsAdmin() - { - bool isAdmin = false; - try - { - string currentToken = CurrentToken(); - isAdmin = IsAdminToken(currentToken); - } - catch (Exception ex) - { - throw ex; - } - return isAdmin; + public bool IsAdmin() + { + bool isAdmin = false; + try + { + string currentToken = CurrentToken(); + isAdmin = IsAdminToken(currentToken); + } + catch (Exception ex) + { + throw ex; } + return isAdmin; } + } diff --git a/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminAuthorizationHandler.cs b/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminAuthorizationHandler.cs index c89c917d..5ec98ae3 100644 --- a/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminAuthorizationHandler.cs +++ b/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminAuthorizationHandler.cs @@ -5,52 +5,46 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -namespace PluginCore.AspNetCore.Authorization +namespace PluginCore.AspNetCore.Authorization; + +public class PluginCoreAdminAuthorizationHandler : AuthorizationHandler { - public class PluginCoreAdminAuthorizationHandler : AuthorizationHandler + private readonly AccountManager _accountManager; + + public PluginCoreAdminAuthorizationHandler(AccountManager accountManager) { - private readonly AccountManager _accountManager; + _accountManager = accountManager; + } - public PluginCoreAdminAuthorizationHandler(AccountManager accountManager) + /// + /// 必须在其中呼叫一次 代表满足 ,否则皆为 不满足此 Requirement + /// + /// + /// + /// + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + PluginCoreAdminRequirement requirement) + { + bool isAdmin = this._accountManager.IsAdmin(); + if (!isAdmin) { - _accountManager = accountManager; + context.Fail(); } - - /// - /// 必须在其中呼叫一次 代表满足 ,否则皆为 不满足此 Requirement - /// - /// - /// - /// - protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, - PluginCoreAdminRequirement requirement) + else { - bool isAdmin = this._accountManager.IsAdmin(); - if (!isAdmin) - { - context.Fail(); - } - else - { - // 认证通过后, 可通过下面方式获取 token - var identity = context.User.Identity; + // 认证通过后, 可通过下面方式获取 token + var identity = context.User.Identity; - string token = this._accountManager.CurrentToken(); + string token = this._accountManager.CurrentToken(); - Utils.LogUtil.Info($"通过 Authorization: token: {token}"); + Utils.LogUtil.Info($"通过 Authorization: token: {token}"); - context.Succeed(requirement); - } - - await Task.CompletedTask; + context.Succeed(requirement); } + + await Task.CompletedTask; } } diff --git a/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminAuthorizeAttribute.cs b/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminAuthorizeAttribute.cs index c2407d71..7acda93e 100644 --- a/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminAuthorizeAttribute.cs +++ b/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminAuthorizeAttribute.cs @@ -5,23 +5,17 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; using Microsoft.AspNetCore.Authorization; -namespace PluginCore.AspNetCore.Authorization +namespace PluginCore.AspNetCore.Authorization; + +/// +/// 注意: PluginCoreAdmin -> PluginCore.Admin +/// +public class PluginCoreAdminAuthorizeAttribute : AuthorizeAttribute { - /// - /// 注意: PluginCoreAdmin -> PluginCore.Admin - /// - public class PluginCoreAdminAuthorizeAttribute : AuthorizeAttribute + public PluginCoreAdminAuthorizeAttribute() : base("PluginCore.Admin") { - public PluginCoreAdminAuthorizeAttribute() : base("PluginCore.Admin") - { - } } } diff --git a/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminRequirement.cs b/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminRequirement.cs index d88a3193..16b4679e 100644 --- a/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminRequirement.cs +++ b/src/PluginCore.AspNetCore/Authorization/PluginCoreAdminRequirement.cs @@ -5,20 +5,14 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; using Microsoft.AspNetCore.Authorization; -namespace PluginCore.AspNetCore.Authorization +namespace PluginCore.AspNetCore.Authorization; + +public class PluginCoreAdminRequirement : IAuthorizationRequirement { - public class PluginCoreAdminRequirement : IAuthorizationRequirement + public PluginCoreAdminRequirement() { - public PluginCoreAdminRequirement() - { - } } } diff --git a/src/PluginCore.AspNetCore/BackgroundServices/BackgroundServicesHelper.cs b/src/PluginCore.AspNetCore/BackgroundServices/BackgroundServicesHelper.cs index dc23ff26..6570f1c1 100644 --- a/src/PluginCore.AspNetCore/BackgroundServices/BackgroundServicesHelper.cs +++ b/src/PluginCore.AspNetCore/BackgroundServices/BackgroundServicesHelper.cs @@ -5,21 +5,18 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +using Microsoft.Extensions.DependencyInjection; +namespace PluginCore.AspNetCore.BackgroundServices; -using Microsoft.Extensions.DependencyInjection; - -namespace PluginCore.AspNetCore.BackgroundServices +public static class BackgroundServicesHelper { - public static class BackgroundServicesHelper + public static IServiceCollection AddBackgroundServices(this IServiceCollection services) { - public static IServiceCollection AddBackgroundServices(this IServiceCollection services) - { - //services.AddScoped(typeof(IHostedService), typeof(TimeBackgroundService)); - // AddHostedService: Microsoft.AspNetCore.App - services.AddHostedService(); // 以这种方式注入就是单例 + //services.AddScoped(typeof(IHostedService), typeof(TimeBackgroundService)); + // AddHostedService: Microsoft.AspNetCore.App + services.AddHostedService(); // 以这种方式注入就是单例 - return services; - } + return services; } } diff --git a/src/PluginCore.AspNetCore/BackgroundServices/PluginTimeJobBackgroundService.cs b/src/PluginCore.AspNetCore/BackgroundServices/PluginTimeJobBackgroundService.cs index 33782976..1e30a17d 100644 --- a/src/PluginCore.AspNetCore/BackgroundServices/PluginTimeJobBackgroundService.cs +++ b/src/PluginCore.AspNetCore/BackgroundServices/PluginTimeJobBackgroundService.cs @@ -14,77 +14,78 @@ using System.Threading.Tasks; using PluginCore.Interfaces; using PluginCore.IPlugins; +using PluginCore.IPlugins.Interfaces; +using PluginCore.IPlugins.IPlugins; using PluginCore.Utils; -namespace PluginCore.AspNetCore.BackgroundServices +namespace PluginCore.AspNetCore.BackgroundServices; + +public class PluginTimeJobBackgroundService : TimeBackgroundService { - public class PluginTimeJobBackgroundService : TimeBackgroundService - { - /// - /// 插件与之最近执行时间 - /// 最近执行时间: 10位秒 时间戳 - /// - private readonly Dictionary _pluginAndLastExecuteTimeDic = new Dictionary(); + /// + /// 插件与之最近执行时间 + /// 最近执行时间: 10位秒 时间戳 + /// + private readonly Dictionary _pluginAndLastExecuteTimeDic = new Dictionary(); - private readonly IPluginFinder _pluginFinder; + private readonly IPluginFinder _pluginFinder; - private static readonly object _doWorklock = new object(); + private static readonly object _doWorklock = new object(); - public PluginTimeJobBackgroundService(IPluginFinder pluginFinder) - { - _pluginFinder = pluginFinder; - // 最小间隔 1 秒 - _timerPeriod = TimeSpan.FromSeconds(1); - } + public PluginTimeJobBackgroundService(IPluginFinder pluginFinder) + { + _pluginFinder = pluginFinder; + // 最小间隔 1 秒 + _timerPeriod = TimeSpan.FromSeconds(1); + } - protected override void DoWork(object state) + protected override void DoWork(object state) + { + lock (_doWorklock) { - lock (_doWorklock) - { - //Console.WriteLine("Memory used before collection: {0:N0}", GC.GetTotalMemory(false)); + //Console.WriteLine("Memory used before collection: {0:N0}", GC.GetTotalMemory(false)); - var plugins = this._pluginFinder.EnablePlugins().ToList(); + var plugins = this._pluginFinder.EnablePlugins().ToList(); - List enabledPluginKeyList = new List(); - foreach (var item in plugins) + List enabledPluginKeyList = new List(); + foreach (var item in plugins) + { + string pluginKey = item.GetType().ToString(); + enabledPluginKeyList.Add(pluginKey); + if (this._pluginAndLastExecuteTimeDic.ContainsKey(pluginKey)) { - string pluginKey = item.GetType().ToString(); - enabledPluginKeyList.Add(pluginKey); - if (this._pluginAndLastExecuteTimeDic.ContainsKey(pluginKey)) - { - long lastExecuteTime = this._pluginAndLastExecuteTimeDic[pluginKey]; - long nowTime = DateTime.Now.ToTimeStamp10(); - if (nowTime - lastExecuteTime >= item.SecondsPeriod) - { - // 调用 - Utils.LogUtil.Info($"{pluginKey} {nameof(ITimeJobPlugin)}.{nameof(ITimeJobPlugin.ExecuteAsync)}"); - Task task = item?.ExecuteAsync(); - this._pluginAndLastExecuteTimeDic[pluginKey] = DateTime.Now.ToTimeStamp10(); - } - } - else + long lastExecuteTime = this._pluginAndLastExecuteTimeDic[pluginKey]; + long nowTime = DateTime.Now.ToTimeStamp10(); + if (nowTime - lastExecuteTime >= item.SecondsPeriod) { // 调用 Utils.LogUtil.Info($"{pluginKey} {nameof(ITimeJobPlugin)}.{nameof(ITimeJobPlugin.ExecuteAsync)}"); Task task = item?.ExecuteAsync(); - this._pluginAndLastExecuteTimeDic.Add(pluginKey, DateTime.Now.ToTimeStamp10()); + this._pluginAndLastExecuteTimeDic[pluginKey] = DateTime.Now.ToTimeStamp10(); } } - // 所有插件遍历结束 - // 出现在了 _pluginAndLastExecuteTimeDic 中,但没有出现在 enabledPluginKeyList, 说明为之前启用过,但现在已禁用的插件,需要去除掉 - List keys = this._pluginAndLastExecuteTimeDic.Select(m => m.Key).ToList(); - foreach (string key in keys) + else { - if (!enabledPluginKeyList.Contains(key)) - { - this._pluginAndLastExecuteTimeDic.Remove(key); - } + // 调用 + Utils.LogUtil.Info($"{pluginKey} {nameof(ITimeJobPlugin)}.{nameof(ITimeJobPlugin.ExecuteAsync)}"); + Task task = item?.ExecuteAsync(); + this._pluginAndLastExecuteTimeDic.Add(pluginKey, DateTime.Now.ToTimeStamp10()); } - - GC.Collect(); - GC.WaitForPendingFinalizers(); - //Console.WriteLine("Memory used after full collection: {0:N0}", GC.GetTotalMemory(true)); } + // 所有插件遍历结束 + // 出现在了 _pluginAndLastExecuteTimeDic 中,但没有出现在 enabledPluginKeyList, 说明为之前启用过,但现在已禁用的插件,需要去除掉 + List keys = this._pluginAndLastExecuteTimeDic.Select(m => m.Key).ToList(); + foreach (string key in keys) + { + if (!enabledPluginKeyList.Contains(key)) + { + this._pluginAndLastExecuteTimeDic.Remove(key); + } + } + + GC.Collect(); + GC.WaitForPendingFinalizers(); + //Console.WriteLine("Memory used after full collection: {0:N0}", GC.GetTotalMemory(true)); } } } diff --git a/src/PluginCore.AspNetCore/BackgroundServices/TimeBackgroundService.cs b/src/PluginCore.AspNetCore/BackgroundServices/TimeBackgroundService.cs index 18e6b8dc..744c9134 100644 --- a/src/PluginCore.AspNetCore/BackgroundServices/TimeBackgroundService.cs +++ b/src/PluginCore.AspNetCore/BackgroundServices/TimeBackgroundService.cs @@ -5,41 +5,36 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting; using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace PluginCore.AspNetCore.BackgroundServices +namespace PluginCore.AspNetCore.BackgroundServices; + +public abstract class TimeBackgroundService : IHostedService, IDisposable { - public abstract class TimeBackgroundService : IHostedService, IDisposable - { - protected Timer _timer; - protected TimeSpan _timerPeriod; + protected Timer _timer; + protected TimeSpan _timerPeriod; - public virtual Task StartAsync(CancellationToken cancellationToken) - { - _timer = new Timer(DoWork, null, TimeSpan.Zero, _timerPeriod); + public virtual Task StartAsync(CancellationToken cancellationToken) + { + _timer = new Timer(DoWork, null, TimeSpan.Zero, _timerPeriod); - return Task.CompletedTask; - } + return Task.CompletedTask; + } - public virtual Task StopAsync(CancellationToken cancellationToken) - { - _timer?.Change(Timeout.Infinite, 0); + public virtual Task StopAsync(CancellationToken cancellationToken) + { + _timer?.Change(Timeout.Infinite, 0); - return Task.CompletedTask; - } + return Task.CompletedTask; + } - protected abstract void DoWork(object state); + protected abstract void DoWork(object state); - public virtual void Dispose() - { - _timer?.Dispose(); - } + public virtual void Dispose() + { + _timer?.Dispose(); } } diff --git a/src/PluginCore.AspNetCore/Controllers/AppCenterController.cs b/src/PluginCore.AspNetCore/Controllers/AppCenterController.cs index 755ff341..6a2e44c5 100644 --- a/src/PluginCore.AspNetCore/Controllers/AppCenterController.cs +++ b/src/PluginCore.AspNetCore/Controllers/AppCenterController.cs @@ -18,188 +18,189 @@ using Microsoft.AspNetCore.Mvc; using PluginCore.AspNetCore.Authorization; using PluginCore.AspNetCore.ResponseModel; + using PluginCore.IPlugins.Infrastructure; + //using ResponseModel; -namespace PluginCore.AspNetCore.Controllers +namespace PluginCore.AspNetCore.Controllers; + +/// +/// 应用中心 +/// 插件 +/// +[Route("api/plugincore/admin/[controller]/[action]")] +[PluginCoreAdminAuthorize] +[ApiController] +public class AppCenterController : ControllerBase { + #region Fields + + private static Dictionary _pluginDownloadTasks; + + #endregion + + #region Ctor + + static AppCenterController() + { + _pluginDownloadTasks = new Dictionary(); + } + + public AppCenterController() + { + + } + #endregion + + #region Actions + + #region 插件列表 /// - /// 应用中心 - /// 插件 + /// 插件 /// - [Route("api/plugincore/admin/[controller]/[action]")] - [PluginCoreAdminAuthorize] - [ApiController] - public class AppCenterController : ControllerBase + /// + /// + [HttpGet, HttpPost] + public async Task> Plugins(string query = "") { - #region Fields + BaseResponseModel responseDTO = new BaseResponseModel(); + IList pluginRegistryModels = new List(); + try + { + // 1. TODO: 从json文件中读取插件订阅源 registry url + string registryUrl = ""; + // 2. TODO: 向订阅源发送 http get 获取插件列表信息 eg: http://rem-core-plugins-registry.moeci.com/?query=xxx + IList remotePluginIds = new List(); - private static Dictionary _pluginDownloadTasks; + // 3. 根据本地已有 PluginId 插件情况 状态赋值 + PluginConfigModel pluginConfigModel = PluginConfigModelFactory.Create(); + // IList localPluginIds = pluginConfigModel.EnabledPlugins.Concat(pluginConfigModel.DisabledPlugins).Concat(pluginConfigModel.UninstalledPlugins).ToList(); + IList localPluginIds = PluginPathProvider.AllPluginFolderName(); - #endregion - #region Ctor - static AppCenterController() - { - _pluginDownloadTasks = new Dictionary(); + responseDTO.Code = 1; + responseDTO.Message = "获取远程插件数据成功"; + responseDTO.Data = pluginRegistryModels; } - - public AppCenterController() + catch (Exception ex) { - + responseDTO.Code = -1; + responseDTO.Message = "获取远程插件数据失败: " + ex.Message; + responseDTO.Data = pluginRegistryModels; } - #endregion - #region Actions + return await Task.FromResult(responseDTO); + } + #endregion + + #region 下载插件 + [HttpGet, HttpPost] + public async Task> DownloadPlugin(string pluginDownloadUrl = "") + { + BaseResponseModel responseDTO = new BaseResponseModel(); - #region 插件列表 - /// - /// 插件 - /// - /// - /// - [HttpGet, HttpPost] - public async Task> Plugins(string query = "") + #region 效验 + if (string.IsNullOrEmpty(pluginDownloadUrl)) { - BaseResponseModel responseDTO = new BaseResponseModel(); - IList pluginRegistryModels = new List(); - try - { - // 1. TODO: 从json文件中读取插件订阅源 registry url - string registryUrl = ""; - // 2. TODO: 向订阅源发送 http get 获取插件列表信息 eg: http://rem-core-plugins-registry.moeci.com/?query=xxx - IList remotePluginIds = new List(); - - // 3. 根据本地已有 PluginId 插件情况 状态赋值 - PluginConfigModel pluginConfigModel = PluginConfigModelFactory.Create(); - // IList localPluginIds = pluginConfigModel.EnabledPlugins.Concat(pluginConfigModel.DisabledPlugins).Concat(pluginConfigModel.UninstalledPlugins).ToList(); - IList localPluginIds = PluginPathProvider.AllPluginFolderName(); - - - - responseDTO.Code = 1; - responseDTO.Message = "获取远程插件数据成功"; - responseDTO.Data = pluginRegistryModels; - } - catch (Exception ex) - { - responseDTO.Code = -1; - responseDTO.Message = "获取远程插件数据失败: " + ex.Message; - responseDTO.Data = pluginRegistryModels; - } - - return await Task.FromResult(responseDTO); + responseDTO.Code = -1; + responseDTO.Message = "插件下载地址不正确"; + return responseDTO; } + // TODO: 效验是否本地已经存在相同pluginId的插件 + #endregion - #region 下载插件 - [HttpGet, HttpPost] - public async Task> DownloadPlugin(string pluginDownloadUrl = "") + try + { + // 1.执行下载操作, TODO:存在问题,阻塞对性能不好,但不阻塞又不好通知用户插件下载进度,以及可能存在在插件下载过程中,用户再次点击下载 + WebClient webClient = new WebClient(); + // TODO: 插件下载文件路径 + string pluginDownloadFilePath = ""; + //webClient.DownloadFileAsync(new Uri(pluginDownloadFilePath), ""); + Task task = webClient.DownloadFileTaskAsync(pluginDownloadUrl, pluginDownloadFilePath); + + _pluginDownloadTasks.Add(pluginDownloadUrl, task); + + webClient.DownloadFileCompleted += Plugin_DownloadFileCompleted; + webClient.DownloadProgressChanged += Plugin_DownloadProgressChanged; + webClient.Disposed += WebClient_Disposed; + + responseDTO.Code = 1; + responseDTO.Message = "开始下载插件"; + } + catch (Exception ex) { - BaseResponseModel responseDTO = new BaseResponseModel(); - - #region 效验 - if (string.IsNullOrEmpty(pluginDownloadUrl)) - { - responseDTO.Code = -1; - responseDTO.Message = "插件下载地址不正确"; - return responseDTO; - } - // TODO: 效验是否本地已经存在相同pluginId的插件 - - #endregion - - try - { - // 1.执行下载操作, TODO:存在问题,阻塞对性能不好,但不阻塞又不好通知用户插件下载进度,以及可能存在在插件下载过程中,用户再次点击下载 - WebClient webClient = new WebClient(); - // TODO: 插件下载文件路径 - string pluginDownloadFilePath = ""; - //webClient.DownloadFileAsync(new Uri(pluginDownloadFilePath), ""); - Task task = webClient.DownloadFileTaskAsync(pluginDownloadUrl, pluginDownloadFilePath); - - _pluginDownloadTasks.Add(pluginDownloadUrl, task); - - webClient.DownloadFileCompleted += Plugin_DownloadFileCompleted; - webClient.DownloadProgressChanged += Plugin_DownloadProgressChanged; - webClient.Disposed += WebClient_Disposed; - - responseDTO.Code = 1; - responseDTO.Message = "开始下载插件"; - } - catch (Exception ex) - { - responseDTO.Code = -1; - responseDTO.Message = "下载插件失败: " + ex.Message; - } - - return await Task.FromResult(responseDTO); + responseDTO.Code = -1; + responseDTO.Message = "下载插件失败: " + ex.Message; } + return await Task.FromResult(responseDTO); + } - #endregion - #region 获取插件下载进度 - [HttpGet, HttpPost] - public async Task> DownloadPluginProgress() - { - BaseResponseModel responseDTO = new BaseResponseModel(); - try - { - responseDTO.Data = new { }; + #endregion + #region 获取插件下载进度 + [HttpGet, HttpPost] + public async Task> DownloadPluginProgress() + { + BaseResponseModel responseDTO = new BaseResponseModel(); + try + { + responseDTO.Data = new { }; - responseDTO.Code = 1; - responseDTO.Message = "获取插件下载进度成功"; - } - catch (Exception ex) - { - responseDTO.Code = -1; - responseDTO.Message = "获取插件下载进度失败: " + ex.Message; - } - return await Task.FromResult(responseDTO); + responseDTO.Code = 1; + responseDTO.Message = "获取插件下载进度成功"; + } + catch (Exception ex) + { + responseDTO.Code = -1; + responseDTO.Message = "获取插件下载进度失败: " + ex.Message; } - #endregion - #endregion + return await Task.FromResult(responseDTO); + } + #endregion - #region Helpers + #endregion - /// - /// 插件下载完成 - /// - /// - /// - private void Plugin_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) - { - Console.WriteLine("插件下载完成"); - // 1.从 _pluginDownloadTasks 中移除 - //_pluginDownloadTasks.Remove(); - // 2. 解压插件 + #region Helpers - } + /// + /// 插件下载完成 + /// + /// + /// + private void Plugin_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) + { + Console.WriteLine("插件下载完成"); + // 1.从 _pluginDownloadTasks 中移除 + //_pluginDownloadTasks.Remove(); + // 2. 解压插件 - private void Plugin_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) - { - Console.WriteLine($"插件下载进度改变: {e.ProgressPercentage}% {e.BytesReceived}/{e.TotalBytesToReceive}"); - } + } - private void WebClient_Disposed(object sender, EventArgs e) - { - if (sender is WebClient webClient) - { - Console.WriteLine(webClient.BaseAddress); - } + private void Plugin_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) + { + Console.WriteLine($"插件下载进度改变: {e.ProgressPercentage}% {e.BytesReceived}/{e.TotalBytesToReceive}"); + } - Console.WriteLine(nameof(WebClient_Disposed) + ": " + sender.ToString()); + private void WebClient_Disposed(object sender, EventArgs e) + { + if (sender is WebClient webClient) + { + Console.WriteLine(webClient.BaseAddress); } - #endregion - + Console.WriteLine(nameof(WebClient_Disposed) + ": " + sender.ToString()); } + + #endregion + } diff --git a/src/PluginCore.AspNetCore/Controllers/DebugController.cs b/src/PluginCore.AspNetCore/Controllers/DebugController.cs index 59c6ae90..9fdc8daa 100644 --- a/src/PluginCore.AspNetCore/Controllers/DebugController.cs +++ b/src/PluginCore.AspNetCore/Controllers/DebugController.cs @@ -14,265 +14,261 @@ using PluginCore.Interfaces; using System; using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using System.Linq; using System.Runtime.Loader; -using Microsoft.Extensions.DependencyInjection; -using System.Collections.Concurrent; using PluginCore.AspNetCore.Extensions; -namespace PluginCore.AspNetCore.Controllers +namespace PluginCore.AspNetCore.Controllers; + +/// +/// [ASP.NET Core — 依赖注入\_啊晚的博客-CSDN博客\_asp.net core 依赖注入](https://blog.csdn.net/weixin_37648525/article/details/127942292) +/// [ASP.NET Core中的依赖注入(3): 服务的注册与提供 - Artech - 博客园](https://www.cnblogs.com/artech/p/asp-net-core-di-register.html) +/// [ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】 - Artech - 博客园](https://www.cnblogs.com/artech/p/asp-net-core-di-service-provider-1.html) +/// [dotnet/ServiceProvider.cs at main · dotnet/dotnet](https://github.com/dotnet/dotnet/blob/main/src/runtime/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs) +/// [Net6 DI源码分析Part2 Engine,ServiceProvider - 一身大膘 - 博客园](https://www.cnblogs.com/hts92/p/15800990.html) +/// [【特别的骚气】asp.net core运行时注入服务,实现类库热插拔 - 四处观察 - 博客园](https://www.cnblogs.com/1996-Chinese-Chen/p/16154218.html) +/// +/// ActivatorUtilities.CreateInstance(serviceProvider, "test"); +/// ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider); +/// +/// +[Route("api/plugincore/admin/[controller]/[action]")] +[PluginCoreAdminAuthorize] +[ApiController] +public class DebugController : ControllerBase { - /// - /// [ASP.NET Core — 依赖注入\_啊晚的博客-CSDN博客\_asp.net core 依赖注入](https://blog.csdn.net/weixin_37648525/article/details/127942292) - /// [ASP.NET Core中的依赖注入(3): 服务的注册与提供 - Artech - 博客园](https://www.cnblogs.com/artech/p/asp-net-core-di-register.html) - /// [ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】 - Artech - 博客园](https://www.cnblogs.com/artech/p/asp-net-core-di-service-provider-1.html) - /// [dotnet/ServiceProvider.cs at main · dotnet/dotnet](https://github.com/dotnet/dotnet/blob/main/src/runtime/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs) - /// [Net6 DI源码分析Part2 Engine,ServiceProvider - 一身大膘 - 博客园](https://www.cnblogs.com/hts92/p/15800990.html) - /// [【特别的骚气】asp.net core运行时注入服务,实现类库热插拔 - 四处观察 - 博客园](https://www.cnblogs.com/1996-Chinese-Chen/p/16154218.html) - /// - /// ActivatorUtilities.CreateInstance(serviceProvider, "test"); - /// ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider); - /// - /// - [Route("api/plugincore/admin/[controller]/[action]")] - [PluginCoreAdminAuthorize] - [ApiController] - public class DebugController : ControllerBase - { - #region Fields - private readonly IPluginContextManager _pluginContextManager; - #endregion + #region Fields + private readonly IPluginContextManager _pluginContextManager; + #endregion - #region Ctor - public DebugController(IPluginContextManager pluginContextManager) - { - _pluginContextManager = pluginContextManager; - } - #endregion + #region Ctor + public DebugController(IPluginContextManager pluginContextManager) + { + _pluginContextManager = pluginContextManager; + } + #endregion - #region Actions + #region Actions - [HttpGet, HttpPost] - public async Task> PluginContexts() + [HttpGet, HttpPost] + public async Task> PluginContexts() + { + BaseResponseModel responseModel = new BaseResponseModel(); + try { - BaseResponseModel responseModel = new BaseResponseModel(); - try + var pluginContextList = _pluginContextManager.All(); + Dictionary> keyValuePairs = new Dictionary>(); + foreach (var pluginContext in pluginContextList) { - var pluginContextList = _pluginContextManager.All(); - Dictionary> keyValuePairs = new Dictionary>(); - foreach (var pluginContext in pluginContextList) - { - keyValuePairs.Add($"{pluginContext.GetType().ToString()} - {pluginContext.PluginId} - {pluginContext.GetHashCode()}", pluginContext.Assemblies.Select(m => m.FullName).ToList()); - } - - responseModel.Code = 1; - responseModel.Message = "success"; - responseModel.Data = keyValuePairs; - } - catch (Exception ex) - { - responseModel.Code = -1; - responseModel.Message = "error"; - responseModel.Data = ex.ToString(); + keyValuePairs.Add($"{pluginContext.GetType().ToString()} - {pluginContext.PluginId} - {pluginContext.GetHashCode()}", pluginContext.Assemblies.Select(m => m.FullName).ToList()); } - return await Task.FromResult(responseModel); + responseModel.Code = 1; + responseModel.Message = "success"; + responseModel.Data = keyValuePairs; + } + catch (Exception ex) + { + responseModel.Code = -1; + responseModel.Message = "error"; + responseModel.Data = ex.ToString(); } - [HttpGet, HttpPost] - public async Task> AssemblyLoadContexts() + return await Task.FromResult(responseModel); + } + + [HttpGet, HttpPost] + public async Task> AssemblyLoadContexts() + { + BaseResponseModel responseModel = new BaseResponseModel(); + try { - BaseResponseModel responseModel = new BaseResponseModel(); - try + var assemblyLoadContextDefault = AssemblyLoadContext.Default; + var assemblyLoadContextAll = AssemblyLoadContext.All; + var responseDataModel = new AssemblyLoadContextsResponseDataModel(); + responseDataModel.Default = new AssemblyLoadContextsResponseDataModel.AssemblyLoadContextModel { - var assemblyLoadContextDefault = AssemblyLoadContext.Default; - var assemblyLoadContextAll = AssemblyLoadContext.All; - var responseDataModel = new AssemblyLoadContextsResponseDataModel(); - responseDataModel.Default = new AssemblyLoadContextsResponseDataModel.AssemblyLoadContextModel - { - Name = assemblyLoadContextDefault.Name, - Type = assemblyLoadContextDefault.GetType().ToString(), - Assemblies = assemblyLoadContextDefault.Assemblies.Select(m => new AssemblyModel { FullName = m.FullName, DefinedTypes = m.DefinedTypes.Select(m => m.FullName).ToList() }).ToList() - }; - responseDataModel.All = assemblyLoadContextAll.Select(item => new AssemblyLoadContextsResponseDataModel.AssemblyLoadContextModel - { - Name = item.Name, - Type = item.GetType().ToString(), - Assemblies = item.Assemblies.Select(m => new AssemblyModel { FullName = m.FullName, DefinedTypes = m.DefinedTypes.Select(m => m.FullName).ToList() }).ToList() - }).ToList(); - - responseModel.Code = 1; - responseModel.Message = "success"; - responseModel.Data = responseDataModel; - } - catch (Exception ex) + Name = assemblyLoadContextDefault.Name, + Type = assemblyLoadContextDefault.GetType().ToString(), + Assemblies = assemblyLoadContextDefault.Assemblies.Select(m => new AssemblyModel { FullName = m.FullName, DefinedTypes = m.DefinedTypes.Select(m => m.FullName).ToList() }).ToList() + }; + responseDataModel.All = assemblyLoadContextAll.Select(item => new AssemblyLoadContextsResponseDataModel.AssemblyLoadContextModel { - responseModel.Code = -1; - responseModel.Message = "error"; - responseModel.Data = ex.ToString(); - } - - return await Task.FromResult(responseModel); + Name = item.Name, + Type = item.GetType().ToString(), + Assemblies = item.Assemblies.Select(m => new AssemblyModel { FullName = m.FullName, DefinedTypes = m.DefinedTypes.Select(m => m.FullName).ToList() }).ToList() + }).ToList(); + + responseModel.Code = 1; + responseModel.Message = "success"; + responseModel.Data = responseDataModel; + } + catch (Exception ex) + { + responseModel.Code = -1; + responseModel.Message = "error"; + responseModel.Data = ex.ToString(); } - [HttpGet, HttpPost] - public async Task> Assemblies() + return await Task.FromResult(responseModel); + } + + [HttpGet, HttpPost] + public async Task> Assemblies() + { + BaseResponseModel responseModel = new BaseResponseModel(); + try { - BaseResponseModel responseModel = new BaseResponseModel(); - try + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + List assemblyModels = new List(); + foreach (var item in assemblies) { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - List assemblyModels = new List(); - foreach (var item in assemblies) + assemblyModels.Add(new AssemblyModel { - assemblyModels.Add(new AssemblyModel - { - FullName = item.FullName, - DefinedTypes = item.DefinedTypes.Select(m => m.FullName).ToList() - }); - } - - responseModel.Code = 1; - responseModel.Message = "success"; - responseModel.Data = assemblyModels; - } - catch (Exception ex) - { - responseModel.Code = -1; - responseModel.Message = "error"; - responseModel.Data = ex.ToString(); + FullName = item.FullName, + DefinedTypes = item.DefinedTypes.Select(m => m.FullName).ToList() + }); } - return await Task.FromResult(responseModel); + responseModel.Code = 1; + responseModel.Message = "success"; + responseModel.Data = assemblyModels; + } + catch (Exception ex) + { + responseModel.Code = -1; + responseModel.Message = "error"; + responseModel.Data = ex.ToString(); } - [HttpGet, HttpPost] - public async Task> Services([FromServices] IServiceProvider serviceProvider) + return await Task.FromResult(responseModel); + } + + [HttpGet, HttpPost] + public async Task> Services([FromServices] IServiceProvider serviceProvider) + { + BaseResponseModel responseModel = new BaseResponseModel(); + try { - BaseResponseModel responseModel = new BaseResponseModel(); - try + //IServiceProvider serviceProvider = HttpContext.RequestServices; + //var provider = serviceProvider.GetType().GetProperty("RootProvider", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + //var serviceField = provider.GetType().GetField("_realizedServices", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + //var serviceValue = serviceField.GetValue(provider); + //var funcType = serviceField.FieldType.GetGenericArguments()[1].GetGenericArguments()[0]; + //ConcurrentDictionary> realizedServices = (ConcurrentDictionary>)serviceValue; + + // 获取所有已经注册的服务 + var allService = serviceProvider.GetAllServiceDescriptors(); + + List serviceModels = new List(); + foreach (var item in allService) { - //IServiceProvider serviceProvider = HttpContext.RequestServices; - //var provider = serviceProvider.GetType().GetProperty("RootProvider", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - //var serviceField = provider.GetType().GetField("_realizedServices", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - //var serviceValue = serviceField.GetValue(provider); - //var funcType = serviceField.FieldType.GetGenericArguments()[1].GetGenericArguments()[0]; - //ConcurrentDictionary> realizedServices = (ConcurrentDictionary>)serviceValue; - - // 获取所有已经注册的服务 - var allService = serviceProvider.GetAllServiceDescriptors(); - - List serviceModels = new List(); - foreach (var item in allService) + serviceModels.Add(new ServiceModel { - serviceModels.Add(new ServiceModel + Type = item.Key.ToString(), + ImplementationType = item.Value.ImplementationType?.ToString() ?? "", + Lifetime = item.Value.Lifetime.ToString(), + TypeAssembly = new AssemblyModel { - Type = item.Key.ToString(), - ImplementationType = item.Value.ImplementationType?.ToString() ?? "", - Lifetime = item.Value.Lifetime.ToString(), - TypeAssembly = new AssemblyModel - { - FullName = item.Key.Assembly.FullName, - }, - ImplementationTypeAssembly = new AssemblyModel - { - FullName = item.Value.ImplementationType?.Assembly?.FullName ?? "" - } - }); - } - - responseModel.Code = 1; - responseModel.Message = "success"; - responseModel.Data = serviceModels; - } - catch (Exception ex) - { - responseModel.Code = -1; - responseModel.Message = "error"; - responseModel.Data = ex.ToString(); + FullName = item.Key.Assembly.FullName, + }, + ImplementationTypeAssembly = new AssemblyModel + { + FullName = item.Value.ImplementationType?.Assembly?.FullName ?? "" + } + }); } - return await Task.FromResult(responseModel); + responseModel.Code = 1; + responseModel.Message = "success"; + responseModel.Data = serviceModels; } - - #endregion - - public sealed class AssemblyLoadContextsResponseDataModel + catch (Exception ex) { - public AssemblyLoadContextModel Default - { - get; set; - } + responseModel.Code = -1; + responseModel.Message = "error"; + responseModel.Data = ex.ToString(); + } - public List All - { - get; set; - } + return await Task.FromResult(responseModel); + } - public sealed class AssemblyLoadContextModel - { - public string Name - { - get; set; - } - public string Type - { - get; set; - } - public List Assemblies - { - get; set; - } - } - } + #endregion - public sealed class AssembliesResponseDataModel + public sealed class AssemblyLoadContextsResponseDataModel + { + public AssemblyLoadContextModel Default { + get; set; + } + public List All + { + get; set; } - public sealed class ServiceModel + public sealed class AssemblyLoadContextModel { - public string Type + public string Name { get; set; } - - public string ImplementationType + public string Type { get; set; } - - public string Lifetime + public List Assemblies { get; set; } + } + } - public AssemblyModel TypeAssembly - { - get; set; - } + public sealed class AssembliesResponseDataModel + { - public AssemblyModel ImplementationTypeAssembly - { - get; set; - } + } + + public sealed class ServiceModel + { + public string Type + { + get; set; } - public sealed class AssemblyModel + public string ImplementationType { - public string FullName - { - get; set; - } + get; set; + } - public List DefinedTypes - { - get; set; - } + public string Lifetime + { + get; set; + } + + public AssemblyModel TypeAssembly + { + get; set; } + public AssemblyModel ImplementationTypeAssembly + { + get; set; + } } + + public sealed class AssemblyModel + { + public string FullName + { + get; set; + } + + public List DefinedTypes + { + get; set; + } + } + } diff --git a/src/PluginCore.AspNetCore/Controllers/HomeController.cs b/src/PluginCore.AspNetCore/Controllers/HomeController.cs index 3bf9cffc..4b0e6143 100644 --- a/src/PluginCore.AspNetCore/Controllers/HomeController.cs +++ b/src/PluginCore.AspNetCore/Controllers/HomeController.cs @@ -5,67 +5,56 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; -namespace PluginCore.AspNetCore.Controllers -{ - [Controller] - public class HomeController : Controller - { - #region Old - //private readonly IWebHostEnvironment _webHostEnvironment; - - //public bool IsLocalFronted - //{ - // get - // { - // return PluginCore.Config.PluginCoreConfigFactory.Create().IsLocalFrontend; - // } - //} - - //public string RemoteFronted - //{ - // get - // { - // return PluginCore.Config.PluginCoreConfigFactory.Create().RemoteFrontend; - // } - //} +namespace PluginCore.AspNetCore.Controllers; - //public HomeController(IWebHostEnvironment webHostEnvironment) - //{ - // this._webHostEnvironment = webHostEnvironment; - //} - - //[Route("PluginCore/Admin")] - //public async Task Home() - //{ - // if (this.IsLocalFronted) - // { - // var localIndexFilePath = Path.Combine( - // this._webHostEnvironment.ContentRootPath, "PluginCoreAdmin", "index.html"); - - // return PhysicalFile(localIndexFilePath, "text/html"); - // } - // else - // { - // string htmlStr = string.Empty; - // HttpClient httpClient = new HttpClient(); - // htmlStr = await httpClient.GetStringAsync(this.RemoteFronted + "/index.html"); - - // return Content(htmlStr, "text/html", Encoding.UTF8); - // } - //} - #endregion +[Controller] +public class HomeController : Controller +{ + #region Old + //private readonly IWebHostEnvironment _webHostEnvironment; + + //public bool IsLocalFronted + //{ + // get + // { + // return PluginCore.Config.PluginCoreConfigFactory.Create().IsLocalFrontend; + // } + //} + + //public string RemoteFronted + //{ + // get + // { + // return PluginCore.Config.PluginCoreConfigFactory.Create().RemoteFrontend; + // } + //} + + //public HomeController(IWebHostEnvironment webHostEnvironment) + //{ + // this._webHostEnvironment = webHostEnvironment; + //} + + //[Route("PluginCore/Admin")] + //public async Task Home() + //{ + // if (this.IsLocalFronted) + // { + // var localIndexFilePath = Path.Combine( + // this._webHostEnvironment.ContentRootPath, "PluginCoreAdmin", "index.html"); + + // return PhysicalFile(localIndexFilePath, "text/html"); + // } + // else + // { + // string htmlStr = string.Empty; + // HttpClient httpClient = new HttpClient(); + // htmlStr = await httpClient.GetStringAsync(this.RemoteFronted + "/index.html"); + + // return Content(htmlStr, "text/html", Encoding.UTF8); + // } + //} + #endregion - } } diff --git a/src/PluginCore.AspNetCore/Controllers/PluginWidgetController.cs b/src/PluginCore.AspNetCore/Controllers/PluginWidgetController.cs index 4bf8fca0..85ac5f5d 100644 --- a/src/PluginCore.AspNetCore/Controllers/PluginWidgetController.cs +++ b/src/PluginCore.AspNetCore/Controllers/PluginWidgetController.cs @@ -5,97 +5,91 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading.Tasks; -using PluginCore; -using PluginCore.Models; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using PluginCore.AspNetCore.Authorization; using PluginCore.AspNetCore.ResponseModel; using PluginCore.IPlugins; using System.Text; using PluginCore.Interfaces; +using PluginCore.IPlugins.Interfaces; +using PluginCore.IPlugins.IPlugins; + +namespace PluginCore.AspNetCore.Controllers; -namespace PluginCore.AspNetCore.Controllers +[Route("api/plugincore/[controller]/[action]")] +[ApiController] +public class PluginWidgetController : ControllerBase { - [Route("api/plugincore/[controller]/[action]")] - [ApiController] - public class PluginWidgetController : ControllerBase + #region Fields + private readonly IPluginFinder _pluginFinder; + #endregion + + #region Ctor + public PluginWidgetController(IPluginFinder pluginFinder) { - #region Fields - private readonly IPluginFinder _pluginFinder; - #endregion + _pluginFinder = pluginFinder; + } + #endregion - #region Ctor - public PluginWidgetController(IPluginFinder pluginFinder) + #region Actions + + #region Widget + /// + /// Widget + /// + /// + [HttpGet, HttpPost] + //public async Task> Widget(string widgetKey, string extraPars = "") + public async Task Widget(string widgetKey, string extraPars = "") + { + BaseResponseModel responseModel = new ResponseModel.BaseResponseModel(); + string responseData = ""; + widgetKey = widgetKey.Trim('"', '\''); + string[] extraParsArr = null; + if (!string.IsNullOrEmpty(extraPars)) { - _pluginFinder = pluginFinder; + extraParsArr = extraPars.Split(",", StringSplitOptions.RemoveEmptyEntries); + extraParsArr = extraParsArr.Select(m => m.Trim('"', '\'')).ToArray(); } - #endregion - - #region Actions - - #region Widget - /// - /// Widget - /// - /// - [HttpGet, HttpPost] - //public async Task> Widget(string widgetKey, string extraPars = "") - public async Task Widget(string widgetKey, string extraPars = "") + StringBuilder sb = new StringBuilder(); + sb.AppendLine($""); + try { - BaseResponseModel responseModel = new ResponseModel.BaseResponseModel(); - string responseData = ""; - widgetKey = widgetKey.Trim('"', '\''); - string[] extraParsArr = null; - if (!string.IsNullOrEmpty(extraPars)) + List plugins = this._pluginFinder.EnablePlugins().ToList(); + foreach (var item in plugins) { - extraParsArr = extraPars.Split(",", StringSplitOptions.RemoveEmptyEntries); - extraParsArr = extraParsArr.Select(m => m.Trim('"', '\'')).ToArray(); - } - StringBuilder sb = new StringBuilder(); - sb.AppendLine($""); - try - { - List plugins = this._pluginFinder.EnablePlugins().ToList(); - foreach (var item in plugins) + string widgetStr = await item.Widget(widgetKey, extraParsArr); + if (!string.IsNullOrEmpty(widgetStr)) { - string widgetStr = await item.Widget(widgetKey, extraParsArr); - if (!string.IsNullOrEmpty(widgetStr)) - { - // TODO: 配合 PluginCoreConfig.PluginWidgetDebug - // TODO: PluginCoreConfig 改为 Options 模式, 避免手动反复读取文件 效率低 - //sb.AppendLine($""); + // TODO: 配合 PluginCoreConfig.PluginWidgetDebug + // TODO: PluginCoreConfig 改为 Options 模式, 避免手动反复读取文件 效率低 + //sb.AppendLine($""); - sb.AppendLine(widgetStr); - } + sb.AppendLine(widgetStr); } - } - catch (Exception ex) - { - Utils.LogUtil.Exception(ex); - sb.AppendLine($""); - } - sb.AppendLine($""); - responseData = sb.ToString(); - - responseModel.Code = 1; - responseModel.Message = "Load Widget Success"; - responseModel.Data = responseData; - //return await Task.FromResult(responseModel); - return Content(responseData, "text/html;charset=utf-8"); } - #endregion + catch (Exception ex) + { + Utils.LogUtil.Exception(ex); + sb.AppendLine($""); + } + sb.AppendLine($""); + responseData = sb.ToString(); - #endregion + responseModel.Code = 1; + responseModel.Message = "Load Widget Success"; + responseModel.Data = responseData; + //return await Task.FromResult(responseModel); + return Content(responseData, "text/html;charset=utf-8"); } + #endregion + + #endregion + } diff --git a/src/PluginCore.AspNetCore/Controllers/PluginsController.cs b/src/PluginCore.AspNetCore/Controllers/PluginsController.cs index c6873c0b..2de05544 100644 --- a/src/PluginCore.AspNetCore/Controllers/PluginsController.cs +++ b/src/PluginCore.AspNetCore/Controllers/PluginsController.cs @@ -5,17 +5,11 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; +using System; using System.Collections.Generic; -using System.Configuration; using System.IO; using System.Linq; using System.Threading.Tasks; -//using Core.Common; -//using Framework.Authorization; -using PluginCore; using PluginCore.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -25,655 +19,654 @@ using PluginCore.AspNetCore.ResponseModel; using PluginCore.Interfaces; using PluginCore.AspNetCore.Interfaces; +using PluginCore.IPlugins.Infrastructure; +using PluginCore.IPlugins.Interfaces; -//using ResponseModel; +namespace PluginCore.AspNetCore.Controllers; -namespace PluginCore.AspNetCore.Controllers +[Route("api/plugincore/admin/[controller]/[action]")] +[PluginCoreAdminAuthorize] +[ApiController] +public class PluginsController : ControllerBase { - [Route("api/plugincore/admin/[controller]/[action]")] - [PluginCoreAdminAuthorize] - [ApiController] - public class PluginsController : ControllerBase + #region Fields + private readonly IPluginManager _pluginManager; + private readonly IPluginFinder _pluginFinder; + private readonly IPluginApplicationBuilderManager _pluginApplicationBuilderManager; + #endregion + + #region Ctor + public PluginsController(IPluginManager pluginManager, IPluginFinder pluginFinder, IPluginApplicationBuilderManager pluginApplicationBuilderManager) { - #region Fields - private readonly IPluginManager _pluginManager; - private readonly IPluginFinder _pluginFinder; - private readonly IPluginApplicationBuilderManager _pluginApplicationBuilderManager; - #endregion - - #region Ctor - public PluginsController(IPluginManager pluginManager, IPluginFinder pluginFinder, IPluginApplicationBuilderManager pluginApplicationBuilderManager) + _pluginManager = pluginManager; + _pluginFinder = pluginFinder; + _pluginApplicationBuilderManager = pluginApplicationBuilderManager; + } + #endregion + + #region Actions + + #region 插件列表 + /// + /// 加载插件列表 + /// + /// 插件状态 + /// + [HttpGet, HttpPost] + public async Task> List(string status = "all") + { + BaseResponseModel responseData = new ResponseModel.BaseResponseModel(); + var pluginConfigModel = PluginConfigModelFactory.Create(); + + // 获取所有插件信息 + IList pluginInfoModels = PluginInfoModelFactory.CreateAll(); + IList responseModels = new List(); + string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray(); + + // 添加插件状态 + responseModels = PluginInfoModelToResponseModel(pluginInfoModels, pluginConfigModel, enablePluginIds); + #region 筛选插件状态 + switch (status.ToLower()) { - _pluginManager = pluginManager; - _pluginFinder = pluginFinder; - _pluginApplicationBuilderManager = pluginApplicationBuilderManager; + case "all": + break; + case "enabled": + responseModels = responseModels.Where(m => m.Status == PluginStatus.Enabled).ToList(); + break; + case "disabled": + responseModels = responseModels.Where(m => m.Status == PluginStatus.Disabled).ToList(); + break; + default: + break; } #endregion - #region Actions + responseData.Code = 1; + responseData.Message = "加载插件列表成功"; + responseData.Data = responseModels; - #region 插件列表 - /// - /// 加载插件列表 - /// - /// 插件状态 - /// - [HttpGet, HttpPost] - public async Task> List(string status = "all") - { - BaseResponseModel responseData = new ResponseModel.BaseResponseModel(); - var pluginConfigModel = PluginConfigModelFactory.Create(); + return await Task.FromResult(responseData); + } + #endregion - // 获取所有插件信息 - IList pluginInfoModels = PluginInfoModelFactory.CreateAll(); - IList responseModels = new List(); - string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray(); + #region 卸载插件 + [HttpGet, HttpPost] + public async Task> Uninstall(string pluginId) + { + BaseResponseModel responseData = new BaseResponseModel(); + pluginId = pluginId.Trim(); + var pluginConfigModel = PluginConfigModelFactory.Create(); + // 卸载插件 必须 先禁用插件 + #region 效验 + if (pluginConfigModel.EnabledPlugins.Contains(pluginId)) + { + responseData.Code = -1; + responseData.Message = "卸载失败: 请先禁用此插件"; + return await Task.FromResult(responseData); + } + string pluginDirStr= Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); + string pluginWwwrootDirStr = Path.Combine(PluginPathProvider.PluginsWwwRootDir(), pluginId); + if (!Directory.Exists(pluginDirStr) && !Directory.Exists(pluginWwwrootDirStr)) + { + responseData.Code = -2; + responseData.Message = "卸载失败: 此插件不存在"; + return await Task.FromResult(responseData); + } + #endregion - // 添加插件状态 - responseModels = PluginInfoModelToResponseModel(pluginInfoModels, pluginConfigModel, enablePluginIds); - #region 筛选插件状态 - switch (status.ToLower()) - { - case "all": - break; - case "enabled": - responseModels = responseModels.Where(m => m.Status == PluginStatus.Enabled).ToList(); - break; - case "disabled": - responseModels = responseModels.Where(m => m.Status == PluginStatus.Disabled).ToList(); - break; - default: - break; + try + { + // PS:卸载插件必须先禁用插件,所以此时插件LoadContext已被移除释放(插件Assemblies已被释放), 此处不需移除LoadContext + + // 1.删除物理文件 + var pluginDir = new DirectoryInfo(pluginDirStr); + if (pluginDir.Exists) { + pluginDir.Delete(true); + } + // 虽然 已禁用 时 pluginWwwrootDirStr/pluginId 已删除, 但为确保, 还是再删除一次 + var pluginWwwrootDir = new DirectoryInfo(pluginWwwrootDirStr); + if (pluginWwwrootDir.Exists) { + pluginWwwrootDir.Delete(true); } - #endregion responseData.Code = 1; - responseData.Message = "加载插件列表成功"; - responseData.Data = responseModels; + responseData.Message = "卸载成功"; + } + catch (Exception ex) + { + responseData.Code = -1; + responseData.Message = "卸载失败: " + ex.Message; + } + + return await Task.FromResult(responseData); + } + #endregion + #region 启用插件 + [HttpGet, HttpPost] + public async Task> Enable(string pluginId) + { + BaseResponseModel responseData = new BaseResponseModel(); + var pluginConfigModel = PluginConfigModelFactory.Create(); + // 效验是否存在于 已禁用插件列表 + #region 效验 + pluginId = pluginId.Trim(); + var pluginDir = new DirectoryInfo(Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId)); + if (pluginDir != null && !pluginDir.Exists) + { + responseData.Code = -1; + responseData.Message = "启用失败: 此插件不存在"; + return await Task.FromResult(responseData); + } + string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray(); + if (enablePluginIds.Contains(pluginId)) { + responseData.Code = -2; + responseData.Message = "启用失败: 此插件已启用"; return await Task.FromResult(responseData); } #endregion - #region 卸载插件 - [HttpGet, HttpPost] - public async Task> Uninstall(string pluginId) + try { - BaseResponseModel responseData = new BaseResponseModel(); - pluginId = pluginId.Trim(); - var pluginConfigModel = PluginConfigModelFactory.Create(); - // 卸载插件 必须 先禁用插件 - #region 效验 - if (pluginConfigModel.EnabledPlugins.Contains(pluginId)) + // 1. 创建插件程序集加载上下文, 添加到 PluginsLoadContexts + _pluginManager.LoadPlugin(pluginId); + // 2. 添加到 pluginConfigModel.EnabledPlugins + pluginConfigModel.EnabledPlugins.Add(pluginId); + // 4.保存到 plugin.config.json + PluginConfigModelFactory.Save(pluginConfigModel); + + // 5. 找到此插件实例 + IPlugin plugin = _pluginFinder.Plugin(pluginId); + if (plugin == null) { + // 7.启用不成功, 回滚插件状态: (1)释放插件上下文 (2)更新 plugin.config.json + try + { + _pluginManager.UnloadPlugin(pluginId); + } + catch (Exception ex) + { } + + // 从 pluginConfigModel.EnabledPlugins 移除 + pluginConfigModel.EnabledPlugins.Remove(pluginId); + // 保存到 plugin.config.json + PluginConfigModelFactory.Save(pluginConfigModel); + responseData.Code = -1; - responseData.Message = "卸载失败: 请先禁用此插件"; + responseData.Message = "启用失败: 此插件不存在"; return await Task.FromResult(responseData); } - string pluginDirStr= Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); - string pluginWwwrootDirStr = Path.Combine(PluginPathProvider.PluginsWwwRootDir(), pluginId); - if (!Directory.Exists(pluginDirStr) && !Directory.Exists(pluginWwwrootDirStr)) + // 6.调取插件的 AfterEnable(), 插件开发者可在此回收资源 + var pluginEnableResult = plugin.AfterEnable(); + if (!pluginEnableResult.IsSuccess) { - responseData.Code = -2; - responseData.Message = "卸载失败: 此插件不存在"; + // 7.启用不成功, 回滚插件状态: (1)释放插件上下文 (2)更新 plugin.config.json + try + { + _pluginManager.UnloadPlugin(pluginId); + } + catch (Exception ex) + { } + + // 从 pluginConfigModel.EnabledPlugins 移除 + pluginConfigModel.EnabledPlugins.Remove(pluginId); + // 保存到 plugin.config.json + PluginConfigModelFactory.Save(pluginConfigModel); + + responseData.Code = -1; + responseData.Message = "启用失败: 来自插件的错误信息: " + pluginEnableResult.Message; return await Task.FromResult(responseData); } - #endregion - try - { - // PS:卸载插件必须先禁用插件,所以此时插件LoadContext已被移除释放(插件Assemblies已被释放), 此处不需移除LoadContext - - // 1.删除物理文件 - var pluginDir = new DirectoryInfo(pluginDirStr); - if (pluginDir.Exists) { - pluginDir.Delete(true); - } - // 虽然 已禁用 时 pluginWwwrootDirStr/pluginId 已删除, 但为确保, 还是再删除一次 - var pluginWwwrootDir = new DirectoryInfo(pluginWwwrootDirStr); - if (pluginWwwrootDir.Exists) { - pluginWwwrootDir.Delete(true); - } + // 7. ReBuild + this._pluginApplicationBuilderManager.ReBuild(); - responseData.Code = 1; - responseData.Message = "卸载成功"; - } - catch (Exception ex) + // 8. 尝试复制 插件下的 wwwroot 到 Plugins_wwwroot + string wwwRootDir = PluginPathProvider.WwwRootDir(pluginId); + if (Directory.Exists(wwwRootDir)) { - responseData.Code = -1; - responseData.Message = "卸载失败: " + ex.Message; + string targetDir = PluginPathProvider.PluginWwwRootDir(pluginId); + Utils.FileUtil.CopyFolder(wwwRootDir, targetDir); } - return await Task.FromResult(responseData); + responseData.Code = 1; + responseData.Message = "启用成功"; + } + catch (Exception ex) + { + responseData.Code = -2; + responseData.Message = "启用失败: " + ex.Message; } + + return await Task.FromResult(responseData); + } + #endregion + + #region 禁用插件 + [HttpGet, HttpPost] + public async Task> Disable(string pluginId) + { + BaseResponseModel responseData = new BaseResponseModel(); + #region 效验 + pluginId = pluginId.Trim(); + var pluginConfigModel = PluginConfigModelFactory.Create(); + // string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray(); + // // 效验是否存在于 已启用插件列表 + // if (!enablePluginIds.Contains(pluginId)) + // { + // responseData.Code = -1; + // responseData.Message = "禁用失败: 此插件不存在, 或未启用"; + // return await Task.FromResult(responseData); + // } #endregion - #region 启用插件 - [HttpGet, HttpPost] - public async Task> Enable(string pluginId) + try { - BaseResponseModel responseData = new BaseResponseModel(); - var pluginConfigModel = PluginConfigModelFactory.Create(); - // 效验是否存在于 已禁用插件列表 - #region 效验 - pluginId = pluginId.Trim(); - var pluginDir = new DirectoryInfo(Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId)); - if (pluginDir != null && !pluginDir.Exists) + // 1. 找到此插件实例 + IPlugin plugin = _pluginFinder.Plugin(pluginId); + if (plugin == null) { responseData.Code = -1; - responseData.Message = "启用失败: 此插件不存在"; - return await Task.FromResult(responseData); - } - string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray(); - if (enablePluginIds.Contains(pluginId)) { - responseData.Code = -2; - responseData.Message = "启用失败: 此插件已启用"; + responseData.Message = "禁用失败: 此插件不存在, 或未启用"; return await Task.FromResult(responseData); } - #endregion - try { - // 1. 创建插件程序集加载上下文, 添加到 PluginsLoadContexts - _pluginManager.LoadPlugin(pluginId); - // 2. 添加到 pluginConfigModel.EnabledPlugins - pluginConfigModel.EnabledPlugins.Add(pluginId); - // 4.保存到 plugin.config.json - PluginConfigModelFactory.Save(pluginConfigModel); - - // 5. 找到此插件实例 - IPlugin plugin = _pluginFinder.Plugin(pluginId); - if (plugin == null) + // 2.调取插件的 BeforeDisable(), 插件开发者可在此回收资源 + var pluginDisableResult = plugin.BeforeDisable(); + if (!pluginDisableResult.IsSuccess) { - // 7.启用不成功, 回滚插件状态: (1)释放插件上下文 (2)更新 plugin.config.json - try - { - _pluginManager.UnloadPlugin(pluginId); - } - catch (Exception ex) - { } - - // 从 pluginConfigModel.EnabledPlugins 移除 - pluginConfigModel.EnabledPlugins.Remove(pluginId); - // 保存到 plugin.config.json - PluginConfigModelFactory.Save(pluginConfigModel); - responseData.Code = -1; - responseData.Message = "启用失败: 此插件不存在"; + responseData.Message = "禁用失败: 来自插件的错误信息: " + pluginDisableResult.Message; return await Task.FromResult(responseData); } - // 6.调取插件的 AfterEnable(), 插件开发者可在此回收资源 - var pluginEnableResult = plugin.AfterEnable(); - if (!pluginEnableResult.IsSuccess) - { - // 7.启用不成功, 回滚插件状态: (1)释放插件上下文 (2)更新 plugin.config.json - try - { - _pluginManager.UnloadPlugin(pluginId); - } - catch (Exception ex) - { } - - // 从 pluginConfigModel.EnabledPlugins 移除 + // 3.移除插件对应的程序集加载上下文 + _pluginManager.UnloadPlugin(pluginId); + // 3.1. ReBuild + this._pluginApplicationBuilderManager.ReBuild(); + if (pluginConfigModel.EnabledPlugins.Contains(pluginId)) { + // 4.从 pluginConfigModel.EnabledPlugins 移除 pluginConfigModel.EnabledPlugins.Remove(pluginId); - // 保存到 plugin.config.json + // 5.保存到 plugin.config.json PluginConfigModelFactory.Save(pluginConfigModel); - - responseData.Code = -1; - responseData.Message = "启用失败: 来自插件的错误信息: " + pluginEnableResult.Message; - return await Task.FromResult(responseData); - } - - // 7. ReBuild - this._pluginApplicationBuilderManager.ReBuild(); - - // 8. 尝试复制 插件下的 wwwroot 到 Plugins_wwwroot - string wwwRootDir = PluginPathProvider.WwwRootDir(pluginId); - if (Directory.Exists(wwwRootDir)) - { - string targetDir = PluginPathProvider.PluginWwwRootDir(pluginId); - Utils.FileUtil.CopyFolder(wwwRootDir, targetDir); } - - responseData.Code = 1; - responseData.Message = "启用成功"; } catch (Exception ex) { - responseData.Code = -2; - responseData.Message = "启用失败: " + ex.Message; + Utils.LogUtil.Error(ex.ToString()); + responseData.Code = -1; + responseData.Message = "禁用失败: 此插件不存在, 或未启用"; + return await Task.FromResult(responseData); } - return await Task.FromResult(responseData); - } - #endregion - - #region 禁用插件 - [HttpGet, HttpPost] - public async Task> Disable(string pluginId) - { - BaseResponseModel responseData = new BaseResponseModel(); - #region 效验 - pluginId = pluginId.Trim(); - var pluginConfigModel = PluginConfigModelFactory.Create(); - // string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray(); - // // 效验是否存在于 已启用插件列表 - // if (!enablePluginIds.Contains(pluginId)) - // { - // responseData.Code = -1; - // responseData.Message = "禁用失败: 此插件不存在, 或未启用"; - // return await Task.FromResult(responseData); - // } - #endregion - - try + // 7. 尝试移除 Plugins_wwwroot/PluginId + string pluginWwwRootDir = PluginPathProvider.PluginWwwRootDir(pluginId); + if (Directory.Exists(pluginWwwRootDir)) { - // 1. 找到此插件实例 - IPlugin plugin = _pluginFinder.Plugin(pluginId); - if (plugin == null) - { - responseData.Code = -1; - responseData.Message = "禁用失败: 此插件不存在, 或未启用"; - return await Task.FromResult(responseData); - } - try - { - // 2.调取插件的 BeforeDisable(), 插件开发者可在此回收资源 - var pluginDisableResult = plugin.BeforeDisable(); - if (!pluginDisableResult.IsSuccess) - { - responseData.Code = -1; - responseData.Message = "禁用失败: 来自插件的错误信息: " + pluginDisableResult.Message; - return await Task.FromResult(responseData); - } - // 3.移除插件对应的程序集加载上下文 - _pluginManager.UnloadPlugin(pluginId); - // 3.1. ReBuild - this._pluginApplicationBuilderManager.ReBuild(); - if (pluginConfigModel.EnabledPlugins.Contains(pluginId)) { - // 4.从 pluginConfigModel.EnabledPlugins 移除 - pluginConfigModel.EnabledPlugins.Remove(pluginId); - // 5.保存到 plugin.config.json - PluginConfigModelFactory.Save(pluginConfigModel); - } - } - catch (Exception ex) - { - Utils.LogUtil.Error(ex.ToString()); - responseData.Code = -1; - responseData.Message = "禁用失败: 此插件不存在, 或未启用"; - return await Task.FromResult(responseData); - } + Directory.Delete(pluginWwwRootDir, true); + } - // 7. 尝试移除 Plugins_wwwroot/PluginId - string pluginWwwRootDir = PluginPathProvider.PluginWwwRootDir(pluginId); - if (Directory.Exists(pluginWwwRootDir)) - { - Directory.Delete(pluginWwwRootDir, true); - } + responseData.Code = 1; + responseData.Message = "禁用成功"; + } + catch (Exception ex) + { + responseData.Code = -2; + responseData.Message = "禁用失败: " + ex.Message; + } - responseData.Code = 1; - responseData.Message = "禁用成功"; - } - catch (Exception ex) - { - responseData.Code = -2; - responseData.Message = "禁用失败: " + ex.Message; - } + return await Task.FromResult(responseData); + } + #endregion + + #region 上传插件 + /// + /// 上传插件 + /// + /// 注意: 参数名一定为 file, 对应前端传过来时以 file 为名 + /// + [HttpGet, HttpPost] + public async Task> Upload([FromForm] IFormFile file) + { + BaseResponseModel responseData = new BaseResponseModel(); - return await Task.FromResult(responseData); + #region 效验 + if (file == null) + { + responseData.Code = -1; + responseData.Message = "上传的文件不能为空"; + return responseData; + } + //文件后缀 + string fileExtension = Path.GetExtension(file.FileName);//获取文件格式,拓展名 + // 类型标记 + UploadFileType uploadFileType = UploadFileType.NoAllowedType; + switch (fileExtension) + { + case ".zip": + uploadFileType = UploadFileType.Zip; + break; + case ".nupkg": + uploadFileType = UploadFileType.Nupkg; + break; } - #endregion - #region 上传插件 - /// - /// 上传插件 - /// - /// 注意: 参数名一定为 file, 对应前端传过来时以 file 为名 - /// - [HttpGet, HttpPost] - public async Task> Upload([FromForm] IFormFile file) + if (fileExtension != ".zip" && fileExtension != ".nupkg") { - BaseResponseModel responseData = new BaseResponseModel(); + responseData.Code = -1; + // nupkg 其实就是 zip + responseData.Message = "只能上传 zip 或 nupkg 格式文件"; + return responseData; + } + // PluginCore.AspNetCore-v1.0.2 起 不再限制插件上传大小 + //判断文件大小 + //var fileSize = file.Length; + //if (fileSize > 1024 * 1024 * 5) // 5M + //{ + // responseData.Code = -1; + // responseData.Message = "上传的文件不能大于5MB"; + // return responseData; + //} + #endregion - #region 效验 - if (file == null) + try + { + // 1.先上传到 临时插件上传目录, 用Guid.zip作为保存文件名 + string tempZipFilePath = Path.Combine(PluginPathProvider.TempPluginUploadDir(), Guid.NewGuid() + ".zip"); + using (var fs = System.IO.File.Create(tempZipFilePath)) { - responseData.Code = -1; - responseData.Message = "上传的文件不能为空"; - return responseData; + file.CopyTo(fs); //将上传的文件文件流,复制到fs中 + fs.Flush();//清空文件流 } - //文件后缀 - string fileExtension = Path.GetExtension(file.FileName);//获取文件格式,拓展名 - // 类型标记 - UploadFileType uploadFileType = UploadFileType.NoAllowedType; - switch (fileExtension) + // 2.解压 + bool isDecomparessSuccess = false; + if (uploadFileType == UploadFileType.Zip) { - case ".zip": - uploadFileType = UploadFileType.Zip; - break; - case ".nupkg": - uploadFileType = UploadFileType.Nupkg; - break; + isDecomparessSuccess = Utils.ZipHelper.DecomparessFile(tempZipFilePath, tempZipFilePath.Replace(".zip", ""),null!); + } + else if (uploadFileType == UploadFileType.Nupkg) + { + isDecomparessSuccess = NupkgService.DecomparessFile(tempZipFilePath, tempZipFilePath.Replace(".zip", "")); } - if (fileExtension != ".zip" && fileExtension != ".nupkg") + // 3.删除原压缩包 + System.IO.File.Delete(tempZipFilePath); + if (!isDecomparessSuccess) { responseData.Code = -1; - // nupkg 其实就是 zip - responseData.Message = "只能上传 zip 或 nupkg 格式文件"; + responseData.Message = "解压插件压缩包失败"; return responseData; } - // PluginCore.AspNetCore-v1.0.2 起 不再限制插件上传大小 - //判断文件大小 - //var fileSize = file.Length; - //if (fileSize > 1024 * 1024 * 5) // 5M - //{ - // responseData.Code = -1; - // responseData.Message = "上传的文件不能大于5MB"; - // return responseData; - //} - #endregion - - try + // 4.读取其中的info.json, 获取 PluginId 值 + PluginInfoModel pluginInfoModel = PluginInfoModelFactory.ReadPluginDir(tempZipFilePath.Replace(".zip", "")); + if (pluginInfoModel == null || string.IsNullOrEmpty(pluginInfoModel.PluginId)) { - // 1.先上传到 临时插件上传目录, 用Guid.zip作为保存文件名 - string tempZipFilePath = Path.Combine(PluginPathProvider.TempPluginUploadDir(), Guid.NewGuid() + ".zip"); - using (var fs = System.IO.File.Create(tempZipFilePath)) - { - file.CopyTo(fs); //将上传的文件文件流,复制到fs中 - fs.Flush();//清空文件流 - } - // 2.解压 - bool isDecomparessSuccess = false; - if (uploadFileType == UploadFileType.Zip) - { - isDecomparessSuccess = Utils.ZipHelper.DecomparessFile(tempZipFilePath, tempZipFilePath.Replace(".zip", "")); - } - else if (uploadFileType == UploadFileType.Nupkg) - { - isDecomparessSuccess = NupkgService.DecomparessFile(tempZipFilePath, tempZipFilePath.Replace(".zip", "")); - } - - // 3.删除原压缩包 - System.IO.File.Delete(tempZipFilePath); - if (!isDecomparessSuccess) - { - responseData.Code = -1; - responseData.Message = "解压插件压缩包失败"; - return responseData; - } - // 4.读取其中的info.json, 获取 PluginId 值 - PluginInfoModel pluginInfoModel = PluginInfoModelFactory.ReadPluginDir(tempZipFilePath.Replace(".zip", "")); - if (pluginInfoModel == null || string.IsNullOrEmpty(pluginInfoModel.PluginId)) - { - // 记得删除已不再需要的临时插件文件夹 - Directory.Delete(tempZipFilePath.Replace(".zip", ""), true); - - responseData.Code = -1; - responseData.Message = "不合法的插件"; - return responseData; - } - string pluginId = pluginInfoModel.PluginId; - // 5.检索 此 PluginId 是否本地插件已存在 - var pluginConfigModel = PluginConfigModelFactory.Create(); - // 本地已经存在的 PluginId - IList localExistPluginIds = PluginPathProvider.AllPluginFolderName(); - if (localExistPluginIds.Contains(pluginId)) - { - // 记得删除已不再需要的临时插件文件夹 - Directory.Delete(tempZipFilePath.Replace(".zip", ""), true); - - responseData.Code = -1; - responseData.Message = $"本地已有此插件 (PluginId: {pluginId}), 请前往插件列表删除后, 再上传"; - return responseData; - } - // 6.本地无此插件 -> 移动插件文件夹到 Plugins 下, 并以 PluginId 为插件文件夹名 - string pluginsRootPath = PluginPathProvider.PluginsRootPath(); - string newPluginDir = Path.Combine(pluginsRootPath, pluginId); - Directory.Move(tempZipFilePath.Replace(".zip", ""), newPluginDir); - - // 7. 放入 Plugins 中, 默认为 已禁用 + // 记得删除已不再需要的临时插件文件夹 + Directory.Delete(tempZipFilePath.Replace(".zip", ""), true); - responseData.Code = 1; - responseData.Message = $"上传插件成功 (PluginId: {pluginId})"; + responseData.Code = -1; + responseData.Message = "不合法的插件"; + return responseData; } - catch (Exception ex) + string pluginId = pluginInfoModel.PluginId; + // 5.检索 此 PluginId 是否本地插件已存在 + var pluginConfigModel = PluginConfigModelFactory.Create(); + // 本地已经存在的 PluginId + IList localExistPluginIds = PluginPathProvider.AllPluginFolderName(); + if (localExistPluginIds.Contains(pluginId)) { + // 记得删除已不再需要的临时插件文件夹 + Directory.Delete(tempZipFilePath.Replace(".zip", ""), true); + responseData.Code = -1; - responseData.Message = "上传插件失败: " + ex.Message; - ex = ex.InnerException; - while (ex != null) - { - responseData.Message += " - " + ex.InnerException.Message; - ex = ex.InnerException; - } + responseData.Message = $"本地已有此插件 (PluginId: {pluginId}), 请前往插件列表删除后, 再上传"; + return responseData; } + // 6.本地无此插件 -> 移动插件文件夹到 Plugins 下, 并以 PluginId 为插件文件夹名 + string pluginsRootPath = PluginPathProvider.PluginsRootPath(); + string newPluginDir = Path.Combine(pluginsRootPath, pluginId); + Directory.Move(tempZipFilePath.Replace(".zip", ""), newPluginDir); - return await Task.FromResult(responseData); - } - #endregion + // 7. 放入 Plugins 中, 默认为 已禁用 - #region 查看详细 - [HttpGet, HttpPost] - public async Task> Details(string pluginId) + responseData.Code = 1; + responseData.Message = $"上传插件成功 (PluginId: {pluginId})"; + } + catch (Exception ex) { - BaseResponseModel responseData = new BaseResponseModel(); - - try + responseData.Code = -1; + responseData.Message = "上传插件失败: " + ex.Message; + ex = ex.InnerException; + while (ex != null) { - #region 效验 - pluginId = pluginId.Trim(); - var pluginConfigModel = PluginConfigModelFactory.Create(); - string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray(); - - if (!localPluginIds.Contains(pluginId)) - { - responseData.Code = -1; - responseData.Message = $"查看详细失败: 不存在 {pluginId} 插件"; - return await Task.FromResult(responseData); - } + responseData.Message += " - " + ex.InnerException.Message; + ex = ex.InnerException; + } + } - #endregion + return await Task.FromResult(responseData); + } + #endregion - PluginInfoModel pluginInfoModel = PluginInfoModelFactory.Create(pluginId); - string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray(); - PluginInfoResponseModel pluginInfoResponseModel = PluginInfoModelToResponseModel(new List() { pluginInfoModel }, pluginConfigModel, enablePluginIds).FirstOrDefault(); + #region 查看详细 + [HttpGet, HttpPost] + public async Task> Details(string pluginId) + { + BaseResponseModel responseData = new BaseResponseModel(); + try + { + #region 效验 + pluginId = pluginId.Trim(); + var pluginConfigModel = PluginConfigModelFactory.Create(); + string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray(); - responseData.Code = 1; - responseData.Message = "查看详细成功"; - responseData.Data = pluginInfoResponseModel; - } - catch (Exception ex) + if (!localPluginIds.Contains(pluginId)) { responseData.Code = -1; - responseData.Message = "查看详细失败: " + ex.Message; + responseData.Message = $"查看详细失败: 不存在 {pluginId} 插件"; + return await Task.FromResult(responseData); } - return await Task.FromResult(responseData); - } - #endregion + #endregion - #region 查看文档 - [HttpGet, HttpPost] - public async Task> Readme(string pluginId) - { - BaseResponseModel responseData = new BaseResponseModel(); + PluginInfoModel pluginInfoModel = PluginInfoModelFactory.Create(pluginId); + string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray(); + PluginInfoResponseModel pluginInfoResponseModel = PluginInfoModelToResponseModel(new List() { pluginInfoModel }, pluginConfigModel, enablePluginIds).FirstOrDefault(); - try - { - #region 效验 - pluginId = pluginId.Trim(); - // var pluginConfigModel = PluginConfigModelFactory.Create(); - string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray(); - if (!localPluginIds.Contains(pluginId)) - { - responseData.Code = -1; - responseData.Message = $"查看文档失败: 不存在 {pluginId} 插件"; - return await Task.FromResult(responseData); - } + responseData.Code = 1; + responseData.Message = "查看详细成功"; + responseData.Data = pluginInfoResponseModel; + } + catch (Exception ex) + { + responseData.Code = -1; + responseData.Message = "查看详细失败: " + ex.Message; + } - #endregion + return await Task.FromResult(responseData); + } + #endregion - PluginReadmeModel readmeModel = PluginReadmeModelFactory.Create(pluginId); - PluginReadmeResponseModel readmeResponseModel = new PluginReadmeResponseModel(); - readmeResponseModel.Content = readmeModel?.Content ?? ""; - readmeResponseModel.PluginId = pluginId; + #region 查看文档 + [HttpGet, HttpPost] + public async Task> Readme(string pluginId) + { + BaseResponseModel responseData = new BaseResponseModel(); - responseData.Code = 1; - responseData.Message = "查看文档成功"; - responseData.Data = readmeResponseModel; - } - catch (Exception ex) + try + { + #region 效验 + pluginId = pluginId.Trim(); + // var pluginConfigModel = PluginConfigModelFactory.Create(); + string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray(); + + if (!localPluginIds.Contains(pluginId)) { responseData.Code = -1; - responseData.Message = "查看文档失败: " + ex.Message; + responseData.Message = $"查看文档失败: 不存在 {pluginId} 插件"; + return await Task.FromResult(responseData); } - return await Task.FromResult(responseData); - } - #endregion - - #region 设置 - [HttpGet] - public async Task> Settings(string pluginId) - { - BaseResponseModel responseData = new BaseResponseModel(); + #endregion - try - { - #region 效验 - pluginId = pluginId.Trim(); - // var pluginConfigModel = PluginConfigModelFactory.Create(); - string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray(); + PluginReadmeModel readmeModel = PluginReadmeModelFactory.Create(pluginId); + PluginReadmeResponseModel readmeResponseModel = new PluginReadmeResponseModel(); + readmeResponseModel.Content = readmeModel?.Content ?? ""; + readmeResponseModel.PluginId = pluginId; - if (!localPluginIds.Contains(pluginId)) - { - responseData.Code = -1; - responseData.Message = $"查看设置失败: 不存在 {pluginId} 插件"; - return await Task.FromResult(responseData); - } + responseData.Code = 1; + responseData.Message = "查看文档成功"; + responseData.Data = readmeResponseModel; + } + catch (Exception ex) + { + responseData.Code = -1; + responseData.Message = "查看文档失败: " + ex.Message; + } - #endregion + return await Task.FromResult(responseData); + } + #endregion - string settingsJsonStr = PluginSettingsModelFactory.Create(pluginId); + #region 设置 + [HttpGet] + public async Task> Settings(string pluginId) + { + BaseResponseModel responseData = new BaseResponseModel(); + try + { + #region 效验 + pluginId = pluginId.Trim(); + // var pluginConfigModel = PluginConfigModelFactory.Create(); + string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray(); - responseData.Code = 1; - responseData.Message = "查看设置成功"; - responseData.Data = settingsJsonStr ?? "无设置项"; - } - catch (Exception ex) + if (!localPluginIds.Contains(pluginId)) { responseData.Code = -1; - responseData.Message = "查看设置失败: " + ex.Message; + responseData.Message = $"查看设置失败: 不存在 {pluginId} 插件"; + return await Task.FromResult(responseData); } - return await Task.FromResult(responseData); - } + #endregion - [HttpPost] - public async Task> Settings(PluginSettingsInputModel inputModel) - { - BaseResponseModel responseData = new BaseResponseModel(); + string settingsJsonStr = PluginSettingsModelFactory.Create(pluginId); - try - { - #region 效验 - inputModel.PluginId = inputModel.PluginId.Trim(); - // var pluginConfigModel = PluginConfigModelFactory.Create(); - string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray(); - if (!localPluginIds.Contains(inputModel.PluginId)) - { - responseData.Code = -1; - responseData.Message = $"设置失败: 不存在 {inputModel.PluginId} 插件"; - return await Task.FromResult(responseData); - } + responseData.Code = 1; + responseData.Message = "查看设置成功"; + responseData.Data = settingsJsonStr ?? "无设置项"; + } + catch (Exception ex) + { + responseData.Code = -1; + responseData.Message = "查看设置失败: " + ex.Message; + } - #endregion + return await Task.FromResult(responseData); + } - inputModel.Data = inputModel.Data ?? ""; - PluginSettingsModelFactory.Save(pluginSettingsJsonStr: inputModel.Data, pluginId: inputModel.PluginId); + [HttpPost] + public async Task> Settings(PluginSettingsInputModel inputModel) + { + BaseResponseModel responseData = new BaseResponseModel(); + try + { + #region 效验 + inputModel.PluginId = inputModel.PluginId.Trim(); + // var pluginConfigModel = PluginConfigModelFactory.Create(); + string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray(); - responseData.Code = 1; - responseData.Message = "设置成功"; - responseData.Data = inputModel.Data; - } - catch (Exception ex) + if (!localPluginIds.Contains(inputModel.PluginId)) { responseData.Code = -1; - responseData.Message = "设置失败: " + ex.Message; + responseData.Message = $"设置失败: 不存在 {inputModel.PluginId} 插件"; + return await Task.FromResult(responseData); } - return await Task.FromResult(responseData); + #endregion + + inputModel.Data = inputModel.Data ?? ""; + PluginSettingsModelFactory.Save(pluginSettingsJsonStr: inputModel.Data, pluginId: inputModel.PluginId); + + + responseData.Code = 1; + responseData.Message = "设置成功"; + responseData.Data = inputModel.Data; + } + catch (Exception ex) + { + responseData.Code = -1; + responseData.Message = "设置失败: " + ex.Message; } - #endregion - #endregion + return await Task.FromResult(responseData); + } + #endregion + #endregion - #region Helpers - [NonAction] - private IList PluginInfoModelToResponseModel(IList pluginInfoModels, PluginConfigModel pluginConfigModel, string[] enablePluginIds) + #region Helpers + + [NonAction] + private IList PluginInfoModelToResponseModel(IList pluginInfoModels, PluginConfigModel pluginConfigModel, string[] enablePluginIds) + { + // 获取 Plugins 下所有插件 + // DirectoryInfo pluginsDir = new DirectoryInfo(PluginPathProvider.PluginsRootPath()); + // List pluginIds = pluginsDir?.GetDirectories()?.Select(m => m.Name)?.ToList() ?? new List(); + + IList responseModels = new List(); + #region 添加插件状态信息 + foreach (var model in pluginInfoModels) { - // 获取 Plugins 下所有插件 - // DirectoryInfo pluginsDir = new DirectoryInfo(PluginPathProvider.PluginsRootPath()); - // List pluginIds = pluginsDir?.GetDirectories()?.Select(m => m.Name)?.ToList() ?? new List(); + PluginInfoResponseModel responseModel = new PluginInfoResponseModel(); + responseModel.Author = model.Author; + responseModel.Description = model.Description; + responseModel.DisplayName = model.DisplayName; + responseModel.PluginId = model.PluginId; + responseModel.SupportedVersions = model.SupportedVersions; + responseModel.Version = model.Version; + responseModel.DependPlugins = model.DependPlugins; + + if (pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && !enablePluginIds.Contains(model.PluginId)) { + // 错误情况: 配置 标识 已启用, 但实际没有启用成功 + pluginConfigModel.EnabledPlugins.Remove(model.PluginId); + PluginConfigModelFactory.Save(pluginConfigModel); - IList responseModels = new List(); - #region 添加插件状态信息 - foreach (var model in pluginInfoModels) + responseModel.Status = PluginStatus.Disabled; + } else if(!pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && enablePluginIds.Contains(model.PluginId)) { - PluginInfoResponseModel responseModel = new PluginInfoResponseModel(); - responseModel.Author = model.Author; - responseModel.Description = model.Description; - responseModel.DisplayName = model.DisplayName; - responseModel.PluginId = model.PluginId; - responseModel.SupportedVersions = model.SupportedVersions; - responseModel.Version = model.Version; - responseModel.DependPlugins = model.DependPlugins; - - if (pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && !enablePluginIds.Contains(model.PluginId)) { - // 错误情况: 配置 标识 已启用, 但实际没有启用成功 - pluginConfigModel.EnabledPlugins.Remove(model.PluginId); - PluginConfigModelFactory.Save(pluginConfigModel); - - responseModel.Status = PluginStatus.Disabled; - } else if(!pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && enablePluginIds.Contains(model.PluginId)) - { - // 错误情况: 配置没有标识 已启用, 但实际 已启用 - pluginConfigModel.EnabledPlugins.Add(model.PluginId); - PluginConfigModelFactory.Save(pluginConfigModel); + // 错误情况: 配置没有标识 已启用, 但实际 已启用 + pluginConfigModel.EnabledPlugins.Add(model.PluginId); + PluginConfigModelFactory.Save(pluginConfigModel); - responseModel.Status = PluginStatus.Enabled; - } else if (pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && enablePluginIds.Contains(model.PluginId)) - { - responseModel.Status = PluginStatus.Enabled; - } - else - { - responseModel.Status = PluginStatus.Disabled; - } - responseModels.Add(responseModel); + responseModel.Status = PluginStatus.Enabled; + } else if (pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && enablePluginIds.Contains(model.PluginId)) + { + responseModel.Status = PluginStatus.Enabled; } - #endregion - - return responseModels; + else + { + responseModel.Status = PluginStatus.Disabled; + } + responseModels.Add(responseModel); } + #endregion - public enum UploadFileType - { - NoAllowedType = 0, - Zip = 1, - Nupkg = 2 - } + return responseModels; + } - #endregion + public enum UploadFileType + { + NoAllowedType = 0, + Zip = 1, + Nupkg = 2 } + + #endregion } diff --git a/src/PluginCore.AspNetCore/Controllers/UserController.cs b/src/PluginCore.AspNetCore/Controllers/UserController.cs index 9f5ee3ef..cae8f892 100644 --- a/src/PluginCore.AspNetCore/Controllers/UserController.cs +++ b/src/PluginCore.AspNetCore/Controllers/UserController.cs @@ -5,11 +5,7 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using PluginCore.AspNetCore.Authorization; @@ -17,125 +13,124 @@ using PluginCore.AspNetCore.RequestModel.User; using PluginCore.AspNetCore.ResponseModel; -namespace PluginCore.AspNetCore.Controllers +namespace PluginCore.AspNetCore.Controllers; + +[Route("api/plugincore/admin/[controller]/[action]")] +[ApiController] +public class UserController : ControllerBase { - [Route("api/plugincore/admin/[controller]/[action]")] - [ApiController] - public class UserController : ControllerBase - { - private readonly AccountManager _accountManager; + private readonly AccountManager _accountManager; - public string RemoteFronted + public string RemoteFronted + { + get { - get - { - return PluginCore.Config.PluginCoreConfigFactory.Create().RemoteFrontend; - } + return PluginCore.Config.PluginCoreConfigFactory.Create().RemoteFrontend; } + } - public UserController(AccountManager accountManager) - { - _accountManager = accountManager; - } + public UserController(AccountManager accountManager) + { + _accountManager = accountManager; + } - [HttpGet, HttpPost] - public async Task> Login([FromBody] LoginRequestModel requestModel) - { - BaseResponseModel responseModel = new BaseResponseModel(); + [HttpGet, HttpPost] + public async Task> Login([FromBody] LoginRequestModel requestModel) + { + BaseResponseModel responseModel = new BaseResponseModel(); - try - { - string token = this._accountManager.CreateToken(requestModel.UserName, requestModel.Password); - bool isAdmin = this._accountManager.IsAdminToken(token); - if (!isAdmin) - { - responseModel.Code = -1; - responseModel.Message = "用户名或密码不正确"; - - return await Task.FromResult(responseModel); - } - - responseModel.Code = 1; - responseModel.Message = "登录成功"; - responseModel.Data = new - { - token = token, - userName = requestModel.UserName - }; - } - catch (Exception ex) + try + { + string token = this._accountManager.CreateToken(requestModel.UserName, requestModel.Password); + bool isAdmin = this._accountManager.IsAdminToken(token); + if (!isAdmin) { responseModel.Code = -1; - responseModel.Message = "失败: " + ex.Message; - } + responseModel.Message = "用户名或密码不正确"; - return await Task.FromResult(responseModel); - } + return await Task.FromResult(responseModel); + } - [HttpGet, HttpPost] - public async Task> Logout() - { - BaseResponseModel responseModel = new BaseResponseModel() + responseModel.Code = 1; + responseModel.Message = "登录成功"; + responseModel.Data = new { - Code = 1, - Message = "退出登录成功" + token = token, + userName = requestModel.UserName }; - - return await Task.FromResult(responseModel); } + catch (Exception ex) + { + responseModel.Code = -1; + responseModel.Message = "失败: " + ex.Message; + } + + return await Task.FromResult(responseModel); + } - [PluginCoreAdminAuthorize] - [HttpGet, HttpPost] - public async Task> Info() + [HttpGet, HttpPost] + public async Task> Logout() + { + BaseResponseModel responseModel = new BaseResponseModel() { - BaseResponseModel responseModel = new BaseResponseModel(); + Code = 1, + Message = "退出登录成功" + }; - try - { - string adminUserName = PluginCoreConfigFactory.Create().Admin.UserName; - - responseModel.Code = 1; - responseModel.Message = "成功"; - responseModel.Data = new - { - name = adminUserName, - //avatar = this.RemoteFronted + "/images/avatar.gif" - avatar = "" - }; - } - catch (Exception ex) - { - responseModel.Code = -1; - responseModel.Message = "失败: " + ex.Message; - } + return await Task.FromResult(responseModel); + } - return await Task.FromResult(responseModel); - } + [PluginCoreAdminAuthorize] + [HttpGet, HttpPost] + public async Task> Info() + { + BaseResponseModel responseModel = new BaseResponseModel(); - [PluginCoreAdminAuthorize] - [HttpGet, HttpPost] - public async Task> Update([FromBody] UpdateRequestModel requestModel) + try { - BaseResponseModel responseModel = new BaseResponseModel(); + string adminUserName = PluginCoreConfigFactory.Create().Admin.UserName; - try + responseModel.Code = 1; + responseModel.Message = "成功"; + responseModel.Data = new { - PluginCoreConfig pluginCoreConfig = PluginCoreConfigFactory.Create(); - pluginCoreConfig.Admin.UserName = requestModel.UserName; - pluginCoreConfig.Admin.Password = requestModel.Password; - PluginCoreConfigFactory.Save(pluginCoreConfig); + name = adminUserName, + //avatar = this.RemoteFronted + "/images/avatar.gif" + avatar = "" + }; + } + catch (Exception ex) + { + responseModel.Code = -1; + responseModel.Message = "失败: " + ex.Message; + } - responseModel.Code = 1; - responseModel.Message = "修改成功, 需要重新登录"; - } - catch (Exception ex) - { - responseModel.Code = -1; - responseModel.Message = "失败: " + ex.Message; - } + return await Task.FromResult(responseModel); + } + + [PluginCoreAdminAuthorize] + [HttpGet, HttpPost] + public async Task> Update([FromBody] UpdateRequestModel requestModel) + { + BaseResponseModel responseModel = new BaseResponseModel(); + + try + { + PluginCoreConfig pluginCoreConfig = PluginCoreConfigFactory.Create(); + pluginCoreConfig.Admin.UserName = requestModel.UserName; + pluginCoreConfig.Admin.Password = requestModel.Password; + PluginCoreConfigFactory.Save(pluginCoreConfig); - return await Task.FromResult(responseModel); + responseModel.Code = 1; + responseModel.Message = "修改成功, 需要重新登录"; + } + catch (Exception ex) + { + responseModel.Code = -1; + responseModel.Message = "失败: " + ex.Message; } + return await Task.FromResult(responseModel); } + } diff --git a/src/PluginCore.AspNetCore/Extensions/IServiceProviderExtensions.cs b/src/PluginCore.AspNetCore/Extensions/IServiceProviderExtensions.cs index 39bc7f0d..5776c11d 100644 --- a/src/PluginCore.AspNetCore/Extensions/IServiceProviderExtensions.cs +++ b/src/PluginCore.AspNetCore/Extensions/IServiceProviderExtensions.cs @@ -2,149 +2,147 @@ using System.Collections; using System.Collections.Generic; using System.Reflection; -using System.Text; using Microsoft.Extensions.DependencyInjection; -namespace PluginCore.AspNetCore.Extensions +namespace PluginCore.AspNetCore.Extensions; + +public static class IServiceProviderExtensions { - public static class IServiceProviderExtensions + /// + /// Get all registered + /// + /// + /// + public static Dictionary GetAllServiceDescriptors(this IServiceProvider provider) { - /// - /// Get all registered - /// - /// - /// - public static Dictionary GetAllServiceDescriptors(this IServiceProvider provider) + var serviceProvider = provider.GetPropertyValue("RootProvider"); + var result = new Dictionary(); + + var engine = serviceProvider.GetFieldValue("_engine"); + // var callSiteFactory = engine.GetPropertyValue("CallSiteFactory"); + var callSiteFactory = serviceProvider.GetPropertyValue("CallSiteFactory"); + var descriptorLookup = callSiteFactory.GetFieldValue("_descriptorLookup"); + if (descriptorLookup is IDictionary dictionary) { - var serviceProvider = provider.GetPropertyValue("RootProvider"); - var result = new Dictionary(); - - var engine = serviceProvider.GetFieldValue("_engine"); - // var callSiteFactory = engine.GetPropertyValue("CallSiteFactory"); - var callSiteFactory = serviceProvider.GetPropertyValue("CallSiteFactory"); - var descriptorLookup = callSiteFactory.GetFieldValue("_descriptorLookup"); - if (descriptorLookup is IDictionary dictionary) + foreach (DictionaryEntry entry in dictionary) { - foreach (DictionaryEntry entry in dictionary) - { - result.Add((Type)entry.Key, (ServiceDescriptor)entry.Value.GetPropertyValue("Last")); - } + result.Add((Type)entry.Key, (ServiceDescriptor)entry.Value.GetPropertyValue("Last")); } - - return result; - - #region Old - //if (provider is ServiceProvider serviceProvider) - //{ - // var result = new Dictionary(); - - // var engine = serviceProvider.GetFieldValue("_engine"); - // var callSiteFactory = engine.GetPropertyValue("CallSiteFactory"); - // var descriptorLookup = callSiteFactory.GetFieldValue("_descriptorLookup"); - // if (descriptorLookup is IDictionary dictionary) - // { - // foreach (DictionaryEntry entry in dictionary) - // { - // result.Add((Type)entry.Key, (ServiceDescriptor)entry.Value.GetPropertyValue("Last")); - // } - // } - - // return result; - //} - - //throw new NotSupportedException($"Type '{provider.GetType()}' is not supported!"); - #endregion } + + return result; + + #region Old + //if (provider is ServiceProvider serviceProvider) + //{ + // var result = new Dictionary(); + + // var engine = serviceProvider.GetFieldValue("_engine"); + // var callSiteFactory = engine.GetPropertyValue("CallSiteFactory"); + // var descriptorLookup = callSiteFactory.GetFieldValue("_descriptorLookup"); + // if (descriptorLookup is IDictionary dictionary) + // { + // foreach (DictionaryEntry entry in dictionary) + // { + // result.Add((Type)entry.Key, (ServiceDescriptor)entry.Value.GetPropertyValue("Last")); + // } + // } + + // return result; + //} + + //throw new NotSupportedException($"Type '{provider.GetType()}' is not supported!"); + #endregion } +} - public static class ReflectionHelper - { - // ########################################################################################## - // Get / Set Field - // ########################################################################################## +public static class ReflectionHelper +{ + // ########################################################################################## + // Get / Set Field + // ########################################################################################## - #region Get / Set Field + #region Get / Set Field - public static object GetFieldValue(this object obj, string fieldName) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - Type objType = obj.GetType(); - var fieldInfo = GetFieldInfo(objType, fieldName); - if (fieldInfo == null) - throw new ArgumentOutOfRangeException(fieldName, - $"Couldn't find field {fieldName} in type {objType.FullName}"); - return fieldInfo.GetValue(obj); - } + public static object GetFieldValue(this object obj, string fieldName) + { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + Type objType = obj.GetType(); + var fieldInfo = GetFieldInfo(objType, fieldName); + if (fieldInfo == null) + throw new ArgumentOutOfRangeException(fieldName, + $"Couldn't find field {fieldName} in type {objType.FullName}"); + return fieldInfo.GetValue(obj); + } - public static void SetFieldValue(this object obj, string fieldName, object val) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - Type objType = obj.GetType(); - var fieldInfo = GetFieldInfo(objType, fieldName); - if (fieldInfo == null) - throw new ArgumentOutOfRangeException(fieldName, - $"Couldn't find field {fieldName} in type {objType.FullName}"); - fieldInfo.SetValue(obj, val); - } + public static void SetFieldValue(this object obj, string fieldName, object val) + { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + Type objType = obj.GetType(); + var fieldInfo = GetFieldInfo(objType, fieldName); + if (fieldInfo == null) + throw new ArgumentOutOfRangeException(fieldName, + $"Couldn't find field {fieldName} in type {objType.FullName}"); + fieldInfo.SetValue(obj, val); + } - private static FieldInfo GetFieldInfo(Type type, string fieldName) + private static FieldInfo GetFieldInfo(Type type, string fieldName) + { + FieldInfo fieldInfo = null; + do { - FieldInfo fieldInfo = null; - do - { - fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - type = type.BaseType; - } while (fieldInfo == null && type != null); + fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + type = type.BaseType; + } while (fieldInfo == null && type != null); - return fieldInfo; - } + return fieldInfo; + } - #endregion + #endregion - // ########################################################################################## - // Get / Set Property - // ########################################################################################## + // ########################################################################################## + // Get / Set Property + // ########################################################################################## - #region Get / Set Property + #region Get / Set Property - public static object GetPropertyValue(this object obj, string propertyName) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - Type objType = obj.GetType(); - var propertyInfo = GetPropertyInfo(objType, propertyName); - if (propertyInfo == null) - throw new ArgumentOutOfRangeException(propertyName, - $"Couldn't find property {propertyName} in type {objType.FullName}"); - return propertyInfo.GetValue(obj, null); - } + public static object GetPropertyValue(this object obj, string propertyName) + { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + Type objType = obj.GetType(); + var propertyInfo = GetPropertyInfo(objType, propertyName); + if (propertyInfo == null) + throw new ArgumentOutOfRangeException(propertyName, + $"Couldn't find property {propertyName} in type {objType.FullName}"); + return propertyInfo.GetValue(obj, null); + } - public static void SetPropertyValue(this object obj, string propertyName, object val) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - Type objType = obj.GetType(); - var propertyInfo = GetPropertyInfo(objType, propertyName); - if (propertyInfo == null) - throw new ArgumentOutOfRangeException(propertyName, - $"Couldn't find property {propertyName} in type {objType.FullName}"); - propertyInfo.SetValue(obj, val, null); - } + public static void SetPropertyValue(this object obj, string propertyName, object val) + { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + Type objType = obj.GetType(); + var propertyInfo = GetPropertyInfo(objType, propertyName); + if (propertyInfo == null) + throw new ArgumentOutOfRangeException(propertyName, + $"Couldn't find property {propertyName} in type {objType.FullName}"); + propertyInfo.SetValue(obj, val, null); + } - private static PropertyInfo GetPropertyInfo(Type type, string propertyName) + private static PropertyInfo GetPropertyInfo(Type type, string propertyName) + { + PropertyInfo propertyInfo = null; + do { - PropertyInfo propertyInfo = null; - do - { - propertyInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - type = type.BaseType; - } while (propertyInfo == null && type != null); + propertyInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + type = type.BaseType; + } while (propertyInfo == null && type != null); - return propertyInfo; - } - - #endregion + return propertyInfo; } + + #endregion } diff --git a/src/PluginCore.AspNetCore/Extensions/PluginCoreStartupExtensions.cs b/src/PluginCore.AspNetCore/Extensions/PluginCoreStartupExtensions.cs index 6af84677..e23b7989 100644 --- a/src/PluginCore.AspNetCore/Extensions/PluginCoreStartupExtensions.cs +++ b/src/PluginCore.AspNetCore/Extensions/PluginCoreStartupExtensions.cs @@ -5,20 +5,14 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using PluginCore.Models; +using PluginCore.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -26,260 +20,260 @@ using Microsoft.Extensions.FileProviders; using PluginCore.AspNetCore.Authorization; using PluginCore.AspNetCore.AdminUI; -using PluginCore.Infrastructure; using PluginCore.Interfaces; using PluginCore.AspNetCore.Middlewares; using PluginCore.AspNetCore.BackgroundServices; +using PluginCore.AspNetCore.Implements; using PluginCore.IPlugins; using PluginCore.AspNetCore.Interfaces; -using PluginCore.lmplements; -using PluginCore.AspNetCore.lmplements; +using PluginCore.Implements; +using PluginCore.IPlugins.Infrastructure; +using PluginCore.IPlugins.Interfaces; + +namespace PluginCore.AspNetCore.Extensions; -namespace PluginCore.AspNetCore.Extensions +/// +/// +/// +public static class PluginCoreStartupExtensions { - /// - /// - /// - public static class PluginCoreStartupExtensions - { - private static IWebHostEnvironment _webHostEnvironment; + private static IWebHostEnvironment _webHostEnvironment; - private static IServiceCollection _services; + private static IServiceCollection _services; - private static IServiceProvider _serviceProvider; + private static IServiceProvider _serviceProvider; - /// - /// 若需要替换默认实现, 请在 之前, 若在之后, 则无法影响 主程序启动时默认行为 - /// - /// - public static void AddPluginCore(this IServiceCollection services) - { - #region 注册服务 + /// + /// 若需要替换默认实现, 请在 之前, 若在之后, 则无法影响 主程序启动时默认行为 + /// + /// + public static void AddPluginCore(this IServiceCollection services) + { + #region 注册服务 - #region 仅适用于 ASP.NET Core - // start: 仅用于 ASP.NET Core - // 用于添加插件Controller 时,通知Controller.Action发生变化 - services.AddSingleton(PluginActionDescriptorChangeProvider.Instance); - services.AddSingleton(PluginActionDescriptorChangeProvider.Instance); + #region 仅适用于 ASP.NET Core + // start: 仅用于 ASP.NET Core + // 用于添加插件Controller 时,通知Controller.Action发生变化 + services.AddSingleton(PluginActionDescriptorChangeProvider.Instance); + services.AddSingleton(PluginActionDescriptorChangeProvider.Instance); - services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); - services.TryAddTransient(); - services.TryAddTransient(); - // end: 仅用于 ASP.NET Core - #endregion + services.TryAddTransient(); + services.TryAddTransient(); + // end: 仅用于 ASP.NET Core + #endregion - #region 通用 + #region 通用 - // v1 旧版 - //services.TryAddTransient(); - //services.TryAddTransient(); - //services.TryAddTransient(); - //services.TryAddTransient(); + // v1 旧版 + //services.TryAddTransient(); + //services.TryAddTransient(); + //services.TryAddTransient(); + //services.TryAddTransient(); - services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); - services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); - services.TryAddTransient(); - services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); - // 注意: 它必须单例 - // TODO: 不知道原因, 单例失败, 每次 获取 IPluginFinder 都会获取新的 IPluginContextManager - services.TryAddSingleton(); - services.TryAddSingleton(); - #endregion + // 注意: 它必须单例 + // TODO: 不知道原因, 单例失败, 每次 获取 IPluginFinder 都会获取新的 IPluginContextManager + services.TryAddSingleton(); + services.TryAddSingleton(); + #endregion - #endregion + #endregion - // ************************* 注意: IServiceCollection 是服务列表, 但由 IServiceProvider 来负责解析, AClass 单例 仅在 AServiceProvider 范围内 - _serviceProvider = services.BuildServiceProvider(); + // ************************* 注意: IServiceCollection 是服务列表, 但由 IServiceProvider 来负责解析, AClass 单例 仅在 AServiceProvider 范围内 + _serviceProvider = services.BuildServiceProvider(); - #region ASP.NET Core - //IPluginManager pluginManager = services.BuildServiceProvider().GetService(); - IPluginManager pluginManager = _serviceProvider.GetService(); + #region ASP.NET Core + //IPluginManager pluginManager = services.BuildServiceProvider().GetService(); + IPluginManager pluginManager = _serviceProvider.GetService(); - // 初始化 PluginCore 相关目录 - PluginPathProvider.PluginsRootPath(); + // 初始化 PluginCore 相关目录 + PluginPathProvider.PluginsRootPath(); - // 在程序启动时加载所有 已安装并启用 的插件 + // 在程序启动时加载所有 已安装并启用 的插件 - // 获取 PluginConfigModel - #region 获取 PluginConfigModel - PluginConfigModel pluginConfigModel = PluginConfigModelFactory.Create(); - #endregion + // 获取 PluginConfigModel + #region 获取 PluginConfigModel + PluginConfigModel pluginConfigModel = PluginConfigModelFactory.Create(); + #endregion - // 已启用的插件 - #region 加载 已启用插件的Assemblies - IList enabledPluginIds = pluginConfigModel.EnabledPlugins; - foreach (var pluginId in enabledPluginIds) - { - pluginManager.LoadPlugin(pluginId); - } - #endregion + // 已启用的插件 + #region 加载 已启用插件的Assemblies + IList enabledPluginIds = pluginConfigModel.EnabledPlugins; + foreach (var pluginId in enabledPluginIds) + { + pluginManager.LoadPlugin(pluginId); + } + #endregion - // 将本 Assembly 内的 Controller 添加 - var ass = Assembly.GetExecutingAssembly(); - //IPluginControllerManager pluginControllerManager = services.BuildServiceProvider().GetService(); - IPluginControllerManager pluginControllerManager = _serviceProvider.GetService(); - pluginControllerManager.AddControllers(ass); + // 将本 Assembly 内的 Controller 添加 + var ass = Assembly.GetExecutingAssembly(); + //IPluginControllerManager pluginControllerManager = services.BuildServiceProvider().GetService(); + IPluginControllerManager pluginControllerManager = _serviceProvider.GetService(); + pluginControllerManager.AddControllers(ass); - // IWebHostEnvironment - _webHostEnvironment = services.BuildServiceProvider().GetService(); - #endregion + // IWebHostEnvironment + _webHostEnvironment = services.BuildServiceProvider().GetService(); + #endregion - #region PluginCore Admin 权限 - // 1. 先 Authentication (我是谁) 2. 再 Authorization (我能做什么) + #region PluginCore Admin 权限 + // 1. 先 Authentication (我是谁) 2. 再 Authorization (我能做什么) - // fixed: https://github.com/yiyungent/PluginCore/issues/4 - // System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action configureOptions). - #region 添加认证 Authentication - // 没通过 Authentication: 401 Unauthorized - services.AddAuthentication("PluginCore.Authentication") - .AddScheme("PluginCore.Authentication", "PluginCore.Authentication", - options => { }); - #endregion + // fixed: https://github.com/yiyungent/PluginCore/issues/4 + // System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action configureOptions). + #region 添加认证 Authentication + // 没通过 Authentication: 401 Unauthorized + services.AddAuthentication("PluginCore.Authentication") + .AddScheme("PluginCore.Authentication", "PluginCore.Authentication", + options => { }); + #endregion - #region 添加授权策略-所有标记 [PluginCoreAdminAuthorize] 都需要权限检查 - services.AddSingleton(); + #region 添加授权策略-所有标记 [PluginCoreAdminAuthorize] 都需要权限检查 + services.AddSingleton(); - services.AddAuthorization(options => + services.AddAuthorization(options => + { + options.AddPolicy("PluginCore.Admin", policy => { - options.AddPolicy("PluginCore.Admin", policy => - { - // 无法满足 下方任何一项:HTTP 403 错误 - // 3.需要 检查是否拥有当前请求资源的权限 - policy.Requirements.Add(new PluginCoreAdminRequirement()); - }); + // 无法满足 下方任何一项:HTTP 403 错误 + // 3.需要 检查是否拥有当前请求资源的权限 + policy.Requirements.Add(new PluginCoreAdminRequirement()); }); - #endregion + }); + #endregion - // AccountManager - services.AddTransient(); - // HttpContext.Current - services.TryAddSingleton(); - //services.AddHttpContextAccessor(); - #endregion + // AccountManager + services.AddTransient(); + // HttpContext.Current + services.TryAddSingleton(); + //services.AddHttpContextAccessor(); + #endregion - //IPluginFinder pluginFinder = services.BuildServiceProvider().GetService(); - IPluginFinder pluginFinder = _serviceProvider.GetService(); + //IPluginFinder pluginFinder = services.BuildServiceProvider().GetService(); + IPluginFinder pluginFinder = _serviceProvider.GetService(); - #region IStartupPlugin + #region IStartupPlugin - var plugins = pluginFinder.EnablePlugins()?.OrderBy(m => m.ConfigureServicesOrder)?.ToList(); + var plugins = pluginFinder.EnablePlugins()?.OrderBy(m => m.ConfigureServicesOrder)?.ToList(); - foreach (var item in plugins) - { - // 调用 - Utils.LogUtil.Info($"{item.GetType().ToString()} {nameof(IStartupPlugin)}.{nameof(IStartupPlugin.ConfigureServices)}"); - item?.ConfigureServices(services); - } + foreach (var item in plugins) + { + // 调用 + Utils.LogUtil.Info($"{item.GetType().ToString()} {nameof(IStartupPlugin)}.{nameof(IStartupPlugin.ConfigureServices)}"); + item?.ConfigureServices(services); + } - #endregion + #endregion - // AddBackgroundServices - services.AddBackgroundServices(); + // AddBackgroundServices + services.AddBackgroundServices(); - // 一定要在最后 - _services = services; - } + // 一定要在最后 + _services = services; + } - public static IApplicationBuilder UsePluginCore(this IApplicationBuilder app) - { - // 一定在 PluginCore 添加的中间件中 第一个 - app.UseMiddleware(); + public static IApplicationBuilder UsePluginCore(this IApplicationBuilder app) + { + // 一定在 PluginCore 添加的中间件中 第一个 + app.UseMiddleware(); - app.UsePluginCoreAdminUI(); + app.UsePluginCoreAdminUI(); - // 由于没办法在运行时, 动态 UseStaticFiles(), 因此不再为每一个插件都 UseStaticFiles(), - // 而是统一在一个文件夹下, 插件启用时, 将插件的wwwroot复制到 Plugins_wwwroot/{PluginId}, 禁用时, 再删除 - string pluginwwwrootDir = PluginPathProvider.PluginsWwwRootDir(); - app.UseStaticFiles(new StaticFileOptions - { - FileProvider = new PhysicalFileProvider( - pluginwwwrootDir), - RequestPath = "/Plugins" - }); + // 由于没办法在运行时, 动态 UseStaticFiles(), 因此不再为每一个插件都 UseStaticFiles(), + // 而是统一在一个文件夹下, 插件启用时, 将插件的wwwroot复制到 Plugins_wwwroot/{PluginId}, 禁用时, 再删除 + string pluginwwwrootDir = PluginPathProvider.PluginsWwwRootDir(); + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider( + pluginwwwrootDir), + RequestPath = "/Plugins" + }); - // 发现 UseAuthentication 认证中间件重复添加, 也只会执行一次 认证 - // 但 UseAuthorization 重复添加2次, 则会执行 2次 授权 - app.UseAuthentication(); - app.UseAuthorization(); + // 发现 UseAuthentication 认证中间件重复添加, 也只会执行一次 认证 + // 但 UseAuthorization 重复添加2次, 则会执行 2次 授权 + app.UseAuthentication(); + app.UseAuthorization(); - #region Plugin Middleware - // Plugin Middleware - //app.UseMiddleware(); + #region Plugin Middleware + // Plugin Middleware + //app.UseMiddleware(); - // 一定在 PluginCore 添加的中间件中 最后一个 - app.UseMiddleware(); - #endregion + // 一定在 PluginCore 添加的中间件中 最后一个 + app.UseMiddleware(); + #endregion - //IPluginFinder pluginFinder = _services.BuildServiceProvider().GetService(); - IPluginFinder pluginFinder = _serviceProvider.GetService(); + //IPluginFinder pluginFinder = _services.BuildServiceProvider().GetService(); + IPluginFinder pluginFinder = _serviceProvider.GetService(); - #region AppStart + #region AppStart - var plugins = pluginFinder.EnablePluginsFull()?.ToList(); - var dependencySorter = new PluginCore.Utils.DependencySorter(); - dependencySorter.AddObjects(plugins.Select(m => m.PluginInstance).ToArray()); - foreach (var item in plugins) - { - var dependPlugins = plugins.Where(m => item.PluginInstance.AppStartOrderDependPlugins.Contains(m.PluginId)).Select(m => m.PluginInstance).ToArray(); - dependencySorter.SetDependencies(obj: item.PluginInstance, dependsOnObjects: dependPlugins); - } - var sortedPlugins = dependencySorter.Sort(); - foreach (var item in sortedPlugins) - { - // 调用 - Utils.LogUtil.PluginBehavior(item, typeof(IPlugin), nameof(IPlugin.AppStart)); + var plugins = pluginFinder.EnablePluginsFull()?.ToList(); + var dependencySorter = new PluginCore.Utils.DependencySorter(); + dependencySorter.AddObjects(plugins.Select(m => m.PluginInstance).ToArray()); + foreach (var item in plugins) + { + var dependPlugins = plugins.Where(m => item.PluginInstance.AppStartOrderDependPlugins.Contains(m.PluginId)).Select(m => m.PluginInstance).ToArray(); + dependencySorter.SetDependencies(obj: item.PluginInstance, dependsOnObjects: dependPlugins); + } + var sortedPlugins = dependencySorter.Sort(); + foreach (var item in sortedPlugins) + { + // 调用 + Utils.LogUtil.PluginBehavior(item, typeof(IPlugin), nameof(IPlugin.AppStart)); - item?.AppStart(); - } + item?.AppStart(); + } - #endregion + #endregion - #region IStartupPlugin + #region IStartupPlugin - var startupPlugins = pluginFinder.EnablePlugins()?.OrderBy(m => m.ConfigureOrder)?.ToList(); + var startupPlugins = pluginFinder.EnablePlugins()?.OrderBy(m => m.ConfigureOrder)?.ToList(); - foreach (var item in startupPlugins) - { - // 调用 - Utils.LogUtil.PluginBehavior(item, typeof(IStartupPlugin), nameof(IStartupPlugin.Configure)); + foreach (var item in startupPlugins) + { + // 调用 + Utils.LogUtil.PluginBehavior(item, typeof(IStartupPlugin), nameof(IStartupPlugin.Configure)); - item?.Configure(app); - } + item?.Configure(app); + } - #endregion + #endregion - app.UseMiddleware(); + app.UseMiddleware(); - #region 启动 Log - Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create(); + #region 启动 Log + Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create(); - Utils.LogUtil.Info("启动成功:"); - Utils.LogUtil.Info($"前端模式: {pluginCoreConfig.FrontendMode}"); - Utils.LogUtil.Info($"注意: 更新前端模式 需要 重启站点"); - #endregion + Utils.LogUtil.Info("启动成功:"); + Utils.LogUtil.Info($"前端模式: {pluginCoreConfig.FrontendMode}"); + Utils.LogUtil.Info($"注意: 更新前端模式 需要 重启站点"); + #endregion - return app; - } + return app; + } - } } diff --git a/src/PluginCore.AspNetCore/lmplements/AspNetCorePluginManager.cs b/src/PluginCore.AspNetCore/Implements/AspNetCorePluginManager.cs similarity index 88% rename from src/PluginCore.AspNetCore/lmplements/AspNetCorePluginManager.cs rename to src/PluginCore.AspNetCore/Implements/AspNetCorePluginManager.cs index 2ff9543b..f28aab5d 100644 --- a/src/PluginCore.AspNetCore/lmplements/AspNetCorePluginManager.cs +++ b/src/PluginCore.AspNetCore/Implements/AspNetCorePluginManager.cs @@ -6,20 +6,12 @@ //=================================================== - -using PluginCore.AspNetCore.Interfaces; -using PluginCore.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; -using System.Runtime.Loader; -using System.Text; -using PluginCore.lmplements; -using PluginCore.Infrastructure; +using PluginCore.AspNetCore.Interfaces; +using PluginCore.Interfaces; +using PluginCore.IPlugins.Interfaces; -namespace PluginCore.AspNetCore.lmplements +namespace PluginCore.AspNetCore.Implements { /// /// 一个插件的所有dll由 一个 管理 diff --git a/src/PluginCore.AspNetCore/lmplements/AspNetCorePluginManagerV1.cs b/src/PluginCore.AspNetCore/Implements/AspNetCorePluginManagerV1.cs similarity index 89% rename from src/PluginCore.AspNetCore/lmplements/AspNetCorePluginManagerV1.cs rename to src/PluginCore.AspNetCore/Implements/AspNetCorePluginManagerV1.cs index 33dc37aa..9ce8d9a8 100644 --- a/src/PluginCore.AspNetCore/lmplements/AspNetCorePluginManagerV1.cs +++ b/src/PluginCore.AspNetCore/Implements/AspNetCorePluginManagerV1.cs @@ -6,20 +6,13 @@ //=================================================== - -using PluginCore.AspNetCore.Interfaces; -using PluginCore.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; -using System.Runtime.Loader; -using System.Text; -using PluginCore.lmplements; -using PluginCore.Infrastructure; +using PluginCore.AspNetCore.Interfaces; +using PluginCore.Implements; +using PluginCore.Interfaces; +using PluginCore.IPlugins.Interfaces; -namespace PluginCore.AspNetCore.lmplements +namespace PluginCore.AspNetCore.Implements { // ********* 虽然 看上去和 AspNetCorePluginManager 一样, 但特别保留, 防止需要不一样的处理, 后续更新维护方便 diff --git a/src/PluginCore.AspNetCore/lmplements/PluginActionDescriptorChangeProvider.cs b/src/PluginCore.AspNetCore/Implements/PluginActionDescriptorChangeProvider.cs similarity index 86% rename from src/PluginCore.AspNetCore/lmplements/PluginActionDescriptorChangeProvider.cs rename to src/PluginCore.AspNetCore/Implements/PluginActionDescriptorChangeProvider.cs index 69a461f4..38ef6ebe 100644 --- a/src/PluginCore.AspNetCore/lmplements/PluginActionDescriptorChangeProvider.cs +++ b/src/PluginCore.AspNetCore/Implements/PluginActionDescriptorChangeProvider.cs @@ -6,15 +6,11 @@ //=================================================== - -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.Extensions.Primitives; -using System; -using System.Collections.Generic; -using System.Text; using System.Threading; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Primitives; -namespace PluginCore.AspNetCore.lmplements +namespace PluginCore.AspNetCore.Implements { /// /// 目前采用的第一种方案 diff --git a/src/PluginCore.AspNetCore/lmplements/PluginApplicationBuilderManager.cs b/src/PluginCore.AspNetCore/Implements/PluginApplicationBuilderManager.cs similarity index 92% rename from src/PluginCore.AspNetCore/lmplements/PluginApplicationBuilderManager.cs rename to src/PluginCore.AspNetCore/Implements/PluginApplicationBuilderManager.cs index f65857af..eb7e035d 100644 --- a/src/PluginCore.AspNetCore/lmplements/PluginApplicationBuilderManager.cs +++ b/src/PluginCore.AspNetCore/Implements/PluginApplicationBuilderManager.cs @@ -6,20 +6,16 @@ //=================================================== - -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using PluginCore.AspNetCore.Infrastructure; using PluginCore.AspNetCore.Interfaces; -using PluginCore.Interfaces; -using PluginCore.IPlugins; using PluginCore.AspNetCore.Middlewares; -using PluginCore.AspNetCore.Infrastructure; +using PluginCore.Interfaces; +using PluginCore.IPlugins.AspNetCore.IPlugins; +using PluginCore.IPlugins.Interfaces; -namespace PluginCore.AspNetCore.lmplements +namespace PluginCore.AspNetCore.Implements { public class PluginApplicationBuilderManager : PluginApplicationBuilderManager { diff --git a/src/PluginCore.AspNetCore/lmplements/PluginControllerManager.cs b/src/PluginCore.AspNetCore/Implements/PluginControllerManager.cs similarity index 93% rename from src/PluginCore.AspNetCore/lmplements/PluginControllerManager.cs rename to src/PluginCore.AspNetCore/Implements/PluginControllerManager.cs index 8953a5a8..429db40b 100644 --- a/src/PluginCore.AspNetCore/lmplements/PluginControllerManager.cs +++ b/src/PluginCore.AspNetCore/Implements/PluginControllerManager.cs @@ -6,16 +6,12 @@ //=================================================== - -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using PluginCore.AspNetCore.Interfaces; -using System; -using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Text; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using PluginCore.AspNetCore.Interfaces; -namespace PluginCore.AspNetCore.lmplements +namespace PluginCore.AspNetCore.Implements { public class PluginControllerManager : IPluginControllerManager { diff --git a/src/PluginCore.AspNetCore/Infrastructure/PluginApplicationBuilder.cs b/src/PluginCore.AspNetCore/Infrastructure/PluginApplicationBuilder.cs index f16c5f2e..2e8b4993 100644 --- a/src/PluginCore.AspNetCore/Infrastructure/PluginApplicationBuilder.cs +++ b/src/PluginCore.AspNetCore/Infrastructure/PluginApplicationBuilder.cs @@ -5,9 +5,7 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -15,334 +13,329 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.Internal; - -namespace PluginCore.AspNetCore.Infrastructure -{ - /// - /// Default implementation for . - /// - /// https://github.com/dotnet/aspnetcore/blob/main/src/Http/Http/src/Builder/ApplicationBuilder.cs - /// - public class PluginApplicationBuilder : IApplicationBuilder - { - private const string ServerFeaturesKey = "server.Features"; - private const string ApplicationServicesKey = "application.Services"; - - private readonly List> _components = new List>(); - - private Action _reachEndAction; - - public Action ReachEndAction - { - get - { - return this._reachEndAction; - } - set - { - this._reachEndAction = value; - } - } - public PluginApplicationBuilder() - { +namespace PluginCore.AspNetCore.Infrastructure; - } - - /// - /// Initializes a new instance of . - /// - /// The for application services. - public PluginApplicationBuilder(IServiceProvider serviceProvider, Action reachEndAction) - { - Properties = new Dictionary(StringComparer.Ordinal); - ApplicationServices = serviceProvider; +/// +/// Default implementation for . +/// +/// https://github.com/dotnet/aspnetcore/blob/main/src/Http/Http/src/Builder/ApplicationBuilder.cs +/// +public class PluginApplicationBuilder : IApplicationBuilder +{ + private const string ServerFeaturesKey = "server.Features"; + private const string ApplicationServicesKey = "application.Services"; - _reachEndAction = reachEndAction; - } + private readonly List> _components = new List>(); - /// - /// Initializes a new instance of . - /// - /// The for application services. - /// The server instance that hosts the application. - public PluginApplicationBuilder(IServiceProvider serviceProvider, Action reachEndAction, object server) - : this(serviceProvider, reachEndAction) - { - SetProperty(ServerFeaturesKey, server); - } + private Action _reachEndAction; - private PluginApplicationBuilder(PluginApplicationBuilder builder) + public Action ReachEndAction + { + get { - Properties = new CopyOnWriteDictionary(builder.Properties, StringComparer.Ordinal); + return this._reachEndAction; } - - /// - /// Gets the for application services. - /// - public IServiceProvider ApplicationServices + set { - get - { - //return GetProperty(ApplicationServicesKey)!; - return null; - } - set - { - SetProperty(ApplicationServicesKey, value); - } + this._reachEndAction = value; } + } - /// - /// Gets the for server features. - /// - public IFeatureCollection ServerFeatures - { - get - { - // ! (null 包容)运算符(C# 参考) - // https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/null-forgiving - // TODO: 报错 - //var result = GetProperty(ServerFeaturesKey)!; + public PluginApplicationBuilder() + { - IFeatureCollection rtn = null; + } - return rtn; - } - } + /// + /// Initializes a new instance of . + /// + /// The for application services. + public PluginApplicationBuilder(IServiceProvider serviceProvider, Action reachEndAction) + { + Properties = new Dictionary(StringComparer.Ordinal); + ApplicationServices = serviceProvider; - /// - /// Gets a set of properties for . - /// - public IDictionary Properties { get; } + _reachEndAction = reachEndAction; + } - //private T? GetProperty(string key) - //{ + /// + /// Initializes a new instance of . + /// + /// The for application services. + /// The server instance that hosts the application. + public PluginApplicationBuilder(IServiceProvider serviceProvider, Action reachEndAction, object server) + : this(serviceProvider, reachEndAction) + { + SetProperty(ServerFeaturesKey, server); + } - // return Properties.TryGetValue(key, out var value) ? (T?)value : default(T); - //} + private PluginApplicationBuilder(PluginApplicationBuilder builder) + { + Properties = new CopyOnWriteDictionary(builder.Properties, StringComparer.Ordinal); + } - private void SetProperty(string key, T value) + /// + /// Gets the for application services. + /// + public IServiceProvider ApplicationServices + { + get { - Properties[key] = value; + //return GetProperty(ApplicationServicesKey)!; + return null; } - - /// - /// Adds the middleware to the application request pipeline. - /// - /// The middleware. - /// An instance of after the operation has completed. - public IApplicationBuilder Use(Func middleware) + set { - _components.Add(middleware); - return this; + SetProperty(ApplicationServicesKey, value); } + } - /// - /// Creates a copy of this application builder. - /// - /// The created clone has the same properties as the current instance, but does not copy - /// the request pipeline. - /// - /// - /// The cloned instance. - public IApplicationBuilder New() + /// + /// Gets the for server features. + /// + public IFeatureCollection ServerFeatures + { + get { - return new PluginApplicationBuilder(this); - } + // ! (null 包容)运算符(C# 参考) + // https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/null-forgiving + // TODO: 报错 + //var result = GetProperty(ServerFeaturesKey)!; - /// - /// Produces a that executes added middlewares. - /// - /// The . - public RequestDelegate Build() - { - RequestDelegate app = context => - { - #region Old - // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened. - // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware. - //var endpoint = context.GetEndpoint(); - //var endpointRequestDelegate = endpoint?.RequestDelegate; - //if (endpointRequestDelegate != null) - //{ - // var message = - // $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " + - // $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " + - // $"routing."; - // throw new InvalidOperationException(message); - //} - #endregion - - this._reachEndAction(); - - //context.Response.StatusCode = StatusCodes.Status404NotFound; - return Task.CompletedTask; - }; - - for (var c = _components.Count - 1; c >= 0; c--) - { - app = _components[c](app); - } + IFeatureCollection rtn = null; - return app; + return rtn; } } + /// + /// Gets a set of properties for . + /// + public IDictionary Properties { get; } + + //private T? GetProperty(string key) + //{ + // return Properties.TryGetValue(key, out var value) ? (T?)value : default(T); + //} + + private void SetProperty(string key, T value) + { + Properties[key] = value; + } /// - /// https://github.com/dotnet/aspnetcore/blob/8b30d862de6c9146f466061d51aa3f1414ee2337/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs + /// Adds the middleware to the application request pipeline. /// - /// - /// - internal class CopyOnWriteDictionary : IDictionary where TKey : notnull + /// The middleware. + /// An instance of after the operation has completed. + public IApplicationBuilder Use(Func middleware) { - private readonly IDictionary _sourceDictionary; - private readonly IEqualityComparer _comparer; - private IDictionary? _innerDictionary; - - public CopyOnWriteDictionary( - IDictionary sourceDictionary, - IEqualityComparer comparer) - { - if (sourceDictionary == null) - { - throw new ArgumentNullException(nameof(sourceDictionary)); - } - - if (comparer == null) - { - throw new ArgumentNullException(nameof(comparer)); - } + _components.Add(middleware); + return this; + } - _sourceDictionary = sourceDictionary; - _comparer = comparer; - } + /// + /// Creates a copy of this application builder. + /// + /// The created clone has the same properties as the current instance, but does not copy + /// the request pipeline. + /// + /// + /// The cloned instance. + public IApplicationBuilder New() + { + return new PluginApplicationBuilder(this); + } - private IDictionary ReadDictionary + /// + /// Produces a that executes added middlewares. + /// + /// The . + public RequestDelegate Build() + { + RequestDelegate app = context => { - get - { - return _innerDictionary ?? _sourceDictionary; - } + #region Old + // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened. + // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware. + //var endpoint = context.GetEndpoint(); + //var endpointRequestDelegate = endpoint?.RequestDelegate; + //if (endpointRequestDelegate != null) + //{ + // var message = + // $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " + + // $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " + + // $"routing."; + // throw new InvalidOperationException(message); + //} + #endregion + + this._reachEndAction(); + + //context.Response.StatusCode = StatusCodes.Status404NotFound; + return Task.CompletedTask; + }; + + for (var c = _components.Count - 1; c >= 0; c--) + { + app = _components[c](app); } - private IDictionary WriteDictionary - { - get - { - if (_innerDictionary == null) - { - _innerDictionary = new Dictionary(_sourceDictionary, - _comparer); - } + return app; + } +} - return _innerDictionary; - } - } - public virtual ICollection Keys - { - get - { - return ReadDictionary.Keys; - } - } - public virtual ICollection Values +/// +/// https://github.com/dotnet/aspnetcore/blob/8b30d862de6c9146f466061d51aa3f1414ee2337/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs +/// +/// +/// +internal class CopyOnWriteDictionary : IDictionary where TKey : notnull +{ + private readonly IDictionary _sourceDictionary; + private readonly IEqualityComparer _comparer; + private IDictionary? _innerDictionary; + + public CopyOnWriteDictionary( + IDictionary sourceDictionary, + IEqualityComparer comparer) + { + if (sourceDictionary == null) { - get - { - return ReadDictionary.Values; - } + throw new ArgumentNullException(nameof(sourceDictionary)); } - public virtual int Count + if (comparer == null) { - get - { - return ReadDictionary.Count; - } + throw new ArgumentNullException(nameof(comparer)); } - public virtual bool IsReadOnly + _sourceDictionary = sourceDictionary; + _comparer = comparer; + } + + private IDictionary ReadDictionary + { + get { - get - { - return false; - } + return _innerDictionary ?? _sourceDictionary; } + } - public virtual TValue this[TKey key] + private IDictionary WriteDictionary + { + get { - get + if (_innerDictionary == null) { - return ReadDictionary[key]; + _innerDictionary = new Dictionary(_sourceDictionary, + _comparer); } - set - { - WriteDictionary[key] = value; - } - } - public virtual bool ContainsKey(TKey key) - { - return ReadDictionary.ContainsKey(key); + return _innerDictionary; } + } - public virtual void Add(TKey key, TValue value) + public virtual ICollection Keys + { + get { - WriteDictionary.Add(key, value); + return ReadDictionary.Keys; } + } - public virtual bool Remove(TKey key) + public virtual ICollection Values + { + get { - return WriteDictionary.Remove(key); + return ReadDictionary.Values; } + } - public virtual bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + public virtual int Count + { + get { - return ReadDictionary.TryGetValue(key, out value); + return ReadDictionary.Count; } + } - public virtual void Add(KeyValuePair item) + public virtual bool IsReadOnly + { + get { - WriteDictionary.Add(item); + return false; } + } - public virtual void Clear() + public virtual TValue this[TKey key] + { + get { - WriteDictionary.Clear(); + return ReadDictionary[key]; } - - public virtual bool Contains(KeyValuePair item) + set { - return ReadDictionary.Contains(item); + WriteDictionary[key] = value; } + } - public virtual void CopyTo(KeyValuePair[] array, int arrayIndex) - { - ReadDictionary.CopyTo(array, arrayIndex); - } + public virtual bool ContainsKey(TKey key) + { + return ReadDictionary.ContainsKey(key); + } - public bool Remove(KeyValuePair item) - { - return WriteDictionary.Remove(item); - } + public virtual void Add(TKey key, TValue value) + { + WriteDictionary.Add(key, value); + } - public virtual IEnumerator> GetEnumerator() - { - return ReadDictionary.GetEnumerator(); - } + public virtual bool Remove(TKey key) + { + return WriteDictionary.Remove(key); + } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public virtual bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + return ReadDictionary.TryGetValue(key, out value); } + public virtual void Add(KeyValuePair item) + { + WriteDictionary.Add(item); + } + + public virtual void Clear() + { + WriteDictionary.Clear(); + } + public virtual bool Contains(KeyValuePair item) + { + return ReadDictionary.Contains(item); + } + public virtual void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ReadDictionary.CopyTo(array, arrayIndex); + } + + public bool Remove(KeyValuePair item) + { + return WriteDictionary.Remove(item); + } + + public virtual IEnumerator> GetEnumerator() + { + return ReadDictionary.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } } diff --git a/src/PluginCore.AspNetCore/Infrastructure/PluginCoreHostingStartup.cs b/src/PluginCore.AspNetCore/Infrastructure/PluginCoreHostingStartup.cs index e7018e46..977fb37f 100644 --- a/src/PluginCore.AspNetCore/Infrastructure/PluginCoreHostingStartup.cs +++ b/src/PluginCore.AspNetCore/Infrastructure/PluginCoreHostingStartup.cs @@ -5,56 +5,49 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; using PluginCore.AspNetCore.Extensions; [assembly: HostingStartup(typeof(PluginCore.AspNetCore.Infrastructure.PluginCoreHostingStartup))] -namespace PluginCore.AspNetCore.Infrastructure +namespace PluginCore.AspNetCore.Infrastructure; + +/// +/// https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/host/platform-specific-configuration?view=aspnetcore-5.0 +/// +public class PluginCoreHostingStartup : IHostingStartup { - /// - /// https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/host/platform-specific-configuration?view=aspnetcore-5.0 - /// - public class PluginCoreHostingStartup : IHostingStartup + public PluginCoreHostingStartup() { - public PluginCoreHostingStartup() - { - } + } + + public void Configure(IWebHostBuilder builder) + { + //builder.ConfigureAppConfiguration(config => + //{ + + //}); - public void Configure(IWebHostBuilder builder) + // 注意: 无论是通过 Program.cs 中 webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "PluginCore"); + // 还是 通过环境变量 指定承载启动程序集, 都是先执行 外部的承载启动程序集, 再执行主程序的 Startup.cs, 因此在这时, 有些 service 还没有注册 + + // TODO: 不知道, 重复 Add, Use 会导致什么, 没有做防止重复 + builder.ConfigureServices(services => + { + // fixed: https://github.com/yiyungent/PluginCore/issues/1 + // System.InvalidOperationException: 'Unable to resolve service for type 'Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager' + // TODO: 不确定, 这样是否可行, 事实上之后主程序还会 Add 一次, 不知道是否会导致存在多个 实例 + // 失败: 不是一个实例, 导致无法改变 Controller + //Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager applicationPartManager = + // new Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager(); + //services.AddSingleton(applicationPartManager); + + services.AddPluginCore(); + }); + + builder.Configure(app => { - //builder.ConfigureAppConfiguration(config => - //{ - - //}); - - // 注意: 无论是通过 Program.cs 中 webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "PluginCore"); - // 还是 通过环境变量 指定承载启动程序集, 都是先执行 外部的承载启动程序集, 再执行主程序的 Startup.cs, 因此在这时, 有些 service 还没有注册 - - // TODO: 不知道, 重复 Add, Use 会导致什么, 没有做防止重复 - builder.ConfigureServices(services => - { - // fixed: https://github.com/yiyungent/PluginCore/issues/1 - // System.InvalidOperationException: 'Unable to resolve service for type 'Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager' - // TODO: 不确定, 这样是否可行, 事实上之后主程序还会 Add 一次, 不知道是否会导致存在多个 实例 - // 失败: 不是一个实例, 导致无法改变 Controller - //Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager applicationPartManager = - // new Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager(); - //services.AddSingleton(applicationPartManager); - - services.AddPluginCore(); - }); - - builder.Configure(app => - { - app.UsePluginCore(); - }); - } + app.UsePluginCore(); + }); } } diff --git a/src/PluginCore.AspNetCore/Interfaces/IPluginApplicationBuilderManager.cs b/src/PluginCore.AspNetCore/Interfaces/IPluginApplicationBuilderManager.cs index f97f9020..344d0a31 100644 --- a/src/PluginCore.AspNetCore/Interfaces/IPluginApplicationBuilderManager.cs +++ b/src/PluginCore.AspNetCore/Interfaces/IPluginApplicationBuilderManager.cs @@ -5,19 +5,13 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +using Microsoft.AspNetCore.Http; +namespace PluginCore.AspNetCore.Interfaces; -using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.AspNetCore.Interfaces +public interface IPluginApplicationBuilderManager { - public interface IPluginApplicationBuilderManager - { - void ReBuild(); + void ReBuild(); - RequestDelegate GetBuildResult(); - } + RequestDelegate GetBuildResult(); } diff --git a/src/PluginCore.AspNetCore/Interfaces/IPluginControllerManager.cs b/src/PluginCore.AspNetCore/Interfaces/IPluginControllerManager.cs index 889a3d2d..83dba8cf 100644 --- a/src/PluginCore.AspNetCore/Interfaces/IPluginControllerManager.cs +++ b/src/PluginCore.AspNetCore/Interfaces/IPluginControllerManager.cs @@ -5,19 +5,13 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; using System.Reflection; -using System.Text; -namespace PluginCore.AspNetCore.Interfaces +namespace PluginCore.AspNetCore.Interfaces; + +public interface IPluginControllerManager { - public interface IPluginControllerManager - { - void AddControllers(Assembly assembly); + void AddControllers(Assembly assembly); - void RemoveControllers(string pluginId); - } + void RemoveControllers(string pluginId); } diff --git a/src/PluginCore.AspNetCore/Middlewares/PluginContentFilterMiddleware.cs b/src/PluginCore.AspNetCore/Middlewares/PluginContentFilterMiddleware.cs index 13a822b6..23f6a2d6 100644 --- a/src/PluginCore.AspNetCore/Middlewares/PluginContentFilterMiddleware.cs +++ b/src/PluginCore.AspNetCore/Middlewares/PluginContentFilterMiddleware.cs @@ -5,10 +5,6 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -17,132 +13,132 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using PluginCore.Interfaces; +using PluginCore.IPlugins.Interfaces; + +namespace PluginCore.AspNetCore.Middlewares; -namespace PluginCore.AspNetCore.Middlewares +/// +/// TODO: 未测试 +/// +public class PluginContentFilterMiddleware { - /// - /// TODO: 未测试 - /// - public class PluginContentFilterMiddleware - { - private readonly RequestDelegate _next; + private readonly RequestDelegate _next; - private readonly IPluginFinder _pluginFinder; + private readonly IPluginFinder _pluginFinder; - public PluginContentFilterMiddleware( - RequestDelegate next, - IWebHostEnvironment hostingEnv, - ILoggerFactory loggerFactory, - IPluginFinder pluginFinder) - { - _next = next; - _pluginFinder = pluginFinder; - } + public PluginContentFilterMiddleware( + RequestDelegate next, + IWebHostEnvironment hostingEnv, + ILoggerFactory loggerFactory, + IPluginFinder pluginFinder) + { + _next = next; + _pluginFinder = pluginFinder; + } - public async Task InvokeAsync(HttpContext httpContext) - { - var httpMethod = httpContext.Request.Method; - var path = httpContext.Request.Path.Value; + public async Task InvokeAsync(HttpContext httpContext) + { + var httpMethod = httpContext.Request.Method; + var path = httpContext.Request.Path.Value; - // 在请求下一个 middleware 前过滤 - await RequestBodyFilter(httpContext); + // 在请求下一个 middleware 前过滤 + await RequestBodyFilter(httpContext); - // Call the next delegate/middleware in the pipeline - await _next(httpContext); + // Call the next delegate/middleware in the pipeline + await _next(httpContext); - if (httpMethod == "GET" && path.EndsWith(".css") || path.EndsWith(".js") || path.EndsWith(".html")) - { - // middleware 回退时 过滤 - await ResponseBodyFilter(httpContext); - } + if (httpMethod == "GET" && path.EndsWith(".css") || path.EndsWith(".js") || path.EndsWith(".html")) + { + // middleware 回退时 过滤 + await ResponseBodyFilter(httpContext); } + } - private async Task RequestBodyFilter(HttpContext httpContext) + private async Task RequestBodyFilter(HttpContext httpContext) + { + string content = string.Empty; + using (var memoryStream = new MemoryStream()) { - string content = string.Empty; - using (var memoryStream = new MemoryStream()) - { - // Response.Body - await httpContext.Request.Body.CopyToAsync(memoryStream); + // Response.Body + await httpContext.Request.Body.CopyToAsync(memoryStream); - long pos = httpContext.Request.Body.Position; - - using (var reader = new StreamReader(memoryStream, Encoding.UTF8)) - { - content = await reader.ReadToEndAsync(); - } - } + long pos = httpContext.Request.Body.Position; - var plugins = this._pluginFinder.EnablePlugins().ToList(); - - foreach (var item in plugins) + using (var reader = new StreamReader(memoryStream, Encoding.UTF8)) { - // 调用 - content = await item?.RequestBodyFilter(httpContext.Request.Path.Value, content); + content = await reader.ReadToEndAsync(); } + } - // 更新 Request.Body + var plugins = this._pluginFinder.EnablePlugins().ToList(); - #region 方式1 - //var requestStream = new MemoryStream(); - //using (StreamWriter writer = new StreamWriter(requestStream, Encoding.UTF8)) - //{ - // await writer.WriteAsync(content); - //} - //httpContext.Request.Body = requestStream; - #endregion + foreach (var item in plugins) + { + // 调用 + content = await item?.RequestBodyFilter(httpContext.Request.Path.Value, content); + } + // 更新 Request.Body - #region 方式2 - httpContext.Request.Body.Seek(0, SeekOrigin.Begin); - byte[] buffer = Encoding.UTF8.GetBytes(content); - await httpContext.Request.Body.WriteAsync(buffer, 0, buffer.Length); - #endregion - } + #region 方式1 + //var requestStream = new MemoryStream(); + //using (StreamWriter writer = new StreamWriter(requestStream, Encoding.UTF8)) + //{ + // await writer.WriteAsync(content); + //} + //httpContext.Request.Body = requestStream; + #endregion - private async Task ResponseBodyFilter(HttpContext httpContext) - { - string content = string.Empty; - using (var memoryStream = new MemoryStream()) - { - // Response.Body - await httpContext.Response.Body.CopyToAsync(memoryStream); - long pos = httpContext.Response.Body.Position; + #region 方式2 + httpContext.Request.Body.Seek(0, SeekOrigin.Begin); + byte[] buffer = Encoding.UTF8.GetBytes(content); + await httpContext.Request.Body.WriteAsync(buffer, 0, buffer.Length); + #endregion + } - using (var reader = new StreamReader(memoryStream, Encoding.UTF8)) - { - content = await reader.ReadToEndAsync(); - } - } + private async Task ResponseBodyFilter(HttpContext httpContext) + { + string content = string.Empty; + using (var memoryStream = new MemoryStream()) + { + // Response.Body + await httpContext.Response.Body.CopyToAsync(memoryStream); - var plugins = this._pluginFinder.EnablePlugins().ToList(); + long pos = httpContext.Response.Body.Position; - foreach (var item in plugins) + using (var reader = new StreamReader(memoryStream, Encoding.UTF8)) { - // 调用 - content = await item?.ReponseBodyFilter(httpContext.Request.Path.Value, content); + content = await reader.ReadToEndAsync(); } + } + + var plugins = this._pluginFinder.EnablePlugins().ToList(); - // 更新 ResponseBody + foreach (var item in plugins) + { + // 调用 + content = await item?.ReponseBodyFilter(httpContext.Request.Path.Value, content); + } - #region 方式1 - //var responseStream = new MemoryStream(); - //using (StreamWriter writer = new StreamWriter(responseStream, Encoding.UTF8)) - //{ - // await writer.WriteAsync(content); - //} - //httpContext.Response.Body = responseStream; - #endregion + // 更新 ResponseBody + #region 方式1 + //var responseStream = new MemoryStream(); + //using (StreamWriter writer = new StreamWriter(responseStream, Encoding.UTF8)) + //{ + // await writer.WriteAsync(content); + //} + //httpContext.Response.Body = responseStream; + #endregion - #region 方式2 - httpContext.Response.Body.Seek(0, SeekOrigin.Begin); - byte[] buffer = Encoding.UTF8.GetBytes(content); - await httpContext.Response.Body.WriteAsync(buffer, 0, buffer.Length); - #endregion - } + #region 方式2 + httpContext.Response.Body.Seek(0, SeekOrigin.Begin); + byte[] buffer = Encoding.UTF8.GetBytes(content); + await httpContext.Response.Body.WriteAsync(buffer, 0, buffer.Length); + #endregion } + } diff --git a/src/PluginCore.AspNetCore/Middlewares/PluginHttpEndFilterMiddleware.cs b/src/PluginCore.AspNetCore/Middlewares/PluginHttpEndFilterMiddleware.cs index cf37f216..d680bdc0 100644 --- a/src/PluginCore.AspNetCore/Middlewares/PluginHttpEndFilterMiddleware.cs +++ b/src/PluginCore.AspNetCore/Middlewares/PluginHttpEndFilterMiddleware.cs @@ -5,66 +5,61 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using PluginCore.Interfaces; +using PluginCore.IPlugins.Interfaces; + +namespace PluginCore.AspNetCore.Middlewares; -namespace PluginCore.AspNetCore.Middlewares +/// +/// 一定在 PluginCore 添加的中间件中 最后一个 +/// +public class PluginHttpEndFilterMiddleware { - /// - /// 一定在 PluginCore 添加的中间件中 最后一个 - /// - public class PluginHttpEndFilterMiddleware - { - private readonly RequestDelegate _next; + private readonly RequestDelegate _next; - private readonly IPluginFinder _pluginFinder; + private readonly IPluginFinder _pluginFinder; - public PluginHttpEndFilterMiddleware( - RequestDelegate next, - IWebHostEnvironment hostingEnv, - ILoggerFactory loggerFactory, - IPluginFinder pluginFinder) - { - _next = next; - _pluginFinder = pluginFinder; - } + public PluginHttpEndFilterMiddleware( + RequestDelegate next, + IWebHostEnvironment hostingEnv, + ILoggerFactory loggerFactory, + IPluginFinder pluginFinder) + { + _next = next; + _pluginFinder = pluginFinder; + } - public async Task InvokeAsync(HttpContext httpContext) - { - var httpMethod = httpContext.Request.Method; - var path = httpContext.Request.Path.Value; + public async Task InvokeAsync(HttpContext httpContext) + { + var httpMethod = httpContext.Request.Method; + var path = httpContext.Request.Path.Value; - // 在请求下一个 middleware 前过滤 + // 在请求下一个 middleware 前过滤 - // Call the next delegate/middleware in the pipeline - await _next(httpContext); + // Call the next delegate/middleware in the pipeline + await _next(httpContext); - // middleware 回退时 过滤 - await Filter(httpContext); - } + // middleware 回退时 过滤 + await Filter(httpContext); + } - private async Task Filter(HttpContext httpContext) - { - var plugins = this._pluginFinder.EnablePlugins().ToList(); + private async Task Filter(HttpContext httpContext) + { + var plugins = this._pluginFinder.EnablePlugins().ToList(); - foreach (var item in plugins) - { - // 调用 - await item?.HttpEndFilter(httpContext); - } + foreach (var item in plugins) + { + // 调用 + await item?.HttpEndFilter(httpContext); } + } - } } diff --git a/src/PluginCore.AspNetCore/Middlewares/PluginHttpStartFilterMiddleware.cs b/src/PluginCore.AspNetCore/Middlewares/PluginHttpStartFilterMiddleware.cs index 72aa4fcc..b92b4b6b 100644 --- a/src/PluginCore.AspNetCore/Middlewares/PluginHttpStartFilterMiddleware.cs +++ b/src/PluginCore.AspNetCore/Middlewares/PluginHttpStartFilterMiddleware.cs @@ -6,65 +6,61 @@ //=================================================== - -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using PluginCore.Interfaces; +using PluginCore.IPlugins.Interfaces; + +namespace PluginCore.AspNetCore.Middlewares; -namespace PluginCore.AspNetCore.Middlewares +/// +/// 一定在 PluginCore 添加的中间件中 第一个 +/// +public class PluginHttpStartFilterMiddleware { - /// - /// 一定在 PluginCore 添加的中间件中 第一个 - /// - public class PluginHttpStartFilterMiddleware - { - private readonly RequestDelegate _next; + private readonly RequestDelegate _next; - private readonly IPluginFinder _pluginFinder; + private readonly IPluginFinder _pluginFinder; - public PluginHttpStartFilterMiddleware( - RequestDelegate next, - IWebHostEnvironment hostingEnv, - ILoggerFactory loggerFactory, - IPluginFinder pluginFinder) - { - _next = next; - _pluginFinder = pluginFinder; - } + public PluginHttpStartFilterMiddleware( + RequestDelegate next, + IWebHostEnvironment hostingEnv, + ILoggerFactory loggerFactory, + IPluginFinder pluginFinder) + { + _next = next; + _pluginFinder = pluginFinder; + } - public async Task InvokeAsync(HttpContext httpContext) - { - var httpMethod = httpContext.Request.Method; - var path = httpContext.Request.Path.Value; + public async Task InvokeAsync(HttpContext httpContext) + { + var httpMethod = httpContext.Request.Method; + var path = httpContext.Request.Path.Value; - // 在请求下一个 middleware 前过滤 - await Filter(httpContext); + // 在请求下一个 middleware 前过滤 + await Filter(httpContext); - // Call the next delegate/middleware in the pipeline - await _next(httpContext); + // Call the next delegate/middleware in the pipeline + await _next(httpContext); - // middleware 回退时 过滤 - } + // middleware 回退时 过滤 + } - private async Task Filter(HttpContext httpContext) - { - var plugins = this._pluginFinder.EnablePlugins().ToList(); + private async Task Filter(HttpContext httpContext) + { + var plugins = this._pluginFinder.EnablePlugins().ToList(); - foreach (var item in plugins) - { - // 调用 - await item?.HttpStartFilter(httpContext); - } + foreach (var item in plugins) + { + // 调用 + await item?.HttpStartFilter(httpContext); } + } - } } diff --git a/src/PluginCore.AspNetCore/Middlewares/PluginStartupXMiddleware.cs b/src/PluginCore.AspNetCore/Middlewares/PluginStartupXMiddleware.cs index 6dce2424..68f79334 100644 --- a/src/PluginCore.AspNetCore/Middlewares/PluginStartupXMiddleware.cs +++ b/src/PluginCore.AspNetCore/Middlewares/PluginStartupXMiddleware.cs @@ -5,66 +5,58 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System; using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using PluginCore.AspNetCore.Interfaces; -using PluginCore.Infrastructure; -namespace PluginCore.AspNetCore.Middlewares +namespace PluginCore.AspNetCore.Middlewares; + +public class PluginStartupXMiddleware { - public class PluginStartupXMiddleware + private readonly RequestDelegate _next; + + public PluginStartupXMiddleware(RequestDelegate next) { - private readonly RequestDelegate _next; + _next = next; + } - public PluginStartupXMiddleware(RequestDelegate next) - { - _next = next; - } + public static Action ReachedEndAction { get; set; } = () => { _isReachedEnd = true; }; - public static Action ReachedEndAction { get; set; } = () => { _isReachedEnd = true; }; + private static bool _isReachedEnd; - private static bool _isReachedEnd; + public async Task InvokeAsync(HttpContext httpContext, IPluginApplicationBuilderManager pluginApplicationBuilderManager) + { + //bool isReachedEnd = false; + _isReachedEnd = false; - public async Task InvokeAsync(HttpContext httpContext, IPluginApplicationBuilderManager pluginApplicationBuilderManager) + try { - //bool isReachedEnd = false; - _isReachedEnd = false; - - try - { - RequestDelegate requestDelegate = pluginApplicationBuilderManager.GetBuildResult(); + RequestDelegate requestDelegate = pluginApplicationBuilderManager.GetBuildResult(); - await requestDelegate(httpContext); - } - catch (Exception ex) - { - // InvalidOperationException: The request reached the end of the pipeline without executing the endpoint: 'AspNetCore3_1.Controllers.WeatherForecastController.Get (AspNetCore3_1)'. Please register the EndpointMiddleware using 'IApplicationBuilder.UseEndpoints(...)' if using routing. - Utils.LogUtil.Error(ex.ToString()); - if (ex.InnerException != null) - { - Utils.LogUtil.Error(ex.InnerException.ToString()); - } - } - - if (_isReachedEnd) - { - // Call the next delegate/middleware in the pipeline - await _next(httpContext); - } - else + await requestDelegate(httpContext); + } + catch (Exception ex) + { + // InvalidOperationException: The request reached the end of the pipeline without executing the endpoint: 'AspNetCore3_1.Controllers.WeatherForecastController.Get (AspNetCore3_1)'. Please register the EndpointMiddleware using 'IApplicationBuilder.UseEndpoints(...)' if using routing. + Utils.LogUtil.Error(ex.ToString()); + if (ex.InnerException != null) { - // 没有抵达 End, 说明在插件的 middleware 中已堵塞, 准备返回 响应 + Utils.LogUtil.Error(ex.InnerException.ToString()); } + } - + if (_isReachedEnd) + { + // Call the next delegate/middleware in the pipeline + await _next(httpContext); + } + else + { + // 没有抵达 End, 说明在插件的 middleware 中已堵塞, 准备返回 响应 } + } + } diff --git a/src/PluginCore.AspNetCore/PluginCore.AspNetCore.csproj b/src/PluginCore.AspNetCore/PluginCore.AspNetCore.csproj index 0957d4d0..04b76845 100644 --- a/src/PluginCore.AspNetCore/PluginCore.AspNetCore.csproj +++ b/src/PluginCore.AspNetCore/PluginCore.AspNetCore.csproj @@ -1,7 +1,6 @@  - netcoreapp3.1;net5.0;net6.0 PluginCore.AspNetCore 1.3.1 yiyun @@ -12,6 +11,7 @@ https://github.com/yiyungent/PluginCore/blob/main/LICENSE PluginCore PluginCore.AspNetCore true + net7.0 diff --git a/src/PluginCore.AspNetCore/RequestModel/User/LoginRequestModel.cs b/src/PluginCore.AspNetCore/RequestModel/User/LoginRequestModel.cs index 966bb26a..d5544af5 100644 --- a/src/PluginCore.AspNetCore/RequestModel/User/LoginRequestModel.cs +++ b/src/PluginCore.AspNetCore/RequestModel/User/LoginRequestModel.cs @@ -6,17 +6,11 @@ //=================================================== +namespace PluginCore.AspNetCore.RequestModel.User; -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.AspNetCore.RequestModel.User +public class LoginRequestModel { - public class LoginRequestModel - { - public string UserName { get; set; } + public string UserName { get; set; } - public string Password { get; set; } - } + public string Password { get; set; } } diff --git a/src/PluginCore.AspNetCore/RequestModel/User/UpdateRequestMode.cs b/src/PluginCore.AspNetCore/RequestModel/User/UpdateRequestMode.cs index 232f961f..4f0ca239 100644 --- a/src/PluginCore.AspNetCore/RequestModel/User/UpdateRequestMode.cs +++ b/src/PluginCore.AspNetCore/RequestModel/User/UpdateRequestMode.cs @@ -6,17 +6,11 @@ //=================================================== +namespace PluginCore.AspNetCore.RequestModel.User; -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.AspNetCore.RequestModel.User +public class UpdateRequestModel { - public class UpdateRequestModel - { - public string UserName { get; set; } + public string UserName { get; set; } - public string Password { get; set; } - } + public string Password { get; set; } } diff --git a/src/PluginCore.AspNetCore/ResponseModel/BaseResponseModel.cs b/src/PluginCore.AspNetCore/ResponseModel/BaseResponseModel.cs index d65a13b2..f722f822 100644 --- a/src/PluginCore.AspNetCore/ResponseModel/BaseResponseModel.cs +++ b/src/PluginCore.AspNetCore/ResponseModel/BaseResponseModel.cs @@ -7,14 +7,13 @@ -namespace PluginCore.AspNetCore.ResponseModel +namespace PluginCore.AspNetCore.ResponseModel; + +public class BaseResponseModel { - public class BaseResponseModel - { - public int Code { get; set; } + public int Code { get; set; } - public string Message { get; set; } + public string Message { get; set; } - public object Data { get; set; } - } + public object Data { get; set; } } diff --git a/src/PluginCore.IPlugins.AspNetCore/IPlugins/IContentFilterPlugin.cs b/src/PluginCore.IPlugins.AspNetCore/IPlugins/IContentFilterPlugin.cs index c4066761..03212b12 100644 --- a/src/PluginCore.IPlugins.AspNetCore/IPlugins/IContentFilterPlugin.cs +++ b/src/PluginCore.IPlugins.AspNetCore/IPlugins/IContentFilterPlugin.cs @@ -5,23 +5,16 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -namespace PluginCore.IPlugins +namespace PluginCore.IPlugins; + +/// +/// 目前未有效化,占坑 +/// +public interface IContentFilterPlugin : IPlugin { - /// - /// 目前未有效化,占坑 - /// - public interface IContentFilterPlugin : IPlugin - { - Task RequestBodyFilter(string urlPath, string content); + Task RequestBodyFilter(string urlPath, string content); - Task ReponseBodyFilter(string urlPath, string content); - } + Task ReponseBodyFilter(string urlPath, string content); } diff --git a/src/PluginCore.IPlugins.AspNetCore/IPlugins/IHttpFilterPlugin.cs b/src/PluginCore.IPlugins.AspNetCore/IPlugins/IHttpFilterPlugin.cs index e1cb04eb..077a1f3d 100644 --- a/src/PluginCore.IPlugins.AspNetCore/IPlugins/IHttpFilterPlugin.cs +++ b/src/PluginCore.IPlugins.AspNetCore/IPlugins/IHttpFilterPlugin.cs @@ -5,23 +5,17 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -namespace PluginCore.IPlugins +namespace PluginCore.IPlugins; + +/// +/// 实验性: 不确定有效, 发现问题,请 new issue +/// +public interface IHttpFilterPlugin : IPlugin { - /// - /// 实验性: 不确定有效, 发现问题,请 new issue - /// - public interface IHttpFilterPlugin : IPlugin - { - Task HttpEndFilter(HttpContext httpContext); + Task HttpEndFilter(HttpContext httpContext); - Task HttpStartFilter(HttpContext httpContext); - } + Task HttpStartFilter(HttpContext httpContext); } diff --git a/src/PluginCore.IPlugins.AspNetCore/IPlugins/IStartupPlugin.cs b/src/PluginCore.IPlugins.AspNetCore/IPlugins/IStartupPlugin.cs index 551862d1..8fe93a4e 100644 --- a/src/PluginCore.IPlugins.AspNetCore/IPlugins/IStartupPlugin.cs +++ b/src/PluginCore.IPlugins.AspNetCore/IPlugins/IStartupPlugin.cs @@ -5,34 +5,31 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; -namespace PluginCore.IPlugins +namespace PluginCore.IPlugins; + +/// +/// 实验阶段 +/// 无法热插拔: 需要启用插件后, 再 重启站点 +/// +public interface IStartupPlugin : IPlugin { /// - /// 实验阶段 - /// 无法热插拔: 需要启用插件后, 再 重启站点 + /// This method gets called by the runtime. Use this method to add services to the container. /// - public interface IStartupPlugin : IPlugin - { - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// - /// - void ConfigureServices(IServiceCollection services); + /// + void ConfigureServices(IServiceCollection services); - int ConfigureServicesOrder { get; } + int ConfigureServicesOrder { get; } - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - /// - /// - void Configure(IApplicationBuilder app); + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// + /// + void Configure(IApplicationBuilder app); - int ConfigureOrder { get; } - } + int ConfigureOrder { get; } } diff --git a/src/PluginCore.IPlugins.AspNetCore/IPlugins/IStartupXPlugin.cs b/src/PluginCore.IPlugins.AspNetCore/IPlugins/IStartupXPlugin.cs index cd2c9005..05b475ac 100644 --- a/src/PluginCore.IPlugins.AspNetCore/IPlugins/IStartupXPlugin.cs +++ b/src/PluginCore.IPlugins.AspNetCore/IPlugins/IStartupXPlugin.cs @@ -6,38 +6,33 @@ //=================================================== - -using System; -using System.Collections.Generic; -using System.Text; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; -namespace PluginCore.IPlugins +namespace PluginCore.IPlugins.AspNetCore.IPlugins; + +/// +/// 实验阶段 +/// 热插拔: 已有效化 +/// +public interface IStartupXPlugin : IPlugin { /// - /// 实验阶段 - /// 热插拔: 已有效化 + /// This method gets called by the runtime. Use this method to add services to the container. + /// 未有效化 + /// + /// + void ConfigureServices(IServiceCollection services); + + + int ConfigureServicesOrder { get; } + + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. /// - public interface IStartupXPlugin : IPlugin - { - /// - /// This method gets called by the runtime. Use this method to add services to the container. - /// 未有效化 - /// - /// - void ConfigureServices(IServiceCollection services); - - - int ConfigureServicesOrder { get; } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - /// - /// - void Configure(IApplicationBuilder app); - - int ConfigureOrder { get; } - } + /// + /// + void Configure(IApplicationBuilder app); + + int ConfigureOrder { get; } } diff --git a/src/PluginCore.IPlugins.AspNetCore/PluginCore.IPlugins.AspNetCore.csproj b/src/PluginCore.IPlugins.AspNetCore/PluginCore.IPlugins.AspNetCore.csproj index 99f79852..17040706 100644 --- a/src/PluginCore.IPlugins.AspNetCore/PluginCore.IPlugins.AspNetCore.csproj +++ b/src/PluginCore.IPlugins.AspNetCore/PluginCore.IPlugins.AspNetCore.csproj @@ -1,7 +1,6 @@  - netstandard2.0 PluginCore.IPlugins.AspNetCore 0.1.0 yiyun @@ -12,6 +11,7 @@ https://github.com/yiyungent/PluginCore/blob/main/LICENSE PluginCore PluginCore.IPlugins.AspNetCore true + net7.0 diff --git a/src/PluginCore.IPlugins/BasePlugin.cs b/src/PluginCore.IPlugins/BasePlugin.cs index 4bbfabac..469c4e5e 100644 --- a/src/PluginCore.IPlugins/BasePlugin.cs +++ b/src/PluginCore.IPlugins/BasePlugin.cs @@ -5,61 +5,56 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; using System.Collections.Generic; -using System.Text; -namespace PluginCore.IPlugins +namespace PluginCore.IPlugins; + +public abstract class BasePlugin : IPlugin { - public abstract class BasePlugin : IPlugin + /// + /// 在启用插件之后: 这时插件Assemblies已被加载(插件上下文已load) + /// + /// + /// 当 IsSuccess 为 False, 主程序之后会回滚插件状态: (1)unload插件上下文 (2)更新plugin.config.json,标记为禁用状态 + /// + public virtual (bool IsSuccess, string Message) AfterEnable() { - /// - /// 在启用插件之后: 这时插件Assemblies已被加载(插件上下文已load) - /// - /// - /// 当 IsSuccess 为 False, 主程序之后会回滚插件状态: (1)unload插件上下文 (2)更新plugin.config.json,标记为禁用状态 - /// - public virtual (bool IsSuccess, string Message) AfterEnable() - { - return (true, "启用成功"); - } + return (true, "启用成功"); + } - /// - /// 在禁用插件之前: 这时插件Assemblies还未被释放(插件上下文未Unload) - /// - /// - /// 只有当 IsSuccess 为 True, 主程序之后才会释放插件上下文, 并标记为已禁用 - /// 当 IsSuccess 为 False, 主程序不会释放插件上下文,也不会标记为禁用, 插件维持原状态 - /// - public virtual (bool IsSuccess, string Message) BeforeDisable() - { - return (true, "禁用成功"); - } + /// + /// 在禁用插件之前: 这时插件Assemblies还未被释放(插件上下文未Unload) + /// + /// + /// 只有当 IsSuccess 为 True, 主程序之后才会释放插件上下文, 并标记为已禁用 + /// 当 IsSuccess 为 False, 主程序不会释放插件上下文,也不会标记为禁用, 插件维持原状态 + /// + public virtual (bool IsSuccess, string Message) BeforeDisable() + { + return (true, "禁用成功"); + } - /// - /// TODO: 更新未做 - /// - /// - /// - /// - public virtual (bool IsSuccess, string Message) Update(string currentVersion, string targetVersion) - { - return (true, "更新成功"); - } + /// + /// TODO: 更新未做 + /// + /// + /// + /// + public virtual (bool IsSuccess, string Message) Update(string currentVersion, string targetVersion) + { + return (true, "更新成功"); + } - public virtual void AppStart() - { + public virtual void AppStart() + { - } + } - public virtual List AppStartOrderDependPlugins + public virtual List AppStartOrderDependPlugins + { + get { - get - { - return new List(); - } + return new List(); } } } diff --git a/src/PluginCore.IPlugins/IPlugin.cs b/src/PluginCore.IPlugins/IPlugin.cs index aa9b9400..b2d550b5 100644 --- a/src/PluginCore.IPlugins/IPlugin.cs +++ b/src/PluginCore.IPlugins/IPlugin.cs @@ -5,51 +5,46 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; using System.Collections.Generic; -using System.Text; -namespace PluginCore.IPlugins +namespace PluginCore.IPlugins; + +public interface IPlugin { - public interface IPlugin - { - /// - /// 在启用插件之后: 这时插件Assemblies已被加载(插件上下文已load) - /// - /// - /// 当 IsSuccess 为 False, 主程序之后会回滚插件状态: (1)unload插件上下文 (2)更新plugin.config.json,标记为禁用状态 - /// - (bool IsSuccess, string Message) AfterEnable(); - - /// - /// 在禁用插件之前: 这时插件Assemblies还未被释放(插件上下文未Unload) - /// - /// - /// 只有当 IsSuccess 为 True, 主程序之后才会释放插件上下文, 并标记为已禁用 - /// 当 IsSuccess 为 False, 主程序不会释放插件上下文,也不会标记为禁用, 插件维持原状态 - /// - (bool IsSuccess, string Message) BeforeDisable(); - - /// - /// TODO: 更新未做 - /// - /// - /// - /// - (bool IsSuccess, string Message) Update(string currentVersion, string targetVersion); - - /// - /// 主应用程序启动时 - /// - void AppStart(); - - /// - /// 启动顺序: 此插件 所依赖的前置插件 - /// - /// - List AppStartOrderDependPlugins { get; } - - } + /// + /// 在启用插件之后: 这时插件Assemblies已被加载(插件上下文已load) + /// + /// + /// 当 IsSuccess 为 False, 主程序之后会回滚插件状态: (1)unload插件上下文 (2)更新plugin.config.json,标记为禁用状态 + /// + (bool IsSuccess, string Message) AfterEnable(); + + /// + /// 在禁用插件之前: 这时插件Assemblies还未被释放(插件上下文未Unload) + /// + /// + /// 只有当 IsSuccess 为 True, 主程序之后才会释放插件上下文, 并标记为已禁用 + /// 当 IsSuccess 为 False, 主程序不会释放插件上下文,也不会标记为禁用, 插件维持原状态 + /// + (bool IsSuccess, string Message) BeforeDisable(); + + /// + /// TODO: 更新未做 + /// + /// + /// + /// + (bool IsSuccess, string Message) Update(string currentVersion, string targetVersion); + + /// + /// 主应用程序启动时 + /// + void AppStart(); + + /// + /// 启动顺序: 此插件 所依赖的前置插件 + /// + /// + List AppStartOrderDependPlugins { get; } + } diff --git a/src/PluginCore.IPlugins/IPlugins/ITimeJobPlugin.cs b/src/PluginCore.IPlugins/IPlugins/ITimeJobPlugin.cs index c1118576..3a0c174b 100644 --- a/src/PluginCore.IPlugins/IPlugins/ITimeJobPlugin.cs +++ b/src/PluginCore.IPlugins/IPlugins/ITimeJobPlugin.cs @@ -6,24 +6,19 @@ //=================================================== - -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; -namespace PluginCore.IPlugins +namespace PluginCore.IPlugins.IPlugins; + +/// +/// 定时任务 +/// +public interface ITimeJobPlugin : IPlugin { /// - /// 定时任务 + /// 间隔秒数 /// - public interface ITimeJobPlugin : IPlugin - { - /// - /// 间隔秒数 - /// - long SecondsPeriod { get; } + long SecondsPeriod { get; } - Task ExecuteAsync(); - } + Task ExecuteAsync(); } diff --git a/src/PluginCore.IPlugins/IPlugins/IWidgetPlugin.cs b/src/PluginCore.IPlugins/IPlugins/IWidgetPlugin.cs index e9ff0153..943a5b34 100644 --- a/src/PluginCore.IPlugins/IPlugins/IWidgetPlugin.cs +++ b/src/PluginCore.IPlugins/IPlugins/IWidgetPlugin.cs @@ -6,19 +6,14 @@ //=================================================== - -using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; -namespace PluginCore.IPlugins +namespace PluginCore.IPlugins.IPlugins; + +public interface IWidgetPlugin : IPlugin { - public interface IWidgetPlugin : IPlugin - { - // 这种方式限定: 不合适, 一个插件, 可能需要注入多个地方 - //string WidgetKey { get; } + // 这种方式限定: 不合适, 一个插件, 可能需要注入多个地方 + //string WidgetKey { get; } - Task Widget(string widgetKey, params string[] extraPars); - } + Task Widget(string widgetKey, params string[] extraPars); } diff --git a/src/PluginCore.IPlugins/Infrastructure/PluginPathProvider.cs b/src/PluginCore.IPlugins/Infrastructure/PluginPathProvider.cs index 86fd6648..efe630c0 100644 --- a/src/PluginCore.IPlugins/Infrastructure/PluginPathProvider.cs +++ b/src/PluginCore.IPlugins/Infrastructure/PluginPathProvider.cs @@ -5,210 +5,205 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System.Collections; using System.Collections.Generic; using System.IO; -using System.Reflection; -namespace PluginCore +namespace PluginCore.IPlugins.Infrastructure; + +public class PluginPathProvider { - public class PluginPathProvider - { - static PluginPathProvider() + static PluginPathProvider() + { + // 初始化插件目录 + string appDataDir = Path.Combine(Directory.GetCurrentDirectory(), "App_Data"); + if (!Directory.Exists(appDataDir)) { - // 初始化插件目录 - string appDataDir = Path.Combine(Directory.GetCurrentDirectory(), "App_Data"); - if (!Directory.Exists(appDataDir)) - { - Directory.CreateDirectory(appDataDir); - } - - string pluginConfigJsonFilePath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "plugin.config.json"); - string pluginConfigJson = "{\"EnabledPlugins\":[],\"DisabledPlugins\":[],\"UninstalledPlugins\":[]}"; - if (!File.Exists(pluginConfigJsonFilePath)) - { - File.WriteAllText(pluginConfigJsonFilePath, pluginConfigJson, System.Text.Encoding.UTF8); - } - - string tempPluginUploadDir = TempPluginUploadDir(); - if (!Directory.Exists(tempPluginUploadDir)) - { - Directory.CreateDirectory(tempPluginUploadDir); - } - - string pluginsDir = PluginsRootPath(); - if (!Directory.Exists(pluginsDir)) - { - Directory.CreateDirectory(pluginsDir); - } - - string pluginsWwwRootDir = PluginsWwwRootDir(); - if (!Directory.Exists(pluginsWwwRootDir)) - { - Directory.CreateDirectory(pluginsWwwRootDir); - } - - string pluginCoreAdminDir = PluginCoreAdminDir(); - if (!Directory.Exists(pluginCoreAdminDir)) - { - Directory.CreateDirectory(pluginCoreAdminDir); - } + Directory.CreateDirectory(appDataDir); } - /// - /// 临时插件上传目录路径 - /// eg: F:\Com\me\Repos\Remember.Core\src\Presentation\WebApi\App_Data\TempPluginUpload - /// - /// - public static string TempPluginUploadDir() + string pluginConfigJsonFilePath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "plugin.config.json"); + string pluginConfigJson = "{\"EnabledPlugins\":[],\"DisabledPlugins\":[],\"UninstalledPlugins\":[]}"; + if (!File.Exists(pluginConfigJsonFilePath)) { - string tempPluginUploadDir = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "TempPluginUpload"); - return tempPluginUploadDir; + File.WriteAllText(pluginConfigJsonFilePath, pluginConfigJson, System.Text.Encoding.UTF8); } - /// - /// 获取 Plugins 的路径 - /// - /// - public static string PluginsRootPath() + string tempPluginUploadDir = TempPluginUploadDir(); + if (!Directory.Exists(tempPluginUploadDir)) { - string pluginRootPath = Path.Combine(Directory.GetCurrentDirectory(), "Plugins"); - - return pluginRootPath; + Directory.CreateDirectory(tempPluginUploadDir); } - /// - /// 获取目标插件文件夹名 - /// - /// 目标插件完整目录路径 - /// - public static string GetPluginFolderNameByDir(string pluginDir) + string pluginsDir = PluginsRootPath(); + if (!Directory.Exists(pluginsDir)) { - string pluginRootPath = PluginsRootPath(); - string pluginFolderName = pluginDir.Replace(pluginRootPath + Path.DirectorySeparatorChar, ""); - - return pluginFolderName; + Directory.CreateDirectory(pluginsDir); } - /// - /// 所有插件的完整目录路径 - /// - /// - public static IList AllPluginDir() + string pluginsWwwRootDir = PluginsWwwRootDir(); + if (!Directory.Exists(pluginsWwwRootDir)) { - string pluginRootPath = PluginsRootPath(); - string[] pluginDirs = Directory.GetDirectories(pluginRootPath, "*"); - - return pluginDirs; + Directory.CreateDirectory(pluginsWwwRootDir); } - /// - /// 所有插件的文件夹名 - /// - /// - public static IList AllPluginFolderName() + string pluginCoreAdminDir = PluginCoreAdminDir(); + if (!Directory.Exists(pluginCoreAdminDir)) { - IList pluginFolderNames = new List(); - IList pluginDirs = AllPluginDir(); - foreach (var dir in pluginDirs) - { - string pluginFolderName = GetPluginFolderNameByDir(dir); - pluginFolderNames.Add(pluginFolderName); - } - - return pluginFolderNames; + Directory.CreateDirectory(pluginCoreAdminDir); } + } - /// - /// Plugins/{pluginId}/wwwroot - /// - /// + /// + /// 临时插件上传目录路径 + /// eg: F:\Com\me\Repos\Remember.Core\src\Presentation\WebApi\App_Data\TempPluginUpload + /// + /// + public static string TempPluginUploadDir() + { + string tempPluginUploadDir = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "TempPluginUpload"); + return tempPluginUploadDir; + } - public static string WwwRootDir(string pluginId) - { - string wwwrootDir = Path.Combine(PluginsRootPath(), pluginId, "wwwroot"); + /// + /// 获取 Plugins 的路径 + /// + /// + public static string PluginsRootPath() + { + string pluginRootPath = Path.Combine(Directory.GetCurrentDirectory(), "Plugins"); - return wwwrootDir; - } + return pluginRootPath; + } - /// - /// Plugins/{currentPluginId}/wwwroot - /// - /// - //public static string CurrentWwwRootDir() - //{ - // string wwwrootDir = Path.Combine(PluginsRootPath(), CurrentPluginId(), "wwwroot"); + /// + /// 获取目标插件文件夹名 + /// + /// 目标插件完整目录路径 + /// + public static string GetPluginFolderNameByDir(string pluginDir) + { + string pluginRootPath = PluginsRootPath(); + string pluginFolderName = pluginDir.Replace(pluginRootPath + Path.DirectorySeparatorChar, ""); - // return wwwrootDir; - //} + return pluginFolderName; + } + /// + /// 所有插件的完整目录路径 + /// + /// + public static IList AllPluginDir() + { + string pluginRootPath = PluginsRootPath(); + string[] pluginDirs = Directory.GetDirectories(pluginRootPath, "*"); - /// - /// Plugins_wwwroot - /// - /// - public static string PluginsWwwRootDir() - { - string pluginsWwwRootDir = Path.Combine(Directory.GetCurrentDirectory(), "Plugins_wwwroot"); + return pluginDirs; + } - return pluginsWwwRootDir; + /// + /// 所有插件的文件夹名 + /// + /// + public static IList AllPluginFolderName() + { + IList pluginFolderNames = new List(); + IList pluginDirs = AllPluginDir(); + foreach (var dir in pluginDirs) + { + string pluginFolderName = GetPluginFolderNameByDir(dir); + pluginFolderNames.Add(pluginFolderName); } - /// - /// Plugins_wwwroot/pluginId - /// - /// - /// - public static string PluginWwwRootDir(string pluginId) - { - string pluginWwwRootDir = Path.Combine(PluginsWwwRootDir(), pluginId); + return pluginFolderNames; + } - return pluginWwwRootDir; - } + /// + /// Plugins/{pluginId}/wwwroot + /// + /// - /// - /// Plugins_wwwroot/currentPluginId - /// - /// - //public static string CurrentPluginWwwRootDir() - //{ - // string pluginId = CurrentPluginId(); + public static string WwwRootDir(string pluginId) + { + string wwwrootDir = Path.Combine(PluginsRootPath(), pluginId, "wwwroot"); - // string pluginWwwRootDir = PluginWwwRootDir(pluginId); + return wwwrootDir; + } - // return pluginWwwRootDir; - //} + /// + /// Plugins/{currentPluginId}/wwwroot + /// + /// + //public static string CurrentWwwRootDir() + //{ + // string wwwrootDir = Path.Combine(PluginsRootPath(), CurrentPluginId(), "wwwroot"); - public static string PluginCoreAdminDir() - { - string pluginCoreAdminDir = Path.Combine(Directory.GetCurrentDirectory(), "PluginCoreAdmin"); + // return wwwrootDir; + //} - return pluginCoreAdminDir; - } + /// + /// Plugins_wwwroot + /// + /// + public static string PluginsWwwRootDir() + { + string pluginsWwwRootDir = Path.Combine(Directory.GetCurrentDirectory(), "Plugins_wwwroot"); - /// - /// 规范: - /// PluginId = 插件程序集名 (PluginId.dll) - /// - /// TODO: 无法获取当前 PluginId - /// - /// - //public static string CurrentPluginId() - //{ - // //var ass = Assembly.GetExecutingAssembly(); // 错误: PluginCore.IPlugins - // //var ass = Assembly.GetCallingAssembly(); // 错误: PluginCore.IPlugins - // //var ass = Assembly.GetEntryAssembly(); // 错误: AspNetCore3_1 - + return pluginsWwwRootDir; + } + + /// + /// Plugins_wwwroot/pluginId + /// + /// + /// + public static string PluginWwwRootDir(string pluginId) + { + string pluginWwwRootDir = Path.Combine(PluginsWwwRootDir(), pluginId); - // string pluginId = ass.GetName().Name; + return pluginWwwRootDir; + } + + /// + /// Plugins_wwwroot/currentPluginId + /// + /// + //public static string CurrentPluginWwwRootDir() + //{ + // string pluginId = CurrentPluginId(); + + // string pluginWwwRootDir = PluginWwwRootDir(pluginId); - // return pluginId; - //} + // return pluginWwwRootDir; + //} + public static string PluginCoreAdminDir() + { + string pluginCoreAdminDir = Path.Combine(Directory.GetCurrentDirectory(), "PluginCoreAdmin"); + return pluginCoreAdminDir; } + + + /// + /// 规范: + /// PluginId = 插件程序集名 (PluginId.dll) + /// + /// TODO: 无法获取当前 PluginId + /// + /// + //public static string CurrentPluginId() + //{ + // //var ass = Assembly.GetExecutingAssembly(); // 错误: PluginCore.IPlugins + // //var ass = Assembly.GetCallingAssembly(); // 错误: PluginCore.IPlugins + // //var ass = Assembly.GetEntryAssembly(); // 错误: AspNetCore3_1 + + + // string pluginId = ass.GetName().Name; + + // return pluginId; + //} + + } diff --git a/src/PluginCore.IPlugins/Infrastructure/PluginSettingsModelFactory.cs b/src/PluginCore.IPlugins/Infrastructure/PluginSettingsModelFactory.cs index 150d9e07..f15fbfce 100644 --- a/src/PluginCore.IPlugins/Infrastructure/PluginSettingsModelFactory.cs +++ b/src/PluginCore.IPlugins/Infrastructure/PluginSettingsModelFactory.cs @@ -6,158 +6,154 @@ //=================================================== - -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Text; -using PluginCore.Models; +using PluginCore.IPlugins.Models; +namespace PluginCore.IPlugins.Infrastructure; -namespace PluginCore +public class PluginSettingsModelFactory { - public class PluginSettingsModelFactory + // TODO: Linux 文件名下区分大小写, windows不区分, 目前必须为 README.md + private const string SettingsFile = "settings.json"; + + #region 即时读取 + public static T Create + (string pluginId) + where T : PluginSettingsModel { - // TODO: Linux 文件名下区分大小写, windows不区分, 目前必须为 README.md - private const string SettingsFile = "settings.json"; + PluginSettingsModel rtnModel = new PluginSettingsModel(); + string pluginDir = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); + string pluginSettingsFilePath = Path.Combine(pluginDir, SettingsFile); - #region 即时读取 - public static T Create - (string pluginId) - where T : PluginSettingsModel + if (!File.Exists(pluginSettingsFilePath)) + { + return null; + } + try { - PluginSettingsModel rtnModel = new PluginSettingsModel(); - string pluginDir = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); - string pluginSettingsFilePath = Path.Combine(pluginDir, SettingsFile); - - if (!File.Exists(pluginSettingsFilePath)) - { - return null; - } - try - { - string settingsStr = File.ReadAllText(pluginSettingsFilePath, Encoding.UTF8); - rtnModel = System.Text.Json.JsonSerializer.Deserialize(settingsStr); - } - catch (Exception ex) - { - rtnModel = null; - } - - return rtnModel as T; + string settingsStr = File.ReadAllText(pluginSettingsFilePath, Encoding.UTF8); + rtnModel = System.Text.Json.JsonSerializer.Deserialize(settingsStr); + } + catch + { + rtnModel = null; } - public static string Create - (string pluginId) + return rtnModel as T; + } + + public static string Create + (string pluginId) + { + string rtnStr = string.Empty; + string pluginDir = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); + string pluginSettingsFilePath = Path.Combine(pluginDir, SettingsFile); + + if (!File.Exists(pluginSettingsFilePath)) { - string rtnStr = string.Empty; - string pluginDir = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); - string pluginSettingsFilePath = Path.Combine(pluginDir, SettingsFile); - - if (!File.Exists(pluginSettingsFilePath)) - { - return null; - } - try - { - rtnStr = File.ReadAllText(pluginSettingsFilePath, Encoding.UTF8); - } - catch (Exception ex) - { - rtnStr = null; - } - - return rtnStr; + return null; } - #endregion + try + { + rtnStr = File.ReadAllText(pluginSettingsFilePath, Encoding.UTF8); + } + catch + { + rtnStr = null; + } + + return rtnStr; + } + #endregion - #region 保存 - public static void Save(T pluginSettingsModel, string pluginId) + #region 保存 + public static void Save(T pluginSettingsModel, string pluginId) where T : PluginSettingsModel + { + if (pluginSettingsModel == null) { - if (pluginSettingsModel == null) - { - throw new ArgumentNullException(nameof(pluginSettingsModel)); - } - try - { - string pluginSettingsJsonStr = System.Text.Json.JsonSerializer.Serialize(pluginSettingsModel); - string pluginSettingsFilePath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId, SettingsFile); - //File.WriteAllText(pluginSettingsFilePath, pluginSettingsJsonStr, Encoding.UTF8); - // 写的时候加缩进 - File.WriteAllText(pluginSettingsFilePath, ConvertJsonString(pluginSettingsJsonStr), Encoding.UTF8); - } - catch (Exception ex) - { } - + throw new ArgumentNullException(nameof(pluginSettingsModel)); } - - public static void Save(string pluginSettingsJsonStr, string pluginId) + try { - if (pluginSettingsJsonStr == null) - { - throw new ArgumentNullException(nameof(pluginSettingsJsonStr)); - } - try - { - string pluginSettingsFilePath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId, SettingsFile); - //File.WriteAllText(pluginSettingsFilePath, pluginSettingsJsonStr, Encoding.UTF8); - // 写的时候加缩进 - File.WriteAllText(pluginSettingsFilePath, ConvertJsonString(pluginSettingsJsonStr), Encoding.UTF8); - } - catch (Exception ex) - { } - + string pluginSettingsJsonStr = System.Text.Json.JsonSerializer.Serialize(pluginSettingsModel); + string pluginSettingsFilePath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId, SettingsFile); + //File.WriteAllText(pluginSettingsFilePath, pluginSettingsJsonStr, Encoding.UTF8); + // 写的时候加缩进 + File.WriteAllText(pluginSettingsFilePath, ConvertJsonString(pluginSettingsJsonStr), Encoding.UTF8); } - #endregion + catch + { } + + } - #region 格式化JSON字符串 - private static string ConvertJsonString(string str) + public static void Save(string pluginSettingsJsonStr, string pluginId) + { + if (pluginSettingsJsonStr == null) { - //格式化json字符串 - #region 使用Newtonsoft.Json格式化 JSON字符串 - //Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); - //TextReader tr = new StringReader(str); - //Newtonsoft.Json.JsonTextReader jtr = new Newtonsoft.Json.JsonTextReader(tr); - //object obj = serializer.Deserialize(jtr); - //if (obj != null) - //{ - // StringWriter textWriter = new StringWriter(); - // Newtonsoft.Json.JsonTextWriter jsonWriter = new Newtonsoft.Json.JsonTextWriter(textWriter) - // { - // Formatting = Newtonsoft.Json.Formatting.Indented, - // Indentation = 4, - // IndentChar = ' ' - // }; - // serializer.Serialize(jsonWriter, obj); - // return textWriter.ToString(); - //} - //else - //{ - // return str; - //} - #endregion - - #region 使用 System.Text.Json 格式化 JSON字符串 - // https://blog.csdn.net/essity/article/details/84644510 - System.Text.Json.JsonSerializerOptions options = new System.Text.Json.JsonSerializerOptions(); - // 设置支持中文的unicode编码: 这样就不会自动转码,而是原样展现 - options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; - // 启用缩进设置 - options.WriteIndented = true; - - // 注意: object 不会丢失json数据, 但不能使用 dynamic, 会报编译错误 - object jsonObj = System.Text.Json.JsonSerializer.Deserialize(str); - - // Error CS0656 Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create' - // dynamic jsonObj = System.Text.Json.JsonSerializer.Deserialize(str); - - string rtnStr = System.Text.Json.JsonSerializer.Serialize(jsonObj, options); - - return rtnStr; - #endregion + throw new ArgumentNullException(nameof(pluginSettingsJsonStr)); } + try + { + string pluginSettingsFilePath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId, SettingsFile); + //File.WriteAllText(pluginSettingsFilePath, pluginSettingsJsonStr, Encoding.UTF8); + // 写的时候加缩进 + File.WriteAllText(pluginSettingsFilePath, ConvertJsonString(pluginSettingsJsonStr), Encoding.UTF8); + } + catch + { } + + } + #endregion + + #region 格式化JSON字符串 + private static string ConvertJsonString(string str) + { + //格式化json字符串 + #region 使用Newtonsoft.Json格式化 JSON字符串 + //Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); + //TextReader tr = new StringReader(str); + //Newtonsoft.Json.JsonTextReader jtr = new Newtonsoft.Json.JsonTextReader(tr); + //object obj = serializer.Deserialize(jtr); + //if (obj != null) + //{ + // StringWriter textWriter = new StringWriter(); + // Newtonsoft.Json.JsonTextWriter jsonWriter = new Newtonsoft.Json.JsonTextWriter(textWriter) + // { + // Formatting = Newtonsoft.Json.Formatting.Indented, + // Indentation = 4, + // IndentChar = ' ' + // }; + // serializer.Serialize(jsonWriter, obj); + // return textWriter.ToString(); + //} + //else + //{ + // return str; + //} #endregion + #region 使用 System.Text.Json 格式化 JSON字符串 + // https://blog.csdn.net/essity/article/details/84644510 + System.Text.Json.JsonSerializerOptions options = new System.Text.Json.JsonSerializerOptions(); + // 设置支持中文的unicode编码: 这样就不会自动转码,而是原样展现 + options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + // 启用缩进设置 + options.WriteIndented = true; + + // 注意: object 不会丢失json数据, 但不能使用 dynamic, 会报编译错误 + object jsonObj = System.Text.Json.JsonSerializer.Deserialize(str); + + // Error CS0656 Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create' + // dynamic jsonObj = System.Text.Json.JsonSerializer.Deserialize(str); + + string rtnStr = System.Text.Json.JsonSerializer.Serialize(jsonObj, options); + + return rtnStr; + #endregion } + #endregion + } diff --git a/src/PluginCore.IPlugins/Interfaces/IPluginFinder.cs b/src/PluginCore.IPlugins/Interfaces/IPluginFinder.cs index 1925789c..5779c119 100644 --- a/src/PluginCore.IPlugins/Interfaces/IPluginFinder.cs +++ b/src/PluginCore.IPlugins/Interfaces/IPluginFinder.cs @@ -6,72 +6,67 @@ //=================================================== - -using System; using System.Collections.Generic; -using System.Text; -using PluginCore.IPlugins; -namespace PluginCore.Interfaces +namespace PluginCore.IPlugins.Interfaces; + +public interface IPluginFinder { - public interface IPluginFinder - { - #region 实现了指定接口或类型 的启用插件 + #region 实现了指定接口或类型 的启用插件 - /// - /// 实现了指定接口或类型 的启用插件 - /// - /// 可以是一个接口,一个抽象类,一个普通实现类, 只要实现了 即可 - /// - IEnumerable EnablePlugins() - where TPlugin : IPlugin; // BasePlugin + /// + /// 实现了指定接口或类型 的启用插件 + /// + /// 可以是一个接口,一个抽象类,一个普通实现类, 只要实现了 即可 + /// + IEnumerable EnablePlugins() + where TPlugin : IPlugin; // BasePlugin - /// - /// 实现了指定接口或类型 的启用插件 - /// - /// 可以是一个接口,一个抽象类,一个普通实现类, 只要实现了 即可 - /// - IEnumerable<(TPlugin PluginInstance, string PluginId)> EnablePluginsFull() - where TPlugin : IPlugin; // BasePlugin + /// + /// 实现了指定接口或类型 的启用插件 + /// + /// 可以是一个接口,一个抽象类,一个普通实现类, 只要实现了 即可 + /// + IEnumerable<(TPlugin PluginInstance, string PluginId)> EnablePluginsFull() + where TPlugin : IPlugin; // BasePlugin - /// - /// 所有启用的插件 的 PluginId - /// - /// - IEnumerable EnablePluginIds() - where TPlugin : IPlugin; // BasePlugin - #endregion + /// + /// 所有启用的插件 的 PluginId + /// + /// + IEnumerable EnablePluginIds() + where TPlugin : IPlugin; // BasePlugin + #endregion - #region 所有启用的插件 + #region 所有启用的插件 - /// - /// 所有启用的插件 - /// - /// - IEnumerable EnablePlugins(); + /// + /// 所有启用的插件 + /// + /// + IEnumerable EnablePlugins(); - /// - /// 所有启用的插件 - /// - /// - IEnumerable<(IPlugin PluginInstance, string PluginId)> EnablePluginsFull(); + /// + /// 所有启用的插件 + /// + /// + IEnumerable<(IPlugin PluginInstance, string PluginId)> EnablePluginsFull(); - /// - /// 所有启用的插件 的 PluginId - /// - /// - IEnumerable EnablePluginIds(); - #endregion + /// + /// 所有启用的插件 的 PluginId + /// + /// + IEnumerable EnablePluginIds(); + #endregion - #region 获取指定 pluginId 的启用插件 + #region 获取指定 pluginId 的启用插件 - /// - /// 获取指定 pluginId 的启用插件 - /// - /// - /// 1.插件未启用返回null, 2.找不到此插件上下文返回null 3.找不到插件主dll返回null 4.插件主dll中找不到实现了IPlugin的Type返回null, 5.无法实例化插件返回null - IPlugin Plugin(string pluginId); - #endregion + /// + /// 获取指定 pluginId 的启用插件 + /// + /// + /// 1.插件未启用返回null, 2.找不到此插件上下文返回null 3.找不到插件主dll返回null 4.插件主dll中找不到实现了IPlugin的Type返回null, 5.无法实例化插件返回null + IPlugin Plugin(string pluginId); + #endregion - } } diff --git a/src/PluginCore.IPlugins/Interfaces/IPluginManager.cs b/src/PluginCore.IPlugins/Interfaces/IPluginManager.cs index 548850bd..d495764a 100644 --- a/src/PluginCore.IPlugins/Interfaces/IPluginManager.cs +++ b/src/PluginCore.IPlugins/Interfaces/IPluginManager.cs @@ -6,17 +6,11 @@ //=================================================== +namespace PluginCore.IPlugins.Interfaces; -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.Interfaces +public interface IPluginManager { - public interface IPluginManager - { - void LoadPlugin(string pluginId); + void LoadPlugin(string pluginId); - void UnloadPlugin(string pluginId); - } + void UnloadPlugin(string pluginId); } diff --git a/src/PluginCore.IPlugins/Models/PluginSettingsModel.cs b/src/PluginCore.IPlugins/Models/PluginSettingsModel.cs index c8a0c9b9..a1031df3 100644 --- a/src/PluginCore.IPlugins/Models/PluginSettingsModel.cs +++ b/src/PluginCore.IPlugins/Models/PluginSettingsModel.cs @@ -6,20 +6,14 @@ //=================================================== +namespace PluginCore.IPlugins.Models; -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.Models +/// +/// 插件设置模型 +/// 对应插件目录下 settings.json +/// 插件开发者自己的插件设置模型 必须继承该类 +/// +public class PluginSettingsModel { - /// - /// 插件设置模型 - /// 对应插件目录下 settings.json - /// 插件开发者自己的插件设置模型 必须继承该类 - /// - public class PluginSettingsModel - { - } } diff --git a/src/PluginCore.IPlugins/PluginCore.IPlugins.csproj b/src/PluginCore.IPlugins/PluginCore.IPlugins.csproj index 2651fb6e..6967916a 100644 --- a/src/PluginCore.IPlugins/PluginCore.IPlugins.csproj +++ b/src/PluginCore.IPlugins/PluginCore.IPlugins.csproj @@ -1,7 +1,6 @@  - netstandard2.0 PluginCore.IPlugins 0.9.0 yiyun @@ -12,6 +11,7 @@ https://github.com/yiyungent/PluginCore/blob/main/LICENSE PluginCore PluginCore.IPlugin true + net7.0 @@ -22,8 +22,8 @@ - - + + diff --git a/src/PluginCore.Registry/Controllers/NuGetController.cs b/src/PluginCore.Registry/Controllers/NuGetController.cs index 6b483007..5af2b527 100644 --- a/src/PluginCore.Registry/Controllers/NuGetController.cs +++ b/src/PluginCore.Registry/Controllers/NuGetController.cs @@ -5,109 +5,104 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Threading.Tasks; using HtmlAgilityPack; using PluginCore.Registry.ResponseModel; using PluginCore.Registry.Utils; -namespace PluginCore.Registry.Controllers +namespace PluginCore.Registry.Controllers; + +[Route("api/[controller]/[action]")] +[ApiController] +public class NuGetController : ControllerBase { - [Route("api/[controller]/[action]")] - [ApiController] - public class NuGetController : ControllerBase + public NuGetController() { - public NuGetController() - { + } + + [HttpGet] + public async Task> List(int pageIndex = 1, string tagName = "PluginCore.Plugin") + { + CommonResponseModel responseModel = new ResponseModel.CommonResponseModel(); + // TODO: 没有使用 ResponseModel + IList responseData = new List(); + + // https://www.nuget.org/packages?page=2&q=Tags:"EF"&sortBy=relevance + // https://www.nuget.org/packages?page=2&q=Tags%3A%22EF%22&sortBy=relevance + string nugetUrl = string.Format("https://www.nuget.org/packages?page={0}&q=Tags%3A%22{1}%22&sortBy=relevance", pageIndex, tagName); + + string htmlStr; + using (HttpClient httpClient = new HttpClient()) + { + htmlStr = await httpClient.GetStringAsync(nugetUrl); } - [HttpGet] - public async Task> List(int pageIndex = 1, string tagName = "PluginCore.Plugin") + var htmlDoc = new HtmlDocument(); + htmlDoc.LoadHtml(htmlStr); + HtmlNode listPackagesNode = htmlDoc.DocumentNode.SelectSingleNode("//div[@class='list-packages']"); + + //if (!listPackagesNode.HasChildNodes) // 不行, 就算里面为空还是 HasChildNodes 返回 True + if (string.IsNullOrEmpty(listPackagesNode.InnerHtml?.Trim())) { - CommonResponseModel responseModel = new ResponseModel.CommonResponseModel(); - // TODO: 没有使用 ResponseModel - IList responseData = new List(); + responseModel.code = 1; + responseModel.message = "加载列表成功"; + responseModel.data = responseData; - // https://www.nuget.org/packages?page=2&q=Tags:"EF"&sortBy=relevance - // https://www.nuget.org/packages?page=2&q=Tags%3A%22EF%22&sortBy=relevance - string nugetUrl = string.Format("https://www.nuget.org/packages?page={0}&q=Tags%3A%22{1}%22&sortBy=relevance", pageIndex, tagName); + return await Task.FromResult(responseModel); + } - string htmlStr; - using (HttpClient httpClient = new HttpClient()) - { - htmlStr = await httpClient.GetStringAsync(nugetUrl); - } + HtmlNodeCollection packageNodes = listPackagesNode.SelectNodes("./article[@class='package']"); - var htmlDoc = new HtmlDocument(); - htmlDoc.LoadHtml(htmlStr); - HtmlNode listPackagesNode = htmlDoc.DocumentNode.SelectSingleNode("//div[@class='list-packages']"); - //if (!listPackagesNode.HasChildNodes) // 不行, 就算里面为空还是 HasChildNodes 返回 True - if (string.IsNullOrEmpty(listPackagesNode.InnerHtml?.Trim())) + foreach (var packageNode in packageNodes) + { + Models.NuGet.NuGetPackageItemModel model = new Models.NuGet.NuGetPackageItemModel(); + + model.PackageIcon = packageNode.SelectSingleNode("./div[@class='row']/div[@class='col-sm-1 hidden-xs hidden-sm col-package-icon']/img[@class='package-icon img-responsive']").Attributes["src"].Value; + model.PackageId = packageNode.SelectSingleNode("./div[@class='row']/div[@class='col-sm-11']/div[@class='package-header']/a[@class='package-title']").Attributes["data-package-id"].Value; + model.PackageVersion = packageNode.SelectSingleNode("./div[@class='row']/div[@class='col-sm-11']/div[@class='package-header']/a[@class='package-title']").Attributes["data-package-version"].Value; + var packageByNodes = packageNode + .SelectSingleNode( + "./div[@class='row']/div[@class='col-sm-11']/div[@class='package-header']/span[@class='package-by']") + .SelectNodes("./a"); + foreach (var node in packageByNodes) { - responseModel.code = 1; - responseModel.message = "加载列表成功"; - responseModel.data = responseData; - - return await Task.FromResult(responseModel); + model.PackageBy.Add(node.InnerText); } - HtmlNodeCollection packageNodes = listPackagesNode.SelectNodes("./article[@class='package']"); - + var packageListNode = packageNode.SelectSingleNode( + "./div[@class='row']/div[@class='col-sm-11']/ul[@class='package-list']"); + var liNodes = packageListNode.SelectNodes("./li"); + model.PackageStat.DownloadCount = Convert.ToInt32(liNodes[0].SelectSingleNode("./span").InnerText.Trim().Replace("total downloads", "").Replace(",", "")); + // TODO: 居然是 9/21/2021, 不是 5 days ago + model.PackageStat.LastUpdatedText = liNodes[1].SelectSingleNode("./span/span").InnerText; + model.PackageStat.LastUpdated = Convert.ToDateTime(liNodes[1].SelectSingleNode("./span/span").Attributes["data-datetime"].Value).ToTimeStamp10(); - foreach (var packageNode in packageNodes) + var tagNodes = liNodes[3].SelectNodes("./span/a"); + foreach (var tagNode in tagNodes) { - Models.NuGet.NuGetPackageItemModel model = new Models.NuGet.NuGetPackageItemModel(); - - model.PackageIcon = packageNode.SelectSingleNode("./div[@class='row']/div[@class='col-sm-1 hidden-xs hidden-sm col-package-icon']/img[@class='package-icon img-responsive']").Attributes["src"].Value; - model.PackageId = packageNode.SelectSingleNode("./div[@class='row']/div[@class='col-sm-11']/div[@class='package-header']/a[@class='package-title']").Attributes["data-package-id"].Value; - model.PackageVersion = packageNode.SelectSingleNode("./div[@class='row']/div[@class='col-sm-11']/div[@class='package-header']/a[@class='package-title']").Attributes["data-package-version"].Value; - var packageByNodes = packageNode - .SelectSingleNode( - "./div[@class='row']/div[@class='col-sm-11']/div[@class='package-header']/span[@class='package-by']") - .SelectNodes("./a"); - foreach (var node in packageByNodes) - { - model.PackageBy.Add(node.InnerText); - } - - var packageListNode = packageNode.SelectSingleNode( - "./div[@class='row']/div[@class='col-sm-11']/ul[@class='package-list']"); - var liNodes = packageListNode.SelectNodes("./li"); - model.PackageStat.DownloadCount = Convert.ToInt32(liNodes[0].SelectSingleNode("./span").InnerText.Trim().Replace("total downloads", "").Replace(",", "")); - // TODO: 居然是 9/21/2021, 不是 5 days ago - model.PackageStat.LastUpdatedText = liNodes[1].SelectSingleNode("./span/span").InnerText; - model.PackageStat.LastUpdated = Convert.ToDateTime(liNodes[1].SelectSingleNode("./span/span").Attributes["data-datetime"].Value).ToTimeStamp10(); - - var tagNodes = liNodes[3].SelectNodes("./span/a"); - foreach (var tagNode in tagNodes) - { - model.PackageStat.Tags.Add(tagNode.InnerText); - } - - model.PackageDetail = packageNode - .SelectSingleNode( - "./div[@class='row']/div[@class='col-sm-11']/div[@class='package-details']").InnerText; - - responseData.Add(model); + model.PackageStat.Tags.Add(tagNode.InnerText); } - responseModel.code = 1; - responseModel.message = "加载列表成功"; - responseModel.data = responseData; + model.PackageDetail = packageNode + .SelectSingleNode( + "./div[@class='row']/div[@class='col-sm-11']/div[@class='package-details']").InnerText; - return await Task.FromResult(responseModel); + responseData.Add(model); } + responseModel.code = 1; + responseModel.message = "加载列表成功"; + responseModel.data = responseData; - + return await Task.FromResult(responseModel); } + + + } diff --git a/src/PluginCore.Registry/Controllers/WeatherForecastController.cs b/src/PluginCore.Registry/Controllers/WeatherForecastController.cs index 14fe889e..268e90ec 100644 --- a/src/PluginCore.Registry/Controllers/WeatherForecastController.cs +++ b/src/PluginCore.Registry/Controllers/WeatherForecastController.cs @@ -6,43 +6,40 @@ //=================================================== - -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; -namespace PluginCore.Registry.Controllers +namespace PluginCore.Registry.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase { - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase + private static readonly string[] Summaries = new[] { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; - private readonly ILogger _logger; + private readonly ILogger _logger; - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } - [HttpGet] - public IEnumerable Get() - { - var rng = new Random(); - return Enumerable.Range(1, 5).Select(index => new WeatherForecast + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); - } } } diff --git a/src/PluginCore.Registry/Models/NuGet/NuGetPackageItemModel.cs b/src/PluginCore.Registry/Models/NuGet/NuGetPackageItemModel.cs index 046c41f5..962c087f 100644 --- a/src/PluginCore.Registry/Models/NuGet/NuGetPackageItemModel.cs +++ b/src/PluginCore.Registry/Models/NuGet/NuGetPackageItemModel.cs @@ -5,52 +5,46 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -namespace PluginCore.Registry.Models.NuGet -{ - public class NuGetPackageItemModel - { - public string PackageIcon { get; set; } +namespace PluginCore.Registry.Models.NuGet; - public string PackageId { get; set; } +public class NuGetPackageItemModel +{ + public string PackageIcon { get; set; } - public string PackageVersion { get; set; } + public string PackageId { get; set; } - public IList PackageBy { get; set; } + public string PackageVersion { get; set; } - public string PackageDetail { get; set; } + public IList PackageBy { get; set; } - public PackageStatModel PackageStat { get; set; } + public string PackageDetail { get; set; } - public NuGetPackageItemModel() - { - this.PackageBy = new List(); - this.PackageStat = new PackageStatModel(); - this.PackageStat.Tags = new List(); - } + public PackageStatModel PackageStat { get; set; } + public NuGetPackageItemModel() + { + this.PackageBy = new List(); + this.PackageStat = new PackageStatModel(); + this.PackageStat.Tags = new List(); + } - public sealed class PackageStatModel - { - public int DownloadCount { get; set; } - public string LastUpdatedText { get; set; } + public sealed class PackageStatModel + { + public int DownloadCount { get; set; } - //public DateTime LastUpdated { get; set; } + public string LastUpdatedText { get; set; } - /// - /// 10位 Unix 时间戳 - /// - public long LastUpdated { get; set; } + //public DateTime LastUpdated { get; set; } - public IList Tags { get; set; } - } + /// + /// 10位 Unix 时间戳 + /// + public long LastUpdated { get; set; } + public IList Tags { get; set; } } + } diff --git a/src/PluginCore.Registry/PluginCore.Registry.csproj b/src/PluginCore.Registry/PluginCore.Registry.csproj index 20b14f0c..0567b2f8 100644 --- a/src/PluginCore.Registry/PluginCore.Registry.csproj +++ b/src/PluginCore.Registry/PluginCore.Registry.csproj @@ -1,12 +1,12 @@ - net5.0 + net7.0 - - + + diff --git a/src/PluginCore.Registry/Program.cs b/src/PluginCore.Registry/Program.cs index 34b1779c..15065527 100644 --- a/src/PluginCore.Registry/Program.cs +++ b/src/PluginCore.Registry/Program.cs @@ -5,31 +5,22 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -namespace PluginCore.Registry +namespace PluginCore.Registry; + +public class Program { - public class Program + public static void Main(string[] args) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + CreateHostBuilder(args).Build().Run(); } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); } diff --git a/src/PluginCore.Registry/ResponseModel/CommonResponseModel.cs b/src/PluginCore.Registry/ResponseModel/CommonResponseModel.cs index b5b99739..3e30b061 100644 --- a/src/PluginCore.Registry/ResponseModel/CommonResponseModel.cs +++ b/src/PluginCore.Registry/ResponseModel/CommonResponseModel.cs @@ -7,17 +7,16 @@ -namespace PluginCore.Registry.ResponseModel +namespace PluginCore.Registry.ResponseModel; + +/// +/// 其实没必要小写 属性名, ApiController 会自动转首字母小写 +/// +public class CommonResponseModel { - /// - /// 其实没必要小写 属性名, ApiController 会自动转首字母小写 - /// - public class CommonResponseModel - { - public int code { get; set; } + public int code { get; set; } - public string message { get; set; } + public string message { get; set; } - public object data { get; set; } - } + public object data { get; set; } } diff --git a/src/PluginCore.Registry/Startup.cs b/src/PluginCore.Registry/Startup.cs index e82deb23..04a8ea02 100644 --- a/src/PluginCore.Registry/Startup.cs +++ b/src/PluginCore.Registry/Startup.cs @@ -5,64 +5,54 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -namespace PluginCore.Registry +namespace PluginCore.Registry; + +public class Startup { - public class Startup + public Startup(IConfiguration configuration) { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } + Configuration = configuration; + } - public IConfiguration Configuration { get; } + public IConfiguration Configuration { get; } - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { - services.AddControllers(); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "PluginCore.Registry", Version = "v1" }); - }); - } + services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "PluginCore.Registry", Version = "v1" }); + }); + } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseSwagger(); - app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "PluginCore.Registry v1")); - } + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "PluginCore.Registry v1")); + } - app.UseHttpsRedirection(); + app.UseHttpsRedirection(); - app.UseRouting(); + app.UseRouting(); - app.UseAuthorization(); + app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); } } diff --git a/src/PluginCore.Registry/Utils/DateTimeUtil.cs b/src/PluginCore.Registry/Utils/DateTimeUtil.cs index 17c7003b..73c3290b 100644 --- a/src/PluginCore.Registry/Utils/DateTimeUtil.cs +++ b/src/PluginCore.Registry/Utils/DateTimeUtil.cs @@ -17,100 +17,99 @@ /// /// Unix时间戳:是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。(10 位数字) /// -namespace PluginCore.Registry.Utils +namespace PluginCore.Registry.Utils; + +public static class DateTimeUtil { - public static class DateTimeUtil - { - public static DateTime DateTime1970 = new DateTime(1970, 1, 1).ToLocalTime(); + public static DateTime DateTime1970 = new DateTime(1970, 1, 1).ToLocalTime(); - #region Unix 10位时间戳-总秒数 - /// - /// C# DateTime转换为Unix时间戳 - /// - public static long ToTimeStamp10(this DateTime dateTime) - { - // 相差秒数 - long timeStamp = (long)(dateTime.ToLocalTime() - DateTime1970).TotalSeconds; + #region Unix 10位时间戳-总秒数 + /// + /// C# DateTime转换为Unix时间戳 + /// + public static long ToTimeStamp10(this DateTime dateTime) + { + // 相差秒数 + long timeStamp = (long)(dateTime.ToLocalTime() - DateTime1970).TotalSeconds; - return timeStamp; - } + return timeStamp; + } - /// - /// C# DateTime转换为Unix时间戳 - /// - public static long ToTimeStamp10(this DateTime? dateTime) + /// + /// C# DateTime转换为Unix时间戳 + /// + public static long ToTimeStamp10(this DateTime? dateTime) + { + if (dateTime == null) { - if (dateTime == null) - { - return 0; - } - // 相差秒数 - long timeStamp = ToTimeStamp10((DateTime)dateTime); - - return timeStamp; + return 0; } + // 相差秒数 + long timeStamp = ToTimeStamp10((DateTime)dateTime); - /// - /// Unix时间戳转换为C# DateTime - /// - public static DateTime ToDateTime10(this long timeStamp10) - { - DateTime dateTime = DateTime1970.AddSeconds(timeStamp10).ToLocalTime(); + return timeStamp; + } - return dateTime; - } - #endregion + /// + /// Unix时间戳转换为C# DateTime + /// + public static DateTime ToDateTime10(this long timeStamp10) + { + DateTime dateTime = DateTime1970.AddSeconds(timeStamp10).ToLocalTime(); - #region JavaScript 13位时间戳-总毫秒数 - /// - /// C# DateTime转换为JavaScript时间戳 - /// - public static long ToTimeStamp13(this DateTime dateTime) - { - // 相差毫秒数 - long timeStamp = (long)(dateTime.ToLocalTime() - DateTime1970).TotalMilliseconds; + return dateTime; + } + #endregion - return timeStamp; - } + #region JavaScript 13位时间戳-总毫秒数 + /// + /// C# DateTime转换为JavaScript时间戳 + /// + public static long ToTimeStamp13(this DateTime dateTime) + { + // 相差毫秒数 + long timeStamp = (long)(dateTime.ToLocalTime() - DateTime1970).TotalMilliseconds; - /// - /// C# DateTime转换为JavaScript时间戳 - /// - public static long ToTimeStamp13(this DateTime? dateTime) + return timeStamp; + } + + /// + /// C# DateTime转换为JavaScript时间戳 + /// + public static long ToTimeStamp13(this DateTime? dateTime) + { + if (dateTime == null) { - if (dateTime == null) - { - return 0; - } - // 相差秒数 - long timeStamp = ToTimeStamp13((DateTime)dateTime); - - return timeStamp; + return 0; } + // 相差秒数 + long timeStamp = ToTimeStamp13((DateTime)dateTime); - /// - /// JavaScript时间戳转换为C# DateTime - /// - public static DateTime ToDateTime13(this long timeStamp13) - { - DateTime dateTime = DateTime1970.AddMilliseconds(timeStamp13).ToLocalTime(); + return timeStamp; + } - return dateTime; - } - #endregion + /// + /// JavaScript时间戳转换为C# DateTime + /// + public static DateTime ToDateTime13(this long timeStamp13) + { + DateTime dateTime = DateTime1970.AddMilliseconds(timeStamp13).ToLocalTime(); - #region 获取当前Unix时间戳 - public static long NowTimeStamp10() - { - return ToTimeStamp10(DateTime.Now); - } - #endregion + return dateTime; + } + #endregion - #region 获取当前JavaScript时间戳 - public static long NowTimeStamp13() - { - return ToTimeStamp13(DateTime.Now); - } - #endregion + #region 获取当前Unix时间戳 + public static long NowTimeStamp10() + { + return ToTimeStamp10(DateTime.Now); + } + #endregion + + #region 获取当前JavaScript时间戳 + public static long NowTimeStamp13() + { + return ToTimeStamp13(DateTime.Now); } + #endregion } diff --git a/src/PluginCore.Registry/WeatherForecast.cs b/src/PluginCore.Registry/WeatherForecast.cs index 1293a484..89a512f3 100644 --- a/src/PluginCore.Registry/WeatherForecast.cs +++ b/src/PluginCore.Registry/WeatherForecast.cs @@ -5,20 +5,17 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - using System; -namespace PluginCore.Registry +namespace PluginCore.Registry; + +public class WeatherForecast { - public class WeatherForecast - { - public DateTime Date { get; set; } + public DateTime Date { get; set; } - public int TemperatureC { get; set; } + public int TemperatureC { get; set; } - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - public string Summary { get; set; } - } + public string Summary { get; set; } } diff --git a/src/PluginCore/Config/PluginCoreConfig.cs b/src/PluginCore/Config/PluginCoreConfig.cs index 92e77bf3..d382a8fc 100644 --- a/src/PluginCore/Config/PluginCoreConfig.cs +++ b/src/PluginCore/Config/PluginCoreConfig.cs @@ -5,44 +5,37 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +namespace PluginCore.Config; - -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.Config +public class PluginCoreConfig { - public class PluginCoreConfig + public PluginCoreConfig() { - public PluginCoreConfig() - { - this.Admin = new AdminModel(); - } - - public AdminModel Admin { get; set; } + this.Admin = new AdminModel(); + } - /// - /// LocalEmbedded - /// LocalFolder - /// RemoteCDN - /// - public string FrontendMode { get; set; } = "LocalEmbedded"; + public AdminModel Admin { get; set; } - public string RemoteFrontend { get; set; } = "https://cdn.jsdelivr.net/gh/yiyungent/plugincore-admin-frontend@0.3.1/dist-cdn"; + /// + /// LocalEmbedded + /// LocalFolder + /// RemoteCDN + /// + public string FrontendMode { get; set; } = "LocalEmbedded"; - /// - /// 开启后: - /// 1. 页面的 Widget 会显示插件的详细插入点 - /// - public bool PluginWidgetDebug { get; set; } = false; + public string RemoteFrontend { get; set; } = "https://cdn.jsdelivr.net/gh/yiyungent/plugincore-admin-frontend@0.3.1/dist-cdn"; - public sealed class AdminModel - { - public string UserName { get; set; } = "admin"; + /// + /// 开启后: + /// 1. 页面的 Widget 会显示插件的详细插入点 + /// + public bool PluginWidgetDebug { get; set; } = false; - public string Password { get; set; } = "ABC12345"; - } + public sealed class AdminModel + { + public string UserName { get; set; } = "admin"; + public string Password { get; set; } = "ABC12345"; } + } diff --git a/src/PluginCore/Config/PluginCoreConfigFactory.cs b/src/PluginCore/Config/PluginCoreConfigFactory.cs index fe085353..14e6cc6a 100644 --- a/src/PluginCore/Config/PluginCoreConfigFactory.cs +++ b/src/PluginCore/Config/PluginCoreConfigFactory.cs @@ -5,64 +5,59 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Text; using System.Text.Json; -using PluginCore.Models; -namespace PluginCore.Config +namespace PluginCore.Config; + +public class PluginCoreConfigFactory { - public class PluginCoreConfigFactory - { - private const string FileName = "PluginCore.Config.json"; + private const string FileName = "PluginCore.Config.json"; - #region 即时读取 - public static PluginCoreConfig Create() + #region 即时读取 + public static PluginCoreConfig? Create() + { + PluginCoreConfig? pluginCoreConfig = new PluginCoreConfig(); + string pluginCoreConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", FileName); + string pluginCoreConfigJsonStr = string.Empty; + if (!File.Exists(pluginCoreConfigFilePath)) { - PluginCoreConfig pluginCoreConfig = new PluginCoreConfig(); - string pluginCoreConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", FileName); - string pluginCoreConfigJsonStr = string.Empty; - if (!File.Exists(pluginCoreConfigFilePath)) - { - // 不存在, 则新建初始化默认 - pluginCoreConfigJsonStr = JsonSerializer.Serialize(pluginCoreConfig); - File.WriteAllText(pluginCoreConfigFilePath, pluginCoreConfigJsonStr, Encoding.UTF8); - - return pluginCoreConfig; - } - - pluginCoreConfigJsonStr = File.ReadAllText(pluginCoreConfigFilePath, Encoding.UTF8); - JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); - jsonSerializerOptions.PropertyNameCaseInsensitive = true; - pluginCoreConfig = JsonSerializer.Deserialize(pluginCoreConfigJsonStr, jsonSerializerOptions); + // 不存在, 则新建初始化默认 + pluginCoreConfigJsonStr = JsonSerializer.Serialize(pluginCoreConfig); + File.WriteAllText(pluginCoreConfigFilePath, pluginCoreConfigJsonStr, Encoding.UTF8); return pluginCoreConfig; } - #endregion - - #region 保存 - public static void Save(PluginCoreConfig pluginCoreConfig) - { - if (pluginCoreConfig == null) - { - throw new ArgumentNullException(nameof(pluginCoreConfig)); - } - try - { - string pluginCoreConfigJsonStr = JsonSerializer.Serialize(pluginCoreConfig); - string pluginCoreConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", FileName); - File.WriteAllText(pluginCoreConfigFilePath, pluginCoreConfigJsonStr, Encoding.UTF8); - } - catch (Exception ex) - { } - } - #endregion + pluginCoreConfigJsonStr = File.ReadAllText(pluginCoreConfigFilePath, Encoding.UTF8); + JsonSerializerOptions? jsonSerializerOptions = new JsonSerializerOptions() { + PropertyNameCaseInsensitive = true + }; + pluginCoreConfig = JsonSerializer.Deserialize(pluginCoreConfigJsonStr, jsonSerializerOptions); + return pluginCoreConfig; + } + #endregion + #region 保存 + public static void Save(PluginCoreConfig pluginCoreConfig) + { + if (pluginCoreConfig == null) + { + throw new ArgumentNullException(nameof(pluginCoreConfig)); + } + try + { + string pluginCoreConfigJsonStr = JsonSerializer.Serialize(pluginCoreConfig); + string pluginCoreConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", FileName); + File.WriteAllText(pluginCoreConfigFilePath, pluginCoreConfigJsonStr, Encoding.UTF8); + } + catch + { + // ignored + } } + #endregion } diff --git a/src/PluginCore/Implements/CollectibleAssemblyLoadContext.cs b/src/PluginCore/Implements/CollectibleAssemblyLoadContext.cs new file mode 100644 index 00000000..6ee21097 --- /dev/null +++ b/src/PluginCore/Implements/CollectibleAssemblyLoadContext.cs @@ -0,0 +1,42 @@ +//=================================================== +// License: Apache-2.0 +// Contributors: yiyungent@gmail.com +// Project: https://moeci.com/PluginCore +// GitHub: https://github.com/yiyungent/PluginCore +//=================================================== + + + +using PluginCore.Interfaces; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; + +namespace PluginCore.Implements; + +/// +/// 一个可回收的程序集加载上下文 +/// 在整个插件加载上下文的设计上,每个插件都使用一个单独的CollectibleAssemblyLoadContext来加载,所有插件的CollectibleAssemblyLoadContext都放在一个PluginsLoadContext对象中 +/// +public class CollectibleAssemblyLoadContext : AssemblyLoadContext, IPluginContext, ICollectibleAssemblyLoadContext +{ + public string PluginId + { + get + { + return this.Name ?? ""; + } + } + + public CollectibleAssemblyLoadContext(string? name) + : base(isCollectible: true, name: name) + { + } + + protected override Assembly? Load(AssemblyName name) + { + return null; + } +} diff --git a/src/PluginCore/Implements/LazyPluginLoadContext.cs b/src/PluginCore/Implements/LazyPluginLoadContext.cs new file mode 100644 index 00000000..16f17ea1 --- /dev/null +++ b/src/PluginCore/Implements/LazyPluginLoadContext.cs @@ -0,0 +1,76 @@ +//=================================================== +// License: Apache-2.0 +// Contributors: yiyungent@gmail.com +// Project: https://moeci.com/PluginCore +// GitHub: https://github.com/yiyungent/PluginCore +//=================================================== + +using PluginCore.Interfaces; +using System; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using System.Linq; + +namespace PluginCore.Implements; + +/// +/// 修改自下方 +/// https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support +/// 此方法依赖于 插件项目生成的 HelloWorldPlugin.deps.json 与 runtimes 文件夹, +/// 虽然官方文档没写, 但最好还带上 HelloWorldPlugin.runtimeconfig.json +/// 插件项目 .csproj 其它注意, 看文档 +/// +public class LazyPluginLoadContext : CollectibleAssemblyLoadContext, IPluginContext +{ + private AssemblyDependencyResolver _resolver; + + /// + /// 加了一个可回收 + /// + /// + public LazyPluginLoadContext(string pluginId, string pluginMainDllFilePath) : base(name: pluginId) + { + _resolver = new AssemblyDependencyResolver(pluginMainDllFilePath); + } + + protected override Assembly? Load(AssemblyName assemblyName) + { + // 1. 先尝试 从本插件文件夹中搜索 + string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); + if (assemblyPath != null) + { + //return LoadFromAssemblyPath(assemblyPath); + using (var fs = new FileStream(assemblyPath, FileMode.Open)) + { + // 使用此方法, 就不会导致dll被锁定 + // 锁定dll 会导致: 1. 无法通过复制粘贴替换 更新 2. 无法删除 + return LoadFromStream(fs); + } + } + // 2. 再尝试 从其他启用的插件文件夹中搜索 + // 实测: 可在下方搜索, 主程序包括的 assemblyName 与 启用插件加载的 assemblyName 都会位于其中 + var assList = AppDomain.CurrentDomain.GetAssemblies(); + var temp = assList.FirstOrDefault(m => m.GetName().FullName == assemblyName.FullName); + if (temp != null) + { + return temp; + } + + // 3. 最后搜索不到, 返回 null, 即代表使用主程序提供, 如果最后几次都为 null, 则会报错 + // 当启用本插件/触碰到本插件中的一些类型时, 而当主程序中没有提供相关的此 assemblyName 时, 也会触发此方法 来尝试加载 + + return null; + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (libraryPath != null) + { + return LoadUnmanagedDllFromPath(libraryPath); + } + + return IntPtr.Zero; + } +} diff --git a/src/PluginCore/Implements/PluginContextManager.cs b/src/PluginCore/Implements/PluginContextManager.cs new file mode 100644 index 00000000..415ada0b --- /dev/null +++ b/src/PluginCore/Implements/PluginContextManager.cs @@ -0,0 +1,97 @@ +//=================================================== +// License: Apache-2.0 +// Contributors: yiyungent@gmail.com +// Project: https://moeci.com/PluginCore +// GitHub: https://github.com/yiyungent/PluginCore +//=================================================== + +using PluginCore.Interfaces; +using System.Collections.Generic; +using System.Linq; + +namespace PluginCore.Implements; + +/// +/// 启用插件时加载进来, 禁用插件时移除释放 +/// 只有已启用的插件才有上下文 +/// https://www.cnblogs.com/lwqlun/p/11395828.html +/// 1.当加载插件的时候,我们需要将当前插件的程序集加载上下文放到_pluginContexts字典中。字典的key是插件的名称,字典的value是插件的程序集加载上下文。 +/// 2.当移除一个插件的时候,我们需要使用Unload方法,来释放当前的程序集加载上下文。 +/// +public class PluginContextManager : IPluginContextManager +{ + #region Fields + + private static int _newCount; + + //private Dictionary + // _pluginContexts; + + private Dictionary _pluginContexts + { + get + { + return PluginContextStore.PluginContexts; + } + } + + #endregion + + #region Ctor + public PluginContextManager() + { + //_pluginContexts = new Dictionary(); + + #region 计数 + _newCount++; + if (_newCount > 1) + { +#if DEBUG + Utils.LogUtil.Error($"警告: {nameof(PluginContextManager)} 被 new {_newCount} 次"); +#endif + } + #endregion + } + #endregion + + #region Methods + + public List All() + { + return _pluginContexts.Select(p => p.Value).ToList(); + } + + public bool Any(string pluginId) + { + return _pluginContexts.ContainsKey(pluginId); + } + + public void Remove(string pluginId) + { + if (_pluginContexts.ContainsKey(pluginId)) + { + _pluginContexts[pluginId].Unload(); + _pluginContexts.Remove(pluginId); + } + } + + public IPluginContext Get(string pluginId) + { + return _pluginContexts[pluginId]; + } + + public void Add(string pluginId, IPluginContext context) + { + _pluginContexts.Add(pluginId, context); + } + #endregion + +} + +/// +/// fixed: 由于 单例失败, 因此临时解决方案 +/// +public static class PluginContextStore +{ + public static Dictionary PluginContexts = new Dictionary(); +} diff --git a/src/PluginCore/Implements/PluginContextPack.cs b/src/PluginCore/Implements/PluginContextPack.cs new file mode 100644 index 00000000..8bfe8452 --- /dev/null +++ b/src/PluginCore/Implements/PluginContextPack.cs @@ -0,0 +1,44 @@ +//=================================================== +// License: Apache-2.0 +// Contributors: yiyungent@gmail.com +// Project: https://moeci.com/PluginCore +// GitHub: https://github.com/yiyungent/PluginCore +//=================================================== + +using PluginCore.Interfaces; +using System.IO; +using System.Reflection; +using PluginCore.IPlugins.Infrastructure; + +namespace PluginCore.Implements; + +public class PluginContextPack : IPluginContextPack +{ + public IPluginContext Pack(string pluginId) + { + #region 加载插件主dll + + // 插件的主dll, 不包括插件项目引用的dll + string pluginMainDllFilePath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId, $"{pluginId}.dll"); + // 此插件的 加载上下文 + var context = new PluginLoadContext(pluginId, pluginMainDllFilePath); + + // 微软文档推荐 LoadFromAssemblyName + Assembly pluginMainAssembly = context.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(pluginMainDllFilePath))); + + #region 第2种方法: 未在这种情况下测试 + //using (var fs = new FileStream(pluginMainDllFilePath, FileMode.Open)) + //{ + // // 使用此方法, 就不会导致dll被锁定 + // pluginMainAssembly = context.LoadFromStream(fs); + + // // 加载其中的控制器 + // _pluginControllerManager.AddControllers(pluginMainAssembly); + //} + #endregion + + #endregion + + return context; + } +} diff --git a/src/PluginCore/Implements/PluginContextPackV1.cs b/src/PluginCore/Implements/PluginContextPackV1.cs new file mode 100644 index 00000000..4b976964 --- /dev/null +++ b/src/PluginCore/Implements/PluginContextPackV1.cs @@ -0,0 +1,100 @@ +//=================================================== +// License: Apache-2.0 +// Contributors: yiyungent@gmail.com +// Project: https://moeci.com/PluginCore +// GitHub: https://github.com/yiyungent/PluginCore +//=================================================== + +using PluginCore.Interfaces; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using PluginCore.IPlugins.Infrastructure; + +namespace PluginCore.Implements; + +public class PluginContextPackV1 : IPluginContextPack +{ + public IList SkipDlls { get; set; } + + public IPluginContext Pack(string pluginId) + { + SkipDlls = new List(); + + #region 跳过1: 程序目录下 以单独dll 出现 + // 获取主程序 已经存在的(不许在再加载的 dll) + string basePath = AppContext.BaseDirectory; + //Console.WriteLine($"PluginCore.PluginManager: basePath: {basePath}"); + // { "Core.dll", "Domain.dll", "Framework.dll", "Services.dll", "Repositories.dll", "PluginCore.dll", ... } + SkipDlls = new DirectoryInfo(basePath).GetFiles("*.dll").Select(m => m.Name).ToList(); + #endregion + + #region 跳过2: 打包进入1个dll 或 打包进 1个exe + // 注意: 用户可能将 dll 打包在 一个dll中, 或打包进 exe, 因此 通过此方式 确保跳过 + // 主程序所有 位于 AssemblyLoadContext.Default + List skipAssembliesName = AssemblyLoadContext.Default.Assemblies + .Select(m => m.GetName()) + .Select(m => m.Name).ToList(); + foreach (var name in skipAssembliesName) + { + this.SkipDlls.Add($"{name}.dll"); + } + #endregion + + + // 此插件的 加载上下文 + var context = new CollectibleAssemblyLoadContext(pluginId); + + // TODO:未测试 加载插件引用的dll: 方法二: + //AssemblyName[] referenceAssemblyNames = pluginMainAssembly.GetReferencedAssemblies(); + //foreach (var assemblyName in referenceAssemblyNames) + //{ + // context.LoadFromAssemblyName(assemblyName); + //} + + // 跳过不需要加载的 dll, eg: ASP.NET Core Shared Framework, 主程序中已有dll + string[] skipDlls = SkipDlls.ToArray(); //new string[] { "Core.dll", "Domain.dll", "Framework.dll", "Services.dll", "Repositories.dll", "PluginCore.dll" }; + + // 注意: 先加载插件引用的dll, 因为可能在插件主dll的Controller中立即使用了引用的dll + + #region 加载插件引用的dll + // 加载插件引用的dll + // eg: xxx/Plugins/HelloWorld + string pluginDirPath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); + var pluginDir = new DirectoryInfo(pluginDirPath); + // 插件引用的所有dll (排除 主dll 和 skipDlls ) + // 注意: 主程序中已有dll 必须跳过, 应为这些默认Context中已经加载, 而如果插件Context再次加载, 则认为这两个是不同Assembly, 导致其中的Type转换失败 + // 这里简单来说,意思就是当在一个自定义LoadContext中加载程序集的时候,如果找不到这个程序集,程序会自动去默认LoadContext中查找,如果默认LoadContext中都找不到,就会返回null。 + // 这里我突然想到会不会是因为DemoPlugin1、DemoPlugin2以及主站点的AssemblyLoadContext都加载了Mystique.Core.dll程序集的缘故,虽然他们加载的是同一个程序集,但是因为LoadContext不同,所以系统认为它是2个程序集。 + // 参考: https://www.cnblogs.com/lwqlun/p/12930713.html + var allReferenceFileInfos = pluginDir.GetFiles("*.dll").Where(p => + p.Name != $"{pluginId}.dll" + && + !skipDlls.Contains(p.Name)); + foreach (FileInfo file in allReferenceFileInfos) + { + using (var sr = new StreamReader(file.OpenRead())) + { + context.LoadFromStream(sr.BaseStream); + } + } + #endregion + + #region 加载插件主dll + // 插件的主dll, 不包括插件项目引用的dll + string pluginMainDllFilePath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId, $"{pluginId}.dll"); + Assembly pluginMainAssembly; + using (var fs = new FileStream(pluginMainDllFilePath, FileMode.Open)) + { + // 使用此方法, 就不会导致dll被锁定 + pluginMainAssembly = context.LoadFromStream(fs); + } + #endregion + + + return context; + } +} diff --git a/src/PluginCore/Implements/PluginFinder.cs b/src/PluginCore/Implements/PluginFinder.cs new file mode 100644 index 00000000..1fe959d3 --- /dev/null +++ b/src/PluginCore/Implements/PluginFinder.cs @@ -0,0 +1,294 @@ +//=================================================== +// License: Apache-2.0 +// Contributors: yiyungent@gmail.com +// Project: https://moeci.com/PluginCore +// GitHub: https://github.com/yiyungent/PluginCore +//=================================================== + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using PluginCore.Interfaces; +using PluginCore.IPlugins; +using PluginCore.IPlugins.Interfaces; + +namespace PluginCore.Implements; + +/// +/// 插件发现者: 找启用的插件(1.plugin.config.json中启用 2. 有插件上下文) +/// TODO: 其实是没必要再效验plugin.config.json的,因为只有启用的插件才有上下文, 为了保险,暂时这么做 +/// 注意: 这意味着一个启用的插件需同时满足这两个条件 +/// +public class PluginFinder : IPluginFinder +{ + #region Fields + + /// + /// 用来解析插件构造函数需要的服务 + /// + private readonly IServiceProvider _serviceProvider; + + /// + /// TODO: 未使用 + /// Key: 实现了 IPlugin 的插件类 + /// Value: 此插件类 允许的 行为(众多实现了 IPlugin 的钩子接口) + /// + private static ConcurrentDictionary> _pluginAllowedBehavior; + + #endregion + + public IPluginContextManager PluginContextManager { get; set; } + + #region Ctor + public PluginFinder(IPluginContextManager pluginContextManager, IServiceProvider serviceProvider) + { + this.PluginContextManager = pluginContextManager; + _serviceProvider = serviceProvider; + } + + static PluginFinder() + { + // TODO: 初始化: 默认为信任模式: 插件允许所有行为 + _pluginAllowedBehavior = new ConcurrentDictionary>(); + } + #endregion + + + #region 允许激活行为的 启用插件 + /// + /// TODO: 实现了指定接口或类型 的启用插件 并且 允许激活此行为(接口) + /// + /// TODO: 考虑后,还是没办法限制住插件恶意行为, 因为无法限制插件 修改配置文件 + /// + /// + /// + public IEnumerable ActivatedPlugins() + where TPlugin : IPlugin + { + var enablePlugins = EnablePlugins(); + foreach (var plugin in enablePlugins) + { + // TODO: 检查是否允许 此插件行为 + yield return plugin; + } + } + #endregion + + #region 实现了指定接口或类型 的启用插件 + /// + /// 实现了指定接口或类型 的启用插件 + /// + /// 可以是一个接口,一个抽象类,一个普通实现类, 只要实现了 即可 + /// + public IEnumerable<(TPlugin PluginInstance, string PluginId)> EnablePluginsFull() where TPlugin : IPlugin // BasePlugin + { + // TODO: 目前这里还有问题, 不应该写为 BasePlugin, 不利于扩展, 不利于插件开发者自己实现 Install , Uninstall等 + + // 1.所有启用的插件 PluginId + var pluginConfigModel = PluginConfigModelFactory.Create(); + IList enablePluginIds = pluginConfigModel.EnabledPlugins; + foreach (var pluginId in enablePluginIds) + { + if (this.PluginContextManager.Any(pluginId)) + { + // 2.找到插件对应的Context + var context = this.PluginContextManager.Get(pluginId); + // 3.找插件 主 Assembly + // Assembly.FullName: HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + Assembly pluginMainAssembly = context.Assemblies.FirstOrDefault(m => m.FullName.StartsWith($"{pluginId}, Version=")); + if (pluginMainAssembly == null) + { + continue; + } + // 4.从插件主Assembly中 找实现了 TPlugin 接口的 Type, 若有多个,只要一个 + Type pluginType = pluginMainAssembly.ExportedTypes.FirstOrDefault(m => + (m.BaseType == typeof(TPlugin) || m.GetInterfaces().Contains(typeof(TPlugin))) + && + !m.IsInterface + && + !m.IsAbstract + ); + if (pluginType == null) + { + continue; + } + // 5.实例化插件 Type + //(TPlugin)Activator.CreateInstance(pluginType,); + //try to resolve plugin as unregistered service + //object instance = EngineContext.Current.ResolveUnregistered(pluginType); + object instance = ResolveUnregistered(pluginType); + //try to get typed instance + TPlugin typedInstance = (TPlugin)instance; + if (typedInstance == null) + { + continue; + } + + yield return (PluginInstance: typedInstance, PluginId: pluginId); + } + } + + } + + /// + /// 实现了指定接口或类型 的启用插件 + /// + /// 可以是一个接口,一个抽象类,一个普通实现类, 只要实现了 即可 + /// + public IEnumerable EnablePlugins() where TPlugin : IPlugin // BasePlugin + { + return EnablePluginsFull().Select(m => m.PluginInstance); + } + #endregion + + #region 所有启用的插件 + /// + /// 所有启用的插件 + /// + /// + public IEnumerable EnablePlugins() + { + return EnablePluginsFull().Select(m => m.PluginInstance); + } + + /// + /// 所有启用的插件 + /// + /// + public IEnumerable<(IPlugin PluginInstance, string PluginId)> EnablePluginsFull() + { + return EnablePluginsFull(); + } + + /// + /// 所有启用的插件 的 PluginId + /// + /// + public IEnumerable EnablePluginIds() where TPlugin : IPlugin // BasePlugin + { + // 1.所有启用的插件 PluginId + var pluginConfigModel = PluginConfigModelFactory.Create(); + IList enablePluginIds = pluginConfigModel.EnabledPlugins; + foreach (var pluginId in enablePluginIds) + { + if (this.PluginContextManager.Any(pluginId)) + { + yield return pluginId; + } + } + } + + /// + /// 所有启用的插件 的 PluginId + /// + /// + public IEnumerable EnablePluginIds() + { + return EnablePluginIds(); + } + #endregion + + #region 获取指定 pluginId 的启用插件 + /// + /// 获取指定 pluginId 的启用插件 + /// + /// + /// 1.插件未启用返回null, 2.找不到此插件上下文返回null 3.找不到插件主dll返回null 4.插件主dll中找不到实现了IPlugin的Type返回null, 5.无法实例化插件返回null + public IPlugin Plugin(string pluginId) + { + // 1.所有启用的插件 PluginId + var pluginConfigModel = PluginConfigModelFactory.Create(); + IList enablePluginIds = pluginConfigModel.EnabledPlugins; + // 插件未启用返回null + if (!enablePluginIds.Contains(pluginId)) + { + return null; + } + + // 找不到此插件上下文返回null + if (!this.PluginContextManager.Any(pluginId)) + { + return null; + } + + // 2.找到插件对应的Context + var context = this.PluginContextManager.Get(pluginId); + // 3.找插件 主 Assembly + // Assembly.FullName: HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + Assembly pluginMainAssembly = context.Assemblies.Where(m => m.FullName.StartsWith($"{pluginId}, Version=")).FirstOrDefault(); + // 找不到插件主dll返回null + if (pluginMainAssembly == null) + { + return null; + } + // 4.从插件主Assembly中 找实现了 TPlugin 接口的 Type, 若有多个,只要一个 + Type pluginType = pluginMainAssembly.ExportedTypes.FirstOrDefault(m => + (m.BaseType == typeof(IPlugin) || m.GetInterfaces().Contains(typeof(IPlugin))) + && + !m.IsInterface + && + !m.IsAbstract + ); + // 插件主dll中找不到实现了IPlugin的Type返回null + if (pluginType == null) + { + return null; + } + // 5.实例化插件 Type + //(TPlugin)Activator.CreateInstance(pluginType,); + //try to resolve plugin as unregistered service + //object instance = EngineContext.Current.ResolveUnregistered(pluginType); + object instance = ResolveUnregistered(pluginType); + //try to get typed instance + IPlugin typedInstance = (IPlugin)instance; + // 无法实例化插件返回null + if (typedInstance == null) + { + return null; + } + + return typedInstance; + } + #endregion + + #region 获取未IOC注册的类型实例 + /// + /// 获取未IOC注册的类型实例 + /// 此类型的构造函数可以依赖注入, 将通过ASP.NET Core 自带依赖注入系统进行注入 + /// + /// + /// + protected virtual object ResolveUnregistered(Type type) + { + Exception innerException = null; + foreach (var constructor in type.GetConstructors()) + { + try + { + //try to resolve constructor parameters + var parameters = constructor.GetParameters().Select(parameter => + { + //var service = Resolve(parameter.ParameterType); + var service = _serviceProvider.GetService(parameter.ParameterType); + if (service == null) + throw new Exception("Unknown dependency"); + return service; + }); + + //all is ok, so create instance + return Activator.CreateInstance(type, parameters.ToArray()); + } + catch (Exception ex) + { + innerException = ex; + } + } + + throw new Exception("No constructor was found that had all the dependencies satisfied.", innerException); + + } + #endregion + +} diff --git a/src/PluginCore/Implements/PluginLoadContext.cs b/src/PluginCore/Implements/PluginLoadContext.cs new file mode 100644 index 00000000..93007212 --- /dev/null +++ b/src/PluginCore/Implements/PluginLoadContext.cs @@ -0,0 +1,20 @@ +//=================================================== +// License: Apache-2.0 +// Contributors: yiyungent@gmail.com +// Project: https://moeci.com/PluginCore +// GitHub: https://github.com/yiyungent/PluginCore +//=================================================== + +using PluginCore.Interfaces; + +namespace PluginCore.Implements; + +/// +/// LazyPluginLoadContext +/// +public class PluginLoadContext : LazyPluginLoadContext, IPluginContext +{ + public PluginLoadContext(string pluginId, string pluginMainDllFilePath) : base(pluginId, pluginMainDllFilePath) + { + } +} diff --git a/src/PluginCore/Implements/PluginManager.cs b/src/PluginCore/Implements/PluginManager.cs new file mode 100644 index 00000000..b86c5e09 --- /dev/null +++ b/src/PluginCore/Implements/PluginManager.cs @@ -0,0 +1,47 @@ +//=================================================== +// License: Apache-2.0 +// Contributors: yiyungent@gmail.com +// Project: https://moeci.com/PluginCore +// GitHub: https://github.com/yiyungent/PluginCore +//=================================================== + + +using PluginCore.Interfaces; +using PluginCore.IPlugins.Interfaces; + +namespace PluginCore.Implements; + +/// +/// 一个插件的所有dll由 一个 管理 +/// 记录管理了所有 插件的 +/// 是对 的封装, 使其更好管理插件加载释放的行为 +/// +public class PluginManager : IPluginManager +{ + public IPluginContextManager PluginContextManager { get; set; } + + public IPluginContextPack PluginContextPack { get; set; } + + public PluginManager(IPluginContextManager pluginContextManager, IPluginContextPack pluginContextPack) + { + this.PluginContextManager = pluginContextManager; + this.PluginContextPack = pluginContextPack; + } + + /// + /// 加载插件程序集 + /// + /// + public void LoadPlugin(string pluginId) + { + IPluginContext context = this.PluginContextPack.Pack(pluginId); + + // 这个插件加载上下文 放入 集合中 + this.PluginContextManager.Add(pluginId, context); + } + + public void UnloadPlugin(string pluginId) + { + this.PluginContextManager.Remove(pluginId); + } +} diff --git a/src/PluginCore/Implements/PositivePluginLoadContext.cs b/src/PluginCore/Implements/PositivePluginLoadContext.cs new file mode 100644 index 00000000..82f8a266 --- /dev/null +++ b/src/PluginCore/Implements/PositivePluginLoadContext.cs @@ -0,0 +1,105 @@ +using System.Reflection.Metadata; +//=================================================== +// License: Apache-2.0 +// Contributors: yiyungent@gmail.com +// Project: https://moeci.com/PluginCore +// GitHub: https://github.com/yiyungent/PluginCore +//=================================================== + +using PluginCore.Interfaces; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using System.Linq; + +namespace PluginCore.Implements; + +/// +/// 修改自下方 +/// https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support +/// 此方法依赖于 插件项目生成的 HelloWorldPlugin.deps.json 与 runtimes 文件夹, +/// 虽然官方文档没写, 但最好还带上 HelloWorldPlugin.runtimeconfig.json +/// 插件项目 .csproj 其它注意, 看文档 +/// +public class PositivePluginLoadContext : CollectibleAssemblyLoadContext, IPluginContext +{ + private AssemblyDependencyResolver _resolver; + + /// + /// 即 插件的 Entrypoint Assembly + /// + public AssemblyName MainAssemblyName{ get; protected set; } + + public IEnumerable ReferencedAssemblyNames { get; protected set; } + + /// + /// 加了一个可回收 + /// + /// + public PositivePluginLoadContext(string pluginId, string pluginMainDllFilePath) : base(name: pluginId) + { + _resolver = new AssemblyDependencyResolver(pluginMainDllFilePath); + + // 主动加载 存在此插件文件夹的 依赖项 + using (var mainFs = new FileStream(pluginMainDllFilePath, FileMode.Open)) + { + // 使用此方法, 就不会导致dll被锁定 + // 锁定dll 会导致: 1. 无法通过复制粘贴替换 更新 2. 无法删除 + Assembly mainAssembly = LoadFromStream(mainFs); + this.MainAssemblyName = AssemblyName.GetAssemblyName(pluginMainDllFilePath); + // this.MainAssemblyName = mainAssembly.GetName(); + AssemblyName[] referencedAssemblies = mainAssembly.GetReferencedAssemblies(); + this.ReferencedAssemblyNames = referencedAssemblies.AsEnumerable(); + + // TODO: + AssemblyFile assemblyFile = new AssemblyFile(); + + foreach (var referencedAssembly in referencedAssemblies) + { + string assemblyPath = _resolver.ResolveAssemblyToPath(referencedAssembly); + if (assemblyPath != null) + { + //return LoadFromAssemblyPath(assemblyPath); + using (var fs = new FileStream(assemblyPath, FileMode.Open)) + { + // 使用此方法, 就不会导致dll被锁定 + // 锁定dll 会导致: 1. 无法通过复制粘贴替换 更新 2. 无法删除 + LoadFromStream(fs); + } + } + } + } + } + + protected override Assembly? Load(AssemblyName assemblyName) + { + // 1. 先尝试 从本插件文件夹中搜索 (无需, 因为前面已先主动加载, 再次被调用此方法只能是其它情况: 有不存在于此插件文件夹下的依赖项 被需要) + + // 2. 再尝试 从其他启用的插件文件夹中搜索 + // 实测: 可在下方搜索, 主程序包括的 assemblyName 与 启用插件加载的 assemblyName 都会位于其中 + var assList = AppDomain.CurrentDomain.GetAssemblies(); + var temp = assList.FirstOrDefault(m => m.GetName().FullName == assemblyName.FullName); + if (temp != null) + { + return temp; + } + + // 3. 最后搜索不到, 返回 null, 即代表使用主程序提供, 如果最后几次都为 null, 则会报错 + // 当启用本插件/触碰到本插件中的一些类型时, 而当主程序中没有提供相关的此 assemblyName 时, 也会触发此方法 来尝试加载 + + return null; + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (libraryPath != null) + { + return LoadUnmanagedDllFromPath(libraryPath); + } + + return IntPtr.Zero; + } +} diff --git a/src/PluginCore/Infrastructure/NupkgService.cs b/src/PluginCore/Infrastructure/NupkgService.cs index 19ec1e0c..260b2485 100644 --- a/src/PluginCore/Infrastructure/NupkgService.cs +++ b/src/PluginCore/Infrastructure/NupkgService.cs @@ -5,142 +5,137 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Linq; -using System.Text; -namespace PluginCore.Infrastructure +namespace PluginCore.Infrastructure; + +public class NupkgService { - public class NupkgService + /// + /// 将 xxx.nupkg 根据当前环境 解压成 对应 插件目录结构 + /// xxx.zip 形式的插件安装包: 直接解压即可 + /// + /// + /// + /// + /// + public static bool DecomparessFile(string sourceFile, string destinationDirectory) { - /// - /// 将 xxx.nupkg 根据当前环境 解压成 对应 插件目录结构 - /// xxx.zip 形式的插件安装包: 直接解压即可 - /// - /// - /// - /// - /// - public static bool DecomparessFile(string sourceFile, string destinationDirectory = null) + bool isDecomparessSuccess = false; + + if (!File.Exists(sourceFile)) + { + throw new FileNotFoundException("要解压的文件不存在", sourceFile); + } + + if (string.IsNullOrWhiteSpace(destinationDirectory)) + { + destinationDirectory = Path.GetDirectoryName(sourceFile); + } + + try { - bool isDecomparessSuccess = false; + // 注意: 之前重命名过: Guid.zip + destinationDirectory = destinationDirectory.Replace(".zip", ""); - if (!File.Exists(sourceFile)) + isDecomparessSuccess = Utils.ZipHelper.FastDecomparessFile(sourceFile, destinationDirectory); + if (!isDecomparessSuccess) { - throw new FileNotFoundException("要解压的文件不存在", sourceFile); + return isDecomparessSuccess; } + // 到这里已经解压完成, 开始解析 + + var netVersion = Utils.RuntimeUtil.RuntimeNetVersion; - if (string.IsNullOrWhiteSpace(destinationDirectory)) + // netcoreapp3.1 net5.0 + // dll 文件等 + string libDirPath = Path.Combine(destinationDirectory, "lib"); + string netFolderName = string.Empty; + if (netVersion >= new Version("3.1") && Directory.Exists(Path.Combine(libDirPath, $"netcoreapp{netVersion.Major}.{netVersion.Minor}"))) { - destinationDirectory = Path.GetDirectoryName(sourceFile); + netFolderName = $"netcoreapp{netVersion.Major}.{netVersion.Minor}"; } - - try + else if (netVersion.Major >= 5 && Directory.Exists(Path.Combine(libDirPath, $"net{netVersion.Major}.{netVersion.Minor}"))) { - // 注意: 之前重命名过: Guid.zip - destinationDirectory = destinationDirectory.Replace(".zip", ""); - - isDecomparessSuccess = Utils.ZipHelper.FastDecomparessFile(sourceFile, destinationDirectory); - if (!isDecomparessSuccess) - { - return isDecomparessSuccess; - } - // 到这里已经解压完成, 开始解析 - - var netVersion = Utils.RuntimeUtil.RuntimeNetVersion; - - // netcoreapp3.1 net5.0 - // dll 文件等 - string libDirPath = Path.Combine(destinationDirectory, "lib"); - string netFolderName = string.Empty; - if (netVersion >= new Version("3.1") && Directory.Exists(Path.Combine(libDirPath, $"netcoreapp{netVersion.Major}.{netVersion.Minor}"))) - { - netFolderName = $"netcoreapp{netVersion.Major}.{netVersion.Minor}"; - } - else if (netVersion.Major >= 5 && Directory.Exists(Path.Combine(libDirPath, $"net{netVersion.Major}.{netVersion.Minor}"))) - { - netFolderName = $"net{netVersion.Major}.{netVersion.Minor}"; - } - else if (Directory.Exists(Path.Combine(libDirPath, "netstandard2.0"))) - { - netFolderName = $"netstandard2.0"; - } - else if (Directory.Exists(Path.Combine(libDirPath, "netstandard2.1"))) - { - netFolderName = $"netstandard2.1"; - } - else - { - throw new Exception("暂不支持 .NET Core 3.1 以下版本, 也不支持 .NET Framework "); - } - // 1. ./lib/netcoreapp3.1 - string libNetDirPath = Path.Combine(libDirPath, netFolderName); - // 移动到插件根目录 - //Directory.Move(libNetDirPath, destinationDirectory); // 错误: 这样移动会导致 包含根目录文件夹名 - Utils.FileUtil.CopyFolder(libNetDirPath, destinationDirectory); - // 只需要这些, 其他删除 - Directory.Delete(libDirPath, true); - - Utils.LogUtil.Info($"加载 nupkg 中 dll: {sourceFile} ; {libNetDirPath}"); - - // 2. 普通文件: 例如 wwwroot - // 2.1 ./content - string contentDirPath = Path.Combine(destinationDirectory, "content"); - bool isExistContentDir = Directory.Exists(contentDirPath); - bool isFinishedContent = false; - if (isExistContentDir) + netFolderName = $"net{netVersion.Major}.{netVersion.Minor}"; + } + else if (Directory.Exists(Path.Combine(libDirPath, "netstandard2.0"))) + { + netFolderName = $"netstandard2.0"; + } + else if (Directory.Exists(Path.Combine(libDirPath, "netstandard2.1"))) + { + netFolderName = $"netstandard2.1"; + } + else + { + throw new Exception("暂不支持 .NET Core 3.1 以下版本, 也不支持 .NET Framework "); + } + // 1. ./lib/netcoreapp3.1 + string libNetDirPath = Path.Combine(libDirPath, netFolderName); + // 移动到插件根目录 + //Directory.Move(libNetDirPath, destinationDirectory); // 错误: 这样移动会导致 包含根目录文件夹名 + Utils.FileUtil.CopyFolder(libNetDirPath, destinationDirectory); + // 只需要这些, 其他删除 + Directory.Delete(libDirPath, true); + + Utils.LogUtil.Info($"加载 nupkg 中 dll: {sourceFile} ; {libNetDirPath}"); + + // 2. 普通文件: 例如 wwwroot + // 2.1 ./content + string contentDirPath = Path.Combine(destinationDirectory, "content"); + bool isExistContentDir = Directory.Exists(contentDirPath); + bool isFinishedContent = false; + if (isExistContentDir) + { + DirectoryInfo dir = new DirectoryInfo(contentDirPath); + bool isExistFile = dir.GetFiles()?.Length >= 1 || dir.GetDirectories()?.Length >= 1; + if (isExistFile) { - DirectoryInfo dir = new DirectoryInfo(contentDirPath); - bool isExistFile = dir.GetFiles()?.Length >= 1 || dir.GetDirectories()?.Length >= 1; - if (isExistFile) - { - Utils.FileUtil.CopyFolder(contentDirPath, destinationDirectory); - isFinishedContent = true; + Utils.FileUtil.CopyFolder(contentDirPath, destinationDirectory); + isFinishedContent = true; - Utils.LogUtil.Info($"加载 nupkg 中 非dll: ./content : {sourceFile} ; {contentDirPath}"); - } + Utils.LogUtil.Info($"加载 nupkg 中 非dll: ./content : {sourceFile} ; {contentDirPath}"); } - else + } + else + { + // 2.2 ./contentFiles/any/netFolderName + // 在 ./content 中没有找到文件, 再尝试 此文件夹 + if (!isFinishedContent) { - // 2.2 ./contentFiles/any/netFolderName - // 在 ./content 中没有找到文件, 再尝试 此文件夹 - if (!isFinishedContent) + string contentFilesDirPath = Path.Combine(destinationDirectory, "contentFiles"); + DirectoryInfo dir = new DirectoryInfo(contentFilesDirPath); + int? childDirLength = dir.GetDirectories()?.Length; + if (childDirLength >= 1) { - string contentFilesDirPath = Path.Combine(destinationDirectory, "contentFiles"); - DirectoryInfo dir = new DirectoryInfo(contentFilesDirPath); - int? childDirLength = dir.GetDirectories()?.Length; - if (childDirLength >= 1) + DirectoryInfo anyDir = dir.GetDirectories()[0]; + DirectoryInfo netFolderDir = anyDir.GetDirectories(netFolderName)?.FirstOrDefault(); + if (netFolderDir != null) { - DirectoryInfo anyDir = dir.GetDirectories()[0]; - DirectoryInfo netFolderDir = anyDir.GetDirectories(netFolderName)?.FirstOrDefault(); - if (netFolderDir != null) - { - Utils.FileUtil.CopyFolder(netFolderDir.FullName, destinationDirectory); - - Utils.LogUtil.Info($"加载 nupkg 中 非dll: ./contentFiles/any/netFolderName : {sourceFile} ; {netFolderDir.FullName}"); - } + Utils.FileUtil.CopyFolder(netFolderDir.FullName, destinationDirectory); + + Utils.LogUtil.Info($"加载 nupkg 中 非dll: ./contentFiles/any/netFolderName : {sourceFile} ; {netFolderDir.FullName}"); } } } + } - } - catch (Exception ex) - { - Utils.LogUtil.Error(ex.ToString()); - Utils.LogUtil.Error(ex.InnerException?.ToString() ?? ""); - - throw ex; - } + } + catch (Exception ex) + { + Utils.LogUtil.Error(ex.ToString()); + Utils.LogUtil.Error(ex.InnerException?.ToString() ?? ""); - return isDecomparessSuccess; + throw ex; } + + return isDecomparessSuccess; } } diff --git a/src/PluginCore/Infrastructure/PluginServiceProvide.cs b/src/PluginCore/Infrastructure/PluginServiceProvide.cs index d8273c45..a9f9b125 100644 --- a/src/PluginCore/Infrastructure/PluginServiceProvide.cs +++ b/src/PluginCore/Infrastructure/PluginServiceProvide.cs @@ -6,14 +6,6 @@ //=================================================== - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; - //namespace PluginCore.Infrastructure //{ // /// diff --git a/src/PluginCore/Interfaces/ICollectibleAssemblyLoadContext.cs b/src/PluginCore/Interfaces/ICollectibleAssemblyLoadContext.cs index 1b9aff14..079e00a3 100644 --- a/src/PluginCore/Interfaces/ICollectibleAssemblyLoadContext.cs +++ b/src/PluginCore/Interfaces/ICollectibleAssemblyLoadContext.cs @@ -5,18 +5,11 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +namespace PluginCore.Interfaces; - -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.Interfaces +/// +/// 暂时无用, 仅作为约束标记 +/// +public interface ICollectibleAssemblyLoadContext { - /// - /// 暂时无用, 仅作为约束标记 - /// - public interface ICollectibleAssemblyLoadContext - { - } } diff --git a/src/PluginCore/Interfaces/IPluginContext.cs b/src/PluginCore/Interfaces/IPluginContext.cs index e582102e..7dc962f6 100644 --- a/src/PluginCore/Interfaces/IPluginContext.cs +++ b/src/PluginCore/Interfaces/IPluginContext.cs @@ -5,36 +5,29 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; using System.Collections.Generic; using System.IO; using System.Reflection; -using System.Runtime.Loader; -using System.Text; -namespace PluginCore.Interfaces -{ - /// - /// 每个插件的所有 Assembly 打包到此中 - /// - public interface IPluginContext - { - string PluginId { get; } +namespace PluginCore.Interfaces; - IEnumerable Assemblies { get; } - - Assembly LoadFromAssemblyName(AssemblyName assemblyName); +/// +/// 每个插件的所有 Assembly 打包到此中 +/// +public interface IPluginContext +{ + string PluginId { get; } - void Unload(); + IEnumerable Assemblies { get; } + Assembly LoadFromAssemblyName(AssemblyName assemblyName); - /// - /// 暂时用不到 - /// - /// - /// - Assembly LoadFromStream(Stream assembly); - } + void Unload(); + + /// + /// 暂时用不到 + /// + /// + /// + Assembly LoadFromStream(Stream assembly); } diff --git a/src/PluginCore/Interfaces/IPluginContextManager.cs b/src/PluginCore/Interfaces/IPluginContextManager.cs index 2921755f..5e76d8a2 100644 --- a/src/PluginCore/Interfaces/IPluginContextManager.cs +++ b/src/PluginCore/Interfaces/IPluginContextManager.cs @@ -5,26 +5,19 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using PluginCore.lmplements; -using System; using System.Collections.Generic; -using System.Runtime.Loader; -using System.Text; -namespace PluginCore.Interfaces +namespace PluginCore.Interfaces; + +public interface IPluginContextManager { - public interface IPluginContextManager - { - List All(); + List All(); - bool Any(string pluginId); + bool Any(string pluginId); - void Remove(string pluginId); + void Remove(string pluginId); - IPluginContext Get(string pluginId); + IPluginContext Get(string pluginId); - void Add(string pluginId, IPluginContext context); - } + void Add(string pluginId, IPluginContext context); } diff --git a/src/PluginCore/Interfaces/IPluginContextPack.cs b/src/PluginCore/Interfaces/IPluginContextPack.cs index 635cb146..cdf1a0c9 100644 --- a/src/PluginCore/Interfaces/IPluginContextPack.cs +++ b/src/PluginCore/Interfaces/IPluginContextPack.cs @@ -5,22 +5,14 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +namespace PluginCore.Interfaces; - -using System; -using System.Collections.Generic; -using System.Runtime.Loader; -using System.Text; - -namespace PluginCore.Interfaces +public interface IPluginContextPack { - public interface IPluginContextPack - { - /// - /// 将 此插件 打包 到一个 中 - /// - /// - /// - IPluginContext Pack(string pluginId); - } + /// + /// 将 此插件 打包 到一个 中 + /// + /// + /// + IPluginContext Pack(string pluginId); } diff --git a/src/PluginCore/Models/PluginConfigModel.cs b/src/PluginCore/Models/PluginConfigModel.cs index 4ed5896d..0dd4e09b 100644 --- a/src/PluginCore/Models/PluginConfigModel.cs +++ b/src/PluginCore/Models/PluginConfigModel.cs @@ -6,33 +6,29 @@ //=================================================== - -using System; using System.Collections.Generic; -using System.Text; -namespace PluginCore.Models +namespace PluginCore.Models; + +/// +/// 所有插件的配置信息模型 +/// 对应 WebApi/App_Data/plugin.config.json +/// ------------- +/// Plugins = 已启用 + 已禁用 +/// 上传放入 Plugins 后, 默认为 已禁用 +/// +public class PluginConfigModel { /// - /// 所有插件的配置信息模型 - /// 对应 WebApi/App_Data/plugin.config.json - /// ------------- - /// Plugins = 已启用 + 已禁用 - /// 上传放入 Plugins 后, 默认为 已禁用 + /// 启用的插件列表: PluginID + /// 属于 插件 已安装 /// - public class PluginConfigModel - { - /// - /// 启用的插件列表: PluginID - /// 属于 插件 已安装 - /// - public List EnabledPlugins { get; set; } + public List EnabledPlugins { get; set; } - #region ctor - public PluginConfigModel() - { - this.EnabledPlugins = new List(); - } - #endregion + #region ctor + public PluginConfigModel() + { + this.EnabledPlugins = new List(); } + #endregion } diff --git a/src/PluginCore/Models/PluginInfoModel.cs b/src/PluginCore/Models/PluginInfoModel.cs index 2af0ad11..6b5077b6 100644 --- a/src/PluginCore/Models/PluginInfoModel.cs +++ b/src/PluginCore/Models/PluginInfoModel.cs @@ -5,50 +5,45 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; using System.Collections.Generic; -using System.Text; -namespace PluginCore.Models +namespace PluginCore.Models; + +/// +/// 插件信息模型 +/// 对应插件目录下 info.json +/// 约定: 插件文件夹名=PluginID +/// 约定: 插件文件夹名=插件主程序集(Assembly)名 .dll +/// eg: plugins/payment payment.dll +/// 约定: 插件文件夹下 logo.png 为插件图标 +/// 约定: 插件文件夹下 README.md 为插件说明文件 +/// 约定: 插件文件夹下 settings.json 为插件设置文件 +/// +public class PluginInfoModel { - /// - /// 插件信息模型 - /// 对应插件目录下 info.json - /// 约定: 插件文件夹名=PluginID - /// 约定: 插件文件夹名=插件主程序集(Assembly)名 .dll - /// eg: plugins/payment payment.dll - /// 约定: 插件文件夹下 logo.png 为插件图标 - /// 约定: 插件文件夹下 README.md 为插件说明文件 - /// 约定: 插件文件夹下 settings.json 为插件设置文件 - /// - public class PluginInfoModel - { - public string PluginId { get; set; } + public string PluginId { get; set; } - public string DisplayName { get; set; } + public string DisplayName { get; set; } - public string Description { get; set; } + public string Description { get; set; } - public string Author { get; set; } + public string Author { get; set; } - public string Version { get; set; } + public string Version { get; set; } - public IList SupportedVersions { get; set; } + public IList SupportedVersions { get; set; } - /// - /// 前置依赖插件 - /// - /// - public IList DependPlugins { get; set; } + /// + /// 前置依赖插件 + /// + /// + public IList DependPlugins { get; set; } - #region Ctor - public PluginInfoModel() - { - this.SupportedVersions = new List(); - this.DependPlugins = new List(); - } - #endregion + #region Ctor + public PluginInfoModel() + { + this.SupportedVersions = new List(); + this.DependPlugins = new List(); } + #endregion } diff --git a/src/PluginCore/Models/PluginInfoResponseModel.cs b/src/PluginCore/Models/PluginInfoResponseModel.cs index ab43f829..a82d33a5 100644 --- a/src/PluginCore/Models/PluginInfoResponseModel.cs +++ b/src/PluginCore/Models/PluginInfoResponseModel.cs @@ -5,25 +5,18 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +namespace PluginCore.Models; - -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.Models +public class PluginInfoResponseModel : PluginInfoModel { - public class PluginInfoResponseModel : PluginInfoModel - { - /// - /// 插件状态 - /// - public PluginStatus Status { get; set; } - } + /// + /// 插件状态 + /// + public PluginStatus Status { get; set; } +} - public enum PluginStatus - { - Enabled = 0, - Disabled = 1 - } +public enum PluginStatus +{ + Enabled = 0, + Disabled = 1 } diff --git a/src/PluginCore/Models/PluginReadmeModel.cs b/src/PluginCore/Models/PluginReadmeModel.cs index a8b9f3f2..1878899e 100644 --- a/src/PluginCore/Models/PluginReadmeModel.cs +++ b/src/PluginCore/Models/PluginReadmeModel.cs @@ -5,18 +5,11 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +namespace PluginCore.Models; - -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.Models +public class PluginReadmeModel { - public class PluginReadmeModel - { - public string PluginId { get; set; } + public string PluginId { get; set; } - public string Content { get; set; } - } + public string Content { get; set; } } diff --git a/src/PluginCore/Models/PluginReadmeResponseModel.cs b/src/PluginCore/Models/PluginReadmeResponseModel.cs index 75d41daa..3641328a 100644 --- a/src/PluginCore/Models/PluginReadmeResponseModel.cs +++ b/src/PluginCore/Models/PluginReadmeResponseModel.cs @@ -5,15 +5,8 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +namespace PluginCore.Models; - -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.Models +public class PluginReadmeResponseModel : PluginReadmeModel { - public class PluginReadmeResponseModel : PluginReadmeModel - { - } } diff --git a/src/PluginCore/Models/PluginRegistryResponseModel.cs b/src/PluginCore/Models/PluginRegistryResponseModel.cs index fa2c323f..56013ae0 100644 --- a/src/PluginCore/Models/PluginRegistryResponseModel.cs +++ b/src/PluginCore/Models/PluginRegistryResponseModel.cs @@ -5,39 +5,32 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +namespace PluginCore.Models; +public class PluginRegistryResponseModel : PluginInfoModel +{ + public string DownloadUrl { get; set; } -using System; -using System.Collections.Generic; -using System.Text; + /// + /// 此属性值由获取后根据本地插件情况赋值 + /// + public PluginStatus Status { get; set; } -namespace PluginCore.Models -{ - public class PluginRegistryResponseModel : PluginInfoModel + public enum PluginStatus { - public string DownloadUrl { get; set; } - /// - /// 此属性值由获取后根据本地插件情况赋值 + /// 本地无此 PluginId 的插件 /// - public PluginStatus Status { get; set; } - - public enum PluginStatus - { - /// - /// 本地无此 PluginId 的插件 - /// - LocalWithout, + LocalWithout, - /// - /// 本地已存在此 PluginId 的插件 - /// - LocalExist - } + /// + /// 本地已存在此 PluginId 的插件 + /// + LocalExist } +} - public class PluginRegistryDTO - { +public class PluginRegistryDTO +{ - } } diff --git a/src/PluginCore/Models/PluginSettingsInputModel.cs b/src/PluginCore/Models/PluginSettingsInputModel.cs index 64e16b73..d3ea26c0 100644 --- a/src/PluginCore/Models/PluginSettingsInputModel.cs +++ b/src/PluginCore/Models/PluginSettingsInputModel.cs @@ -5,18 +5,11 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +namespace PluginCore.Models; - -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.Models +public class PluginSettingsInputModel { - public class PluginSettingsInputModel - { - public string PluginId { get; set; } + public string PluginId { get; set; } - public string Data { get; set; } - } + public string Data { get; set; } } diff --git a/src/PluginCore/PluginConfigModelFactory.cs b/src/PluginCore/PluginConfigModelFactory.cs index f43c5ea0..2f2fa09e 100644 --- a/src/PluginCore/PluginConfigModelFactory.cs +++ b/src/PluginCore/PluginConfigModelFactory.cs @@ -15,75 +15,74 @@ using System.Text.Json; using PluginCore.Models; -namespace PluginCore +namespace PluginCore; + +public class PluginConfigModelFactory { - public class PluginConfigModelFactory + #region 即时读取 + public static PluginConfigModel Create() { - #region 即时读取 - public static PluginConfigModel Create() - { - PluginConfigModel pluginConfigModel = new PluginConfigModel(); - string pluginConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "plugin.config.json"); - string pluginConfigJsonStr = File.ReadAllText(pluginConfigFilePath, Encoding.UTF8); - JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); - jsonSerializerOptions.PropertyNameCaseInsensitive = true; - pluginConfigModel = JsonSerializer.Deserialize(pluginConfigJsonStr, jsonSerializerOptions); - pluginConfigModel = EnabledPluginsSort(pluginConfigModel); + string pluginConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "plugin.config.json"); + string pluginConfigJsonStr = File.ReadAllText(pluginConfigFilePath, Encoding.UTF8); + JsonSerializerOptions jsonSerializerOptions = new() { + PropertyNameCaseInsensitive = true + }; + PluginConfigModel pluginConfigModel = JsonSerializer.Deserialize(pluginConfigJsonStr, jsonSerializerOptions); + pluginConfigModel = EnabledPluginsSort(pluginConfigModel); - return pluginConfigModel; - } - #endregion + return pluginConfigModel; + } + #endregion - #region 保存 - public static void Save(PluginConfigModel pluginConfigModel) + #region 保存 + public static void Save(PluginConfigModel pluginConfigModel) + { + if (pluginConfigModel == null) { - if (pluginConfigModel == null) - { - throw new ArgumentNullException(nameof(pluginConfigModel)); - } - try - { - pluginConfigModel = EnabledPluginsSort(pluginConfigModel); - string pluginConfigJsonStr = JsonSerializer.Serialize(pluginConfigModel); - string pluginConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "plugin.config.json"); - File.WriteAllText(pluginConfigFilePath, pluginConfigJsonStr, Encoding.UTF8); - } - catch (Exception ex) - { } + throw new ArgumentNullException(nameof(pluginConfigModel)); + } + try + { + pluginConfigModel = EnabledPluginsSort(pluginConfigModel); + string pluginConfigJsonStr = JsonSerializer.Serialize(pluginConfigModel); + string pluginConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "plugin.config.json"); + File.WriteAllText(pluginConfigFilePath, pluginConfigJsonStr, Encoding.UTF8); + } + catch (Exception ex) + { } - } - #endregion + } + #endregion - #region 确保建立正确的依赖顺序 - public static PluginConfigModel EnabledPluginsSort(PluginConfigModel pluginConfigModel) { - var dependencySorter = new PluginCore.Utils.DependencySorter(); - dependencySorter.AddObjects(pluginConfigModel.EnabledPlugins.ToArray()); - foreach (var plugin in pluginConfigModel.EnabledPlugins) - { - try - { - IList dependPlugins = PluginInfoModelFactory.Create(plugin).DependPlugins; - if (dependPlugins != null && dependPlugins.Count >= 1) { - dependencySorter.SetDependencies(obj: plugin, dependsOnObjects: dependPlugins.ToArray()); - } - } - catch (System.Exception ex) - { - } - } + #region 确保建立正确的依赖顺序 + public static PluginConfigModel EnabledPluginsSort(PluginConfigModel pluginConfigModel) { + var dependencySorter = new PluginCore.Utils.DependencySorter(); + dependencySorter.AddObjects(pluginConfigModel.EnabledPlugins.ToArray()); + foreach (var plugin in pluginConfigModel.EnabledPlugins) + { try { - var sortedPlugins = dependencySorter.Sort(); - if (sortedPlugins != null && sortedPlugins.Length >= 1) { - pluginConfigModel.EnabledPlugins = sortedPlugins.ToList(); + IList dependPlugins = PluginInfoModelFactory.Create(plugin).DependPlugins; + if (dependPlugins != null && dependPlugins.Count >= 1) { + dependencySorter.SetDependencies(obj: plugin, dependsOnObjects: dependPlugins.ToArray()); } } catch (System.Exception ex) { } - - return pluginConfigModel; } - #endregion + try + { + var sortedPlugins = dependencySorter.Sort(); + if (sortedPlugins != null && sortedPlugins.Length >= 1) { + pluginConfigModel.EnabledPlugins = sortedPlugins.ToList(); + } + } + catch (System.Exception ex) + { + } + + return pluginConfigModel; } + #endregion } diff --git a/src/PluginCore/PluginCore.csproj b/src/PluginCore/PluginCore.csproj index d954fcdf..6545ebce 100644 --- a/src/PluginCore/PluginCore.csproj +++ b/src/PluginCore/PluginCore.csproj @@ -1,7 +1,6 @@  - netcoreapp3.1;net5.0;net6.0 PluginCore 2.2.0 yiyun @@ -12,15 +11,16 @@ https://github.com/yiyungent/PluginCore/blob/main/LICENSE PluginCore true + net7.0 - 9.0 - enable + latest + disable - + diff --git a/src/PluginCore/PluginInfoModelFactory.cs b/src/PluginCore/PluginInfoModelFactory.cs index 819d4b59..0e7b472f 100644 --- a/src/PluginCore/PluginInfoModelFactory.cs +++ b/src/PluginCore/PluginInfoModelFactory.cs @@ -5,102 +5,98 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.Json; +using PluginCore.IPlugins.Infrastructure; using PluginCore.Models; -namespace PluginCore +namespace PluginCore; + +public class PluginInfoModelFactory { - public class PluginInfoModelFactory + private const string InfoJson = "info.json"; + + #region 即时读取指定 plugin info.json + public static PluginInfoModel? Create(string pluginId) { - private const string InfoJson = "info.json"; + PluginInfoModel? pluginInfoModel = new PluginInfoModel(); + string pluginDir = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); + string pluginInfoFilePath = Path.Combine(pluginDir, InfoJson); - #region 即时读取指定 plugin info.json - public static PluginInfoModel Create(string pluginId) + if (!File.Exists(pluginInfoFilePath)) { - PluginInfoModel pluginInfoModel = new PluginInfoModel(); - string pluginDir = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); - string pluginInfoFilePath = Path.Combine(pluginDir, InfoJson); - - if (!File.Exists(pluginInfoFilePath)) - { - return null; - } - try - { - string pluginInfoJsonStr = File.ReadAllText(pluginInfoFilePath, Encoding.UTF8); - JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); - jsonSerializerOptions.PropertyNameCaseInsensitive = true; - pluginInfoModel = JsonSerializer.Deserialize(pluginInfoJsonStr, jsonSerializerOptions); - } - catch (Exception ex) - { - pluginInfoModel = null; - } - - return pluginInfoModel; + return null; } - #endregion - - #region 即时读取插件目录下所有 plugin info.json - public static IList CreateAll() + try { - IList pluginInfoModels = new List(); - IList pluginDirs = PluginPathProvider.AllPluginDir(); - foreach (var dir in pluginDirs) - { - // 从 dir 中解析出 pluginId - // 约定: 插件文件夹名=PluginID=插件主.dll - string pluginId = PluginPathProvider.GetPluginFolderNameByDir(dir); - PluginInfoModel model = Create(pluginId); - pluginInfoModels.Add(model); - } - // 去除为 null: 目标插件信息不存在,或者格式错误的 - pluginInfoModels = pluginInfoModels.Where(m => m != null).ToList(); - - return pluginInfoModels; + string pluginInfoJsonStr = File.ReadAllText(pluginInfoFilePath, Encoding.UTF8); + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); + jsonSerializerOptions.PropertyNameCaseInsensitive = true; + pluginInfoModel = JsonSerializer.Deserialize(pluginInfoJsonStr, jsonSerializerOptions); } - #endregion - - #region 从指定插件目录读取插件信息 - /// - /// 从指定插件目录读取插件信息 - /// 可以用于读取临时插件上传目录中的插件信息 - /// - /// - /// - public static PluginInfoModel ReadPluginDir(string pluginDir) + catch (Exception ex) { - PluginInfoModel pluginInfoModel = new PluginInfoModel(); - string pluginInfoFilePath = Path.Combine(pluginDir, InfoJson); + pluginInfoModel = null; + } - if (!File.Exists(pluginInfoFilePath)) - { - return null; - } - try - { - string pluginInfoJsonStr = File.ReadAllText(pluginInfoFilePath, Encoding.UTF8); - JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); - jsonSerializerOptions.PropertyNameCaseInsensitive = true; - pluginInfoModel = JsonSerializer.Deserialize(pluginInfoJsonStr, jsonSerializerOptions); - } - catch (Exception ex) - { - pluginInfoModel = null; - } + return pluginInfoModel; + } + #endregion - return pluginInfoModel; - } - #endregion + #region 即时读取插件目录下所有 plugin info.json + public static IList CreateAll() + { + IList pluginInfoModels = new List(); + IList pluginDirs = PluginPathProvider.AllPluginDir(); + foreach (var dir in pluginDirs) + { + // 从 dir 中解析出 pluginId + // 约定: 插件文件夹名=PluginID=插件主.dll + string pluginId = PluginPathProvider.GetPluginFolderNameByDir(dir); + PluginInfoModel? model = Create(pluginId); + pluginInfoModels.Add(model); + } + // 去除为 null: 目标插件信息不存在,或者格式错误的 + pluginInfoModels = pluginInfoModels.Where(m => m != null).ToList(); + return pluginInfoModels; } + #endregion + + #region 从指定插件目录读取插件信息 + /// + /// 从指定插件目录读取插件信息 + /// 可以用于读取临时插件上传目录中的插件信息 + /// + /// + /// + public static PluginInfoModel ReadPluginDir(string pluginDir) + { + PluginInfoModel pluginInfoModel = new PluginInfoModel(); + string pluginInfoFilePath = Path.Combine(pluginDir, InfoJson); + + if (!File.Exists(pluginInfoFilePath)) + { + return null; + } + try + { + string pluginInfoJsonStr = File.ReadAllText(pluginInfoFilePath, Encoding.UTF8); + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(); + jsonSerializerOptions.PropertyNameCaseInsensitive = true; + pluginInfoModel = JsonSerializer.Deserialize(pluginInfoJsonStr, jsonSerializerOptions); + } + catch (Exception ex) + { + pluginInfoModel = null; + } + + return pluginInfoModel; + } + #endregion - } diff --git a/src/PluginCore/PluginReadmeModelFactory.cs b/src/PluginCore/PluginReadmeModelFactory.cs index 09271203..34607a91 100644 --- a/src/PluginCore/PluginReadmeModelFactory.cs +++ b/src/PluginCore/PluginReadmeModelFactory.cs @@ -5,49 +5,45 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Text; -using System.Text.Json; +using PluginCore.IPlugins.Infrastructure; using PluginCore.Models; -namespace PluginCore +namespace PluginCore; + +/// +/// TODO: 目前这样读取出来的包含了 windows 换行符 \r\n +/// +public class PluginReadmeModelFactory { - /// - /// TODO: 目前这样读取出来的包含了 windows 换行符 \r\n - /// - public class PluginReadmeModelFactory + // TODO: Linux 文件名下区分大小写, windows不区分, 目前必须为 README.md + private const string ReadmeFile = "README.md"; + + #region 即时读取 + public static PluginReadmeModel Create(string pluginId) { - // TODO: Linux 文件名下区分大小写, windows不区分, 目前必须为 README.md - private const string ReadmeFile = "README.md"; + PluginReadmeModel readmeModel = new PluginReadmeModel(); + string pluginDir = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); + string pluginReadmeFilePath = Path.Combine(pluginDir, ReadmeFile); - #region 即时读取 - public static PluginReadmeModel Create(string pluginId) + if (!File.Exists(pluginReadmeFilePath)) { - PluginReadmeModel readmeModel = new PluginReadmeModel(); - string pluginDir = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); - string pluginReadmeFilePath = Path.Combine(pluginDir, ReadmeFile); - - if (!File.Exists(pluginReadmeFilePath)) - { - return null; - } - try - { - string readmeStr = File.ReadAllText(pluginReadmeFilePath, Encoding.UTF8); - readmeModel.PluginId = pluginId; - readmeModel.Content = readmeStr; - } - catch (Exception ex) - { - readmeModel = null; - } - - return readmeModel; + return null; } - #endregion + try + { + string readmeStr = File.ReadAllText(pluginReadmeFilePath, Encoding.UTF8); + readmeModel.PluginId = pluginId; + readmeModel.Content = readmeStr; + } + catch (Exception ex) + { + readmeModel = null; + } + + return readmeModel; } + #endregion } diff --git a/src/PluginCore/Utils/DateTimeUtil.cs b/src/PluginCore/Utils/DateTimeUtil.cs index db576eae..4979c918 100644 --- a/src/PluginCore/Utils/DateTimeUtil.cs +++ b/src/PluginCore/Utils/DateTimeUtil.cs @@ -6,111 +6,106 @@ //=================================================== - -using System; -// Project: SimCaptcha -// https://github.com/yiyungent/SimCaptcha -// Author: yiyun +using System; /// /// JavaScript时间戳:是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数。(13 位数字) /// /// Unix时间戳:是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。(10 位数字) /// -namespace PluginCore.Utils +namespace PluginCore.Utils; + +public static class DateTimeUtil { - public static class DateTimeUtil - { - public static DateTime DateTime1970 = new DateTime(1970, 1, 1).ToLocalTime(); + public static DateTime DateTime1970 = new DateTime(1970, 1, 1).ToLocalTime(); - #region Unix 10位时间戳-总秒数 - /// - /// C# DateTime转换为Unix时间戳 - /// - public static long ToTimeStamp10(this DateTime dateTime) - { - // 相差秒数 - long timeStamp = (long)(dateTime.ToLocalTime() - DateTime1970).TotalSeconds; + #region Unix 10位时间戳-总秒数 + /// + /// C# DateTime转换为Unix时间戳 + /// + public static long ToTimeStamp10(this DateTime dateTime) + { + // 相差秒数 + long timeStamp = (long)(dateTime.ToLocalTime() - DateTime1970).TotalSeconds; - return timeStamp; - } + return timeStamp; + } - /// - /// C# DateTime转换为Unix时间戳 - /// - public static long ToTimeStamp10(this DateTime? dateTime) + /// + /// C# DateTime转换为Unix时间戳 + /// + public static long ToTimeStamp10(this DateTime? dateTime) + { + if (dateTime == null) { - if (dateTime == null) - { - return 0; - } - // 相差秒数 - long timeStamp = ToTimeStamp10((DateTime)dateTime); - - return timeStamp; + return 0; } + // 相差秒数 + long timeStamp = ToTimeStamp10((DateTime)dateTime); - /// - /// Unix时间戳转换为C# DateTime - /// - public static DateTime ToDateTime10(this long timeStamp10) - { - DateTime dateTime = DateTime1970.AddSeconds(timeStamp10).ToLocalTime(); + return timeStamp; + } - return dateTime; - } - #endregion + /// + /// Unix时间戳转换为C# DateTime + /// + public static DateTime ToDateTime10(this long timeStamp10) + { + DateTime dateTime = DateTime1970.AddSeconds(timeStamp10).ToLocalTime(); - #region JavaScript 13位时间戳-总毫秒数 - /// - /// C# DateTime转换为JavaScript时间戳 - /// - public static long ToTimeStamp13(this DateTime dateTime) - { - // 相差毫秒数 - long timeStamp = (long)(dateTime.ToLocalTime() - DateTime1970).TotalMilliseconds; + return dateTime; + } + #endregion - return timeStamp; - } + #region JavaScript 13位时间戳-总毫秒数 + /// + /// C# DateTime转换为JavaScript时间戳 + /// + public static long ToTimeStamp13(this DateTime dateTime) + { + // 相差毫秒数 + long timeStamp = (long)(dateTime.ToLocalTime() - DateTime1970).TotalMilliseconds; - /// - /// C# DateTime转换为JavaScript时间戳 - /// - public static long ToTimeStamp13(this DateTime? dateTime) + return timeStamp; + } + + /// + /// C# DateTime转换为JavaScript时间戳 + /// + public static long ToTimeStamp13(this DateTime? dateTime) + { + if (dateTime == null) { - if (dateTime == null) - { - return 0; - } - // 相差秒数 - long timeStamp = ToTimeStamp13((DateTime)dateTime); - - return timeStamp; + return 0; } + // 相差秒数 + long timeStamp = ToTimeStamp13((DateTime)dateTime); - /// - /// JavaScript时间戳转换为C# DateTime - /// - public static DateTime ToDateTime13(this long timeStamp13) - { - DateTime dateTime = DateTime1970.AddMilliseconds(timeStamp13).ToLocalTime(); + return timeStamp; + } - return dateTime; - } - #endregion + /// + /// JavaScript时间戳转换为C# DateTime + /// + public static DateTime ToDateTime13(this long timeStamp13) + { + DateTime dateTime = DateTime1970.AddMilliseconds(timeStamp13).ToLocalTime(); - #region 获取当前Unix时间戳 - public static long NowTimeStamp10() - { - return ToTimeStamp10(DateTime.Now); - } - #endregion + return dateTime; + } + #endregion - #region 获取当前JavaScript时间戳 - public static long NowTimeStamp13() - { - return ToTimeStamp13(DateTime.Now); - } - #endregion + #region 获取当前Unix时间戳 + public static long NowTimeStamp10() + { + return ToTimeStamp10(DateTime.Now); + } + #endregion + + #region 获取当前JavaScript时间戳 + public static long NowTimeStamp13() + { + return ToTimeStamp13(DateTime.Now); } + #endregion } diff --git a/src/PluginCore/Utils/DependencySorter.cs b/src/PluginCore/Utils/DependencySorter.cs index 51e159aa..476c36c6 100644 --- a/src/PluginCore/Utils/DependencySorter.cs +++ b/src/PluginCore/Utils/DependencySorter.cs @@ -8,12 +8,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -namespace PluginCore.Utils -{ - /// +namespace PluginCore.Utils; + +/// /// https://stackoverflow.com/questions/4106862/how-to-sort-depended-objects-by-dependency/ /// /// Definition: http://en.wikipedia.org/wiki/Topological_sorting @@ -24,7 +23,7 @@ namespace PluginCore.Utils /// /// 2012.03.21 - ThangTran: rewritten based on . /// - public class DependencySorter + public class DependencySorter where T : notnull { //************************************************** // @@ -190,4 +189,3 @@ public CircularReferenceException() { } } -} \ No newline at end of file diff --git a/src/PluginCore/Utils/FileUtil.cs b/src/PluginCore/Utils/FileUtil.cs index 5ecf863d..3a2016ce 100644 --- a/src/PluginCore/Utils/FileUtil.cs +++ b/src/PluginCore/Utils/FileUtil.cs @@ -5,172 +5,168 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Text; -namespace PluginCore.Utils +namespace PluginCore.Utils; + +public class FileUtil { - public class FileUtil + #region 复制大文件 + public static void CopyBigFile(string originalFilePath, string destFilePath) { - #region 复制大文件 - public static void CopyBigFile(string originalFilePath, string destFilePath) + // 定义读文件流 + using (FileStream fsr = new FileStream(originalFilePath, FileMode.Open)) { - // 定义读文件流 - using (FileStream fsr = new FileStream(originalFilePath, FileMode.Open)) + // 定义写文件流 + using (FileStream fsw = new FileStream(destFilePath, FileMode.OpenOrCreate)) { - // 定义写文件流 - using (FileStream fsw = new FileStream(destFilePath, FileMode.OpenOrCreate)) + // 申请1M内存空间 + byte[] buffer = new byte[1024 * 1024]; + // 无限循环中反复读写,直到读完写完 + while (true) { - // 申请1M内存空间 - byte[] buffer = new byte[1024 * 1024]; - // 无限循环中反复读写,直到读完写完 - while (true) + int readCount = fsr.Read(buffer, 0, buffer.Length); + fsw.Write(buffer, 0, readCount); + if (readCount < buffer.Length) { - int readCount = fsr.Read(buffer, 0, buffer.Length); - fsw.Write(buffer, 0, readCount); - if (readCount < buffer.Length) - { - break; - } + break; } } } } - #endregion + } + #endregion - #region 移动大文件 - public static void MoveBigFile(string originalFilePath, string destFilePath) + #region 移动大文件 + public static void MoveBigFile(string originalFilePath, string destFilePath) + { + try { - try - { - CopyBigFile(originalFilePath, destFilePath); - File.Delete(originalFilePath); - } - catch (Exception ex) - { - throw ex; - } + CopyBigFile(originalFilePath, destFilePath); + File.Delete(originalFilePath); } - #endregion - - #region 复制文件夹 - /// - /// 复制文件夹及文件 - /// (不包括源文件夹根目录名称, 只是复制其中内容到目标文件夹) - /// https://www.cnblogs.com/wangjianhui008/p/3234519.html - /// - /// 原文件路径 - /// 目标文件路径 - /// - public static bool CopyFolder(string sourceFolder, string destFolder) + catch (Exception ex) + { + throw ex; + } + } + #endregion + + #region 复制文件夹 + /// + /// 复制文件夹及文件 + /// (不包括源文件夹根目录名称, 只是复制其中内容到目标文件夹) + /// https://www.cnblogs.com/wangjianhui008/p/3234519.html + /// + /// 原文件路径 + /// 目标文件路径 + /// + public static bool CopyFolder(string sourceFolder, string destFolder) + { + try { - try + // 如果目标路径不存在,则创建目标路径 + if (!System.IO.Directory.Exists(destFolder)) { - // 如果目标路径不存在,则创建目标路径 - if (!System.IO.Directory.Exists(destFolder)) - { - System.IO.Directory.CreateDirectory(destFolder); - } - // 得到原文件根目录下的所有文件 - string[] files = System.IO.Directory.GetFiles(sourceFolder); - foreach (string file in files) - { - string name = System.IO.Path.GetFileName(file); - string dest = System.IO.Path.Combine(destFolder, name); - System.IO.File.Copy(file, dest);//复制文件 - } - // 得到原文件根目录下的所有文件夹 - string[] folders = System.IO.Directory.GetDirectories(sourceFolder); - foreach (string folder in folders) - { - string name = System.IO.Path.GetFileName(folder); - string dest = System.IO.Path.Combine(destFolder, name); - CopyFolder(folder, dest);//构建目标路径,递归复制文件 - } - return true; + System.IO.Directory.CreateDirectory(destFolder); } - catch (Exception e) + // 得到原文件根目录下的所有文件 + string[] files = System.IO.Directory.GetFiles(sourceFolder); + foreach (string file in files) { - - return false; + string name = System.IO.Path.GetFileName(file); + string dest = System.IO.Path.Combine(destFolder, name); + System.IO.File.Copy(file, dest);//复制文件 } + // 得到原文件根目录下的所有文件夹 + string[] folders = System.IO.Directory.GetDirectories(sourceFolder); + foreach (string folder in folders) + { + string name = System.IO.Path.GetFileName(folder); + string dest = System.IO.Path.Combine(destFolder, name); + CopyFolder(folder, dest);//构建目标路径,递归复制文件 + } + return true; + } + catch (Exception e) + { + return false; } - #endregion - #region 计算MD5 + } + #endregion + + #region 计算MD5 - /// - /// 获取文件的MD5码 - /// - /// 传入的文件名(含路径及后缀名) - /// - public string GetMD5HashFromFile(string filePath) + /// + /// 获取文件的MD5码 + /// + /// 传入的文件名(含路径及后缀名) + /// + public string GetMD5HashFromFile(string filePath) + { + try { - try - { - FileStream file = new FileStream(filePath, System.IO.FileMode.Open); - System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); - byte[] retVal = md5.ComputeHash(file); - file.Close(); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < retVal.Length; i++) - { - sb.Append(retVal[i].ToString("x2")); - } - return sb.ToString(); - } - catch (Exception ex) + FileStream file = new FileStream(filePath, System.IO.FileMode.Open); + System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); + byte[] retVal = md5.ComputeHash(file); + file.Close(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < retVal.Length; i++) { - throw new Exception("GetMD5HashFromFile() fail,error:" + ex.Message); + sb.Append(retVal[i].ToString("x2")); } + return sb.ToString(); + } + catch (Exception ex) + { + throw new Exception("GetMD5HashFromFile() fail,error:" + ex.Message); } + } - public static string GetFileMD5(string filePath) + public static string GetFileMD5(string filePath) + { + FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + int bufferSize = 1048576; + byte[] buff = new byte[bufferSize]; + System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); + md5.Initialize(); + long offset = 0; + while (offset < fs.Length) { - FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); - int bufferSize = 1048576; - byte[] buff = new byte[bufferSize]; - System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); - md5.Initialize(); - long offset = 0; - while (offset < fs.Length) - { - long readSize = bufferSize; - if (offset + readSize > fs.Length) - readSize = fs.Length - offset; - fs.Read(buff, 0, Convert.ToInt32(readSize)); - if (offset + readSize < fs.Length) - md5.TransformBlock(buff, 0, Convert.ToInt32(readSize), buff, 0); - else - md5.TransformFinalBlock(buff, 0, Convert.ToInt32(readSize)); - offset += bufferSize; - } - if (offset >= fs.Length) - { - fs.Close(); - byte[] result = md5.Hash; - md5.Clear(); - StringBuilder sb = new StringBuilder(32); - for (int i = 0; i < result.Length; i++) - sb.Append(result[i].ToString("X2")); - return sb.ToString(); - } + long readSize = bufferSize; + if (offset + readSize > fs.Length) + readSize = fs.Length - offset; + fs.Read(buff, 0, Convert.ToInt32(readSize)); + if (offset + readSize < fs.Length) + md5.TransformBlock(buff, 0, Convert.ToInt32(readSize), buff, 0); else - { - fs.Close(); - return null; - } + md5.TransformFinalBlock(buff, 0, Convert.ToInt32(readSize)); + offset += bufferSize; } + if (offset >= fs.Length) + { + fs.Close(); + byte[] result = md5.Hash; + md5.Clear(); + StringBuilder sb = new StringBuilder(32); + for (int i = 0; i < result.Length; i++) + sb.Append(result[i].ToString("X2")); + return sb.ToString(); + } + else + { + fs.Close(); + return null; + } + } - #endregion + #endregion - } } diff --git a/src/PluginCore/Utils/LogUtil.cs b/src/PluginCore/Utils/LogUtil.cs index 9aa628c3..2b469e77 100644 --- a/src/PluginCore/Utils/LogUtil.cs +++ b/src/PluginCore/Utils/LogUtil.cs @@ -5,84 +5,79 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; +using System; using System.Reflection; -using System.Text; using System.Linq; -namespace PluginCore.Utils +namespace PluginCore.Utils; + +public class LogUtil { - public class LogUtil + public const string Sign = "PluginCore"; + + public static void Info(string message) { - public const string Sign = "PluginCore"; + Console.ForegroundColor = ConsoleColor.Green; + Console.Write($"{Sign}: "); + Console.ResetColor(); + DateTime now = DateTime.Now; + Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss")); + Console.WriteLine(message); + } - public static void Info(string message) - { - Console.ForegroundColor = ConsoleColor.Green; - Console.Write($"{Sign}: "); - Console.ResetColor(); - DateTime now = DateTime.Now; - Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss")); - Console.WriteLine(message); - } + public static void Error(string message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Write($"{Sign}: "); + Console.ResetColor(); + DateTime now = DateTime.Now; + Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss")); + Console.WriteLine(message); + } - public static void Error(string message) + public static void Exception(Exception ex) + { + Error(ex.ToString()); + Exception exception = ex; + while (exception.InnerException != null) { - Console.ForegroundColor = ConsoleColor.Red; - Console.Write($"{Sign}: "); - Console.ResetColor(); - DateTime now = DateTime.Now; - Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss")); - Console.WriteLine(message); + exception = ex.InnerException; + Error(exception.Message); } + } - public static void Exception(Exception ex) - { - Error(ex.ToString()); - Exception exception = ex; - while (exception.InnerException != null) - { - exception = ex.InnerException; - Error(exception.Message); - } - } + public static void PluginBehavior(T plugin, Type iplugin, string methodName) + where T : IPlugins.IPlugin + { + // TODO: Bug: 无法区别 相同方法名, 参数不同的 重载方法 + MethodInfo behavior = iplugin.GetMethods().FirstOrDefault(m => m.Name == methodName); - public static void PluginBehavior(T plugin, Type iplugin, string methodName) - where T : IPlugins.IPlugin + ParameterInfo[] pars = behavior.GetParameters(); + string parsStr = string.Empty; + if (pars != null && pars.Length > 0) { - // TODO: Bug: 无法区别 相同方法名, 参数不同的 重载方法 - MethodInfo behavior = iplugin.GetMethods().FirstOrDefault(m => m.Name == methodName); - - ParameterInfo[] pars = behavior.GetParameters(); - string parsStr = string.Empty; - if (pars != null && pars.Length > 0) - { - var parTypes = pars.OrderBy(m => m.Position).Select(m => m.ParameterType.Name).ToArray(); - parsStr = string.Join(", ", parTypes); - } - - // A程序集.APlugin - string pluginStr = plugin.GetType().ToString(); - // A程序集.AClass.AMethod() - string message = $"{behavior.DeclaringType.ToString()}.{behavior.Name}({parsStr})"; + var parTypes = pars.OrderBy(m => m.Position).Select(m => m.ParameterType.Name).ToArray(); + parsStr = string.Join(", ", parTypes); + } + // A程序集.APlugin + string pluginStr = plugin.GetType().ToString(); + // A程序集.AClass.AMethod() + string message = $"{behavior.DeclaringType.ToString()}.{behavior.Name}({parsStr})"; - // TODO: 1.写入日志文件 + // TODO: 1.写入日志文件 - // 2.控制台输出 - Console.ForegroundColor = ConsoleColor.Blue; - Console.Write($"{Sign}: {pluginStr}: "); - Console.ResetColor(); - DateTime now = DateTime.Now; - Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss")); - Console.WriteLine(message); - } + // 2.控制台输出 + Console.ForegroundColor = ConsoleColor.Blue; + Console.Write($"{Sign}: {pluginStr}: "); + Console.ResetColor(); + DateTime now = DateTime.Now; + Console.WriteLine(now.ToString("yyyy-MM-dd HH:mm:ss")); + Console.WriteLine(message); } + } diff --git a/src/PluginCore/Utils/Md5Helper.cs b/src/PluginCore/Utils/Md5Helper.cs index 6d53448b..423cdb09 100644 --- a/src/PluginCore/Utils/Md5Helper.cs +++ b/src/PluginCore/Utils/Md5Helper.cs @@ -5,36 +5,30 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using System; -using System.Collections.Generic; -using System.Linq; using System.Security.Cryptography; using System.Text; -namespace PluginCore.Utils +namespace PluginCore.Utils; + +public class Md5Helper { - public class Md5Helper + #region MD5加密为32位16进制字符串 + /// + /// MD5加密为32位16进制字符串 + /// + /// 原输入字符串 + /// 返回加密后的字符串 + public static string MD5Encrypt32(string source) { - #region MD5加密为32位16进制字符串 - /// - /// MD5加密为32位16进制字符串 - /// - /// 原输入字符串 - /// 返回加密后的字符串 - public static string MD5Encrypt32(string source) + MD5 md5 = MD5.Create(); + byte[] buffer = md5.ComputeHash(Encoding.UTF8.GetBytes(source)); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < buffer.Length; i++) { - MD5 md5 = MD5.Create(); - byte[] buffer = md5.ComputeHash(Encoding.UTF8.GetBytes(source)); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < buffer.Length; i++) - { - // "x2" 转换为 16进制 - sb.Append(buffer[i].ToString("x2")); - } - return sb.ToString(); + // "x2" 转换为 16进制 + sb.Append(buffer[i].ToString("x2")); } - #endregion + return sb.ToString(); } + #endregion } diff --git a/src/PluginCore/Utils/RuntimeUtil.cs b/src/PluginCore/Utils/RuntimeUtil.cs index 1b3463b7..77620c97 100644 --- a/src/PluginCore/Utils/RuntimeUtil.cs +++ b/src/PluginCore/Utils/RuntimeUtil.cs @@ -5,22 +5,17 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== +using System; +namespace PluginCore.Utils; -using System; -using System.Collections.Generic; -using System.Text; - -namespace PluginCore.Utils +public class RuntimeUtil { - public class RuntimeUtil + public static Version RuntimeNetVersion { - public static Version RuntimeNetVersion + get { - get - { - return Environment.Version; - } + return Environment.Version; } } } diff --git a/src/PluginCore/Utils/ZipHelper.cs b/src/PluginCore/Utils/ZipHelper.cs index 23bef481..81ea5b80 100644 --- a/src/PluginCore/Utils/ZipHelper.cs +++ b/src/PluginCore/Utils/ZipHelper.cs @@ -5,439 +5,434 @@ // GitHub: https://github.com/yiyungent/PluginCore //=================================================== - - -using ICSharpCode.SharpZipLib.Zip; +using ICSharpCode.SharpZipLib.Zip; using System; using System.Collections.Generic; using System.IO; -namespace PluginCore.Utils +namespace PluginCore.Utils; + +public class ZipHelper { - public class ZipHelper + /// + /// 缓存字节数 + /// + private const int BufferSize = 4096; + + /// + /// 压缩最小等级 + /// + public const int CompressionLevelMin = 0; + + /// + /// 压缩最大等级 + /// + public const int CompressionLevelMax = 9; + + /// + /// 获取所有文件系统对象 + /// + /// 源路径 + /// 顶级文件夹 + /// 字典中Key为完整路径,Value为文件(夹)名称 + private static Dictionary GetAllFileSystemEntities(string source, string topDirectory) { - /// - /// 缓存字节数 - /// - private const int BufferSize = 4096; - - /// - /// 压缩最小等级 - /// - public const int CompressionLevelMin = 0; - - /// - /// 压缩最大等级 - /// - public const int CompressionLevelMax = 9; - - /// - /// 获取所有文件系统对象 - /// - /// 源路径 - /// 顶级文件夹 - /// 字典中Key为完整路径,Value为文件(夹)名称 - private static Dictionary GetAllFileSystemEntities(string source, string topDirectory) - { - Dictionary entitiesDictionary = new Dictionary(); - entitiesDictionary.Add(source, source.Replace(topDirectory, "")); + Dictionary entitiesDictionary = new Dictionary(); + entitiesDictionary.Add(source, source.Replace(topDirectory, "")); - if (Directory.Exists(source)) + if (Directory.Exists(source)) + { + //一次性获取下级所有目录,避免递归 + string[] directories = Directory.GetDirectories(source, "*.*", SearchOption.AllDirectories); + foreach (string directory in directories) { - //一次性获取下级所有目录,避免递归 - string[] directories = Directory.GetDirectories(source, "*.*", SearchOption.AllDirectories); - foreach (string directory in directories) - { - entitiesDictionary.Add(directory, directory.Replace(topDirectory, "")); - } - - string[] files = Directory.GetFiles(source, "*.*", SearchOption.AllDirectories); - foreach (string file in files) - { - entitiesDictionary.Add(file, file.Replace(topDirectory, "")); - } + entitiesDictionary.Add(directory, directory.Replace(topDirectory, "")); } - return entitiesDictionary; + string[] files = Directory.GetFiles(source, "*.*", SearchOption.AllDirectories); + foreach (string file in files) + { + entitiesDictionary.Add(file, file.Replace(topDirectory, "")); + } } - /// - /// 校验压缩等级 - /// - /// - /// - private static int CheckCompressionLevel(int compressionLevel) - { - compressionLevel = compressionLevel < CompressionLevelMin ? CompressionLevelMin : compressionLevel; - compressionLevel = compressionLevel > CompressionLevelMax ? CompressionLevelMax : compressionLevel; - return compressionLevel; - } + return entitiesDictionary; + } - #region 字节压缩与解压 + /// + /// 校验压缩等级 + /// + /// + /// + private static int CheckCompressionLevel(int compressionLevel) + { + compressionLevel = compressionLevel < CompressionLevelMin ? CompressionLevelMin : compressionLevel; + compressionLevel = compressionLevel > CompressionLevelMax ? CompressionLevelMax : compressionLevel; + return compressionLevel; + } - /// - /// 压缩字节数组 - /// - /// 源字节数组 - /// 压缩等级 - /// 密码 - /// 压缩后的字节数组 - public static byte[] CompressBytes(byte[] sourceBytes, string password = null, int compressionLevel = 6) - { - byte[] result = new byte[] { }; + #region 字节压缩与解压 + + /// + /// 压缩字节数组 + /// + /// 源字节数组 + /// 压缩等级 + /// 密码 + /// 压缩后的字节数组 + public static byte[] CompressBytes(byte[] sourceBytes, string password, int compressionLevel = 6) + { + byte[] result = new byte[] { }; - if (sourceBytes.Length > 0) + if (sourceBytes.Length > 0) + { + try { - try + using (MemoryStream tempStream = new MemoryStream()) { - using (MemoryStream tempStream = new MemoryStream()) + using (MemoryStream readStream = new MemoryStream(sourceBytes)) { - using (MemoryStream readStream = new MemoryStream(sourceBytes)) + using (ZipOutputStream zipStream = new ZipOutputStream(tempStream)) { - using (ZipOutputStream zipStream = new ZipOutputStream(tempStream)) - { - zipStream.Password = password;//设置密码 - zipStream.SetLevel(CheckCompressionLevel(compressionLevel));//设置压缩等级 - - ZipEntry zipEntry = new ZipEntry("ZipBytes"); - zipEntry.DateTime = DateTime.Now; - zipEntry.Size = sourceBytes.Length; - zipStream.PutNextEntry(zipEntry); - int readLength = 0; - byte[] buffer = new byte[BufferSize]; + zipStream.Password = password;//设置密码 + zipStream.SetLevel(CheckCompressionLevel(compressionLevel));//设置压缩等级 - do - { - readLength = readStream.Read(buffer, 0, BufferSize); - zipStream.Write(buffer, 0, readLength); - } while (readLength == BufferSize); + ZipEntry zipEntry = new ZipEntry("ZipBytes"); + zipEntry.DateTime = DateTime.Now; + zipEntry.Size = sourceBytes.Length; + zipStream.PutNextEntry(zipEntry); + int readLength = 0; + byte[] buffer = new byte[BufferSize]; - readStream.Close(); - zipStream.Flush(); - zipStream.Finish(); - result = tempStream.ToArray(); - zipStream.Close(); - } + do + { + readLength = readStream.Read(buffer, 0, BufferSize); + zipStream.Write(buffer, 0, readLength); + } while (readLength == BufferSize); + + readStream.Close(); + zipStream.Flush(); + zipStream.Finish(); + result = tempStream.ToArray(); + zipStream.Close(); } } } - catch (System.Exception ex) - { - throw new Exception("压缩字节数组发生错误", ex); - } } - - return result; + catch (System.Exception ex) + { + throw new Exception("压缩字节数组发生错误", ex); + } } - /// - /// 解压字节数组 - /// - /// 源字节数组 - /// 密码 - /// 解压后的字节数组 - public static byte[] DecompressBytes(byte[] sourceBytes, string password = null) - { - byte[] result = new byte[] { }; + return result; + } + + /// + /// 解压字节数组 + /// + /// 源字节数组 + /// 密码 + /// 解压后的字节数组 + public static byte[] DecompressBytes(byte[] sourceBytes, string password) + { + byte[] result = new byte[] { }; - if (sourceBytes.Length > 0) + if (sourceBytes.Length > 0) + { + try { - try + using (MemoryStream tempStream = new MemoryStream(sourceBytes)) { - using (MemoryStream tempStream = new MemoryStream(sourceBytes)) + using (MemoryStream writeStream = new MemoryStream()) { - using (MemoryStream writeStream = new MemoryStream()) + using (ZipInputStream zipStream = new ZipInputStream(tempStream)) { - using (ZipInputStream zipStream = new ZipInputStream(tempStream)) + zipStream.Password = password; + ZipEntry zipEntry = zipStream.GetNextEntry(); + + if (zipEntry != null) { - zipStream.Password = password; - ZipEntry zipEntry = zipStream.GetNextEntry(); + byte[] buffer = new byte[BufferSize]; + int readLength = 0; - if (zipEntry != null) + do { - byte[] buffer = new byte[BufferSize]; - int readLength = 0; - - do - { - readLength = zipStream.Read(buffer, 0, BufferSize); - writeStream.Write(buffer, 0, readLength); - } while (readLength == BufferSize); - - writeStream.Flush(); - result = writeStream.ToArray(); - writeStream.Close(); - } - zipStream.Close(); + readLength = zipStream.Read(buffer, 0, BufferSize); + writeStream.Write(buffer, 0, readLength); + } while (readLength == BufferSize); + + writeStream.Flush(); + result = writeStream.ToArray(); + writeStream.Close(); } + zipStream.Close(); } } } - catch (System.Exception ex) - { - throw new Exception("解压字节数组发生错误", ex); - } } - return result; + catch (System.Exception ex) + { + throw new Exception("解压字节数组发生错误", ex); + } } + return result; + } - #endregion + #endregion - #region 文件压缩与解压 + #region 文件压缩与解压 - /// - /// 为压缩准备文件系统对象 - /// - /// - /// - private static Dictionary PrepareFileSystementities(IEnumerable sourceFileEntityPathList) + /// + /// 为压缩准备文件系统对象 + /// + /// + /// + private static Dictionary PrepareFileSystementities(IEnumerable sourceFileEntityPathList) + { + Dictionary fileEntityDictionary = new Dictionary();//文件字典 + string parentDirectoryPath = ""; + foreach (string fileEntityPath in sourceFileEntityPathList) { - Dictionary fileEntityDictionary = new Dictionary();//文件字典 - string parentDirectoryPath = ""; - foreach (string fileEntityPath in sourceFileEntityPathList) + string path = fileEntityPath; + //保证传入的文件夹也被压缩进文件 + if (path.EndsWith(@"\")) { - string path = fileEntityPath; - //保证传入的文件夹也被压缩进文件 - if (path.EndsWith(@"\")) - { - path = path.Remove(path.LastIndexOf(@"\")); - } + path = path.Remove(path.LastIndexOf(@"\")); + } - parentDirectoryPath = Path.GetDirectoryName(path) + @"\"; + parentDirectoryPath = Path.GetDirectoryName(path) + @"\"; - if (parentDirectoryPath.EndsWith(@":\\"))//防止根目录下把盘符压入的错误 - { - parentDirectoryPath = parentDirectoryPath.Replace(@"\\", @"\"); - } + if (parentDirectoryPath.EndsWith(@":\\"))//防止根目录下把盘符压入的错误 + { + parentDirectoryPath = parentDirectoryPath.Replace(@"\\", @"\"); + } - //获取目录中所有的文件系统对象 - Dictionary subDictionary = GetAllFileSystemEntities(path, parentDirectoryPath); + //获取目录中所有的文件系统对象 + Dictionary subDictionary = GetAllFileSystemEntities(path, parentDirectoryPath); - //将文件系统对象添加到总的文件字典中 - foreach (string key in subDictionary.Keys) + //将文件系统对象添加到总的文件字典中 + foreach (string key in subDictionary.Keys) + { + if (!fileEntityDictionary.ContainsKey(key))//检测重复项 { - if (!fileEntityDictionary.ContainsKey(key))//检测重复项 - { - fileEntityDictionary.Add(key, subDictionary[key]); - } + fileEntityDictionary.Add(key, subDictionary[key]); } } - return fileEntityDictionary; } + return fileEntityDictionary; + } - /// - /// 压缩单个文件/文件夹 - /// - /// 源文件/文件夹路径列表 - /// 压缩文件路径 - /// 注释信息 - /// 压缩密码 - /// 压缩等级,范围从0到9,可选,默认为6 - /// - public static bool CompressFile(string path, string zipFilePath, - string comment = null, string password = null, int compressionLevel = 6) - { - return CompressFile(new string[] { path }, zipFilePath, comment, password, compressionLevel); - } + /// + /// 压缩单个文件/文件夹 + /// + /// 源文件/文件夹路径列表 + /// 压缩文件路径 + /// 注释信息 + /// 压缩密码 + /// 压缩等级,范围从0到9,可选,默认为6 + /// + public static bool CompressFile(string path, string zipFilePath, + string comment, string password, int compressionLevel = 6) + { + return CompressFile(new string[] { path }, zipFilePath, comment, password, compressionLevel); + } - /// - /// 压缩多个文件/文件夹 - /// - /// 源文件/文件夹路径列表 - /// 压缩文件路径 - /// 注释信息 - /// 压缩密码 - /// 压缩等级,范围从0到9,可选,默认为6 - /// - public static bool CompressFile(IEnumerable sourceList, string zipFilePath, - string comment = null, string password = null, int compressionLevel = 6) - { - bool result = false; + /// + /// 压缩多个文件/文件夹 + /// + /// 源文件/文件夹路径列表 + /// 压缩文件路径 + /// 注释信息 + /// 压缩密码 + /// 压缩等级,范围从0到9,可选,默认为6 + /// + public static bool CompressFile(IEnumerable sourceList, string zipFilePath, + string comment, string password, int compressionLevel = 6) + { + bool result = false; - try + try + { + //检测目标文件所属的文件夹是否存在,如果不存在则建立 + string zipFileDirectory = Path.GetDirectoryName(zipFilePath); + if (!Directory.Exists(zipFileDirectory)) { - //检测目标文件所属的文件夹是否存在,如果不存在则建立 - string zipFileDirectory = Path.GetDirectoryName(zipFilePath); - if (!Directory.Exists(zipFileDirectory)) - { - Directory.CreateDirectory(zipFileDirectory); - } + Directory.CreateDirectory(zipFileDirectory); + } - Dictionary dictionaryList = PrepareFileSystementities(sourceList); + Dictionary dictionaryList = PrepareFileSystementities(sourceList); - using (ZipOutputStream zipStream = new ZipOutputStream(File.Create(zipFilePath))) - { - zipStream.Password = password;//设置密码 - zipStream.SetComment(comment);//添加注释 - zipStream.SetLevel(CheckCompressionLevel(compressionLevel));//设置压缩等级 + using (ZipOutputStream zipStream = new ZipOutputStream(File.Create(zipFilePath))) + { + zipStream.Password = password;//设置密码 + zipStream.SetComment(comment);//添加注释 + zipStream.SetLevel(CheckCompressionLevel(compressionLevel));//设置压缩等级 - foreach (string key in dictionaryList.Keys)//从字典取文件添加到压缩文件 + foreach (string key in dictionaryList.Keys)//从字典取文件添加到压缩文件 + { + if (File.Exists(key))//判断是文件还是文件夹 { - if (File.Exists(key))//判断是文件还是文件夹 + FileInfo fileItem = new FileInfo(key); + + using (FileStream readStream = fileItem.Open(FileMode.Open, + FileAccess.Read, FileShare.Read)) { - FileInfo fileItem = new FileInfo(key); + ZipEntry zipEntry = new ZipEntry(dictionaryList[key]); + zipEntry.DateTime = fileItem.LastWriteTime; + zipEntry.Size = readStream.Length; + zipStream.PutNextEntry(zipEntry); + int readLength = 0; + byte[] buffer = new byte[BufferSize]; - using (FileStream readStream = fileItem.Open(FileMode.Open, - FileAccess.Read, FileShare.Read)) + do { - ZipEntry zipEntry = new ZipEntry(dictionaryList[key]); - zipEntry.DateTime = fileItem.LastWriteTime; - zipEntry.Size = readStream.Length; - zipStream.PutNextEntry(zipEntry); - int readLength = 0; - byte[] buffer = new byte[BufferSize]; - - do - { - readLength = readStream.Read(buffer, 0, BufferSize); - zipStream.Write(buffer, 0, readLength); - } while (readLength == BufferSize); + readLength = readStream.Read(buffer, 0, BufferSize); + zipStream.Write(buffer, 0, readLength); + } while (readLength == BufferSize); - readStream.Close(); - } - } - else//对文件夹的处理 - { - ZipEntry zipEntry = new ZipEntry(dictionaryList[key] + "/"); - zipStream.PutNextEntry(zipEntry); + readStream.Close(); } } - - zipStream.Flush(); - zipStream.Finish(); - zipStream.Close(); + else//对文件夹的处理 + { + ZipEntry zipEntry = new ZipEntry(dictionaryList[key] + "/"); + zipStream.PutNextEntry(zipEntry); + } } - result = true; - } - catch (System.Exception ex) - { - throw new Exception("压缩文件失败", ex); + zipStream.Flush(); + zipStream.Finish(); + zipStream.Close(); } - return result; + result = true; } + catch (System.Exception ex) + { + throw new Exception("压缩文件失败", ex); + } + + return result; + } + + /// + /// 解压文件到指定文件夹 + /// + /// 压缩文件 + /// 目标文件夹,如果为空则解压到当前文件夹下 + /// 密码 + /// + public static bool DecomparessFile(string sourceFile, string destinationDirectory, string password) + { + bool result = false; - /// - /// 解压文件到指定文件夹 - /// - /// 压缩文件 - /// 目标文件夹,如果为空则解压到当前文件夹下 - /// 密码 - /// - public static bool DecomparessFile(string sourceFile, string destinationDirectory = null, string password = null) + if (!File.Exists(sourceFile)) { - bool result = false; + throw new FileNotFoundException("要解压的文件不存在", sourceFile); + } - if (!File.Exists(sourceFile)) - { - throw new FileNotFoundException("要解压的文件不存在", sourceFile); - } + if (string.IsNullOrWhiteSpace(destinationDirectory)) + { + destinationDirectory = Path.GetDirectoryName(sourceFile); + } - if (string.IsNullOrWhiteSpace(destinationDirectory)) + try + { + if (!Directory.Exists(destinationDirectory)) { - destinationDirectory = Path.GetDirectoryName(sourceFile); + Directory.CreateDirectory(destinationDirectory); } - try + using (ZipInputStream zipStream = new ZipInputStream(File.Open(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))) { - if (!Directory.Exists(destinationDirectory)) - { - Directory.CreateDirectory(destinationDirectory); - } + zipStream.Password = password; + ZipEntry zipEntry = zipStream.GetNextEntry(); - using (ZipInputStream zipStream = new ZipInputStream(File.Open(sourceFile, FileMode.Open, - FileAccess.Read, FileShare.Read))) + while (zipEntry != null) { - zipStream.Password = password; - ZipEntry zipEntry = zipStream.GetNextEntry(); - - while (zipEntry != null) + if (zipEntry.IsDirectory)//如果是文件夹则创建 { - if (zipEntry.IsDirectory)//如果是文件夹则创建 - { - Directory.CreateDirectory(Path.Combine(destinationDirectory, - Path.GetDirectoryName(zipEntry.Name))); - } - else + Directory.CreateDirectory(Path.Combine(destinationDirectory, Path.GetDirectoryName(zipEntry.Name))); + } + else + { + string fileName = Path.GetFileName(zipEntry.Name); + if (!string.IsNullOrEmpty(fileName) && fileName.Trim().Length > 0) { - string fileName = Path.GetFileName(zipEntry.Name); - if (!string.IsNullOrEmpty(fileName) && fileName.Trim().Length > 0) + FileInfo fileItem = new FileInfo(Path.Combine(destinationDirectory, zipEntry.Name)); + if (fileItem.Directory is null) + continue; + if (!Directory.Exists(fileItem.Directory.FullName)) + Directory.CreateDirectory(fileItem.Directory.FullName); + using (FileStream writeStream = fileItem.Create()) { - FileInfo fileItem = new FileInfo(Path.Combine(destinationDirectory, zipEntry.Name)); - if (fileItem.Directory is null) - continue; - if (!Directory.Exists(fileItem.Directory.FullName)) - Directory.CreateDirectory(fileItem.Directory.FullName); - using (FileStream writeStream = fileItem.Create()) + byte[] buffer = new byte[BufferSize]; + int readLength = 0; + + do { - byte[] buffer = new byte[BufferSize]; - int readLength = 0; - - do - { - readLength = zipStream.Read(buffer, 0, BufferSize); - writeStream.Write(buffer, 0, readLength); - } while (readLength == BufferSize); - - writeStream.Flush(); - writeStream.Close(); - } - fileItem.LastWriteTime = zipEntry.DateTime; + readLength = zipStream.Read(buffer, 0, BufferSize); + writeStream.Write(buffer, 0, readLength); + } while (readLength == BufferSize); + + writeStream.Flush(); + writeStream.Close(); } + fileItem.LastWriteTime = zipEntry.DateTime; } - zipEntry = zipStream.GetNextEntry();//获取下一个文件 } - - zipStream.Close(); + zipEntry = zipStream.GetNextEntry();//获取下一个文件 } - result = true; - } - catch (System.Exception ex) - { - throw new Exception("文件解压发生错误", ex); - } - return result; + zipStream.Close(); + } + result = true; } - - - public static bool FastDecomparessFile(string sourceFile, string destinationDirectory = null) + catch (Exception ex) { - bool result = false; + throw new Exception("文件解压发生错误", ex); + } - if (!File.Exists(sourceFile)) - { - throw new FileNotFoundException("要解压的文件不存在", sourceFile); - } + return result; + } - if (string.IsNullOrWhiteSpace(destinationDirectory)) - { - destinationDirectory = Path.GetDirectoryName(sourceFile); - } - try - { - if (!Directory.Exists(destinationDirectory)) - { - Directory.CreateDirectory(destinationDirectory); - } + public static bool FastDecomparessFile(string sourceFile, string destinationDirectory) + { + bool result = false; - FastZip fastZip = new FastZip(); - string fileFilter = null; + if (!File.Exists(sourceFile)) + { + throw new FileNotFoundException("要解压的文件不存在", sourceFile); + } - fastZip.ExtractZip(sourceFile, destinationDirectory, fileFilter); + if (string.IsNullOrWhiteSpace(destinationDirectory)) + { + destinationDirectory = Path.GetDirectoryName(sourceFile); + } - result = true; - } - catch (Exception ex) + try + { + if (!Directory.Exists(destinationDirectory)) { - throw new Exception("文件解压发生错误", ex); + Directory.CreateDirectory(destinationDirectory); } - return result; + FastZip fastZip = new FastZip(); + string fileFilter = null; + + fastZip.ExtractZip(sourceFile, destinationDirectory, fileFilter); + + result = true; + } + catch (Exception ex) + { + throw new Exception("文件解压发生错误", ex); } - #endregion + return result; } + + #endregion } diff --git a/src/PluginCore/lmplements/CollectibleAssemblyLoadContext.cs b/src/PluginCore/lmplements/CollectibleAssemblyLoadContext.cs deleted file mode 100644 index 8ac30809..00000000 --- a/src/PluginCore/lmplements/CollectibleAssemblyLoadContext.cs +++ /dev/null @@ -1,43 +0,0 @@ -//=================================================== -// License: Apache-2.0 -// Contributors: yiyungent@gmail.com -// Project: https://moeci.com/PluginCore -// GitHub: https://github.com/yiyungent/PluginCore -//=================================================== - - - -using PluginCore.Interfaces; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Runtime.Loader; -using System.Text; - -namespace PluginCore.lmplements -{ - /// - /// 一个可回收的程序集加载上下文 - /// 在整个插件加载上下文的设计上,每个插件都使用一个单独的CollectibleAssemblyLoadContext来加载,所有插件的CollectibleAssemblyLoadContext都放在一个PluginsLoadContext对象中 - /// - public class CollectibleAssemblyLoadContext : AssemblyLoadContext, IPluginContext, ICollectibleAssemblyLoadContext - { - public string PluginId - { - get - { - return this.Name ?? ""; - } - } - - public CollectibleAssemblyLoadContext(string? name) - : base(isCollectible: true, name: name) - { - } - - protected override Assembly Load(AssemblyName name) - { - return null; - } - } -} diff --git a/src/PluginCore/lmplements/LazyPluginLoadContext.cs b/src/PluginCore/lmplements/LazyPluginLoadContext.cs deleted file mode 100644 index 0c620bec..00000000 --- a/src/PluginCore/lmplements/LazyPluginLoadContext.cs +++ /dev/null @@ -1,81 +0,0 @@ -//=================================================== -// License: Apache-2.0 -// Contributors: yiyungent@gmail.com -// Project: https://moeci.com/PluginCore -// GitHub: https://github.com/yiyungent/PluginCore -//=================================================== - - - -using PluginCore.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Runtime.Loader; -using System.Text; -using System.Linq; - -namespace PluginCore.lmplements -{ - /// - /// 修改自下方 - /// https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support - /// 此方法依赖于 插件项目生成的 HelloWorldPlugin.deps.json 与 runtimes 文件夹, - /// 虽然官方文档没写, 但最好还带上 HelloWorldPlugin.runtimeconfig.json - /// 插件项目 .csproj 其它注意, 看文档 - /// - public class LazyPluginLoadContext : CollectibleAssemblyLoadContext, IPluginContext - { - private AssemblyDependencyResolver _resolver; - - /// - /// 加了一个可回收 - /// - /// - public LazyPluginLoadContext(string pluginId, string pluginMainDllFilePath) : base(name: pluginId) - { - _resolver = new AssemblyDependencyResolver(pluginMainDllFilePath); - } - - protected override Assembly Load(AssemblyName assemblyName) - { - // 1. 先尝试 从本插件文件夹中搜索 - string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); - if (assemblyPath != null) - { - //return LoadFromAssemblyPath(assemblyPath); - using (var fs = new FileStream(assemblyPath, FileMode.Open)) - { - // 使用此方法, 就不会导致dll被锁定 - // 锁定dll 会导致: 1. 无法通过复制粘贴替换 更新 2. 无法删除 - return LoadFromStream(fs); - } - } - // 2. 再尝试 从其他启用的插件文件夹中搜索 - // 实测: 可在下方搜索, 主程序包括的 assemblyName 与 启用插件加载的 assemblyName 都会位于其中 - var assList = AppDomain.CurrentDomain.GetAssemblies(); - var temp = assList.FirstOrDefault(m => m.GetName().FullName == assemblyName.FullName); - if (temp != null) - { - return temp; - } - - // 3. 最后搜索不到, 返回 null, 即代表使用主程序提供, 如果最后几次都为 null, 则会报错 - // 当启用本插件/触碰到本插件中的一些类型时, 而当主程序中没有提供相关的此 assemblyName 时, 也会触发此方法 来尝试加载 - - return null; - } - - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { - string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); - if (libraryPath != null) - { - return LoadUnmanagedDllFromPath(libraryPath); - } - - return IntPtr.Zero; - } - } -} diff --git a/src/PluginCore/lmplements/PluginContextManager.cs b/src/PluginCore/lmplements/PluginContextManager.cs deleted file mode 100644 index 913b005a..00000000 --- a/src/PluginCore/lmplements/PluginContextManager.cs +++ /dev/null @@ -1,107 +0,0 @@ -//=================================================== -// License: Apache-2.0 -// Contributors: yiyungent@gmail.com -// Project: https://moeci.com/PluginCore -// GitHub: https://github.com/yiyungent/PluginCore -//=================================================== - - - -using PluginCore.Interfaces; -using PluginCore.lmplements; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Loader; -using System.Text; - -namespace PluginCore.lmplements -{ - /// - /// 启用插件时加载进来, 禁用插件时移除释放 - /// 只有已启用的插件才有上下文 - /// https://www.cnblogs.com/lwqlun/p/11395828.html - /// 1.当加载插件的时候,我们需要将当前插件的程序集加载上下文放到_pluginContexts字典中。字典的key是插件的名称,字典的value是插件的程序集加载上下文。 - /// 2.当移除一个插件的时候,我们需要使用Unload方法,来释放当前的程序集加载上下文。 - /// - public class PluginContextManager : IPluginContextManager - { - #region Fields - - private static int _newCount; - - //private Dictionary - // _pluginContexts; - - private Dictionary _pluginContexts - { - get - { - return PluginContextStore.PluginContexts; - } - } - - #endregion - - #region Ctor - public PluginContextManager() - { - //_pluginContexts = new Dictionary(); - - #region 计数 - _newCount++; - if (_newCount > 1) - { -#if DEBUG - Utils.LogUtil.Error($"警告: {nameof(PluginContextManager)} 被 new {_newCount} 次"); -#endif - } - #endregion - } - #endregion - - #region Methods - - public List All() - { - return _pluginContexts.Select(p => p.Value).ToList(); - } - - public bool Any(string pluginId) - { - return _pluginContexts.ContainsKey(pluginId); - } - - public void Remove(string pluginId) - { - if (_pluginContexts.ContainsKey(pluginId)) - { - _pluginContexts[pluginId].Unload(); - _pluginContexts.Remove(pluginId); - } - } - - public IPluginContext Get(string pluginId) - { - return _pluginContexts[pluginId]; - } - - public void Add(string pluginId, IPluginContext context) - { - _pluginContexts.Add(pluginId, context); - } - #endregion - - } - - /// - /// fixed: 由于 单例失败, 因此临时解决方案 - /// - public static class PluginContextStore - { - public static Dictionary PluginContexts = new Dictionary(); - } - - - -} diff --git a/src/PluginCore/lmplements/PluginContextPack.cs b/src/PluginCore/lmplements/PluginContextPack.cs deleted file mode 100644 index 7bae738c..00000000 --- a/src/PluginCore/lmplements/PluginContextPack.cs +++ /dev/null @@ -1,51 +0,0 @@ -//=================================================== -// License: Apache-2.0 -// Contributors: yiyungent@gmail.com -// Project: https://moeci.com/PluginCore -// GitHub: https://github.com/yiyungent/PluginCore -//=================================================== - - - -using PluginCore.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Runtime.Loader; -using System.Text; - -namespace PluginCore.lmplements -{ - public class PluginContextPack : IPluginContextPack - { - public IPluginContext Pack(string pluginId) - { - #region 加载插件主dll - - // 插件的主dll, 不包括插件项目引用的dll - string pluginMainDllFilePath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId, $"{pluginId}.dll"); - // 此插件的 加载上下文 - var context = new PluginLoadContext(pluginId, pluginMainDllFilePath); - Assembly pluginMainAssembly; - // 微软文档推荐 LoadFromAssemblyName - pluginMainAssembly = context.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(pluginMainDllFilePath))); - - #region 第2种方法: 未在这种情况下测试 - //using (var fs = new FileStream(pluginMainDllFilePath, FileMode.Open)) - //{ - // // 使用此方法, 就不会导致dll被锁定 - // pluginMainAssembly = context.LoadFromStream(fs); - - // // 加载其中的控制器 - // _pluginControllerManager.AddControllers(pluginMainAssembly); - //} - #endregion - - #endregion - - - return context; - } - } -} diff --git a/src/PluginCore/lmplements/PluginContextPackV1.cs b/src/PluginCore/lmplements/PluginContextPackV1.cs deleted file mode 100644 index 665e0b7b..00000000 --- a/src/PluginCore/lmplements/PluginContextPackV1.cs +++ /dev/null @@ -1,103 +0,0 @@ -//=================================================== -// License: Apache-2.0 -// Contributors: yiyungent@gmail.com -// Project: https://moeci.com/PluginCore -// GitHub: https://github.com/yiyungent/PluginCore -//=================================================== - - - -using PluginCore.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.Loader; -using System.Text; - -namespace PluginCore.lmplements -{ - public class PluginContextPackV1 : IPluginContextPack - { - public IList SkipDlls { get; set; } - - public IPluginContext Pack(string pluginId) - { - SkipDlls = new List(); - - #region 跳过1: 程序目录下 以单独dll 出现 - // 获取主程序 已经存在的(不许在再加载的 dll) - string basePath = AppContext.BaseDirectory; - //Console.WriteLine($"PluginCore.PluginManager: basePath: {basePath}"); - // { "Core.dll", "Domain.dll", "Framework.dll", "Services.dll", "Repositories.dll", "PluginCore.dll", ... } - SkipDlls = new DirectoryInfo(basePath).GetFiles("*.dll").Select(m => m.Name).ToList(); - #endregion - - #region 跳过2: 打包进入1个dll 或 打包进 1个exe - // 注意: 用户可能将 dll 打包在 一个dll中, 或打包进 exe, 因此 通过此方式 确保跳过 - // 主程序所有 位于 AssemblyLoadContext.Default - List skipAssembliesName = AssemblyLoadContext.Default.Assemblies - .Select(m => m.GetName()) - .Select(m => m.Name).ToList(); - foreach (var name in skipAssembliesName) - { - this.SkipDlls.Add($"{name}.dll"); - } - #endregion - - - // 此插件的 加载上下文 - var context = new CollectibleAssemblyLoadContext(pluginId); - - // TODO:未测试 加载插件引用的dll: 方法二: - //AssemblyName[] referenceAssemblyNames = pluginMainAssembly.GetReferencedAssemblies(); - //foreach (var assemblyName in referenceAssemblyNames) - //{ - // context.LoadFromAssemblyName(assemblyName); - //} - - // 跳过不需要加载的 dll, eg: ASP.NET Core Shared Framework, 主程序中已有dll - string[] skipDlls = SkipDlls.ToArray(); //new string[] { "Core.dll", "Domain.dll", "Framework.dll", "Services.dll", "Repositories.dll", "PluginCore.dll" }; - - // 注意: 先加载插件引用的dll, 因为可能在插件主dll的Controller中立即使用了引用的dll - - #region 加载插件引用的dll - // 加载插件引用的dll - // eg: xxx/Plugins/HelloWorld - string pluginDirPath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId); - var pluginDir = new DirectoryInfo(pluginDirPath); - // 插件引用的所有dll (排除 主dll 和 skipDlls ) - // 注意: 主程序中已有dll 必须跳过, 应为这些默认Context中已经加载, 而如果插件Context再次加载, 则认为这两个是不同Assembly, 导致其中的Type转换失败 - // 这里简单来说,意思就是当在一个自定义LoadContext中加载程序集的时候,如果找不到这个程序集,程序会自动去默认LoadContext中查找,如果默认LoadContext中都找不到,就会返回null。 - // 这里我突然想到会不会是因为DemoPlugin1、DemoPlugin2以及主站点的AssemblyLoadContext都加载了Mystique.Core.dll程序集的缘故,虽然他们加载的是同一个程序集,但是因为LoadContext不同,所以系统认为它是2个程序集。 - // 参考: https://www.cnblogs.com/lwqlun/p/12930713.html - var allReferenceFileInfos = pluginDir.GetFiles("*.dll").Where(p => - p.Name != $"{pluginId}.dll" - && - !skipDlls.Contains(p.Name)); - foreach (FileInfo file in allReferenceFileInfos) - { - using (var sr = new StreamReader(file.OpenRead())) - { - context.LoadFromStream(sr.BaseStream); - } - } - #endregion - - #region 加载插件主dll - // 插件的主dll, 不包括插件项目引用的dll - string pluginMainDllFilePath = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId, $"{pluginId}.dll"); - Assembly pluginMainAssembly; - using (var fs = new FileStream(pluginMainDllFilePath, FileMode.Open)) - { - // 使用此方法, 就不会导致dll被锁定 - pluginMainAssembly = context.LoadFromStream(fs); - } - #endregion - - - return context; - } - } -} diff --git a/src/PluginCore/lmplements/PluginFinder.cs b/src/PluginCore/lmplements/PluginFinder.cs deleted file mode 100644 index 302240c3..00000000 --- a/src/PluginCore/lmplements/PluginFinder.cs +++ /dev/null @@ -1,308 +0,0 @@ -//=================================================== -// License: Apache-2.0 -// Contributors: yiyungent@gmail.com -// Project: https://moeci.com/PluginCore -// GitHub: https://github.com/yiyungent/PluginCore -//=================================================== - - - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.Loader; -using System.Text; -using Microsoft.Extensions.DependencyInjection; -using PluginCore.Infrastructure; -using PluginCore.Interfaces; -using PluginCore.IPlugins; - -namespace PluginCore.lmplements -{ - - /// - /// 插件发现者: 找启用的插件(1.plugin.config.json中启用 2. 有插件上下文) - /// TODO: 其实是没必要再效验plugin.config.json的,因为只有启用的插件才有上下文, 为了保险,暂时这么做 - /// 注意: 这意味着一个启用的插件需同时满足这两个条件 - /// - public class PluginFinder : IPluginFinder - { - #region Fields - - /// - /// 用来解析插件构造函数需要的服务 - /// - private readonly IServiceProvider _serviceProvider; - - /// - /// TODO: 未使用 - /// Key: 实现了 IPlugin 的插件类 - /// Value: 此插件类 允许的 行为(众多实现了 IPlugin 的钩子接口) - /// - private static ConcurrentDictionary> _pluginAllowedBehavior; - - #endregion - - public IPluginContextManager PluginContextManager { get; set; } - - #region Ctor - public PluginFinder(IPluginContextManager pluginContextManager, IServiceProvider serviceProvider) - { - this.PluginContextManager = pluginContextManager; - _serviceProvider = serviceProvider; - } - - static PluginFinder() - { - // TODO: 初始化: 默认为信任模式: 插件允许所有行为 - _pluginAllowedBehavior = new ConcurrentDictionary>(); - } - #endregion - - - #region 允许激活行为的 启用插件 - /// - /// TODO: 实现了指定接口或类型 的启用插件 并且 允许激活此行为(接口) - /// - /// TODO: 考虑后,还是没办法限制住插件恶意行为, 因为无法限制插件 修改配置文件 - /// - /// - /// - public IEnumerable ActivatedPlugins() - where TPlugin : IPlugin - { - var enablePlugins = EnablePlugins(); - foreach (var plugin in enablePlugins) - { - // TODO: 检查是否允许 此插件行为 - - - yield return plugin; - } - } - #endregion - - #region 实现了指定接口或类型 的启用插件 - /// - /// 实现了指定接口或类型 的启用插件 - /// - /// 可以是一个接口,一个抽象类,一个普通实现类, 只要实现了 即可 - /// - public IEnumerable<(TPlugin PluginInstance, string PluginId)> EnablePluginsFull() - where TPlugin : IPlugin // BasePlugin - { - // TODO: 目前这里还有问题, 不应该写为 BasePlugin, 不利于扩展, 不利于插件开发者自己实现 Install , Uninstall等 - - // 1.所有启用的插件 PluginId - var pluginConfigModel = PluginConfigModelFactory.Create(); - IList enablePluginIds = pluginConfigModel.EnabledPlugins; - foreach (var pluginId in enablePluginIds) - { - if (this.PluginContextManager.Any(pluginId)) - { - // 2.找到插件对应的Context - var context = this.PluginContextManager.Get(pluginId); - // 3.找插件 主 Assembly - // Assembly.FullName: HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null - Assembly pluginMainAssembly = context.Assemblies.Where(m => m.FullName.StartsWith($"{pluginId}, Version=")).FirstOrDefault(); - if (pluginMainAssembly == null) - { - continue; - } - // 4.从插件主Assembly中 找实现了 TPlugin 接口的 Type, 若有多个,只要一个 - Type pluginType = pluginMainAssembly.ExportedTypes.Where(m => - (m.BaseType == typeof(TPlugin) || m.GetInterfaces().Contains(typeof(TPlugin))) - && - !m.IsInterface - && - !m.IsAbstract - ).FirstOrDefault(); - if (pluginType == null) - { - continue; - } - // 5.实例化插件 Type - //(TPlugin)Activator.CreateInstance(pluginType,); - //try to resolve plugin as unregistered service - //object instance = EngineContext.Current.ResolveUnregistered(pluginType); - object instance = ResolveUnregistered(pluginType); - //try to get typed instance - TPlugin typedInstance = (TPlugin)instance; - if (typedInstance == null) - { - continue; - } - - yield return (PluginInstance: typedInstance, PluginId: pluginId); - } - } - - } - - /// - /// 实现了指定接口或类型 的启用插件 - /// - /// 可以是一个接口,一个抽象类,一个普通实现类, 只要实现了 即可 - /// - public IEnumerable EnablePlugins() - where TPlugin : IPlugin // BasePlugin - { - return EnablePluginsFull().Select(m => m.PluginInstance); - } - #endregion - - #region 所有启用的插件 - /// - /// 所有启用的插件 - /// - /// - public IEnumerable EnablePlugins() - { - return EnablePluginsFull().Select(m => m.PluginInstance); - } - - /// - /// 所有启用的插件 - /// - /// - public IEnumerable<(IPlugin PluginInstance, string PluginId)> EnablePluginsFull() - { - return EnablePluginsFull(); - } - - /// - /// 所有启用的插件 的 PluginId - /// - /// - public IEnumerable EnablePluginIds() - where TPlugin : IPlugin // BasePlugin - { - // 1.所有启用的插件 PluginId - var pluginConfigModel = PluginConfigModelFactory.Create(); - IList enablePluginIds = pluginConfigModel.EnabledPlugins; - foreach (var pluginId in enablePluginIds) - { - if (this.PluginContextManager.Any(pluginId)) - { - yield return pluginId; - } - } - } - - /// - /// 所有启用的插件 的 PluginId - /// - /// - public IEnumerable EnablePluginIds() - { - return EnablePluginIds(); - } - #endregion - - #region 获取指定 pluginId 的启用插件 - /// - /// 获取指定 pluginId 的启用插件 - /// - /// - /// 1.插件未启用返回null, 2.找不到此插件上下文返回null 3.找不到插件主dll返回null 4.插件主dll中找不到实现了IPlugin的Type返回null, 5.无法实例化插件返回null - public IPlugin Plugin(string pluginId) - { - // 1.所有启用的插件 PluginId - var pluginConfigModel = PluginConfigModelFactory.Create(); - IList enablePluginIds = pluginConfigModel.EnabledPlugins; - // 插件未启用返回null - if (!enablePluginIds.Contains(pluginId)) - { - return null; - } - - // 找不到此插件上下文返回null - if (!this.PluginContextManager.Any(pluginId)) - { - return null; - } - - // 2.找到插件对应的Context - var context = this.PluginContextManager.Get(pluginId); - // 3.找插件 主 Assembly - // Assembly.FullName: HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null - Assembly pluginMainAssembly = context.Assemblies.Where(m => m.FullName.StartsWith($"{pluginId}, Version=")).FirstOrDefault(); - // 找不到插件主dll返回null - if (pluginMainAssembly == null) - { - return null; - } - // 4.从插件主Assembly中 找实现了 TPlugin 接口的 Type, 若有多个,只要一个 - Type pluginType = pluginMainAssembly.ExportedTypes.Where(m => - (m.BaseType == typeof(IPlugin) || m.GetInterfaces().Contains(typeof(IPlugin))) - && - !m.IsInterface - && - !m.IsAbstract - ).FirstOrDefault(); - // 插件主dll中找不到实现了IPlugin的Type返回null - if (pluginType == null) - { - return null; - } - // 5.实例化插件 Type - //(TPlugin)Activator.CreateInstance(pluginType,); - //try to resolve plugin as unregistered service - //object instance = EngineContext.Current.ResolveUnregistered(pluginType); - object instance = ResolveUnregistered(pluginType); - //try to get typed instance - IPlugin typedInstance = (IPlugin)instance; - // 无法实例化插件返回null - if (typedInstance == null) - { - return null; - } - - return typedInstance; - } - #endregion - - #region 获取未IOC注册的类型实例 - /// - /// 获取未IOC注册的类型实例 - /// 此类型的构造函数可以依赖注入, 将通过ASP.NET Core 自带依赖注入系统进行注入 - /// - /// - /// - protected virtual object ResolveUnregistered(Type type) - { - - Exception innerException = null; - foreach (var constructor in type.GetConstructors()) - { - try - { - //try to resolve constructor parameters - var parameters = constructor.GetParameters().Select(parameter => - { - //var service = Resolve(parameter.ParameterType); - var service = _serviceProvider.GetService(parameter.ParameterType); - if (service == null) - throw new Exception("Unknown dependency"); - return service; - }); - - //all is ok, so create instance - return Activator.CreateInstance(type, parameters.ToArray()); - } - catch (Exception ex) - { - innerException = ex; - } - } - - throw new Exception("No constructor was found that had all the dependencies satisfied.", innerException); - - } - #endregion - - } -} diff --git a/src/PluginCore/lmplements/PluginLoadContext.cs b/src/PluginCore/lmplements/PluginLoadContext.cs deleted file mode 100644 index 5837b4f4..00000000 --- a/src/PluginCore/lmplements/PluginLoadContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -//=================================================== -// License: Apache-2.0 -// Contributors: yiyungent@gmail.com -// Project: https://moeci.com/PluginCore -// GitHub: https://github.com/yiyungent/PluginCore -//=================================================== - - - -using PluginCore.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Runtime.Loader; -using System.Text; -using System.Linq; - -namespace PluginCore.lmplements -{ - /// - /// LazyPluginLoadContext - /// - public class PluginLoadContext : LazyPluginLoadContext, IPluginContext - { - public PluginLoadContext(string pluginId, string pluginMainDllFilePath) : base(pluginId, pluginMainDllFilePath) - { - } - } -} diff --git a/src/PluginCore/lmplements/PluginManager.cs b/src/PluginCore/lmplements/PluginManager.cs deleted file mode 100644 index 160cd133..00000000 --- a/src/PluginCore/lmplements/PluginManager.cs +++ /dev/null @@ -1,56 +0,0 @@ -//=================================================== -// License: Apache-2.0 -// Contributors: yiyungent@gmail.com -// Project: https://moeci.com/PluginCore -// GitHub: https://github.com/yiyungent/PluginCore -//=================================================== - - - -using PluginCore.Infrastructure; -using PluginCore.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.Loader; -using System.Text; - -namespace PluginCore.lmplements -{ - /// - /// 一个插件的所有dll由 一个 管理 - /// 记录管理了所有 插件的 - /// 是对 的封装, 使其更好管理插件加载释放的行为 - /// - public class PluginManager : IPluginManager - { - public IPluginContextManager PluginContextManager { get; set; } - - public IPluginContextPack PluginContextPack { get; set; } - - public PluginManager(IPluginContextManager pluginContextManager, IPluginContextPack pluginContextPack) - { - this.PluginContextManager = pluginContextManager; - this.PluginContextPack = pluginContextPack; - } - - /// - /// 加载插件程序集 - /// - /// - public void LoadPlugin(string pluginId) - { - IPluginContext context = this.PluginContextPack.Pack(pluginId); - - // 这个插件加载上下文 放入 集合中 - this.PluginContextManager.Add(pluginId, context); - } - - public void UnloadPlugin(string pluginId) - { - this.PluginContextManager.Remove(pluginId); - } - } -} diff --git a/src/PluginCore/lmplements/PositivePluginLoadContext.cs b/src/PluginCore/lmplements/PositivePluginLoadContext.cs deleted file mode 100644 index aa56a1d8..00000000 --- a/src/PluginCore/lmplements/PositivePluginLoadContext.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.Reflection.Metadata; -//=================================================== -// License: Apache-2.0 -// Contributors: yiyungent@gmail.com -// Project: https://moeci.com/PluginCore -// GitHub: https://github.com/yiyungent/PluginCore -//=================================================== - - - -using PluginCore.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Runtime.Loader; -using System.Text; -using System.Linq; - -namespace PluginCore.lmplements -{ - /// - /// 修改自下方 - /// https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support - /// 此方法依赖于 插件项目生成的 HelloWorldPlugin.deps.json 与 runtimes 文件夹, - /// 虽然官方文档没写, 但最好还带上 HelloWorldPlugin.runtimeconfig.json - /// 插件项目 .csproj 其它注意, 看文档 - /// - public class PositivePluginLoadContext : CollectibleAssemblyLoadContext, IPluginContext - { - private AssemblyDependencyResolver _resolver; - - /// - /// 即 插件的 Entrypoint Assembly - /// - public AssemblyName MainAssemblyName{ get; protected set; } - - public IEnumerable ReferencedAssemblyNames { get; protected set; } - - /// - /// 加了一个可回收 - /// - /// - public PositivePluginLoadContext(string pluginId, string pluginMainDllFilePath) : base(name: pluginId) - { - _resolver = new AssemblyDependencyResolver(pluginMainDllFilePath); - - // 主动加载 存在此插件文件夹的 依赖项 - using (var mainFs = new FileStream(pluginMainDllFilePath, FileMode.Open)) - { - // 使用此方法, 就不会导致dll被锁定 - // 锁定dll 会导致: 1. 无法通过复制粘贴替换 更新 2. 无法删除 - Assembly mainAssembly = LoadFromStream(mainFs); - this.MainAssemblyName = AssemblyName.GetAssemblyName(pluginMainDllFilePath); - // this.MainAssemblyName = mainAssembly.GetName(); - AssemblyName[] referencedAssemblies = mainAssembly.GetReferencedAssemblies(); - this.ReferencedAssemblyNames = referencedAssemblies.AsEnumerable(); - - // TODO: - AssemblyFile assemblyFile = new AssemblyFile(); - - foreach (var referencedAssembly in referencedAssemblies) - { - string assemblyPath = _resolver.ResolveAssemblyToPath(referencedAssembly); - if (assemblyPath != null) - { - //return LoadFromAssemblyPath(assemblyPath); - using (var fs = new FileStream(assemblyPath, FileMode.Open)) - { - // 使用此方法, 就不会导致dll被锁定 - // 锁定dll 会导致: 1. 无法通过复制粘贴替换 更新 2. 无法删除 - LoadFromStream(fs); - } - } - } - } - } - - protected override Assembly Load(AssemblyName assemblyName) - { - // 1. 先尝试 从本插件文件夹中搜索 (无需, 因为前面已先主动加载, 再次被调用此方法只能是其它情况: 有不存在于此插件文件夹下的依赖项 被需要) - - // 2. 再尝试 从其他启用的插件文件夹中搜索 - // 实测: 可在下方搜索, 主程序包括的 assemblyName 与 启用插件加载的 assemblyName 都会位于其中 - var assList = AppDomain.CurrentDomain.GetAssemblies(); - var temp = assList.FirstOrDefault(m => m.GetName().FullName == assemblyName.FullName); - if (temp != null) - { - return temp; - } - - // 3. 最后搜索不到, 返回 null, 即代表使用主程序提供, 如果最后几次都为 null, 则会报错 - // 当启用本插件/触碰到本插件中的一些类型时, 而当主程序中没有提供相关的此 assemblyName 时, 也会触发此方法 来尝试加载 - - return null; - } - - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { - string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); - if (libraryPath != null) - { - return LoadUnmanagedDllFromPath(libraryPath); - } - - return IntPtr.Zero; - } - } -}