├── .gitignore ├── Directory.Build.props ├── LICENSE ├── PluginFactory.sln ├── README.md ├── doc └── api.md ├── package-icon.png ├── samples └── TestPluginA │ ├── ShareOptions.cs │ ├── TestConfigPlugin.cs │ ├── TestConfigPluginWithInit.cs │ ├── TestInitPlugin.cs │ ├── TestPlugin.cs │ └── TestPluginA.csproj ├── src ├── Abstractions │ ├── Configration │ │ ├── IPluginConfigrationProvider.cs │ │ ├── PluginConfigrationOptions.cs │ │ ├── PluginConfigrationProvider.cs │ │ └── PluginFactoryConfigration.cs │ ├── IPlugin.cs │ ├── IPluginContext.cs │ ├── IPluginFactory.cs │ ├── IPluginInitContext.cs │ ├── IPluginLoader.cs │ ├── ISupportConfigPlugin.cs │ ├── ISupportInitPlugin.cs │ ├── IsolationAssemblyLoadContext.cs │ ├── PluginAttribute.cs │ ├── PluginBase.cs │ ├── PluginFactoryOptions.cs │ ├── PluginInfo.cs │ ├── SupportConfigPluginBase.cs │ └── Xfrogcn.PluginFactory.Abstractions.csproj └── PluginFactory │ ├── DefaultPluginFactory.cs │ ├── DefaultPluginLoader.cs │ ├── HostBuilderExtensions.cs │ ├── PluginContext.cs │ ├── PluginFactoryServiceCollectionExtensions.cs │ ├── PluginInfoLogValue.cs │ ├── PluginInitContext.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ └── Xfrogcn.PluginFactory.csproj └── test ├── Abstractions.Test ├── IsolationLoader.cs ├── PluginConfigrationOptionsTest.cs ├── PluginConfigrationProviderTest.cs └── Xfrogcn.PluginFactory.Abstractions.Test.csproj ├── ClassA ├── ClassA.csproj ├── TestClassA.cs └── build.cmd ├── ClassB ├── ClassB.csproj ├── TestClassB.cs └── build.cmd └── PluginFactory.Test ├── DefaultPluginFactoryConfigTest.cs ├── DefaultPluginFactoryTest.cs ├── DefaultPluginLoaderTest.cs ├── TestPluginA.cs ├── TestPluginB.cs ├── TestPluginC.cs ├── TestPluginD.cs ├── TestPluginE.cs ├── Xfrogcn.PluginFactory.Test.csproj └── build-plugin.cmd /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true 235 | **/wwwroot/lib/ 236 | 237 | # RIA/Silverlight projects 238 | Generated_Code/ 239 | 240 | # Backup & report files from converting an old project file 241 | # to a newer Visual Studio version. Backup files are not needed, 242 | # because we have git ;-) 243 | _UpgradeReport_Files/ 244 | Backup*/ 245 | UpgradeLog*.XML 246 | UpgradeLog*.htm 247 | ServiceFabricBackup/ 248 | *.rptproj.bak 249 | 250 | # SQL Server files 251 | *.mdf 252 | *.ldf 253 | *.ndf 254 | 255 | # Business Intelligence projects 256 | *.rdl.data 257 | *.bim.layout 258 | *.bim_*.settings 259 | *.rptproj.rsuser 260 | 261 | # Microsoft Fakes 262 | FakesAssemblies/ 263 | 264 | # GhostDoc plugin setting file 265 | *.GhostDoc.xml 266 | 267 | # Node.js Tools for Visual Studio 268 | .ntvs_analysis.dat 269 | node_modules/ 270 | 271 | # Visual Studio 6 build log 272 | *.plg 273 | 274 | # Visual Studio 6 workspace options file 275 | *.opt 276 | 277 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 278 | *.vbw 279 | 280 | # Visual Studio LightSwitch build output 281 | **/*.HTMLClient/GeneratedArtifacts 282 | **/*.DesktopClient/GeneratedArtifacts 283 | **/*.DesktopClient/ModelManifest.xml 284 | **/*.Server/GeneratedArtifacts 285 | **/*.Server/ModelManifest.xml 286 | _Pvt_Extensions 287 | 288 | # Paket dependency manager 289 | .paket/paket.exe 290 | paket-files/ 291 | 292 | # FAKE - F# Make 293 | .fake/ 294 | 295 | # JetBrains Rider 296 | .idea/ 297 | *.sln.iml 298 | 299 | # CodeRush personal settings 300 | .cr/personal 301 | 302 | # Python Tools for Visual Studio (PTVS) 303 | __pycache__/ 304 | *.pyc 305 | 306 | # Cake - Uncomment if you are using it 307 | # tools/** 308 | # !tools/packages.config 309 | 310 | # Tabs Studio 311 | *.tss 312 | 313 | # Telerik's JustMock configuration file 314 | *.jmconfig 315 | 316 | # BizTalk build output 317 | *.btp.cs 318 | *.btm.cs 319 | *.odx.cs 320 | *.xsd.cs 321 | 322 | # OpenCover UI analysis results 323 | OpenCover/ 324 | 325 | # Azure Stream Analytics local run output 326 | ASALocalRun/ 327 | 328 | # MSBuild Binary and Structured Log 329 | *.binlog 330 | 331 | # NVidia Nsight GPU debugger configuration file 332 | *.nvuser 333 | 334 | # MFractors (Xamarin productivity tool) working folder 335 | .mfractor/ 336 | 337 | # Local History for Visual Studio 338 | .localhistory/ 339 | 340 | # BeatPulse healthcheck temp database 341 | healthchecksdb 342 | **/output -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.0.1 4 | 王海波 5 | 王海波 6 | https://github.com/xfrogcn/pluginfactory 7 | git 8 | net5.0 9 | package-icon.png 10 | 11 | 12 | 13 | Xfrogcn Plugin Factory 14 | 15 | 16 | MIT 17 | 18 | 19 | xfrogcn@163.com 20 | 21 | true 22 | 23 | 24 | 25 | True 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 无叶菜 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PluginFactory.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30406.217 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xfrogcn.PluginFactory.Abstractions", "src\Abstractions\Xfrogcn.PluginFactory.Abstractions.csproj", "{DFAA6ADD-58E5-47AD-9277-A61B9809A083}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0CBBE3B7-974D-4065-987B-2F36A6D1F8D6}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{90495F73-ABF2-4B63-90AA-D6A4B50D65AF}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3687AC96-B900-49BA-8E61-BC1D42ED378E}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClassA", "test\ClassA\ClassA.csproj", "{DB2F5525-EBC1-44C6-9FCA-876A649E3609}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClassB", "test\ClassB\ClassB.csproj", "{CEC58C66-A2BF-41E5-A897-1B095D798E66}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xfrogcn.PluginFactory.Abstractions.Test", "test\Abstractions.Test\Xfrogcn.PluginFactory.Abstractions.Test.csproj", "{472C9DF9-A102-4286-A9BF-DD36FFCB3C96}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xfrogcn.PluginFactory", "src\PluginFactory\Xfrogcn.PluginFactory.csproj", "{9999A5B1-06ED-42EB-AA72-3EEBCD2FEAAA}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xfrogcn.PluginFactory.Test", "test\PluginFactory.Test\Xfrogcn.PluginFactory.Test.csproj", "{08409CE2-3EDA-4F50-ACCE-9BFB88839297}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestPluginA", "samples\TestPluginA\TestPluginA.csproj", "{024EA651-9C96-4669-8E20-00BDF6B9AB1C}" 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FD64627A-57ED-467B-B455-8A496D44FE4D}" 27 | ProjectSection(SolutionItems) = preProject 28 | Directory.Build.props = Directory.Build.props 29 | EndProjectSection 30 | EndProject 31 | Global 32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 33 | Debug|Any CPU = Debug|Any CPU 34 | Release|Any CPU = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {DFAA6ADD-58E5-47AD-9277-A61B9809A083}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {DFAA6ADD-58E5-47AD-9277-A61B9809A083}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {DFAA6ADD-58E5-47AD-9277-A61B9809A083}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {DFAA6ADD-58E5-47AD-9277-A61B9809A083}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {DB2F5525-EBC1-44C6-9FCA-876A649E3609}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {DB2F5525-EBC1-44C6-9FCA-876A649E3609}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {DB2F5525-EBC1-44C6-9FCA-876A649E3609}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {DB2F5525-EBC1-44C6-9FCA-876A649E3609}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {CEC58C66-A2BF-41E5-A897-1B095D798E66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {CEC58C66-A2BF-41E5-A897-1B095D798E66}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {CEC58C66-A2BF-41E5-A897-1B095D798E66}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {CEC58C66-A2BF-41E5-A897-1B095D798E66}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {472C9DF9-A102-4286-A9BF-DD36FFCB3C96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {472C9DF9-A102-4286-A9BF-DD36FFCB3C96}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {472C9DF9-A102-4286-A9BF-DD36FFCB3C96}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {472C9DF9-A102-4286-A9BF-DD36FFCB3C96}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {9999A5B1-06ED-42EB-AA72-3EEBCD2FEAAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {9999A5B1-06ED-42EB-AA72-3EEBCD2FEAAA}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {9999A5B1-06ED-42EB-AA72-3EEBCD2FEAAA}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {9999A5B1-06ED-42EB-AA72-3EEBCD2FEAAA}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {08409CE2-3EDA-4F50-ACCE-9BFB88839297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {08409CE2-3EDA-4F50-ACCE-9BFB88839297}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {08409CE2-3EDA-4F50-ACCE-9BFB88839297}.Release|Any CPU.ActiveCfg = Release|Any CPU 60 | {08409CE2-3EDA-4F50-ACCE-9BFB88839297}.Release|Any CPU.Build.0 = Release|Any CPU 61 | {024EA651-9C96-4669-8E20-00BDF6B9AB1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {024EA651-9C96-4669-8E20-00BDF6B9AB1C}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {024EA651-9C96-4669-8E20-00BDF6B9AB1C}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {024EA651-9C96-4669-8E20-00BDF6B9AB1C}.Release|Any CPU.Build.0 = Release|Any CPU 65 | EndGlobalSection 66 | GlobalSection(SolutionProperties) = preSolution 67 | HideSolutionNode = FALSE 68 | EndGlobalSection 69 | GlobalSection(NestedProjects) = preSolution 70 | {DFAA6ADD-58E5-47AD-9277-A61B9809A083} = {0CBBE3B7-974D-4065-987B-2F36A6D1F8D6} 71 | {DB2F5525-EBC1-44C6-9FCA-876A649E3609} = {3687AC96-B900-49BA-8E61-BC1D42ED378E} 72 | {CEC58C66-A2BF-41E5-A897-1B095D798E66} = {3687AC96-B900-49BA-8E61-BC1D42ED378E} 73 | {472C9DF9-A102-4286-A9BF-DD36FFCB3C96} = {3687AC96-B900-49BA-8E61-BC1D42ED378E} 74 | {9999A5B1-06ED-42EB-AA72-3EEBCD2FEAAA} = {0CBBE3B7-974D-4065-987B-2F36A6D1F8D6} 75 | {08409CE2-3EDA-4F50-ACCE-9BFB88839297} = {3687AC96-B900-49BA-8E61-BC1D42ED378E} 76 | {024EA651-9C96-4669-8E20-00BDF6B9AB1C} = {90495F73-ABF2-4B63-90AA-D6A4B50D65AF} 77 | EndGlobalSection 78 | GlobalSection(ExtensibilityGlobals) = postSolution 79 | SolutionGuid = {8C1A8E02-8867-4C7F-99AC-900D923F5575} 80 | EndGlobalSection 81 | EndGlobal 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # .NET Core 插件框架 2 | 3 | pluginfactory 是 .NET Core 下基于依赖注入实现的插件框架,此框架是插件化开发与依赖注入的完美集合,同时融入了 .NET Core 中的配置机制,可以很好地与 ASP.NET Core 等框架融合。 4 | 5 | - [.NET Core 插件框架](#net-core-插件框架) 6 | - [使用向导](#使用向导) 7 | - [安装](#安装) 8 | - [在主程序中启用](#在主程序中启用) 9 | - [通过`IHostBuilder`的`UsePluginFactory`方法启用插件库](#通过ihostbuilder的usepluginfactory方法启用插件库) 10 | - [通过`IServiceCollection`的`AddPluginFactory`方法启用插件库](#通过iservicecollection的addpluginfactory方法启用插件库) 11 | - [编写插件](#编写插件) 12 | - [插件启动](#插件启动) 13 | - [编写支持初始化的插件](#编写支持初始化的插件) 14 | - [使用插件配置](#使用插件配置) 15 | - [插件化 ASP.NET Core](#插件化-aspnet-core) 16 | 17 | 18 | ## 使用向导 19 | 20 | 示例项目可参考:`Xfrogcn.PluginFactory.Example` [Gitee地址](https://gitee.com/WuYeCai/Xfrogcn.PluginFactory.Example) [Github地址](https://github.com/xfrogcn/Xfrogcn.PluginFactory.Example) 21 | 22 | ### 安装 23 | 24 | 在主程序项目中添加`Xfrogcn.PluginFactory`包 25 | 26 | ```dotnet 27 | dotnet add package Xfrogcn.PluginFactory --version 1.0.0 28 | ``` 29 | 30 | 在插件项目中添加`Xfrogcn.PluginFactory.Abstractions`包 31 | 32 | ```dotnet 33 | dotnet add package Xfrogcn.PluginFactory.Abstractions --version 1.0.0 34 | ``` 35 | 36 | ### 在主程序中启用 37 | 38 | 可通过以下两种方式来启用插件库,一是通过在`Host`层级的Use机制以及在依赖注入`IServiceCollection`层级的Add机制,以下分别说明: 39 | 40 | #### 通过`IHostBuilder`的`UsePluginFactory`方法启用插件库 41 | 42 | ```c# 43 | var builder = Host.CreateDefaultBuilder(args); 44 | builder.UsePluginFactory(); 45 | ``` 46 | 47 | `UsePluginFactory`具有多个重载版本,详细请查看[API](./doc/api.md)文档 48 | 默认配置下,将使用程序运行目录下的`Plugins`目录作为插件程序集目录, 使用宿主配置文件作为插件配置文件(通常为appsettings.json) 49 | 你也可以通过使用带有`Assembly`或`IEnumerable`参数的版本直接传入插件所在的程序集 50 | 51 | #### 通过`IServiceCollection`的`AddPluginFactory`方法启用插件库 52 | 53 | ```c# 54 | var builder = Host.CreateDefaultBuilder(args) 55 | .ConfigureServices((hostContext, services) => 56 | { 57 | services.AddPluginFactory(); 58 | }); 59 | ``` 60 | 61 | `AddPluginFactory`具有多个重载版本,详细请查看[API](./doc/api.md)文档 62 | 默认配置下,将使用程序运行目录下的`Plugins`目录作为插件程序集目录 63 | 64 | `注意:` AddPluginFactory方法`不会`使用默认的配置文件作为插件配置,你需要显式地传入`IConfiguration`, 如果是在 ASP.NET Core 环境中,你可以在Startup类中直接获取到 65 | 66 | ### 编写插件 67 | 68 | 插件是实现了IPlugin接口的类,在插件库中也提供了PluginBase基类,一般从此类继承即可。标准插件具有启动和停止方法,通过`IPluginFactory`进行控制。 69 | 70 | 要编写插件,一般遵循以下步骤: 71 | 72 | 1. 创建插件项目(.NET Core 类库),如TestPluginA 73 | 1. 添加`Xfrogcn.PluginFactory.Abstractions`包 74 | 75 | ```nuget 76 | dotnet add package Xfrogcn.PluginFactory.Abstractions 77 | ``` 78 | 79 | 1. 创建插件类,如Plugin,从PluginBase继承 80 | 81 | ```c# 82 | public class Plugin : PluginBase 83 | { 84 | public override Task StartAsync(IPluginContext context) 85 | { 86 | Console.WriteLine("插件A已启动"); 87 | return base.StartAsync(context); 88 | } 89 | 90 | public override Task StopAsync(IPluginContext context) 91 | { 92 | Console.WriteLine("插件A已停止"); 93 | return base.StopAsync(context); 94 | } 95 | } 96 | ``` 97 | 98 | *启动或停止方法中可通过context中的ServiceProvider获取注入服务* 99 | 100 | 1. 通过`PluginAttribute`特性设置插件的元数据 101 | 102 | ```c# 103 | [Plugin(Alias = "PluginA", Description = "测试插件")] 104 | public class Plugin : PluginBase 105 | { 106 | } 107 | ``` 108 | 109 | *插件元数据以及插件载入的插件列表信息可以通过`IPluginLoader.PluginList`获取* 110 | 111 | ### 插件启动 112 | 113 | `IPluginFactory`本身实现了.NET Core扩展库的`IHostedService`机制,故如果你是在宿主环境下使用,如(ASP.NET Core),插件的启动及停止将自动跟随宿主进行 114 | 如果未使用宿主,可通过获取`IPluginFactory`实例调用相应方法来完成 115 | 116 | ```c# 117 | // 手动启动 118 | var pluginFactory = provider.GetRequiredService(); 119 | await pluginFactory.StartAsync(default); 120 | await pluginFactory.StopAsync(default); 121 | ``` 122 | 123 | ### 编写支持初始化的插件 124 | 125 | 在很多场景,我们需要在插件中控制宿主的依赖注入,如注入新的服务等,这时候我们可通过实现支持初始化的插件(`ISupportInitPlugin`)来实现,该接口的`Init`方法将在依赖注入构建之前调用,通过方法参数`IPluginInitContext`中的`ServiceCollection`可以控制宿主注入容器。 126 | 127 | ```c# 128 | [Plugin(Alias = "PluginA", Description = "测试插件")] 129 | public class Plugin : PluginBase, ISupportInitPlugin 130 | { 131 | public void Init(IPluginInitContext context) 132 | { 133 | // 注入服务 134 | //context.ServiceCollection.TryAddScoped(); 135 | } 136 | } 137 | ``` 138 | 139 | ### 使用插件配置 140 | 141 | 插件支持 .NET Core 扩展库中的Options及Configuration机制,你只需要从`SupportConfigPluginBase`类继承实现插件即可,其中TOptions泛型为插件的配置类型。插件配置自动从宿主配置或启用插件工厂时传入的配置中获取,插件配置位于配置下的Plugins节点,该节点下以插件类名称或插件别名(通过`PluginAttribute`特性指定)作为键名,此键之下为插件的配置,如以下配置文件: 142 | 143 | ```appsettings.json 144 | { 145 | "Plugins": { 146 | "PluginA": { 147 | "TestConfig": "Hello World" 148 | }, 149 | 150 | } 151 | } 152 | ``` 153 | 154 | 扩展PluginA实现配置: 155 | 156 | 1. 定义配置类,如PluginOptions 157 | 158 | ```c# 159 | public class PluginOptions 160 | { 161 | public string TestConfig { get; set; } 162 | } 163 | ``` 164 | 165 | 2. 实现插件 166 | 167 | ```c# 168 | [Plugin(Alias = "PluginA", Description = "测试插件")] 169 | public class Plugin : SupportConfigPluginBase, ISupportInitPlugin 170 | { 171 | 172 | public Plugin(IOptionsMonitor options) : base(options) 173 | { 174 | } 175 | 176 | public void Init(IPluginInitContext context) 177 | { 178 | // 注入服务 179 | //context.ServiceCollection.TryAddScoped(); 180 | Console.WriteLine($"Init 插件配置:{Options.TestConfig}"); 181 | } 182 | 183 | public override Task StartAsync(IPluginContext context) 184 | { 185 | Console.WriteLine("插件A已启动"); 186 | Console.WriteLine($"StartAsync 插件配置:{Options.TestConfig}"); 187 | return base.StartAsync(context); 188 | } 189 | 190 | public override Task StopAsync(IPluginContext context) 191 | { 192 | Console.WriteLine("插件A已停止"); 193 | return base.StopAsync(context); 194 | } 195 | ``` 196 | 197 | *注意:在插件初始化方法中也可使用注入的配置* 198 | 3. 跨插件配置 199 | 200 | 有些配置可能需要在多个插件中共享,此时你可通过`Plugins`下的`_Share`节点进行配置,此节点下配置将会被合并到插件配置中,可通过PluginOptions进行访问。 201 | 202 | ```appsettings.json 203 | { 204 | "Plugins": { 205 | "PluginA": { 206 | }, 207 | "_Share": { 208 | "TestConfig": "Hello World" 209 | } 210 | } 211 | } 212 | ``` 213 | 214 | ### 插件化 ASP.NET Core 215 | 216 | 要让 ASP.NET Core 获取得到插件中的控制器,你只需要在插件的初始化方法`Init`中,向MVC注入插件程序集: 217 | 218 | ```c# 219 | context.ServiceCollection.AddMvcCore() 220 | .AddApplicationPart(typeof(Plugin).Assembly); 221 | ``` 222 | -------------------------------------------------------------------------------- /doc/api.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | - [API](#api) 4 | - [IHostBuilder.UsePluginFactory 扩展方法](#ihostbuilderusepluginfactory-扩展方法) 5 | - [UsePluginFactory()](#usepluginfactory) 6 | - [UsePluginFactory(IConfiguration configuration)](#usepluginfactoryiconfiguration-configuration) 7 | - [UsePluginFactory(IConfiguration configuration, Assembly assembly)](#usepluginfactoryiconfiguration-configuration-assembly-assembly) 8 | - [UsePluginFactory(IConfiguration configuration, IEnumerable<Assembly> assemblies)](#usepluginfactoryiconfiguration-configuration-ienumerableassembly-assemblies) 9 | - [UsePluginFactory(Assembly assembly)](#usepluginfactoryassembly-assembly) 10 | - [UsePluginFactory(IEnumerable<Assembly> assemblies)](#usepluginfactoryienumerableassembly-assemblies) 11 | - [UsePluginFactory(Action<PluginFactoryOptions> options)](#usepluginfactoryactionpluginfactoryoptions-options) 12 | - [UsePluginFactory(IConfiguration configuration, Action<PluginFactoryOptions> options)](#usepluginfactoryiconfiguration-configuration-actionpluginfactoryoptions-options) 13 | - [IServiceCollection.AddPluginFactory 扩展方法](#iservicecollectionaddpluginfactory-扩展方法) 14 | - [AddPluginFactory()](#addpluginfactory) 15 | - [AddPluginFactory(IConfiguration configuration)](#addpluginfactoryiconfiguration-configuration) 16 | - [AddPluginFactory(IConfiguration configuration, Assembly assembly)](#addpluginfactoryiconfiguration-configuration-assembly-assembly) 17 | - [AddPluginFactory(IConfiguration configuration, IEnumerable<Assembly> assemblies)](#addpluginfactoryiconfiguration-configuration-ienumerableassembly-assemblies) 18 | - [AddPluginFactory(Action<PluginFactoryOptions> configureOptions)](#addpluginfactoryactionpluginfactoryoptions-configureoptions) 19 | - [AddPluginFactory(IConfiguration configuration, Action<PluginFactoryOptions> configureOptions)](#addpluginfactoryiconfiguration-configuration-actionpluginfactoryoptions-configureoptions) 20 | - [AddPluginFactory(PluginFactoryOptions options, IConfiguration configuration)](#addpluginfactorypluginfactoryoptions-options-iconfiguration-configuration) 21 | - [IPluginLoader](#ipluginloader) 22 | - [PluginList属性](#pluginlist属性) 23 | - [Load()](#load) 24 | - [Init()](#init) 25 | - [IPluginFactory](#ipluginfactory) 26 | - [StartAsync(CancellationToken cancellationToken)](#startasynccancellationtoken-cancellationtoken) 27 | - [StopAsync(CancellationToken cancellationToken)](#stopasynccancellationtoken-cancellationtoken) 28 | - [IPluginContext](#iplugincontext) 29 | - [PluginFactory属性](#pluginfactory属性) 30 | - [ServiceProvider属性](#serviceprovider属性) 31 | - [CancellationToken属性](#cancellationtoken属性) 32 | - [IPluginInitContext](#iplugininitcontext) 33 | - [PluginPath属性](#pluginpath属性) 34 | - [PluginLoader属性](#pluginloader属性) 35 | - [ServiceCollection属性](#servicecollection属性) 36 | - [InitServiceProvider属性](#initserviceprovider属性) 37 | - [IPlugin](#iplugin) 38 | - [StartAsync(IPluginContext context)](#startasynciplugincontext-context) 39 | - [Task StopAsync(IPluginContext context)](#task-stopasynciplugincontext-context) 40 | - [ISupportInitPlugin](#isupportinitplugin) 41 | - [Init(IPluginInitContext context)](#initiplugininitcontext-context) 42 | - [ISupportConfigPlugin](#isupportconfigplugin) 43 | - [PluginFactoryOptions](#pluginfactoryoptions) 44 | - [PluginPath属性](#pluginpath属性-1) 45 | - [FileProvider属性](#fileprovider属性) 46 | - [DisabledPluginList属性](#disabledpluginlist属性) 47 | - [AddAssembly(Assembly assembly)](#addassemblyassembly-assembly) 48 | - [PluginInfo](#plugininfo) 49 | - [Id属性](#id属性) 50 | - [Name属性](#name属性) 51 | - [Description属性](#description属性) 52 | - [Alias属性](#alias属性) 53 | - [IsEnable属性](#isenable属性) 54 | - [PluginType属性](#plugintype属性) 55 | - [CanConfig顺序](#canconfig顺序) 56 | - [CanInit属性](#caninit属性) 57 | - [PluginBase](#pluginbase) 58 | - [SupportConfigPluginBase](#supportconfigpluginbase) 59 | - [PluginAttribute](#pluginattribute) 60 | - [Id属性](#id属性-1) 61 | - [Alias属性](#alias属性-1) 62 | - [Name属性](#name属性-1) 63 | - [Description属性](#description属性-1) 64 | 65 | ## IHostBuilder.UsePluginFactory 扩展方法 66 | 67 | ### UsePluginFactory() 68 | 69 | - 说明:无参数,使用默认配置来启用插件工厂,此时会使用IHostBuilder的配置作为插件配置容器,使用应用目录的Plugins目录作为插件目录 70 | 71 | ### UsePluginFactory(IConfiguration configuration) 72 | 73 | - 说明:使用指定的配置来初始化插件工厂 74 | - 参数列表: 75 | - *configuration*: IConfiguration,插件所使用的配置对象,插件将从此配置的Plugins节点获取插件自身的配置 76 | 77 | ### UsePluginFactory(IConfiguration configuration, Assembly assembly) 78 | 79 | - 说明:使用指定的配置来初始化插件工厂, 并自动加载assembly参数指定的程序集 80 | - 参数列表: 81 | - *configuration*: IConfiguration,插件所使用的配置对象,插件将从此配置的Plugins节点获取插件自身的配置 82 | - *assembly*:Assembly,要载入的插件程序集 83 | 84 | ### UsePluginFactory(IConfiguration configuration, IEnumerable<Assembly> assemblies) 85 | 86 | - 说明:使用指定的配置来初始化插件工厂, 并自动加载assemblies参数指定的程序集列表 87 | - 参数列表: 88 | - *configuration*: IConfiguration,插件所使用的配置对象,插件将从此配置的Plugins节点获取插件自身的配置 89 | - *assemblies*:List<Assembly>,要载入的插件程序集列表 90 | 91 | ### UsePluginFactory(Assembly assembly) 92 | 93 | - 说明:使用默认配置初始化插件工厂, 并自动加载assembly参数指定的程序集 94 | - 参数列表: 95 | - *assembly*:Assembly,要载入的插件程序集 96 | 97 | ### UsePluginFactory(IEnumerable<Assembly> assemblies) 98 | 99 | - 说明:使用默认配置初始化插件工厂, 并自动加载assemblies参数指定的程序集列表 100 | - 参数列表: 101 | - *assemblies*:List<Assembly>,要载入的插件程序集列表 102 | 103 | ### UsePluginFactory(Action<PluginFactoryOptions> options) 104 | 105 | - 说明:使用指定的设置来初始化插件工厂 106 | - 参数列表: 107 | - *options*:Action<PluginFactoryOptions>,设置委托,可设置项参考[PluginFactoryOptions](#PluginFactoryOptions) 108 | 109 | ### UsePluginFactory(IConfiguration configuration, Action<PluginFactoryOptions> options) 110 | 111 | - 说明:指定插件配置容器,并使用指定的设置来初始化插件工厂 112 | - 参数列表: 113 | - *configuration*: IConfiguration,插件所使用的配置对象,插件将从此配置的Plugins节点获取插件自身的配置 114 | - *options*:Action<PluginFactoryOptions>,设置委托,可设置项参考[PluginFactoryOptions](#PluginFactoryOptions) 115 | 116 | ## IServiceCollection.AddPluginFactory 扩展方法 117 | 118 | ### AddPluginFactory() 119 | 120 | - 说明:使用默认设置初始化插件工厂,此时内部会使用内存配置容器作为插件配置,故`不会`从配置文件或其他配置源载入配置;插件目录将使用Plugins目录 121 | 122 | ### AddPluginFactory(IConfiguration configuration) 123 | 124 | - 说明:使用指定的配置来初始化插件工厂 125 | - 参数列表: 126 | - *configuration*: IConfiguration,插件所使用的配置对象,插件将从此配置的Plugins节点获取插件自身的配置 127 | 128 | ### AddPluginFactory(IConfiguration configuration, Assembly assembly) 129 | 130 | - 说明:使用指定的配置来初始化插件工厂, 并自动加载assembly参数指定的程序集 131 | - 参数列表: 132 | - *configuration*: IConfiguration,插件所使用的配置对象,插件将从此配置的Plugins节点获取插件自身的配置 133 | - *assembly*:Assembly,要载入的插件程序集 134 | 135 | ### AddPluginFactory(IConfiguration configuration, IEnumerable<Assembly> assemblies) 136 | 137 | - 说明:使用指定的配置来初始化插件工厂, 并自动加载assemblies参数指定的程序集列表 138 | - 参数列表: 139 | - *configuration*: IConfiguration,插件所使用的配置对象,插件将从此配置的Plugins节点获取插件自身的配置 140 | - *assemblies*:List<Assembly>,要载入的插件程序集列表 141 | 142 | ### AddPluginFactory(Action<PluginFactoryOptions> configureOptions) 143 | 144 | - 说明:使用指定的设置来初始化插件工厂 145 | - 参数列表: 146 | - *options*:Action<PluginFactoryOptions>,设置委托,可设置项参考[PluginFactoryOptions](#PluginFactoryOptions) 147 | 148 | ### AddPluginFactory(IConfiguration configuration, Action<PluginFactoryOptions> configureOptions) 149 | 150 | - 说明:指定插件配置容器,并使用指定的设置来初始化插件工厂 151 | - 参数列表: 152 | - *configuration*: IConfiguration,插件所使用的配置对象,插件将从此配置的Plugins节点获取插件自身的配置 153 | - *options*:Action<PluginFactoryOptions>,设置委托,可设置项参考[PluginFactoryOptions](#PluginFactoryOptions) 154 | 155 | ### AddPluginFactory(PluginFactoryOptions options, IConfiguration configuration) 156 | 157 | - 说明:指定设置对象,并使用指定配置对象来初始化插件工厂 158 | - 参数列表: 159 | - *options*:PluginFactoryOptions,设置对象,可设置项参考[PluginFactoryOptions](#PluginFactoryOptions) 160 | - *configuration*: IConfiguration,插件所使用的配置对象,插件将从此配置的Plugins节点获取插件自身的配置 161 | 162 | ## IPluginLoader 163 | 164 | `IPluginLoader`主要用于从指定位置载入插件 165 | 166 | ### PluginList属性 167 | 168 | - 说明:只读属性,返回已加载的插件列表,类型为IReadOnlyList≶PluginInfo>,参见[PluginInfo](#PluginInfo) 169 | 170 | ### Load() 171 | 172 | - 说明:加载插件程序集,*注意*:通常情况下你无需调用此方法,框架内部会自动调用,如果你要实现自己的加载机制,则可实现此接口 173 | - 返回:void 174 | 175 | ### Init() 176 | 177 | - 说明:初始化插件,*注意*:通常情况下你无需调用此方法,框架内部会自动调用,如果你要实现自己的加载机制,则可实现此接口 178 | - 返回:void 179 | 180 | ## IPluginFactory 181 | 182 | `IPluginFactory`插件工厂接口,用于控制插件的生命周期,该接口从`IHostedService`继承,故支持宿主的托管服务机制 183 | 184 | ### StartAsync(CancellationToken cancellationToken) 185 | 186 | - 说明:启动所有插件 187 | - 参数: 188 | - cancellationToken: CancellationToken,取消监听令牌 189 | - 返回:Task 190 | 191 | ### StopAsync(CancellationToken cancellationToken) 192 | 193 | - 说明:停止所有插件 194 | - 参数: 195 | - cancellationToken: CancellationToken,取消监听令牌 196 | - 返回:Task 197 | 198 | ## IPluginContext 199 | 200 | `IPluginContext`插件上下文,是插件运行时的上下文数据,将传递给插件的相关方法 201 | 202 | ### PluginFactory属性 203 | 204 | - 说明:只读,当前的IPluginFactory实例 205 | 206 | ### ServiceProvider属性 207 | 208 | - 说明:只读,当前的IServiceProvider实例 209 | 210 | ### CancellationToken属性 211 | 212 | - 说明:只读,关联的取消监听令牌,你可以通过此令牌判断上层操作是否已被取消 213 | 214 | ## IPluginInitContext 215 | 216 | `IPluginInitContext`插件初始化上下文,由支持初始化的插件接口[ISupportInitPlugin](#ISupportInitPlugin)的Init方法使用 217 | 218 | ### PluginPath属性 219 | 220 | - 说明:只读,插件路径 221 | 222 | ### PluginLoader属性 223 | 224 | - 说明:只读,当前关联的插件载入器, 请参考[IPluginLoader](#IPluginLoader) 225 | 226 | ### ServiceCollection属性 227 | 228 | - 说明:只读,应用的依赖注入服务容器,通过此属性可注入新的服务 229 | 230 | ### InitServiceProvider属性 231 | 232 | - 说明:只读,初始化所使用的服务提供器,通过此属性可获取依赖的其他服务,*注意通过此提供器可获取初始化插件工厂方法之前所注入的服务。* 233 | 234 | ## IPlugin 235 | 236 | `IPlugin`是插件的基础接口,所有插件需实现此接口 237 | 238 | ### StartAsync(IPluginContext context) 239 | 240 | - 说明:启动插件 241 | - 参数: 242 | - context, [IPluginContext](#IPluginContext), 插件上下文 243 | - 返回:Task 244 | 245 | ### Task StopAsync(IPluginContext context) 246 | 247 | - 说明:停止插件 248 | - 参数: 249 | - context, [IPluginContext](#IPluginContext), 插件上下文 250 | - 返回:Task 251 | 252 | ## ISupportInitPlugin 253 | 254 | `ISupportInitPlugin`接口用于实现插件的初始化机制 255 | 256 | ### Init(IPluginInitContext context) 257 | 258 | - 说明:插件初始化 259 | - 参数: 260 | - context:[IPluginInitContext](#IPluginInitContext), 插件初始化上下文 261 | 262 | ## ISupportConfigPlugin 263 | 264 | `ISupportConfigPlugin`接口用于实现插件的配置机制,它是泛型接口,泛型参数为`TOptions`,表示插件的配置类型 265 | 266 | ## PluginFactoryOptions 267 | 268 | `PluginFactoryOptions`是插件工厂的配置类型 269 | 270 | ### PluginPath属性 271 | 272 | - 说明:获取或设置插件工厂所使用的插件路径 273 | 274 | ### FileProvider属性 275 | 276 | - 说明:获取或设置插件载入时所使用的文件提供器,默认将使用`PhysicalFileProvider`提供器 277 | 278 | ### DisabledPluginList属性 279 | 280 | - 说明,只读,获取当前被禁用的插件列表 281 | 282 | ### AddAssembly(Assembly assembly) 283 | 284 | - 说明:添加插件程序集 285 | - 参数: 286 | - assembly:Assembly,插件程序集 287 | 288 | ## PluginInfo 289 | 290 | `PluginInfo`类用于保存插件的相关信息 291 | 292 | ### Id属性 293 | 294 | - 说明:插件ID 295 | 296 | ### Name属性 297 | 298 | - 说明:插件名称 299 | 300 | ### Description属性 301 | 302 | - 说明:插件说明 303 | 304 | ### Alias属性 305 | 306 | - 说明:插件别名 307 | 308 | ### IsEnable属性 309 | 310 | - 说明:是否启用 311 | 312 | ### PluginType属性 313 | 314 | - 说明:插件的类型信息 315 | 316 | ### CanConfig顺序 317 | 318 | - 说明:插件是否可配置 319 | 320 | ### CanInit属性 321 | 322 | - 说明:插件是否可初始化 323 | 324 | ## PluginBase 325 | 326 | `PluginBase`插件基类,实现了[IPlugin](#IPlugin)接口,你可以从此类继承,然后重写相应的方法 327 | 328 | ## SupportConfigPluginBase 329 | 330 | `SupportConfigPluginBase`支持配置的插件基类,从`PluginBase`继承,并实现了`ISupportConfigPlugin<TOptions>`方法,继承此类,必须提供支持IOptionsMonitor<TOptions> options参数的构造函数,以传入插件配置。 331 | 332 | ## PluginAttribute 333 | 334 | `PluginAttribute`特性,用于设置插件的相关信息。 335 | 336 | ### Id属性 337 | 338 | - 说明:插件ID 339 | 340 | ### Alias属性 341 | 342 | - 说明:插件别名 343 | 344 | ### Name属性 345 | 346 | - 说明:插件名称 347 | 348 | ### Description属性 349 | 350 | - 说明:插件描述 -------------------------------------------------------------------------------- /package-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xfrogcn/Xfrogcn.PluginFactory/af7236b33d649c9572be63b06a8d9d84757b13c5/package-icon.png -------------------------------------------------------------------------------- /samples/TestPluginA/ShareOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace TestPluginA 6 | { 7 | /// 8 | /// 公共配置 9 | /// 10 | public class ShareOptions 11 | { 12 | public string ConnectionString { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/TestPluginA/TestConfigPlugin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Xfrogcn.PluginFactory; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace TestPluginA 8 | { 9 | public class TestConfigPluginOptions : ShareOptions 10 | { 11 | 12 | } 13 | [Plugin(Alias = "TestConfigPlugin")] 14 | public class TestConfigPlugin : SupportConfigPluginBase 15 | { 16 | public TestConfigPlugin(IOptionsMonitor options) : base(options) 17 | { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/TestPluginA/TestConfigPluginWithInit.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Xfrogcn.PluginFactory; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace TestPluginA 8 | { 9 | public class TestConfigPluginWithInitOptions : ShareOptions 10 | { 11 | 12 | } 13 | 14 | [Plugin(Alias = "TestConfigPluginWithInit")] 15 | public class TestConfigPluginWithInit : SupportConfigPluginBase, ISupportInitPlugin 16 | { 17 | public TestConfigPluginWithInit() : base(null) 18 | { 19 | 20 | } 21 | public TestConfigPluginWithInit(IOptionsMonitor options) : base(options) 22 | { 23 | } 24 | 25 | public void Init(IPluginInitContext context) 26 | { 27 | 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /samples/TestPluginA/TestInitPlugin.cs: -------------------------------------------------------------------------------- 1 | using Xfrogcn.PluginFactory; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace TestPluginA 7 | { 8 | [Plugin(Alias = "TestInitPlugin")] 9 | public class TestInitPlugin : PluginBase, ISupportInitPlugin 10 | { 11 | public void Init(IPluginInitContext context) 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/TestPluginA/TestPlugin.cs: -------------------------------------------------------------------------------- 1 | using Xfrogcn.PluginFactory; 2 | using System; 3 | 4 | namespace TestPluginA 5 | { 6 | [Plugin(Alias = "TestPlugin")] 7 | public class TestPlugin : PluginBase 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/TestPluginA/TestPluginA.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Abstractions/Configration/IPluginConfigrationProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Xfrogcn.PluginFactory 4 | { 5 | /// 6 | /// Plugin配置节点获取器 7 | /// 8 | /// 9 | public interface IPluginConfigrationProvider 10 | { 11 | public IConfiguration Configuration { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Abstractions/Configration/PluginConfigrationOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | namespace Xfrogcn.PluginFactory 3 | { 4 | /// 5 | /// 从配置中获取插件设置 6 | /// 7 | /// 插件类型 8 | /// 插件设置 9 | public class PluginConfigrationOptions : ConfigureFromConfigurationOptions 10 | where TPluginOptions : class, new() 11 | { 12 | public PluginConfigrationOptions(IPluginConfigrationProvider provider) : base(provider.Configuration) 13 | { 14 | } 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Abstractions/Configration/PluginConfigrationProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using System; 3 | using System.Linq; 4 | using System.Security.Cryptography.X509Certificates; 5 | using System.Xml.Serialization; 6 | 7 | namespace Xfrogcn.PluginFactory 8 | { 9 | /// 10 | /// PluginConfigrationProvider从 配置中获取配置 11 | /// 每个插件以插件类型全名称或插件别名为键 12 | /// 13 | public class PluginConfigrationProvider : IPluginConfigrationProvider 14 | where TPlugin : IPlugin 15 | { 16 | public const string DEFAULT_SHARE_KEY = "_Share"; 17 | 18 | public PluginConfigrationProvider(PluginFactoryConfigration configration) 19 | { 20 | if (configration == null) 21 | { 22 | throw new ArgumentNullException(nameof(configration)); 23 | } 24 | 25 | Type pluginType = typeof(TPlugin); 26 | string configKey = typeof(TPlugin).FullName; 27 | var section = configration.Configuration.GetSection(configKey); 28 | if (!section.Exists()) 29 | { 30 | // 内嵌类型,将+号替换为. 31 | configKey = configKey.Replace("+", "."); 32 | section= configration.Configuration.GetSection(configKey); 33 | } 34 | 35 | var attr = pluginType.GetCustomAttributes(typeof(PluginAttribute), false).OfType().FirstOrDefault(); 36 | IConfigurationSection aliasSection = null; 37 | if(attr !=null && !String.IsNullOrEmpty(attr.Alias)) 38 | { 39 | configKey = attr.Alias; 40 | var section2 = configration.Configuration.GetSection(configKey); 41 | if (section2.Exists()) 42 | { 43 | aliasSection = section2; 44 | } 45 | 46 | } 47 | 48 | // 共享配置 49 | var shareSection = configration.Configuration.GetSection(DEFAULT_SHARE_KEY); 50 | 51 | 52 | // 合并多个配置 53 | IConfigurationSection[] configList = new IConfigurationSection[] 54 | { 55 | // 共享配置可以被覆盖 56 | shareSection, section, aliasSection 57 | }; 58 | if (configList.Count(x => x != null && x.Exists()) > 1) 59 | { 60 | var cb = new ConfigurationBuilder(); 61 | configList.All(s => 62 | { 63 | if(s !=null && s.Exists()) 64 | { 65 | cb.AddConfiguration(s); 66 | } 67 | return true; 68 | }); 69 | Configuration = cb.Build(); 70 | } 71 | else 72 | { 73 | Configuration = configList.FirstOrDefault(x => x != null && x.Exists()); 74 | if(Configuration == null) 75 | { 76 | Configuration = section; 77 | } 78 | } 79 | 80 | 81 | } 82 | 83 | public IConfiguration Configuration { get; } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Abstractions/Configration/PluginFactoryConfigration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using System; 3 | 4 | namespace Xfrogcn.PluginFactory 5 | { 6 | /// 7 | /// IConfiguration的封装 8 | /// 9 | public class PluginFactoryConfigration 10 | { 11 | public static readonly string DEFAULT_CONFIG_KEY = "Plugins"; 12 | 13 | public PluginFactoryConfigration(IConfiguration configuration) 14 | { 15 | if (configuration == null) 16 | { 17 | throw new ArgumentNullException(nameof(configuration)); 18 | } 19 | Configuration = configuration.GetSection(DEFAULT_CONFIG_KEY); 20 | } 21 | 22 | public IConfiguration Configuration { get; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Abstractions/IPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Xfrogcn.PluginFactory 4 | { 5 | public interface IPlugin 6 | { 7 | Task StartAsync(IPluginContext context); 8 | 9 | Task StopAsync(IPluginContext context); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Abstractions/IPluginContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace Xfrogcn.PluginFactory 5 | { 6 | public interface IPluginContext 7 | { 8 | public IPluginFactory PluginFactory { get; } 9 | 10 | public IServiceProvider ServiceProvider { get; } 11 | 12 | public CancellationToken CancellationToken { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Abstractions/IPluginFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | 3 | namespace Xfrogcn.PluginFactory 4 | { 5 | /// 6 | /// 插件工厂 7 | /// 8 | public interface IPluginFactory : IHostedService 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Abstractions/IPluginInitContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | 4 | namespace Xfrogcn.PluginFactory 5 | { 6 | public interface IPluginInitContext 7 | { 8 | string PluginPath { get; } 9 | 10 | IPluginLoader PluginLoader { get; } 11 | 12 | IServiceCollection ServiceCollection { get; } 13 | 14 | IServiceProvider InitServiceProvider { get; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Abstractions/IPluginLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Xfrogcn.PluginFactory 4 | { 5 | /// 6 | /// 插件载入器 7 | /// 8 | public interface IPluginLoader 9 | { 10 | 11 | /// 12 | /// 插件信息列表 13 | /// 14 | IReadOnlyList PluginList { get; } 15 | 16 | /// 17 | /// 载入插件 18 | /// 19 | /// 20 | void Load(); 21 | 22 | /// 23 | /// 初始化插件 24 | /// 25 | void Init(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Abstractions/ISupportConfigPlugin.cs: -------------------------------------------------------------------------------- 1 | namespace Xfrogcn.PluginFactory 2 | { 3 | /// 4 | /// 支持配置的插件接口 5 | /// 6 | /// 7 | public interface ISupportConfigPlugin 8 | where TOptions: class, new() 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Abstractions/ISupportInitPlugin.cs: -------------------------------------------------------------------------------- 1 | namespace Xfrogcn.PluginFactory 2 | { 3 | /// 4 | /// 支持初始化的插件接口 5 | /// 初始化方法在构建ServiceProvider之前调用,可向服务容器中注入插件自定义的服务 6 | /// 7 | /// 8 | /// 如果插件支持此接口,必须支持无参构造 9 | /// 注意:请毋在初始化接口中设置实例属性或变量,因为初始化方法与Start与Stop方法对应的 10 | /// 插件实例可能不同 11 | /// 12 | public interface ISupportInitPlugin 13 | { 14 | void Init(IPluginInitContext context); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Abstractions/IsolationAssemblyLoadContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.Loader; 6 | 7 | [assembly: InternalsVisibleTo("PluginFactory.Abstractions.Test")] 8 | 9 | namespace Xfrogcn.PluginFactory 10 | { 11 | /// 12 | /// 以隔离的方式加载程序集,每个加载的程序集可以有独立的依赖版本 13 | /// 隔离的插件需位于单独的文件夹中,且文件夹名称需与程序集名称一致,且必须具有deps.json文件 14 | /// 15 | public class IsolationAssemblyLoadContext : AssemblyLoadContext 16 | { 17 | private AssemblyDependencyResolver _resolver; 18 | private readonly string _assemblyName; 19 | 20 | public IsolationAssemblyLoadContext(string assemblyPath) 21 | { 22 | if(String.IsNullOrEmpty(assemblyPath)) 23 | { 24 | throw new ArgumentNullException(nameof(assemblyPath)); 25 | } 26 | _assemblyName = Path.GetFileNameWithoutExtension(assemblyPath); 27 | 28 | _resolver = new AssemblyDependencyResolver(assemblyPath); 29 | } 30 | 31 | public Assembly Load() 32 | { 33 | return Load(new AssemblyName(_assemblyName)); 34 | } 35 | 36 | protected override Assembly Load(AssemblyName assemblyName) 37 | { 38 | string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); 39 | if (assemblyPath != null) 40 | { 41 | return LoadFromAssemblyPath(assemblyPath); 42 | } 43 | return null; 44 | } 45 | 46 | protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) 47 | { 48 | string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); 49 | if (libraryPath != null) 50 | { 51 | return LoadUnmanagedDllFromPath(libraryPath); 52 | } 53 | 54 | return IntPtr.Zero; 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Abstractions/PluginAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Xfrogcn.PluginFactory 4 | { 5 | [AttributeUsage(AttributeTargets.Class, AllowMultiple =false)] 6 | public class PluginAttribute : Attribute 7 | { 8 | /// 9 | /// 插件Id 10 | /// 11 | public string Id { get; set; } 12 | 13 | /// 14 | /// 插件别名,在插件配置中可使用别名作为插件配置键 15 | /// 16 | public string Alias { get; set; } 17 | 18 | /// 19 | /// 插件名称 20 | /// 21 | public string Name { get; set; } 22 | 23 | /// 24 | /// 插件描述 25 | /// 26 | public string Description { get; set; } 27 | 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Abstractions/PluginBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Xfrogcn.PluginFactory 7 | { 8 | public abstract class PluginBase : IPlugin 9 | { 10 | public virtual Task StartAsync(IPluginContext context) 11 | { 12 | return Task.CompletedTask; 13 | } 14 | 15 | public virtual Task StopAsync(IPluginContext context) 16 | { 17 | return Task.CompletedTask; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Abstractions/PluginFactoryOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.FileProviders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | namespace Xfrogcn.PluginFactory 10 | { 11 | public class PluginFactoryOptions 12 | { 13 | public const string DEFAULT_PLUGIN_PATH_KEY = "Path"; 14 | public const string DEFAULT_ISENABLED_KEY = "IsEnabled"; 15 | 16 | /// 17 | /// 插件路径 18 | /// 19 | public string PluginPath { get; set; } 20 | 21 | /// 22 | /// 文件提供器 23 | /// 24 | public IFileProvider FileProvider { get; set; } 25 | 26 | private List _additionalAssemblies = new List(); 27 | public IReadOnlyList AdditionalAssemblies => _additionalAssemblies; 28 | 29 | public Func Predicate { get; set; } = _ => true; 30 | 31 | private List _disabledPluginList = new List(); 32 | 33 | /// 34 | /// 被禁用的插件 35 | /// 36 | public IReadOnlyList DisabledPluginList => _disabledPluginList; 37 | 38 | public void DisablePlugin(string pluginTypeName) 39 | { 40 | if( !_disabledPluginList.Any(x=>x.Equals(pluginTypeName, StringComparison.OrdinalIgnoreCase))) 41 | { 42 | _disabledPluginList.Add(pluginTypeName); 43 | } 44 | } 45 | 46 | public void AddAssembly(Assembly assembly) 47 | { 48 | _additionalAssemblies.Add(assembly); 49 | } 50 | 51 | /// 52 | /// 从配置中获取插件工厂设置 53 | /// 54 | /// 55 | public void ConfigFromConfigration(PluginFactoryConfigration configuration) 56 | { 57 | if (configuration == null) 58 | { 59 | throw new ArgumentNullException(nameof(configuration)); 60 | } 61 | IConfiguration pluginConfig = configuration.Configuration; 62 | string path = pluginConfig[DEFAULT_PLUGIN_PATH_KEY]; 63 | if (!String.IsNullOrEmpty(path) && !String.Equals(path, PluginPath, StringComparison.OrdinalIgnoreCase)) 64 | { 65 | if (!Path.IsPathRooted(path)) 66 | { 67 | path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path); 68 | } 69 | path = Path.GetFullPath(path); 70 | PluginPath = path; 71 | 72 | if (FileProvider == null || FileProvider is PhysicalFileProvider) 73 | { 74 | if(Directory.Exists(path)) 75 | { 76 | FileProvider = new PhysicalFileProvider(path); 77 | } 78 | else 79 | { 80 | FileProvider = null; 81 | } 82 | } 83 | 84 | } 85 | 86 | configDisablePlugin(pluginConfig); 87 | 88 | } 89 | 90 | /// 91 | /// 配置下以插件全类型名称或别名作为键,如果下面存在IsEnabled配置且值为false,0则放入禁止列表 92 | /// 93 | /// 插件工厂根配置节点 94 | private void configDisablePlugin(IConfiguration pluginConfig) 95 | { 96 | foreach(IConfigurationSection section in pluginConfig.GetChildren()) 97 | { 98 | var isEnabledSection = section.GetSection(DEFAULT_ISENABLED_KEY); 99 | if(isEnabledSection.Exists() && (isEnabledSection.Value=="0" || isEnabledSection.Value.ToLower() == "false")) 100 | { 101 | DisablePlugin(section.Key); 102 | } 103 | } 104 | } 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Abstractions/PluginInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Xfrogcn.PluginFactory 4 | { 5 | /// 6 | /// 插件信息 7 | /// 8 | public class PluginInfo 9 | { 10 | /// 11 | /// 插件Id 12 | /// 13 | public string Id { get; set; } 14 | /// 15 | /// 插件名称 16 | /// 17 | public string Name { get; set; } 18 | /// 19 | /// 插件描述 20 | /// 21 | public string Description { get; set; } 22 | 23 | /// 24 | /// 别名 25 | /// 26 | public string Alias { get; set; } 27 | 28 | /// 29 | /// 是否有效 30 | /// 31 | public bool IsEnable { get; set; } = true; 32 | 33 | /// 34 | /// 插件类型 35 | /// 36 | public Type PluginType { get; set; } 37 | 38 | /// 39 | /// 是否可以配置 40 | /// 41 | public bool CanConfig { get; set; } 42 | 43 | /// 44 | /// 是否支持Init 45 | /// 46 | public bool CanInit { get; set; } 47 | 48 | /// 49 | /// 配置类型 50 | /// 51 | public Type ConfigType { get; set; } 52 | 53 | /// 54 | /// 插件顺序 55 | /// 56 | public int Order { get; set; } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Abstractions/SupportConfigPluginBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Xfrogcn.PluginFactory 7 | { 8 | /// 9 | /// 支持配置的插件基类 10 | /// 11 | /// 12 | public abstract class SupportConfigPluginBase : PluginBase, ISupportConfigPlugin 13 | where TOptions : class, new() 14 | { 15 | protected TOptions Options { get; private set; } 16 | public SupportConfigPluginBase(IOptionsMonitor options) 17 | { 18 | if (options != null) 19 | { 20 | Options = options.CurrentValue; 21 | options.OnChange(OnOptionsChanged); 22 | } 23 | } 24 | 25 | protected virtual void OnOptionsChanged(TOptions options) 26 | { 27 | Options = options; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Abstractions/Xfrogcn.PluginFactory.Abstractions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Xfrogcn.PluginFactory.Abstractions 5 | Xfrogcn.PluginFactory 6 | Xfrogcn.PluginFactory.Abstractions 7 | true 8 | true 9 | false 10 | whb-vs-cert.pfx 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/PluginFactory/DefaultPluginFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Security.Cryptography.X509Certificates; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace Xfrogcn.PluginFactory 11 | { 12 | public class DefaultPluginFactory : IPluginFactory 13 | { 14 | private IPluginLoader _loader; 15 | 16 | private readonly ILogger _logger; 17 | 18 | private readonly IServiceProvider _serviceProvider; 19 | 20 | private List _pluginList = new List(); 21 | 22 | public DefaultPluginFactory( 23 | IPluginLoader loader, 24 | ILoggerFactory loggerFactory, 25 | IServiceProvider serviceProvider, 26 | IEnumerable plugins) 27 | { 28 | if (loader == null) 29 | { 30 | throw new ArgumentNullException(nameof(loader)); 31 | } 32 | if( loggerFactory == null) 33 | { 34 | throw new ArgumentNullException(nameof(loggerFactory)); 35 | } 36 | 37 | 38 | 39 | if (plugins != null) 40 | { 41 | List disabledList = loader.PluginList.Where(x => !x.IsEnable).ToList(); 42 | _pluginList.AddRange(plugins); 43 | // 禁用插件列表 44 | } 45 | 46 | 47 | _loader = loader; 48 | _logger = loggerFactory.CreateLogger("PluginFactory"); 49 | _serviceProvider = serviceProvider; 50 | } 51 | 52 | public async Task StartAsync(CancellationToken cancellationToken) 53 | { 54 | await ForEachPlugin(async (plugin, ctx) => 55 | { 56 | Log._pluginBeginStart(_logger, null); 57 | Stopwatch sw = new Stopwatch(); 58 | try 59 | { 60 | await plugin.StartAsync(ctx); 61 | } 62 | catch (Exception e) 63 | { 64 | Log._pluginStartError(_logger, e.ToString(), e); 65 | throw; 66 | } 67 | sw.Stop(); 68 | Log._pluginCompleteStart(_logger, sw.ElapsedMilliseconds, null); 69 | 70 | }, cancellationToken); 71 | } 72 | 73 | public async Task StopAsync(CancellationToken cancellationToken) 74 | { 75 | await ForEachPlugin(async (plugin, ctx) => 76 | { 77 | Log._pluginBeginStop(_logger, null); 78 | Stopwatch sw = new Stopwatch(); 79 | try 80 | { 81 | await plugin.StopAsync(ctx); 82 | } 83 | catch (Exception e) 84 | { 85 | Log._pluginStopError(_logger, e.ToString(), e); 86 | throw; 87 | } 88 | sw.Stop(); 89 | Log._pluginCompleteStop(_logger, sw.ElapsedMilliseconds, null); 90 | 91 | }, cancellationToken); 92 | } 93 | 94 | protected async Task ForEachPlugin(Func proc, CancellationToken cancellationToken) 95 | { 96 | foreach (IPlugin p in _pluginList) 97 | { 98 | PluginInfo pi = getPluginInfo(p.GetType()); 99 | PluginInfoLogValue logValue = new PluginInfoLogValue(pi); 100 | using (var scope = _logger.BeginScope(logValue)) 101 | { 102 | IPluginContext context = new PluginContext( 103 | this, 104 | _serviceProvider, 105 | cancellationToken 106 | ); 107 | await proc(p, context); 108 | } 109 | } 110 | } 111 | 112 | private PluginInfo getPluginInfo(Type pluginType) 113 | { 114 | return _loader.PluginList.First(x => x.PluginType == pluginType); 115 | } 116 | 117 | 118 | public IPlugin GetPluginById(string id) 119 | { 120 | var pi = _loader.PluginList.FirstOrDefault(x => x.Id == id); 121 | if( pi == null) 122 | { 123 | return null; 124 | } 125 | 126 | return _pluginList.FirstOrDefault(x => x.GetType() == pi.PluginType); 127 | } 128 | 129 | public IPlugin GetPluginByName(string name) 130 | { 131 | var pi = _loader.PluginList.FirstOrDefault(x => x.Name == name || x.Alias == name); 132 | if (pi == null) 133 | { 134 | return null; 135 | } 136 | 137 | return _pluginList.FirstOrDefault(x => x.GetType() == pi.PluginType); 138 | } 139 | 140 | public TPlugin GetPlugin() 141 | where TPlugin : class, IPlugin 142 | { 143 | return _pluginList.FirstOrDefault(x => x.GetType() == typeof(TPlugin)) as TPlugin; 144 | } 145 | 146 | static class Log 147 | { 148 | static EventId PluginStartingEventId = new EventId(100, "PluginStarting"); 149 | static EventId PluginStartFinishEventId = new EventId(101, "PluginStartFinish"); 150 | static EventId PluginStoppingEventId = new EventId(102, "PluginStopping"); 151 | static EventId PluginStopFinishEventId = new EventId(103, "PluginStopFinish"); 152 | 153 | static EventId PluginStartErrorEventId = new EventId(110, "PluginStartError"); 154 | static EventId PluginStopErrorEventId = new EventId(111, "PluginStopError"); 155 | 156 | public static Action _pluginBeginStart = 157 | LoggerMessage.Define(LogLevel.Information, PluginStartingEventId, Resources.PluginBeginStart); 158 | public static Action _pluginCompleteStart = 159 | LoggerMessage.Define(LogLevel.Information, PluginStartFinishEventId, Resources.PluginCompleteStart); 160 | public static Action _pluginStartError = 161 | LoggerMessage.Define(LogLevel.Error, PluginStartErrorEventId, Resources.PluginStartException); 162 | 163 | public static Action _pluginBeginStop = 164 | LoggerMessage.Define(LogLevel.Information, PluginStoppingEventId, Resources.PluginBeginStop); 165 | public static Action _pluginCompleteStop = 166 | LoggerMessage.Define(LogLevel.Information, PluginStartFinishEventId, Resources.PluginCompleteStop); 167 | public static Action _pluginStopError = 168 | LoggerMessage.Define(LogLevel.Error, PluginStopErrorEventId, Resources.PluginStopException); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/PluginFactory/DefaultPluginLoader.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Security.Cryptography.X509Certificates; 8 | 9 | namespace Xfrogcn.PluginFactory 10 | { 11 | /// 12 | /// 默认的插件载入器 13 | /// 14 | public class DefaultPluginLoader : IPluginLoader 15 | { 16 | readonly PluginFactoryOptions _options; 17 | readonly IServiceCollection _services; 18 | 19 | public DefaultPluginLoader(PluginFactoryOptions options, IServiceCollection services) 20 | { 21 | _options = options; 22 | _services = services; 23 | } 24 | 25 | private List _pluginList = new List(); 26 | public IReadOnlyList PluginList => _pluginList; 27 | 28 | public virtual void Load() 29 | { 30 | 31 | 32 | lock (_pluginList) 33 | { 34 | // 载入附加组件 35 | foreach(Assembly assembly in _options.AdditionalAssemblies) 36 | { 37 | LoadPluginFromAssembly(assembly); 38 | } 39 | if (_options.FileProvider == null) 40 | { 41 | return; 42 | } 43 | 44 | var dir = _options.FileProvider.GetDirectoryContents(string.Empty); 45 | 46 | if (!dir.Exists) 47 | { 48 | return; 49 | } 50 | foreach (var p in dir) 51 | { 52 | if (p.IsDirectory) 53 | { 54 | // 隔离插件 55 | var pluginDir = _options.FileProvider.GetDirectoryContents(p.Name); 56 | foreach (var pd in pluginDir) 57 | { 58 | if (pd.IsDirectory) 59 | { 60 | continue; 61 | } 62 | string fileName = Path.GetFileNameWithoutExtension(pd.PhysicalPath); 63 | if (fileName.Equals(p.Name, StringComparison.OrdinalIgnoreCase)) 64 | { 65 | // 插件程序集 66 | LoadPluginFromAssembly(pd.PhysicalPath); 67 | } 68 | 69 | } 70 | } 71 | else if (p.PhysicalPath != null && Path.GetExtension(p.PhysicalPath) == ".dll") 72 | { 73 | // 74 | LoadPluginFromAssembly(p.PhysicalPath); 75 | } 76 | 77 | } 78 | 79 | } 80 | } 81 | 82 | protected virtual void LoadPluginFromAssembly(string assemblyPath) 83 | { 84 | if (_options.Predicate!=null && !_options.Predicate(assemblyPath)) 85 | { 86 | return; 87 | } 88 | 89 | IsolationAssemblyLoadContext context = new IsolationAssemblyLoadContext(assemblyPath); 90 | var assembly = context.Load(); 91 | if (assembly != null) 92 | { 93 | LoadPluginFromAssembly(assembly); 94 | } 95 | } 96 | 97 | 98 | protected virtual void LoadPluginFromAssembly(Assembly assembly) 99 | { 100 | if (assembly == null) 101 | { 102 | // 异常 103 | throw new ArgumentNullException(nameof(assembly)); 104 | } 105 | 106 | var types = assembly.GetExportedTypes(); 107 | List plist = new List(); 108 | foreach (Type t in types) 109 | { 110 | PluginInfo pi = LoadPluginFromType(t); 111 | if (pi != null && !_pluginList.Any(p => p.PluginType == pi.PluginType)) 112 | { 113 | _pluginList.Add(pi); 114 | } 115 | } 116 | 117 | } 118 | 119 | protected virtual PluginInfo LoadPluginFromType(Type type) 120 | { 121 | if(type.IsAbstract) 122 | { 123 | return null; 124 | } 125 | 126 | Type[] iTypes = type.GetInterfaces(); 127 | if(iTypes==null || iTypes.Length == 0) 128 | { 129 | return null; 130 | } 131 | var pluginType = typeof(IPlugin); 132 | 133 | 134 | 135 | PluginInfo pi = null; 136 | if (typeof(IPlugin).GetTypeInfo().IsAssignableFrom(type)) 137 | { 138 | pi = new PluginInfo() 139 | { 140 | PluginType = type, 141 | CanInit = false, 142 | CanConfig = false 143 | }; 144 | } 145 | if(pi == null) 146 | { 147 | return null; 148 | } 149 | 150 | var attr = type.GetCustomAttributes(typeof(PluginAttribute), false).OfType().FirstOrDefault(); 151 | if(attr != null) 152 | { 153 | pi.Id = attr.Id; 154 | pi.Name = attr.Name; 155 | pi.Alias = attr.Alias; 156 | pi.Description = attr.Description; 157 | } 158 | pi.Id = string.IsNullOrEmpty(pi.Id) ? type.FullName : pi.Id; 159 | pi.Name = string.IsNullOrEmpty(pi.Name) ? (string.IsNullOrEmpty(pi.Alias) ? type.FullName : pi.Alias) : pi.Name; 160 | 161 | // 初始化 162 | if (typeof(ISupportInitPlugin).IsAssignableFrom(type)) 163 | { 164 | pi.CanInit = true; 165 | } 166 | 167 | // 配置 168 | Type cfgType = iTypes.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ISupportConfigPlugin<>)); 169 | if (cfgType != null) 170 | { 171 | pi.ConfigType = cfgType.GenericTypeArguments[0]; 172 | pi.CanConfig = true; 173 | } 174 | 175 | // 是否禁用 176 | if ( (!String.IsNullOrEmpty(pi.Alias) && _options.DisabledPluginList.Contains(pi.Alias)) || 177 | _options.DisabledPluginList.Contains(pi.Name) || 178 | _options.DisabledPluginList.Contains(pi.PluginType.FullName) || 179 | _options.DisabledPluginList.Contains(pi.PluginType.FullName.Replace("+","."))) 180 | { 181 | pi.IsEnable = false; 182 | } 183 | 184 | return pi; 185 | } 186 | 187 | 188 | public virtual void Init() 189 | { 190 | var initList = _pluginList.Where(x => x.CanInit && x.IsEnable).ToList(); 191 | if (initList.Count == 0) 192 | { 193 | return; 194 | } 195 | 196 | IPluginInitContext initContext = new PluginInitContext(_options.PluginPath, this, _services); 197 | var initInstanceList = initContext.InitServiceProvider.GetRequiredService>(); 198 | foreach (ISupportInitPlugin p in initInstanceList) 199 | { 200 | p.Init(initContext); 201 | } 202 | 203 | } 204 | 205 | 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/PluginFactory/HostBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Xfrogcn.PluginFactory; 7 | 8 | namespace Microsoft.Extensions.Hosting 9 | { 10 | public static class HostBuilderExtensions 11 | { 12 | public static IHostBuilder UsePluginFactory(this IHostBuilder hostBuilder) 13 | { 14 | return UsePluginFactory(hostBuilder, (IConfiguration)null); 15 | } 16 | 17 | public static IHostBuilder UsePluginFactory(this IHostBuilder hostBuilder, IConfiguration configuration) 18 | { 19 | return UsePluginFactory(hostBuilder, configuration, (Assembly)null); 20 | } 21 | 22 | public static IHostBuilder UsePluginFactory(this IHostBuilder hostBuilder, IConfiguration configuration, Assembly assembly) 23 | { 24 | return UsePluginFactory(hostBuilder, configuration, new Assembly[] { assembly }); 25 | } 26 | 27 | 28 | 29 | public static IHostBuilder UsePluginFactory(this IHostBuilder hostBuilder, IConfiguration configuration, IEnumerable< Assembly> assemblies) 30 | { 31 | hostBuilder.ConfigureServices((context, sc) => 32 | { 33 | configuration = configuration ?? context.Configuration; 34 | sc.AddPluginFactory(configuration, assemblies); 35 | }); 36 | return hostBuilder; 37 | } 38 | 39 | public static IHostBuilder UsePluginFactory(this IHostBuilder hostBuilder, Assembly assembly) 40 | { 41 | return UsePluginFactory(hostBuilder, new Assembly[] { assembly }); 42 | } 43 | 44 | public static IHostBuilder UsePluginFactory(this IHostBuilder hostBuilder, IEnumerable assemblies) 45 | { 46 | return UsePluginFactory(hostBuilder, null, assemblies); 47 | } 48 | 49 | public static IHostBuilder UsePluginFactory(this IHostBuilder hostBuilder, Action options) 50 | { 51 | return UsePluginFactory(hostBuilder, null, options); 52 | } 53 | 54 | public static IHostBuilder UsePluginFactory(this IHostBuilder hostBuilder, IConfiguration configuration, Action configureOptions) 55 | { 56 | hostBuilder.ConfigureServices((context, sc) => 57 | { 58 | configuration = configuration ?? context.Configuration; 59 | sc.AddPluginFactory(configuration, configureOptions); 60 | }); 61 | return hostBuilder; 62 | } 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/PluginFactory/PluginContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace Xfrogcn.PluginFactory 8 | { 9 | class PluginContext : IPluginContext 10 | { 11 | public PluginContext(IPluginFactory pluginFactory, IServiceProvider serviceProvider, CancellationToken cancellationToken) 12 | { 13 | PluginFactory = pluginFactory; 14 | ServiceProvider = serviceProvider; 15 | CancellationToken = cancellationToken; 16 | } 17 | 18 | public IPluginFactory PluginFactory { get; } 19 | 20 | public IServiceProvider ServiceProvider { get; } 21 | 22 | public CancellationToken CancellationToken { get; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/PluginFactory/PluginFactoryServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | using Microsoft.Extensions.FileProviders; 9 | using Microsoft.Extensions.Options; 10 | using Xfrogcn.PluginFactory; 11 | 12 | namespace Microsoft.Extensions.DependencyInjection 13 | { 14 | public static class PluginFactoryServiceCollectionExtensions 15 | { 16 | 17 | public static readonly string DEFAULT_PLUGIN_PATH = "Plugins"; 18 | 19 | public static IServiceCollection AddPluginFactory(this IServiceCollection services) 20 | { 21 | services.AddOptions(); 22 | 23 | var options = createDefaultOptions(); 24 | 25 | services.AddPluginFactory(options, null); 26 | 27 | return services; 28 | } 29 | 30 | public static IServiceCollection AddPluginFactory(this IServiceCollection services, IConfiguration configuration) 31 | { 32 | return AddPluginFactory(services, configuration, (Assembly)null); 33 | } 34 | 35 | public static IServiceCollection AddPluginFactory(this IServiceCollection services, IConfiguration configuration, Assembly assembly) 36 | { 37 | return AddPluginFactory(services, configuration, new Assembly[] { assembly }); 38 | } 39 | 40 | 41 | 42 | public static IServiceCollection AddPluginFactory(this IServiceCollection services, IConfiguration configuration, IEnumerable assemblies) 43 | { 44 | if (configuration == null) 45 | { 46 | throw new ArgumentNullException(nameof(configuration)); 47 | } 48 | 49 | //注入配置 50 | PluginFactoryConfigration factoryConfigration = new PluginFactoryConfigration(configuration); 51 | services.TryAddSingleton(factoryConfigration); 52 | 53 | // 从配置中获取设置 54 | PluginFactoryOptions options = createDefaultOptions(); 55 | if (assemblies != null) 56 | { 57 | foreach (var a in assemblies) 58 | { 59 | if (a == null) 60 | { 61 | continue; 62 | } 63 | options.AddAssembly(a); 64 | } 65 | } 66 | options.ConfigFromConfigration(factoryConfigration); 67 | 68 | 69 | services.AddPluginFactory(options, configuration); 70 | 71 | 72 | 73 | return services; 74 | } 75 | 76 | 77 | 78 | 79 | public static IServiceCollection AddPluginFactory(this IServiceCollection services, Action configureOptions) 80 | { 81 | return AddPluginFactory(services, null, configureOptions); 82 | } 83 | 84 | public static IServiceCollection AddPluginFactory(this IServiceCollection services, IConfiguration configuration, Action configureOptions) 85 | { 86 | PluginFactoryOptions options = createDefaultOptions(); 87 | if (configureOptions != null) 88 | { 89 | configureOptions(options); 90 | } 91 | 92 | services.AddPluginFactory(options, configuration); 93 | 94 | return services; 95 | } 96 | 97 | 98 | public static IServiceCollection AddPluginFactory(this IServiceCollection services, PluginFactoryOptions options, IConfiguration configuration) 99 | { 100 | if (options == null) 101 | { 102 | throw new ArgumentNullException(nameof(options)); 103 | } 104 | services.AddLogging(); 105 | services.AddOptions(); 106 | 107 | 108 | services.TryAddSingleton(options); 109 | 110 | if( configuration == null) 111 | { 112 | configuration = new ConfigurationBuilder() 113 | .AddInMemoryCollection() 114 | .Build(); 115 | } 116 | 117 | // 配置根ConfigurationChangeTokenSource需要 118 | services.TryAddSingleton(configuration); 119 | // 插件全局配置 120 | services.TryAddSingleton(new PluginFactoryConfigration(configuration)); 121 | 122 | // 从配置中获取插件设置,以插件类型名称或插件别名作为配置键 123 | services.TryAddSingleton(typeof(IPluginConfigrationProvider<>), typeof(PluginConfigrationProvider<>)); 124 | 125 | // 注册配置变更监听 126 | services.TryAddSingleton(typeof(IOptionsChangeTokenSource<>), typeof(ConfigurationChangeTokenSource<>)); 127 | 128 | // 注入插件工厂 129 | services.TryAddSingleton(); 130 | // 兼容托管服务,在宿主环境中自动调用开始和停止方法 131 | services.AddHostedService((sp) => 132 | { 133 | IPluginFactory factory = sp.GetRequiredService(); 134 | return factory; 135 | }); 136 | 137 | IPluginLoader loader = createPluginLoader(services, options); 138 | // 载入器单例 139 | services.TryAddSingleton(loader); 140 | 141 | 142 | return services; 143 | } 144 | 145 | private static PluginFactoryOptions createDefaultOptions() 146 | { 147 | string pluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DEFAULT_PLUGIN_PATH); 148 | 149 | // 默认设置 150 | PluginFactoryOptions options = new PluginFactoryOptions() 151 | { 152 | PluginPath = pluginPath 153 | }; 154 | if(Directory.Exists(pluginPath)) 155 | { 156 | options.FileProvider = new PhysicalFileProvider(pluginPath); 157 | } 158 | return options; 159 | } 160 | 161 | 162 | private static IPluginLoader createPluginLoader(IServiceCollection services, PluginFactoryOptions options) 163 | { 164 | IPluginLoader loader = new DefaultPluginLoader(options, services); 165 | loader.Load(); 166 | 167 | 168 | foreach (PluginInfo pi in loader.PluginList) 169 | { 170 | if (!pi.IsEnable) 171 | { 172 | continue; 173 | } 174 | services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IPlugin), pi.PluginType)); 175 | } 176 | 177 | 178 | 179 | // 注入配置映射 180 | var list = loader.PluginList.Where(x => x.CanConfig && x.IsEnable).ToList(); 181 | 182 | foreach(PluginInfo pi in list) 183 | { 184 | Type cfgOptionsType = typeof(IConfigureOptions<>).MakeGenericType(pi.ConfigType); 185 | Type impleType = typeof(PluginConfigrationOptions<,>).MakeGenericType(pi.PluginType, pi.ConfigType); 186 | services.TryAddEnumerable(ServiceDescriptor.Singleton(cfgOptionsType, impleType)); 187 | } 188 | 189 | loader.Init(); 190 | return loader; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/PluginFactory/PluginInfoLogValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Xfrogcn.PluginFactory 7 | { 8 | 9 | /// 10 | /// 记录插件信息的日志值,可用于Logger作用域或日志记录 11 | /// 12 | internal class PluginInfoLogValue : IReadOnlyList> 13 | { 14 | private PluginInfo _pluginInfo = null; 15 | 16 | private string _formatted = null; 17 | 18 | private List> _values; 19 | public PluginInfoLogValue(PluginInfo pluginInfo) 20 | { 21 | if (pluginInfo == null) 22 | { 23 | throw new ArgumentNullException(nameof(pluginInfo)); 24 | } 25 | _pluginInfo = pluginInfo; 26 | } 27 | 28 | private List> Values 29 | { 30 | get 31 | { 32 | if (_values == null) 33 | { 34 | var values = new List>(); 35 | values.Add(new KeyValuePair("PluginID", _pluginInfo.Id)); 36 | values.Add(new KeyValuePair("PluginName", _pluginInfo.Name)); 37 | values.Add(new KeyValuePair("PluginAlias", _pluginInfo.Alias??string.Empty)); 38 | values.Add(new KeyValuePair("PluginType", _pluginInfo.PluginType.FullName)); 39 | _values = values; 40 | } 41 | 42 | return _values; 43 | } 44 | } 45 | 46 | 47 | 48 | 49 | 50 | public KeyValuePair this[int index] 51 | { 52 | get 53 | { 54 | if (index < 0 || index >= Count) 55 | { 56 | throw new IndexOutOfRangeException(nameof(index)); 57 | } 58 | 59 | return Values[index]; 60 | } 61 | } 62 | 63 | public int Count => Values.Count; 64 | 65 | public IEnumerator> GetEnumerator() 66 | { 67 | return Values.GetEnumerator(); 68 | } 69 | 70 | IEnumerator IEnumerable.GetEnumerator() 71 | { 72 | return Values.GetEnumerator(); 73 | } 74 | 75 | public override string ToString() 76 | { 77 | if(_formatted == null) 78 | { 79 | var builder = new StringBuilder(); 80 | builder.AppendLine(Resources.PluginInfo + ": "); 81 | 82 | for (var i = 0; i < Values.Count; i++) 83 | { 84 | var kvp = Values[i]; 85 | builder.Append(kvp.Key); 86 | builder.Append(": "); 87 | 88 | foreach (var value in (IEnumerable)kvp.Value) 89 | { 90 | builder.Append(value); 91 | builder.Append(", "); 92 | } 93 | 94 | builder.Remove(builder.Length - 2, 2); 95 | builder.AppendLine(); 96 | } 97 | 98 | _formatted = builder.ToString(); 99 | } 100 | return _formatted; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/PluginFactory/PluginInitContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace Xfrogcn.PluginFactory 6 | { 7 | public class PluginInitContext : IPluginInitContext 8 | { 9 | 10 | public PluginInitContext(string pluginPath, IPluginLoader pluginLoader, IServiceCollection services) 11 | { 12 | PluginLoader = pluginLoader; 13 | ServiceCollection = services; 14 | PluginPath = pluginPath; 15 | IServiceCollection tempServices = new ServiceCollection(); 16 | foreach(var d in services) 17 | { 18 | tempServices.Add(d); 19 | } 20 | // 注入可初始化的插件列表 21 | var pl = pluginLoader.PluginList.Where(x => x.IsEnable && x.CanInit); 22 | foreach(var pi in pl) 23 | { 24 | tempServices.AddSingleton(typeof(ISupportInitPlugin), pi.PluginType); 25 | } 26 | InitServiceProvider = tempServices.BuildServiceProvider(); 27 | } 28 | 29 | public IPluginLoader PluginLoader { get; } 30 | 31 | public IServiceCollection ServiceCollection { get; } 32 | 33 | public IServiceProvider InitServiceProvider { get; } 34 | 35 | public string PluginPath { get; } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/PluginFactory/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Xfrogcn.PluginFactory { 12 | using System; 13 | 14 | 15 | /// 16 | /// 一个强类型的资源类,用于查找本地化的字符串等。 17 | /// 18 | // 此类是由 StronglyTypedResourceBuilder 19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen 21 | // (以 /str 作为命令选项),或重新生成 VS 项目。 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// 返回此类使用的缓存的 ResourceManager 实例。 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xfrogcn.PluginFactory.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// 重写当前线程的 CurrentUICulture 属性 51 | /// 重写当前线程的 CurrentUICulture 属性。 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// 查找类似 A plug-in that supports an initialization interface must provide a parameterless constructor, {0} 的本地化字符串。 65 | /// 66 | internal static string InvalidInitPlugin { 67 | get { 68 | return ResourceManager.GetString("InvalidInitPlugin", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// 查找类似 Starting 的本地化字符串。 74 | /// 75 | internal static string PluginBeginStart { 76 | get { 77 | return ResourceManager.GetString("PluginBeginStart", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// 查找类似 Stopping 的本地化字符串。 83 | /// 84 | internal static string PluginBeginStop { 85 | get { 86 | return ResourceManager.GetString("PluginBeginStop", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// 查找类似 Startup Complete, and it takes {time}ms 的本地化字符串。 92 | /// 93 | internal static string PluginCompleteStart { 94 | get { 95 | return ResourceManager.GetString("PluginCompleteStart", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// 查找类似 Stop Complete, and it takes {time}ms 的本地化字符串。 101 | /// 102 | internal static string PluginCompleteStop { 103 | get { 104 | return ResourceManager.GetString("PluginCompleteStop", resourceCulture); 105 | } 106 | } 107 | 108 | /// 109 | /// 查找类似 PluginInfo 的本地化字符串。 110 | /// 111 | internal static string PluginInfo { 112 | get { 113 | return ResourceManager.GetString("PluginInfo", resourceCulture); 114 | } 115 | } 116 | 117 | /// 118 | /// 查找类似 An exception occurs when the plug-in is started: {ex} 的本地化字符串。 119 | /// 120 | internal static string PluginStartException { 121 | get { 122 | return ResourceManager.GetString("PluginStartException", resourceCulture); 123 | } 124 | } 125 | 126 | /// 127 | /// 查找类似 An exception occurs when the plug-in is stoped: {ex} 的本地化字符串。 128 | /// 129 | internal static string PluginStopException { 130 | get { 131 | return ResourceManager.GetString("PluginStopException", resourceCulture); 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/PluginFactory/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | A plug-in that supports an initialization interface must provide a parameterless constructor, {0} 122 | {0} type name 123 | 124 | 125 | Starting 126 | 127 | 128 | Stopping 129 | 130 | 131 | Startup Complete, and it takes {time}ms 132 | 133 | 134 | Stop Complete, and it takes {time}ms 135 | 136 | 137 | PluginInfo 138 | 139 | 140 | An exception occurs when the plug-in is started: {ex} 141 | 142 | 143 | An exception occurs when the plug-in is stoped: {ex} 144 | 145 | -------------------------------------------------------------------------------- /src/PluginFactory/Xfrogcn.PluginFactory.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Xfrogcn.PluginFactory 5 | Xfrogcn.PluginFactory 6 | true 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ResXFileCodeGenerator 20 | Resources.Designer.cs 21 | 22 | 23 | 24 | 25 | Resources.resx 26 | True 27 | True 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/Abstractions.Test/IsolationLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Runtime.Loader; 5 | using Xunit; 6 | 7 | 8 | namespace Xfrogcn.PluginFactory.Abstractions.Test 9 | { 10 | [Collection("隔离加载")] 11 | [Trait("Group", "隔离加载")] 12 | public class IsolationLoader 13 | { 14 | 15 | string classANormalPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugin-a-n"); 16 | string classAIsolationErrorPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugin-a-i-e"); 17 | string classAIsolationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugin-a-i"); 18 | string classBNormalPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugin-b-n"); 19 | string classBIsolationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugin-b-i"); 20 | string classBIsolationErrorPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugin-b-i-e"); 21 | 22 | [Fact(DisplayName = "加载无依赖库")] 23 | public void Test1() 24 | { 25 | var loader = new IsolationAssemblyLoadContext(Path.Combine(classAIsolationPath, "ClassA/ClassA.dll")); 26 | var assembly = loader.Load(); 27 | Assert.NotNull(assembly); 28 | var types = assembly.GetTypes(); 29 | 30 | // 无deps.json时将会回退通过Default方式载入 31 | loader = new IsolationAssemblyLoadContext(Path.Combine(classANormalPath, "ClassA.dll")); 32 | assembly = loader.Load(); 33 | Assert.NotNull(assembly); 34 | types = assembly.GetTypes(); 35 | } 36 | 37 | [Fact(DisplayName = "加载有依赖库")] 38 | public void Test2() 39 | { 40 | var loader = new IsolationAssemblyLoadContext(Path.Combine(classBNormalPath, "ClassB.dll")); 41 | var assembly = loader.Load(); 42 | Assert.NotNull(assembly); 43 | var types = assembly.GetTypes(); 44 | 45 | loader = new IsolationAssemblyLoadContext(Path.Combine(classBIsolationPath, "ClassB/ClassB.dll")); 46 | assembly = loader.Load(); 47 | Assert.NotNull(assembly); 48 | types = assembly.GetTypes(); 49 | 50 | loader = new IsolationAssemblyLoadContext(Path.Combine(classBIsolationErrorPath, "ClassB/ClassB.dll")); 51 | 52 | assembly = loader.Load(); 53 | loader.Resolving += Loader_Resolving; 54 | Assert.NotNull(assembly); 55 | var types2 = assembly.GetTypes(); 56 | Assert.NotEqual(types2[0].BaseType, types[0].BaseType); 57 | 58 | 59 | // 默认载入器会自动从当前目录解析程序集 60 | var loader2 = Assembly.LoadFile(Path.Combine(classBIsolationErrorPath, "ClassB/ClassB.dll")); 61 | assembly = loader.Load(); 62 | Assert.NotNull(assembly); 63 | types = assembly.GetTypes(); 64 | } 65 | 66 | private Assembly Loader_Resolving(AssemblyLoadContext arg1, AssemblyName arg2) 67 | { 68 | return null; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/Abstractions.Test/PluginConfigrationOptionsTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using Microsoft.Extensions.Options; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace Xfrogcn.PluginFactory.Abstractions.Test 12 | { 13 | [Trait("Group", "PluginConfigrationOptions")] 14 | public class PluginConfigrationOptionsTest 15 | { 16 | class PluginAOptions 17 | { 18 | public string Test { get; set; } 19 | } 20 | 21 | [Plugin(Alias = "PluginA")] 22 | class PluginA : IPlugin 23 | { 24 | public string Test { get; private set; } 25 | 26 | public PluginA(IOptionsMonitor options) 27 | { 28 | Test = options.CurrentValue.Test; 29 | options.OnChange((opt, name) => 30 | { 31 | Test = opt.Test; 32 | }); 33 | } 34 | 35 | public Task StartAsync(IPluginContext context) 36 | { 37 | return Task.CompletedTask; 38 | } 39 | 40 | public Task StopAsync(IPluginContext context) 41 | { 42 | return Task.CompletedTask; 43 | } 44 | } 45 | 46 | [Fact(DisplayName = "Normal")] 47 | public void Test1() 48 | { 49 | Dictionary dic = new Dictionary() 50 | { 51 | { "Plugins:PluginA:Test","A" } 52 | }; 53 | var config = new ConfigurationBuilder() 54 | .AddInMemoryCollection(dic) 55 | .Build(); 56 | 57 | PluginConfigrationOptions optionsAction = new PluginConfigrationOptions( 58 | new PluginConfigrationProvider(new PluginFactoryConfigration(config))); 59 | 60 | PluginAOptions options = new PluginAOptions(); 61 | optionsAction.Configure(options); 62 | 63 | Assert.Equal("A", options.Test); 64 | } 65 | 66 | [Fact(DisplayName = "WithDI")] 67 | public void Test2() 68 | { 69 | Dictionary dic = new Dictionary() 70 | { 71 | { "Plugins:PluginA:Test","A" } 72 | }; 73 | var config = new ConfigurationBuilder() 74 | .AddInMemoryCollection(dic) 75 | .Build(); 76 | 77 | PluginFactoryConfigration rootConfig = new PluginFactoryConfigration(config); 78 | 79 | IServiceCollection services = new ServiceCollection() 80 | .AddSingleton(rootConfig) 81 | .AddSingleton(typeof(IPluginConfigrationProvider<>), typeof(PluginConfigrationProvider<>)) 82 | .AddOptions() 83 | .ConfigureOptions>(); 84 | 85 | var sp = services.BuildServiceProvider(); 86 | var options = sp.GetRequiredService>(); 87 | 88 | Assert.Equal("A", options.Value.Test); 89 | } 90 | 91 | 92 | [Fact(DisplayName = "OptionsChanged")] 93 | public void Test3() 94 | { 95 | Dictionary dic = new Dictionary() 96 | { 97 | { "Plugins:PluginA:Test","A" } 98 | }; 99 | var config = new ConfigurationBuilder() 100 | .AddInMemoryCollection(dic) 101 | .Build(); 102 | 103 | PluginFactoryConfigration rootConfig = new PluginFactoryConfigration(config); 104 | 105 | IServiceCollection services = new ServiceCollection() 106 | .AddSingleton(rootConfig) 107 | .AddSingleton(typeof(IPluginConfigrationProvider<>), typeof(PluginConfigrationProvider<>)) 108 | .AddOptions() 109 | .ConfigureOptions>() 110 | .AddSingleton(typeof(IOptionsChangeTokenSource<>), typeof(ConfigurationChangeTokenSource<>)) 111 | .AddSingleton(); 112 | // 必须 113 | services.TryAddSingleton(config); 114 | 115 | var sp = services.BuildServiceProvider(); 116 | var plugin = sp.GetRequiredService() as PluginA; 117 | 118 | Assert.Equal("A", plugin.Test); 119 | 120 | config["Plugins:PluginA:Test"] = "B"; 121 | config.Reload(); 122 | 123 | Assert.Equal("B", plugin.Test); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /test/Abstractions.Test/PluginConfigrationProviderTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Xfrogcn.PluginFactory.Abstractions.Test 9 | { 10 | [Trait("Group", "PluginConfigrationProvider")] 11 | public class PluginConfigrationProviderTest 12 | { 13 | class PluginA : IPlugin 14 | { 15 | public Task StartAsync(IPluginContext context) 16 | { 17 | return Task.CompletedTask; 18 | } 19 | 20 | public Task StopAsync(IPluginContext context) 21 | { 22 | return Task.CompletedTask; 23 | } 24 | } 25 | 26 | [Plugin(Alias = "PluginB")] 27 | class PluginB : PluginA 28 | { 29 | 30 | } 31 | 32 | [Fact(DisplayName = "Normal")] 33 | public void Test1() 34 | { 35 | Dictionary dic = new Dictionary() 36 | { 37 | { "Plugins:Xfrogcn.PluginFactory.Abstractions.Test.PluginConfigrationProviderTest.PluginA:Test","A" } 38 | }; 39 | var config = new ConfigurationBuilder() 40 | .AddInMemoryCollection(dic) 41 | .Build(); 42 | 43 | PluginFactoryConfigration factoryConfig = new PluginFactoryConfigration(config); 44 | PluginConfigrationProvider provider = new PluginConfigrationProvider(factoryConfig); 45 | Assert.Equal("A", provider.Configuration["Test"]); 46 | } 47 | 48 | [Fact(DisplayName = "Alias")] 49 | public void Test2() 50 | { 51 | Dictionary dic = new Dictionary() 52 | { 53 | { "Plugins:PluginB:Test","B" } 54 | }; 55 | var config = new ConfigurationBuilder() 56 | .AddInMemoryCollection(dic) 57 | .Build(); 58 | 59 | PluginFactoryConfigration factoryConfig = new PluginFactoryConfigration(config); 60 | PluginConfigrationProvider provider = new PluginConfigrationProvider(factoryConfig); 61 | Assert.Equal("B", provider.Configuration["Test"]); 62 | } 63 | 64 | [Fact(DisplayName = "Merge")] 65 | public void Test3() 66 | { 67 | Dictionary dic = new Dictionary() 68 | { 69 | { "Plugins:Xfrogcn.PluginFactory.Abstractions.Test.PluginConfigrationProviderTest.PluginB:Test","B" }, 70 | { "Plugins:PluginB:Test2","B2" } 71 | }; 72 | var config = new ConfigurationBuilder() 73 | .AddInMemoryCollection(dic) 74 | .Build(); 75 | 76 | PluginFactoryConfigration factoryConfig = new PluginFactoryConfigration(config); 77 | PluginConfigrationProvider provider = new PluginConfigrationProvider(factoryConfig); 78 | Assert.Equal("B", provider.Configuration["Test"]); 79 | Assert.Equal("B2", provider.Configuration["Test2"]); 80 | } 81 | 82 | [Fact(DisplayName = "Empty")] 83 | public void Test4() 84 | { 85 | Dictionary dic = new Dictionary() 86 | { 87 | }; 88 | var config = new ConfigurationBuilder() 89 | .AddInMemoryCollection(dic) 90 | .Build(); 91 | 92 | PluginFactoryConfigration factoryConfig = new PluginFactoryConfigration(config); 93 | PluginConfigrationProvider provider = new PluginConfigrationProvider(factoryConfig); 94 | Assert.Empty(provider.Configuration.GetChildren()); 95 | 96 | } 97 | 98 | 99 | 100 | 101 | [Fact(DisplayName = "Share_Normal")] 102 | public void Test5() 103 | { 104 | Dictionary dic = new Dictionary() 105 | { 106 | { "Plugins:PluginB:Test","B" }, 107 | { "Plugins:_Share:Test2","B2" }, 108 | }; 109 | var config = new ConfigurationBuilder() 110 | .AddInMemoryCollection(dic) 111 | .Build(); 112 | 113 | PluginFactoryConfigration factoryConfig = new PluginFactoryConfigration(config); 114 | PluginConfigrationProvider provider = new PluginConfigrationProvider(factoryConfig); 115 | Assert.Equal("B", provider.Configuration["Test"]); 116 | Assert.Equal("B2", provider.Configuration["Test2"]); 117 | } 118 | 119 | [Fact(DisplayName = "Share_Override")] 120 | public void Test6() 121 | { 122 | Dictionary dic = new Dictionary() 123 | { 124 | { "Plugins:PluginB:Test","B" }, 125 | { "Plugins:_Share:Test","B2" }, 126 | }; 127 | var config = new ConfigurationBuilder() 128 | .AddInMemoryCollection(dic) 129 | .Build(); 130 | 131 | PluginFactoryConfigration factoryConfig = new PluginFactoryConfigration(config); 132 | PluginConfigrationProvider provider = new PluginConfigrationProvider(factoryConfig); 133 | Assert.Equal("B", provider.Configuration["Test"]); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /test/Abstractions.Test/Xfrogcn.PluginFactory.Abstractions.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | 6 | PluginFactory.Abstractions.Test 7 | Xfrogcn.PluginFactory.Abstractions.Test 8 | package-icon.png 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | True 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/ClassA/ClassA.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/ClassA/TestClassA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ClassA 4 | { 5 | public class TestClassA 6 | { 7 | public string A { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/ClassA/build.cmd: -------------------------------------------------------------------------------- 1 | dotnet build -o ../Abstractions.Test/bin/Debug/net5.0/plugin-a-n 2 | rm ../Abstractions.Test/bin/Debug/net5.0/plugin-a-n/ClassA.deps.json -f 3 | rm ../Abstractions.Test/bin/Debug/net5.0/plugin-a-n/ClassA.pdb -f 4 | dotnet publish -o ../Abstractions.Test/bin/Debug/net5.0/plugin-a-i-e/ClassA 5 | rm ../Abstractions.Test/bin/Debug/net5.0/plugin-a-i-e/ClassA/ClassA.deps.json -f 6 | rm ../Abstractions.Test/bin/Debug/net5.0/plugin-a-i-e/ClassA/ClassA.pdb -f 7 | dotnet publish --self-contained -o ../Abstractions.Test/bin/Debug/net5.0/plugin-a-i/ClassA 8 | -------------------------------------------------------------------------------- /test/ClassB/ClassB.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/ClassB/TestClassB.cs: -------------------------------------------------------------------------------- 1 | using ClassA; 2 | using System; 3 | 4 | namespace ClassB 5 | { 6 | public class TestClassB : TestClassA 7 | { 8 | public string B { get; set; } 9 | 10 | public override string ToString() 11 | { 12 | return this.A + this.B; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/ClassB/build.cmd: -------------------------------------------------------------------------------- 1 | dotnet publish -o ../Abstractions.Test/bin/Debug/net5.0/plugin-b-i/ClassB 2 | dotnet publish -o ../Abstractions.Test/bin/Debug/net5.0/plugin-b-n 3 | rm ../Abstractions.Test/bin/Debug/net5.0/plugin-b-n/*.deps.json -f 4 | rm ../Abstractions.Test/bin/Debug/net5.0/plugin-b-n/*.pdb -f 5 | dotnet publish -o ../Abstractions.Test/bin/Debug/net5.0/plugin-b-i-e/ClassB 6 | rm ../Abstractions.Test/bin/Debug/net5.0/plugin-b-i-e/ClassB/ClassA.dll -f 7 | rm ../Abstractions.Test/bin/Debug/net5.0/plugin-b-i-e/ClassB/ClassA.pdb -f 8 | cp ../Abstractions.Test/bin/Debug/net5.0/plugin-b-i/ClassB/ClassA.dll ../Abstractions.Test/bin/Debug/net5.0/ClassA.dll -------------------------------------------------------------------------------- /test/PluginFactory.Test/DefaultPluginFactoryConfigTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System.Collections.Generic; 5 | using Microsoft.Extensions.Configuration; 6 | using System.Linq; 7 | using Microsoft.Extensions.Hosting; 8 | using System.Threading.Tasks; 9 | 10 | namespace Xfrogcn.PluginFactory.Test 11 | { 12 | [Trait("Group", "DefaultPluginFactory")] 13 | public class DefaultPluginFactoryConfigTest 14 | { 15 | [Fact(DisplayName = "Normal_Config")] 16 | public void Test1() 17 | { 18 | Dictionary dic = new Dictionary() 19 | { 20 | { "Plugins:Path", "Test/Plugins" } 21 | }; 22 | 23 | var config = new ConfigurationBuilder() 24 | .AddInMemoryCollection(dic) 25 | .Build(); 26 | 27 | 28 | IServiceCollection sc = new ServiceCollection() 29 | .AddPluginFactory(config); 30 | 31 | var sp = sc.BuildServiceProvider(); 32 | var options = sp.GetRequiredService(); 33 | IPluginLoader loader = sp.GetRequiredService(); 34 | 35 | Assert.EndsWith($"Test{System.IO.Path.DirectorySeparatorChar}Plugins", options.PluginPath); 36 | Assert.Empty(loader.PluginList); 37 | 38 | } 39 | 40 | 41 | [Fact(DisplayName = "Plugin_IsEnable")] 42 | public void Test2() 43 | { 44 | Dictionary dic = new Dictionary() 45 | { 46 | { "Plugins:Path", "Test/Plugins" }, 47 | { "Plugins:Xfrogcn.PluginFactory.Test.TestPluginB:IsEnabled", "0" } 48 | }; 49 | 50 | var config = new ConfigurationBuilder() 51 | .AddInMemoryCollection(dic) 52 | .Build(); 53 | 54 | 55 | IServiceCollection sc = new ServiceCollection() 56 | .AddPluginFactory(config, typeof(DefaultPluginFactoryConfigTest).Assembly); 57 | 58 | var sp = sc.BuildServiceProvider(); 59 | var options = sp.GetRequiredService(); 60 | IPluginLoader loader = sp.GetRequiredService(); 61 | DefaultPluginFactory factory = sp.GetRequiredService() as DefaultPluginFactory; 62 | 63 | Assert.Equal(4, loader.PluginList.Count); 64 | PluginInfo pi = loader.PluginList.FirstOrDefault(p => p.PluginType == typeof(TestPluginB)); 65 | Assert.False(pi.IsEnable); 66 | 67 | var p = factory.GetPlugin(); 68 | Assert.Null(p); 69 | 70 | var p2 = factory.GetPlugin(); 71 | Assert.NotNull(p2); 72 | } 73 | 74 | 75 | [Fact(DisplayName = "Plugin_Options")] 76 | public void Test3() 77 | { 78 | Dictionary dic = new Dictionary() 79 | { 80 | { "Plugins:Path", "Test/Plugins" }, 81 | { "Plugins:Xfrogcn.PluginFactory.Test.TestPluginE:ConfigA", "A" }, 82 | { "Plugins:_Share:ConfigB", "B" }, //共享配置 83 | }; 84 | 85 | var config = new ConfigurationBuilder() 86 | .AddInMemoryCollection(dic) 87 | .Build(); 88 | 89 | 90 | IServiceCollection sc = new ServiceCollection() 91 | .AddPluginFactory(config, typeof(DefaultPluginFactoryConfigTest).Assembly); 92 | 93 | var sp = sc.BuildServiceProvider(); 94 | var options = sp.GetRequiredService(); 95 | IPluginLoader loader = sp.GetRequiredService(); 96 | DefaultPluginFactory factory = sp.GetRequiredService() as DefaultPluginFactory; 97 | 98 | TestPluginEService pluginEService = sp.GetRequiredService(); 99 | 100 | var p2 = factory.GetPlugin(); 101 | Assert.Equal(pluginEService, p2.Service); 102 | Assert.Equal("A", pluginEService.Options.ConfigA); 103 | Assert.Equal("B", pluginEService.Options.ConfigB); 104 | 105 | // Options变更 106 | config["Plugins:_Share:ConfigB"] = "B2"; 107 | config.Reload(); 108 | Assert.Equal("B2", pluginEService.Options.ConfigB); 109 | 110 | } 111 | 112 | [Fact(DisplayName = "Host_Builder")] 113 | public async Task Test4() 114 | { 115 | Dictionary dic = new Dictionary() 116 | { 117 | { "Plugins:Path", "Test/Plugins" }, 118 | { "Plugins:PluginFactory.Test.TestPluginE:ConfigA", "A" }, 119 | { "Plugins:_Share:ConfigB", "B" }, //共享配置 120 | }; 121 | 122 | var host = Host.CreateDefaultBuilder() 123 | .ConfigureAppConfiguration(cb => 124 | { 125 | cb.AddInMemoryCollection(dic); 126 | }) 127 | .UsePluginFactory(typeof(DefaultPluginFactoryConfigTest).Assembly) 128 | .Build(); 129 | 130 | await host.StartAsync(); 131 | 132 | DefaultPluginFactory factory = host.Services.GetRequiredService() as DefaultPluginFactory; 133 | var pluginE = factory.GetPlugin(); 134 | Assert.Equal("B", pluginE.Service.Options.ConfigB); 135 | Assert.True(pluginE.IsStarted); 136 | 137 | } 138 | } 139 | 140 | 141 | 142 | } 143 | -------------------------------------------------------------------------------- /test/PluginFactory.Test/DefaultPluginFactoryTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace Xfrogcn.PluginFactory.Test 12 | { 13 | [Trait("Group", "DefaultPluginFactory")] 14 | public class DefaultPluginFactoryTest 15 | { 16 | [Fact(DisplayName = "No_Config")] 17 | public void Test1() 18 | { 19 | // 默认使用Plugins目录作为插件目录 20 | IServiceCollection serviceDescriptors = new ServiceCollection() 21 | .AddPluginFactory(); 22 | 23 | var sp = serviceDescriptors.BuildServiceProvider(); 24 | var factory = sp.GetRequiredService(); 25 | var loader = sp.GetRequiredService(); 26 | Assert.Equal(4, loader.PluginList.Count); 27 | } 28 | 29 | [Fact(DisplayName = "Get_Plugin_Instance")] 30 | public void Test2() 31 | { 32 | // 默认使用Plugins目录作为插件目录 33 | IServiceCollection serviceDescriptors = new ServiceCollection() 34 | .AddPluginFactory(options => 35 | { 36 | // 不载入Plugins目录下文件 37 | options.Predicate = _ => false; 38 | // 加入测试程序集 39 | options.AddAssembly(typeof(DefaultPluginFactoryTest).Assembly); 40 | }); 41 | 42 | var sp = serviceDescriptors.BuildServiceProvider(); 43 | var factory = sp.GetRequiredService() as DefaultPluginFactory; 44 | var loader = sp.GetRequiredService(); 45 | Assert.Equal(4, loader.PluginList.Count); 46 | 47 | // TestPluginA 为私有 48 | var pluginA = factory.GetPlugin(); 49 | Assert.Null(pluginA); 50 | 51 | var pluginB = factory.GetPlugin(); 52 | Assert.NotNull(pluginB); 53 | 54 | var pluginC = factory.GetPlugin(); 55 | Assert.NotNull(pluginC); 56 | 57 | var pluginD = factory.GetPlugin(); 58 | Assert.NotNull(pluginD); 59 | 60 | var pluginE = factory.GetPlugin(); 61 | Assert.NotNull(pluginE); 62 | 63 | } 64 | 65 | [Fact(DisplayName = "Start_Stop")] 66 | public async Task Test3() 67 | { 68 | // 默认使用Plugins目录作为插件目录 69 | IServiceCollection serviceDescriptors = new ServiceCollection() 70 | .AddLogging(loggingBuilder => 71 | { 72 | loggingBuilder.SetMinimumLevel(LogLevel.Trace); 73 | loggingBuilder.AddDebug(); 74 | loggingBuilder.AddConsole(); 75 | }) 76 | .AddPluginFactory(options => 77 | { 78 | // 不载入Plugins目录下文件 79 | options.Predicate = _ => false; 80 | // 加入测试程序集 81 | options.AddAssembly(typeof(DefaultPluginFactoryTest).Assembly); 82 | }); 83 | 84 | var sp = serviceDescriptors.BuildServiceProvider(); 85 | var factory = sp.GetRequiredService() as DefaultPluginFactory; 86 | var loader = sp.GetRequiredService(); 87 | Assert.Equal(4, loader.PluginList.Count); 88 | 89 | CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); 90 | await factory.StartAsync(cancellationTokenSource.Token); 91 | 92 | await factory.StopAsync(cancellationTokenSource.Token); 93 | } 94 | 95 | 96 | [Fact(DisplayName = "Plugin_Init")] 97 | public void Test4() 98 | { 99 | // 默认使用Plugins目录作为插件目录 100 | IServiceCollection serviceDescriptors = new ServiceCollection() 101 | .AddLogging(loggingBuilder => 102 | { 103 | loggingBuilder.SetMinimumLevel(LogLevel.Trace); 104 | loggingBuilder.AddDebug(); 105 | loggingBuilder.AddConsole(); 106 | }) 107 | .AddPluginFactory(options => 108 | { 109 | // 不载入Plugins目录下文件 110 | options.Predicate = _ => false; 111 | // 加入测试程序集 112 | options.AddAssembly(typeof(DefaultPluginFactoryTest).Assembly); 113 | }); 114 | 115 | var sp = serviceDescriptors.BuildServiceProvider(); 116 | var factory = sp.GetRequiredService() as DefaultPluginFactory; 117 | var loader = sp.GetRequiredService(); 118 | Assert.Equal(4, loader.PluginList.Count); 119 | 120 | // TestPluginC.TestDIClass 由 TestPluginC Init注入 121 | var diClass = sp.GetRequiredService(); 122 | Assert.Equal("Hello", diClass.Message); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /test/PluginFactory.Test/DefaultPluginLoaderTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.FileProviders; 3 | using System; 4 | using System.ComponentModel; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Runtime.CompilerServices; 9 | using Xunit; 10 | 11 | namespace Xfrogcn.PluginFactory.Test 12 | { 13 | [Trait("Group", "DefaultPluginLoader")] 14 | public class DefaultPluginLoaderTest 15 | { 16 | [Fact(DisplayName = "Loader")] 17 | public void Test1() 18 | { 19 | PluginFactoryOptions options = new PluginFactoryOptions(); 20 | IServiceCollection services = new ServiceCollection(); 21 | 22 | options.PluginPath = AppDomain.CurrentDomain.BaseDirectory; 23 | options.FileProvider = new PhysicalFileProvider(options.PluginPath); 24 | 25 | options.AddAssembly(Assembly.GetExecutingAssembly()); 26 | 27 | IPluginLoader loader = new DefaultPluginLoader(options, services); 28 | loader.Load(); 29 | 30 | Assert.Equal(4, loader.PluginList.Count); 31 | var pi = loader.PluginList.First(p => p.PluginType == typeof(TestPluginB)); 32 | Assert.True(pi.IsEnable); 33 | Assert.False(pi.CanInit); 34 | Assert.False(pi.CanConfig); 35 | 36 | pi = loader.PluginList.First(p => p.PluginType == typeof(TestPluginC)); 37 | Assert.True(pi.IsEnable); 38 | Assert.True(pi.CanInit); 39 | Assert.False(pi.CanConfig); 40 | 41 | pi = loader.PluginList.First(p => p.PluginType == typeof(TestPluginD)); 42 | Assert.True(pi.IsEnable); 43 | Assert.False(pi.CanInit); 44 | Assert.True(pi.CanConfig); 45 | Assert.Equal(typeof(TestPluginDOptions), pi.ConfigType); 46 | 47 | pi = loader.PluginList.First(p => p.PluginType == typeof(TestPluginE)); 48 | Assert.True(pi.IsEnable); 49 | Assert.True(pi.CanInit); 50 | Assert.True(pi.CanConfig); 51 | Assert.Equal(typeof(TestPluginEOptions), pi.ConfigType); 52 | 53 | } 54 | 55 | [Fact(DisplayName = "Load_From_Dir")] 56 | public void Test2() 57 | { 58 | PluginFactoryOptions options = new PluginFactoryOptions(); 59 | IServiceCollection services = new ServiceCollection(); 60 | 61 | options.PluginPath = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "Plugins"); 62 | options.FileProvider = new PhysicalFileProvider(options.PluginPath); 63 | 64 | IPluginLoader loader = new DefaultPluginLoader(options, services); 65 | loader.Load(); 66 | 67 | Assert.Equal(4, loader.PluginList.Count); 68 | Assert.Equal(4, loader.PluginList.Count); 69 | var pi = loader.PluginList.First(p => p.Alias == "TestPlugin"); 70 | Assert.Equal("TestPlugin", pi.Name); 71 | Assert.True(pi.IsEnable); 72 | Assert.False(pi.CanInit); 73 | Assert.False(pi.CanConfig); 74 | 75 | pi = loader.PluginList.First(p => p.Alias == "TestInitPlugin"); 76 | Assert.Equal("TestInitPlugin", pi.Name); 77 | Assert.True(pi.IsEnable); 78 | Assert.True(pi.CanInit); 79 | Assert.False(pi.CanConfig); 80 | 81 | pi = loader.PluginList.First(p => p.Alias == "TestConfigPlugin"); 82 | Assert.Equal("TestConfigPlugin", pi.Name); 83 | Assert.True(pi.IsEnable); 84 | Assert.False(pi.CanInit); 85 | Assert.True(pi.CanConfig); 86 | Assert.Equal("TestPluginA.TestConfigPluginOptions", pi.ConfigType.FullName); 87 | 88 | pi = loader.PluginList.First(p => p.Alias == "TestConfigPluginWithInit"); 89 | Assert.Equal("TestConfigPluginWithInit", pi.Name); 90 | Assert.True(pi.IsEnable); 91 | Assert.True(pi.CanInit); 92 | Assert.True(pi.CanConfig); 93 | Assert.Equal("TestPluginA.TestConfigPluginWithInitOptions", pi.ConfigType.FullName); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/PluginFactory.Test/TestPluginA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Xfrogcn.PluginFactory.Test 7 | { 8 | class TestPluginA : IPlugin 9 | { 10 | public Task StartAsync(IPluginContext context) 11 | { 12 | return Task.CompletedTask; 13 | } 14 | 15 | public Task StopAsync(IPluginContext context) 16 | { 17 | return Task.CompletedTask; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/PluginFactory.Test/TestPluginB.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Xfrogcn.PluginFactory.Test 7 | { 8 | public class TestPluginB : IPlugin 9 | { 10 | public Task StartAsync(IPluginContext context) 11 | { 12 | return Task.CompletedTask; 13 | } 14 | 15 | public Task StopAsync(IPluginContext context) 16 | { 17 | return Task.CompletedTask; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/PluginFactory.Test/TestPluginC.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Xfrogcn.PluginFactory.Test 7 | { 8 | public class TestPluginC : TestPluginB, ISupportInitPlugin 9 | { 10 | public class TestDIClass 11 | { 12 | public string Message { get; set; } = "Hello"; 13 | } 14 | 15 | 16 | public void Init(IPluginInitContext context) 17 | { 18 | context.ServiceCollection.AddSingleton(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/PluginFactory.Test/TestPluginD.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Xfrogcn.PluginFactory.Test 7 | { 8 | public class TestPluginDOptions 9 | { 10 | 11 | } 12 | 13 | public class TestPluginD : SupportConfigPluginBase 14 | { 15 | public TestPluginD(IOptionsMonitor options) : base(options) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/PluginFactory.Test/TestPluginE.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using System.Threading.Tasks; 7 | 8 | namespace Xfrogcn.PluginFactory.Test 9 | { 10 | public class TestPluginEOptions 11 | { 12 | public string ConfigA { get; set; } 13 | 14 | public string ConfigB { get; set; } 15 | } 16 | 17 | /// 18 | /// 模拟插件注入服务 19 | /// 20 | public class TestPluginEService 21 | { 22 | public TestPluginEOptions Options { get; set; } 23 | } 24 | 25 | public class TestPluginE : SupportConfigPluginBase, ISupportInitPlugin 26 | { 27 | 28 | private readonly TestPluginEService _service = null; 29 | public TestPluginEService Service => _service; 30 | public bool IsStarted { get; private set; } 31 | 32 | private TestPluginEOptions _options; 33 | 34 | /// 35 | /// 初始化使用的构造函数 36 | /// 37 | /// 38 | public TestPluginE(IOptionsMonitor options) : base(options) 39 | { 40 | _options = options.CurrentValue; 41 | } 42 | 43 | public TestPluginE(IOptionsMonitor options, TestPluginEService service) : base(options) 44 | { 45 | _service = service; 46 | _service.Options = options.CurrentValue; 47 | } 48 | 49 | protected override void OnOptionsChanged(TestPluginEOptions options) 50 | { 51 | if (_service != null) 52 | { 53 | _service.Options = options; 54 | } 55 | _options = options; 56 | base.OnOptionsChanged(options); 57 | } 58 | 59 | public void Init(IPluginInitContext context) 60 | { 61 | if (_options == null) 62 | { 63 | throw new Exception("注入配置失败"); 64 | } 65 | context.ServiceCollection.AddSingleton(); 66 | } 67 | 68 | public override Task StartAsync(IPluginContext context) 69 | { 70 | IsStarted = true; 71 | return base.StartAsync(context); 72 | } 73 | 74 | public override Task StopAsync(IPluginContext context) 75 | { 76 | IsStarted = false; 77 | return base.StopAsync(context); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/PluginFactory.Test/Xfrogcn.PluginFactory.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/PluginFactory.Test/build-plugin.cmd: -------------------------------------------------------------------------------- 1 | dotnet publish ../../samples/TestPluginA -o ./bin/Debug/net5.0/Plugins 2 | rm -f ./bin/Debug/net5.0/Plugins/Microsoft.* 3 | rm -f ./bin/Debug/net5.0/Plugins/Xfrogcn.PluginFactory.* 4 | rm -f ./bin/Debug/net5.0/Plugins/*.json --------------------------------------------------------------------------------