注册在应用程序启动时实现接口 (Web API) 的所有类

本文关键字:API Web 应用程序 启动 接口 实现 注册 | 更新日期: 2023-09-27 18:31:07

更新:

基本上,这归结为"如何强制类库在 Web API 站点的应用程序启动时加载,以便我可以一次反映它们并确保我获得某个类的所有实现。 或者,如果没有好的方法可以做到这一点,那么允许该库中的类自行注册的最佳方法是什么?

原始问题:

我正在尝试在我的 Web API 中注册在应用程序启动时实现某个接口的所有类,并将它们放在一个列表中,以便我以后可以找到它们,而无需在每次调用时通过程序集进行反射。

这似乎相当简单,尽管我以前从未做过。因此,在谷歌搜索并阅读了其他一些 Stack Overflow 问题之后,我创建了一个容器类,并放置了一个方法来注册所有具体实现。简化的解决方案如下所示:

public static void RegisterAllBots()
        {
            var type = typeof(IRobot);
            var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(type.IsAssignableFrom);
            foreach (var t in types)
            {
                TypeRepo.Add(t.Name.ToLower(), t);
            }
        }

然后我把我的 RegisterAllBots() 放在 Global.asax 的 Application_Start() 中。

问题是,有时(但并非总是)如果我开始"冷"调试解决方案,它找不到实现,只找到接口本身。如果我在运行之前转到 Build -> Rebuild Solution,它会找到它们。所以我假设这是Visual Studio启动WebHost项目而不重建其他类库项目的问题。

所以我在这里有几个问题。

  1. 我对这里的原因是正确的吗?
  2. 我怎样才能阻止这种情况?
  3. 截至目前,这在生产中会发生吗?我已经将其部署到测试站点,它似乎可以工作,但这可能只是运气,因为有时它确实找到了具体的实现。

注册在应用程序启动时实现接口 (Web API) 的所有类

AppDomain.CurrentDomain.GetAssemblies()将仅返回已加载到当前应用程序域中的程序集,当使用该程序集中的类型时,将加载程序集。

过去每当我需要这样做时,我只是维护一个程序集列表,即

var assemblies = new [] 
{
    typeof(TypeFromAssemblyA).Assembly,
    typeof(TypeFromAssemblyB).Assembly
};

这只需要包含每个程序集中的一个类型,以确保程序集加载到当前应用程序域中。

另一种选择是强制使用 Assembly.GetReferencedAssemblies()Assembly.Load(AssemblyName) 加载所有引用的程序集。有关此方法的更多详细信息,请参阅此问题。在此之后,您可以使用现有代码,因为所有程序集都将加载到应用程序域中。

第三种选择是利用托管扩展性框架 (MEF) 之类的东西来Export并随后从目录中的程序集Import类型。

选择哪个选项将取决于解决方案结构以及发现实现的方式。我喜欢第一种方法,因为它明确了将扫描哪些程序集,但最后一种方法允许在不重新编译的情况下实现更大的可扩展性。

这是我通常在实用程序类中使用它的一种方法来做到这一点。

    public static List<Type> GetTypes<T>()
    {
        var results = new List<Type>();
        var assemblies = AppDomain.CurrentDomain.GetAssemblies();
        foreach (var assembly in assemblies)
        {
            var types = assembly.GetTypes()
                .Where(t => t.IsAbstract == false
                    && (typeof(T).IsInterface == false || t.GetInterfaces().Contains(typeof(T)))
                    && (typeof(T).IsClass == false || t.BaseType == typeof(T)))
                .ToList();
            results.AddRange(types);
        }
        return results;
    }

首先,您不能从每个类库中获取类的所有实现,类实现始终有可能仅存在于存储在桌子下的拇指驱动器上的 dll 中。因此,您必须确定要查找的这些实现的来源。

您的实现将当前AppDomain作为源,因此查找运行时已加载的实现。我猜您正在寻找那些未加载的实现。如果是这样,您必须将自己限制在一组目录(或从外部配置加载该集合描述)以查找包含实现的库(Environment.CurrentDirectory可能就足够了)。你可以有两种情况

您现在了解每个实现

例如,可以将程序集名称和完整类型标识符的列表存储在数据库或配置文件中。如果是这样,可以尝试找到这些特定的程序集,并通过反射 API 加载所需的实现。但是,存在不正确信息的问题:您可以拥有列表中未提及的实现,或者换句话说,您可以在列表中包含有关实现的信息,这些信息在运行时中不存在。此外,你可以有引用每个实现的辅助dll,所以当你加载它时 - 你也会把每个实现加载到AppDomain(从这一点开始,你可以使用你的初始解决方案)

您现在不会了解每个实现

在这种情况下,只有一种方法。对于源目录中的每个 dll:

  • 尝试将程序集从该 dll 加载到当前AppDomain
  • 获取在程序集中定义的所有类型,以查看是否有预期的实现
  • 如果有,请将其装入所需的容器中。如果没有 - 卸载程序集,否则您将拥有巨大的内存开销,没有任何好处。

如果可以对实现强制实施其他代码支持(这是您自己的代码,或者您对第三方实现者有严格的准则),则可以使用管理扩展性框架为您处理此任务:您必须按Export属性标记每个实现,定义组合目录以查找包含实现的库,并启动组合以将每个实现加载到由 ImportMany 属性标记的组合容器。否则,您必须自己准确地实现这些操作,但要注意有问题的情况:

  • 程序集可能无法加载 - 因此请注意异常
  • 程序集可以引用另一个程序集,
  • 因此当加载它时,引用的程序集加载得很好。检查实现时很容易错过这些程序集


就我个人而言,即使在第一种情况下,我也更喜欢使用第二种方式,因为我不喜欢将隐性依赖引入父母的想法。

可以使用 Assembly.LoadAssembly 在运行时加载程序集(如果尚未加载)。 非常简单的例子:

        List<Assembly> loadedAssemblies = new List<Assembly>();
        DirectoryInfo di = new DirectoryInfo("path to my assemblies");
        foreach (FileInfo fi in di.GetFiles("*.dll"))
        {
            try
            {
                loadedAssemblies.Add(Assembly.LoadFrom(fi.FullName));
            }
            catch (Exception ex)
            {
                // handle problems loading the assemblies here - there are a boatload of possible failures
            }
        }
        foreach (Assembly a in loadedAssemblies)
        {
            // Use reflection to do whatever it is that you wanted to do
        }

MSDN 上提供了其他文档:http://msdn.microsoft.com/en-us/library/1009fa28%28v=vs.110%29.aspx

只需使用Scrutor的第三方库即可。

 services.Scan(scan => scan
.AddTypes(typeof(IRequest<>))
.AsSelf()
.WithScopedLifetime());