{"id":13429624,"url":"https://github.com/natemcmaster/DotNetCorePlugins","last_synced_at":"2025-03-16T03:31:59.151Z","repository":{"id":37390248,"uuid":"126920724","full_name":"natemcmaster/DotNetCorePlugins","owner":"natemcmaster","description":".NET Core library for dynamically loading code","archived":false,"fork":false,"pushed_at":"2025-01-05T19:14:35.000Z","size":395,"stargazers_count":1663,"open_issues_count":9,"forks_count":230,"subscribers_count":66,"default_branch":"main","last_synced_at":"2025-03-15T16:38:51.209Z","etag":null,"topics":["dotnet-core"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/natemcmaster.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["natemcmaster"]}},"created_at":"2018-03-27T03:02:37.000Z","updated_at":"2025-03-14T06:52:34.000Z","dependencies_parsed_at":"2024-06-21T14:04:25.676Z","dependency_job_id":"7f78b271-d317-4a91-9737-957d874b5e15","html_url":"https://github.com/natemcmaster/DotNetCorePlugins","commit_stats":{"total_commits":157,"total_committers":18,"mean_commits":8.722222222222221,"dds":0.3439490445859873,"last_synced_commit":"e1e456789ce3116f57551d1338b61eede81998d3"},"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/natemcmaster%2FDotNetCorePlugins","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/natemcmaster%2FDotNetCorePlugins/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/natemcmaster%2FDotNetCorePlugins/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/natemcmaster%2FDotNetCorePlugins/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/natemcmaster","download_url":"https://codeload.github.com/natemcmaster/DotNetCorePlugins/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243796655,"owners_count":20349255,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotnet-core"],"created_at":"2024-07-31T02:00:42.667Z","updated_at":"2025-03-16T03:31:59.144Z","avatar_url":"https://github.com/natemcmaster.png","language":"C#","funding_links":["https://github.com/sponsors/natemcmaster"],"categories":["Frameworks, Libraries and Tools","C\\#","框架, 库和工具"],"sub_categories":["Application Frameworks","应用程序框架"],"readme":".NET Plugins\n============\n\n[![Build Status][ci-badge]][ci] [![Code Coverage][codecov-badge]][codecov]\n[![NuGet][nuget-badge] ![NuGet Downloads][nuget-download-badge]][nuget]\n\n[ci]: https://github.com/natemcmaster/DotNetCorePlugins/actions?query=workflow%3ACI+branch%3Amain\n[ci-badge]: https://github.com/natemcmaster/DotNetCorePlugins/workflows/CI/badge.svg\n[codecov]: https://codecov.io/gh/natemcmaster/DotNetCorePlugins\n[codecov-badge]: https://codecov.io/gh/natemcmaster/DotNetCorePlugins/branch/main/graph/badge.svg?token=l6uSsHZ8nA\n[nuget]: https://www.nuget.org/packages/McMaster.NETCore.Plugins/\n[nuget-badge]: https://img.shields.io/nuget/v/McMaster.NETCore.Plugins.svg?style=flat-square\n[nuget-download-badge]: https://img.shields.io/nuget/dt/McMaster.NETCore.Plugins?style=flat-square\n\n\nThis project provides API for loading .NET assemblies dynamically, executing them as extensions to the main application, and finding and **isolating** the dependencies of the plugin from the main application. It allows fine-grained control over\nassembly isolation and type sharing. Read [more details about type sharing below](#shared-types).\n\n\u003e **Note**\n\u003e\n\u003e 2.0+ of library supports .NET 8. If you still need .NET \u003c 8 support, look for an old 1.* version of this library.\n\nBlog post introducing this project, July 25, 2018: [.NET Core Plugins: Introducing an API for loading .dll files (and their dependencies) as 'plugins'](https://natemcmaster.com/blog/2018/07/25/netcore-plugins/).\n\n**Since 2018, .NET Core 3**\nwas released, and it added stdlib API to improve assembly loading. If you are interested in understanding that API, see \"[Create a .NET Core application with plugins][plugin-tutorial]\" on docs.microsoft.com. The result of this tutorial would be a simpler version of DotNetCorePlugins, but missing some features like an API for unifying types across the load context boundary, hot reload, and .NET Core 2 support.\n\n[plugin-tutorial]: https://docs.microsoft.com/dotnet/core/tutorials/creating-app-with-plugin-support\n\n## Getting started\n\nInstall the [`McMaster.NETCore.Plugins` NuGet package.][nuget]\n\n```\ndotnet add package McMaster.NETCore.Plugins\n```\n\nThe main API to use is `PluginLoader.CreateFromAssemblyFile`.\n\n```csharp\nPluginLoader.CreateFromAssemblyFile(\n    assemblyFile: \"./plugins/MyPlugin/MyPlugin1.dll\",\n    sharedTypes: new [] { typeof(IPlugin), typeof(IServiceCollection), typeof(ILogger) },\n    isUnloadable: true)\n```\n\n* assemblyFile = the file path to the main .dll of the plugin\n* sharedTypes = a list of types which the loader should ensure are unified. (See [What is a shared type?](#shared-types))\n* isUnloadable = Allow this plugin to be unloaded from memory at some point in the future. (Requires ensuring that you have cleaned up all usages of types from the plugin before unloading actually happens.)\n\nSee example projects in [samples/](./samples/) for more detailed, example usage.\n\n## Usage\n\nUsing plugins requires at least two projects: (1) the 'host' app which loads plugins and (2) the plugin,\nbut typically also uses a third, (3) an contracts project which defines the interaction between the plugin\nand the host.\n\nFor a fully functional sample of this, see [samples/hello-world/](./samples/hello-world/) .\n\n### The plugin contract\n\nYou can define your own plugin contract. A minimal contract might look like this.\n\n```csharp\npublic interface IPlugin\n{\n    string GetName();\n}\n```\n\nThere is nothing special about the name \"IPlugin\" or the fact that it's an interface. This is just here to illustrate a concept. Look at [samples/](./samples/) for additional examples of ways you could define the interaction between host and plugins.\n\n### The plugins\n\n\u003e **Warning**\n\u003e\n\u003e Using `netstandard2.0` as the TargetFramework for your plugin project has known issues. Use `net8.0` instead.\n\nA minimal implementation of the plugin could be as simple as this.\n\n```csharp\ninternal class MyPlugin1 : IPlugin\n{\n    public string GetName() =\u003e \"My plugin v1\";\n}\n```\n\nAs mentioned above, this is just an example. This library doesn't require the use of \"IPlugin\" or interfaces or \"GetName()\"\nmethods. This code is only here to demonstrates how you can decouple hosts and plugins, but still use interfaces for type-safe\ninteractions.\n\n### The host\n\nThe host application can load plugins using the `PluginLoader` API. The host app needs to define a way to find\nthe assemblies for the plugin on disk. One way to do this is to follow a convention, such as:\n\n```\nplugins/\n    $PluginName1/\n        $PluginName1.dll\n        (additional plugin files)\n    $PluginName2/\n        $PluginName2.dll\n```\n\n**It is important that each plugin is published into a separate directory.** This will avoid contention between plugins\nand duplicate dependency issues.\n\nYou can prepare the sample plugin above by running\n\n```\ndotnet publish MyPlugin1.csproj --output plugins/MyPlugin1/\n```\n\nAn implementation of a host which finds and loads this plugin might look like this. This sample uses reflection to find\nall types in plugins which implement `IPlugin`, and then initializes the types' parameter-less constructors.\nThis is just one way to implement a host. More examples of how to use plugins can be found in [samples/](./samples/).\n\n```csharp\nusing McMaster.NETCore.Plugins;\n\nvar loaders = new List\u003cPluginLoader\u003e();\n\n// create plugin loaders\nvar pluginsDir = Path.Combine(AppContext.BaseDirectory, \"plugins\");\nforeach (var dir in Directory.GetDirectories(pluginsDir))\n{\n    var dirName = Path.GetFileName(dir);\n    var pluginDll = Path.Combine(dir, dirName + \".dll\");\n    if (File.Exists(pluginDll))\n    {\n        var loader = PluginLoader.CreateFromAssemblyFile(\n            pluginDll,\n            sharedTypes: new [] { typeof(IPlugin) });\n        loaders.Add(loader);\n    }\n}\n\n// Create an instance of plugin types\nforeach (var loader in loaders)\n{\n    foreach (var pluginType in loader\n        .LoadDefaultAssembly()\n        .GetTypes()\n        .Where(t =\u003e typeof(IPlugin).IsAssignableFrom(t) \u0026\u0026 !t.IsAbstract))\n    {\n        // This assumes the implementation of IPlugin has a parameterless constructor\n        IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);\n\n        Console.WriteLine($\"Created plugin instance '{plugin.GetName()}'.\");\n    }\n}\n```\n\n\u003ca id=\"shared-types\"\u003e\u003c/a\u003e\n\n### What is a shared type?\n\nBy default, each instance of `PluginLoader` represents a unique collection of assemblies loaded into memory.\nThis can make it difficult to use the plugin if you want to pass information from plugin to the host and vice versa.\nShared types allow you define the kinds of objects that will be passed between plugin and host.\n\nFor example, let's say you have a simple host app like [samples/hello-world/](./samples/hello-world/), and\ntwo plugins which were compiled with a reference `interface IPlugin`. This interface comes from `Contracts.dll`.\nWhen the application runs, by default, each plugin and the host will have their own version of `Contracts.dll`\nwhich .NET will keep isolated.\n\nThe problem with this isolation is that an object of `IPlugin` created within the \"PluginApple\" or \"PluginBanana\" context does not appear to be an instance of `IPlugin` in any of the other plugin contexts.\n\n![DefaultConfigDiagram](https://i.imgur.com/fHEMBO6.png)\n\nConfiguring a shared type of `IPlugin` allows the .NET to pass objects of this type across the plugin isolation\nboundary. It does this by ignoring the version of `Contracts.dll` in each plugin folder, and sharing the version that comes with the Host.\n\n![SharedTypes](https://i.imgur.com/sTGqPxa.png)\n\nRead [even more details about shared types here](./docs/what-are-shared-types.md).\n\n## Support for MVC and Razor\n\nA common usage for plugins is to load class libraries that contain MVC controllers or Razor Pages. You can\nset up an ASP.NET Core to load controllers and views from a plugin using the `McMaster.NETCore.Plugins.Mvc`\npackage.\n\n```\ndotnet add package McMaster.NETCore.Plugins.Mvc\n```\n\nThe main API to use is `.AddPluginFromAssemblyFile()`, which can be chained onto the call to `.AddMvc()`\nor `.AddRazorPages()` in the `Startup.ConfigureServices` method.\n\n```c#\npublic class Startup\n{\n    public void ConfigureServices(IServiceCollection services)\n    {\n        var pluginFile = Path.Combine(AppContext.BaseDirectory, \"plugins/MyRazorPlugin/MyRazorPlugin.dll\");\n        services\n            .AddMvc()\n            // The AddPluginFromAssemblyFile method comes from McMaster.NETCore.Plugins.Mvc\n            .AddPluginFromAssemblyFile(pluginFile);\n    }\n}\n```\n\nSee example projects in [samples/aspnetcore-mvc/](./samples/aspnetcore-mvc/) for more detailed, example usage.\n\n## Reflection\n\nSometimes you may want to use a plugin along with reflection APIs such as `Type.GetType(string typeName)`\nor `Assembly.Load(string assemblyString)`. Depending on where these APIs are used, they might fail to\nload the assemblies in your plugin. There is an API which you can use to set the _ambient context_\nwhich .NET's reflection APIs will use to load the correct assemblies from your plugin.\n\nExample:\n```c#\nvar loader = PluginLoader.CreateFromAssemblyFile(\"./plugins/MyPlugin/MyPlugin1.dll\");\n\nusing (loader.EnterContextualReflection())\n{\n    var myPluginType = Type.GetType(\"MyPlugin.PluginClass\");\n    var myPluginAssembly = Assembly.Load(\"MyPlugin1\");\n}\n\n```\n\nRead [this post written by .NET Core engineers](https://github.com/dotnet/coreclr/blob/v3.0.0/Documentation/design-docs/AssemblyLoadContext.ContextualReflection.md) for even more details on contextual reflection.\n\n## Overriding the Default Load Context\n\nUnder the hood, DotNetCorePlugins is using a .NET API called [ApplicationLoadContext][alc-api].\nThis creates a scope for resolving assemblies. By default, `PluginLoader` will create a new context\nand fallback to a **default context** if it cannot find an assembly or if type sharing is enabled.\nThe default fallback context is inferred when `PluginLoader` is instantiated. In certain advanced scenarios,\nyou may need to manually change the default context, for instance, plugins which then load more plugins,\nor when running .NET in a custom native host.\n\n[alc-api]: https://docs.microsoft.com/dotnet/api/system.runtime.loader.assemblyloadcontext\n\nTo override the default assembly load context, set `PluginConfig.DefaultContext`. Example:\n\n\n```csharp\nAssemblyLoadContext myCustomDefaultContext = // (something).\nPluginLoader.CreateFromAssemblyFile(dllPath,\n     config =\u003e config.DefaultContext = myCustomDefaultContext);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnatemcmaster%2FDotNetCorePlugins","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnatemcmaster%2FDotNetCorePlugins","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnatemcmaster%2FDotNetCorePlugins/lists"}