C# でアプリのプラグインを読み込む記事を書きました。
追加
一度書いたけど、不十分だったので追加記事書きました。
追加終わり
プラグインを読み込む記事
しかし、この方法だと状況によってはアプリからプラグインのメソッドを呼ぶ際に、引数の型がうまくいかずにエラーになります。
詳細は後日書きますね。
■ 簡単に
簡単に言うと、同じ Type でも別のプラグイン領域の DLL から生まれていると別の型として扱われます。
そのため、アプリの側とプラグイン側、それぞれが独自に同じ DLL を使用している場合、同じ型なのに別の型です。
■ やはりリフレクション! リフレクションはすべてを解決する!
- プラグイン側は object 型を引数にしてメソッドを用意しておく
- アプリは普通に引数を渡す
- プラグイン内では引数のメソッドやプロパティはリフレクションで扱う
■ こんな感じ
Web アプリで DI するためのコードで、いろいろとアプリとして動かすためにコードが入っているので長いです。
使いたいメソッドが拡張メソッドだから長くなっている面もありますけれども。
コード読まないでも、リフレクションで解決したことだけ知っておいていただけるとよいかと思います。
github.com リフレクションのコードをクラスライブラリとしてまとめたところ
using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Metadata; using System.Text; using System.Xml.Linq; namespace RkSoftware.RKPlugin.Reflection; internal static class TypeInitializer { static Dictionary<string, Type> Types = new Dictionary<string, Type>(); static Dictionary<(Type, string), MethodInfo> Methods = new Dictionary<(Type, string), MethodInfo>(); static Type? Type(string typeName) { if (Types.TryGetValue(typeName, out var type) && type != null) return type; type = System.Type.GetType(typeName); if (type == null) return null; Types[typeName] = type; return type; } public static MethodInfo? Method(string typeName, string key, Func<Type, MethodInfo?> func) { var type = Type(typeName); if (type == null) return null; var dictionaryKey = (type, key); if (Methods.TryGetValue(dictionaryKey, out var method) && method != null) return method; method = func(type); if (method == null) return null; Methods[dictionaryKey] = method; return method; } }
using RkSoftware.RKPlugin.Reflection; using System; using System.Collections.Generic; using System.Reflection; using System.Text; namespace RkSoftware.RKPlugin.DependencyInjection; public static class PluginHttpClientFactoryServiceCollection { public static readonly string BaseType = "Microsoft.Extensions.DependencyInjection.HttpClientFactoryServiceCollectionExtensions,Microsoft.Extensions.Http"; public static object? AddHttpClient<TClient, TImplementation>(object services, Func<HttpClient, TImplementation> factory) where TClient : class where TImplementation : class, TClient { const string MethodName = "AddHttpClient<T1, T2>(object, Func<HttpClient, TImplementation>)"; var method = TypeInitializer.Method(BaseType, MethodName, type => { var methodInfo = type?.GetMethods().Where(x => x.Name == "AddHttpClient" && x.GetGenericArguments().Length == 2 && x.GetParameters().Length == 2 && x.GetParameters()[1].Name == "factory" ).FirstOrDefault(); var method = methodInfo?.MakeGenericMethod([typeof(TClient), typeof(TImplementation)]); return method; }); return method?.Invoke(null, [services, factory]); } }
プラグイン側
public class Class1 { public static string ConfigureServices(object service) { var r01 = RkSoftware.RKPlugin.DependencyInjection.PluginHttpClientFactoryServiceCollection.AddHttpClient<ITest, Test>(service, F01)?.ToString(); var r02 = RkSoftware.RKPlugin.DependencyInjection.PluginServiceCollectionService.AddScoped(service, F02)?.ToString(); return string.Join(", ", r02, r02); }
アプリ側
using RkSoftware.RKPlugin; using System.Reflection; var builder = WebApplication.CreateBuilder(args); // Add services to the container. var services = builder.Services; var plugins = PluginLoadContext.LoadExtensions(pluginPath); foreach (var plugin in plugins) foreach (var assembly in plugin.Assemblies) foreach (var type in assembly.GetTypes()) { if (method != null) method.Invoke(null, [services]); }