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

时间:2014-11-20 13:37:14

标签: c# asp.net-mvc asp.net-web-api

更新:

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

原始问题:

我试图在我的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 - >在运行之前重建解决方案,它会找到它们。所以我假设这是Visual Studio启动WebHost项目而不重建其他类库项目的问题。

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

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

4 个答案:

答案 0 :(得分:6)

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

每当我需要这样做时,我只是简单地保留了一个集合列表,即

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

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

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

第三种选择是从目录中的程序集中使用Managed Extensibility Framework (MEF)Export以及随后的Import类型。

您选择哪个选项取决于您的解决方案结构以及您希望如何发现实现。我喜欢第一种方法,因为它明确了哪些程序集将被扫描,但最后一种方法允许更多的可扩展性而无需重新编译。

答案 1 :(得分:1)

这是我在实用程序类中通常使用的方法。

    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;
    }

答案 2 :(得分:0)

首先,您无法从每个类库获得所有类的实现,类实现总是存在于存储在桌面下方的拇指驱动器上的DLL。因此,您必须确定要查找的实现的源。

您的实现具有当前AppDomain作为源,因此查找已由运行时加载的实现。我想你正在寻找那些没有加载的实现。如果是这样,您必须将自己局限于某些目录集(或从外部配置加载该集描述)以查找包含实现的库(Environment.CurrentDirectory可能就足够了)。你可以有两个案例

您现在关于每个实现

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

您现在还不了解每项实施

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

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

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

  • 装配无法加载 - 因此请注意例外
  • 程序集可以引用另一个程序集,因此在加载时,引用的程序集加载很好。检查实施时很容易错过这些程序集


我个人更喜欢在第一种情况下使用第二种方式,因为我不想将对儿童的隐性依赖引入父母。

答案 3 :(得分:0)

如果尚未加载程序集,则可以使用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