当使用自托管时,我能否自动将所有服务托管在app.config中?

7
我正在编写一个应用程序,需要托管多个WCF服务。WCF的一个优点是可以通过在app.config文件中指定设置来配置服务,而无需重新编译。
当自主托管时,似乎没有一种开箱即用的方法可以自动托管在app.config文件中的服务。我发现这个问题提到了可能的解决方案:此问题,即在运行时动态枚举列在app.config中的服务,并为每个服务创建ServiceHost。
然而,我的服务、合同和托管应用程序都位于不同的程序集中。这导致Type.GetType(string name)无法找到我的服务类型(返回null),因为它在不同的程序集中定义。
如何可靠地动态托管app.config文件中列出的所有服务(即,在我的自主托管应用程序中不硬编码new ServiceHost(typeof(MyService)))?
注:我的app.config是使用Visual Studio 2010中的“WCF Configuration Editor”生成的。
还要注意:我的主要目标是通过app.config文件来驱动这一切,以便有一个单一的配置点。我不想在其他位置进行配置。
编辑:我能够读取app.config文件(请参见此处),但需要能够解析不同程序集中的类型。
编辑:下面的一个答案促使我尝试在app.config中指定AssemblyQualifiedName,而不仅是基本类型名称。这能够解决Type.GetType()问题,但ServiceHost.Open()现在无论如何获得类型都会失败并引发InvalidOperationException。
// Fails
string typeName = typeof(MyService).AssemblyQualifiedName;
Type myType = Type.GetType(typeName);
ServiceHost host = new ServiceHost(myType);
host.Open(); // throws InvalidOperationException

// Also fails
Type myType2 = typeof(MyService);
ServiceHost host2 = new ServiceHost(myType2);
host2.Open(); // throws InvalidOperationException

异常详情:

服务 'SO.Example.MyService' 没有任何应用程序(非基础设施)端点。这可能是因为找不到您的应用程序的配置文件,或者因为在配置文件中找不到与服务名称匹配的服务元素,或者因为在服务元素中未定义端点。

我猜 WCF 在解析内部的 app.config 文件时尝试匹配服务名称的字面字符串。

编辑/答案:最终我做的基本上就是下面答案中的方法。我没有使用 Type.GetType(),而是知道我的所有服务都在同一个程序集中,所以我切换到了:

// Get a reference to the assembly which contain all of the service implementations.
Assembly implementationAssembly = Assembly.GetAssembly(typeof(MyService));
...
// When loading the type for the service, load it from the implementing assembly.
Type implementation = implementationAssembly.GetType(serviceElement.Name);

是的,服务的 name= 属性值必须与实现该 WCF 服务的类的完全限定名称完全匹配。不允许对该属性进行任何调整或“扩展”。 - marc_s
很遗憾,如果允许使用程序集限定名称会很有用,这样我就不需要额外做任何事情了。 - Travis
3个回答

5
    // get the <system.serviceModel> / <services> config section
    ServicesSection services = ConfigurationManager.GetSection("system.serviceModel/services") as ServicesSection;

    // get all classs
    var allTypes = AppDomain.CurrentDomain.GetAssemblies().ToList().SelectMany(s => s.GetTypes()).Where(t => t.IsClass == true);

    // enumerate over each <service> node
    foreach (ServiceElement service in services.Services)
    {
        Type serviceType = allTypes.SingleOrDefault(t => t.FullName == service.Name);
        if (serviceType == null)
        {
            continue;
        }

        ServiceHost serviceHost = new ServiceHost(serviceType);
        serviceHost.Open();
    }

根据其他答案,我扩展了代码如下,它可以搜索app.config中所有程序集的服务。


Type.GetType已经可以做到这一点了。它失败的原因是因为程序集本身尚未被加载。 - Travis

4
这绝对是可行的!请看这段代码片段-将其用作基础,然后从此进行:
using System.Configuration;   // don't forget to add a reference to this assembly!

// get the <system.serviceModel> / <services> config section
ServicesSection services = ConfigurationManager.GetSection("system.serviceModel/services") as ServicesSection;

// enumerate over each <service> node
foreach(ServiceElement aService in services.Services)
{
    Console.WriteLine();
    Console.WriteLine("Name: {0} / Behavior: {1}", aService.Name, aService.BehaviorConfiguration);

    // enumerate over all endpoints for that service
    foreach (ServiceEndpointElement see in aService.Endpoints)
    {
        Console.WriteLine("\tEndpoint: Address = {0} / Binding = {1} / Contract = {2}", see.Address, see.Binding, see.Contract);
    }
}

目前这只是打印信息,但你可以在自己的NT服务中使用它来构建服务主机!

更新:好的,抱歉,我错过了你最重要的一点 - 实际服务位于不同的程序集中。

在这种情况下,您需要根据需要动态加载这些程序集 - 例如,您可以“简单地知道”要加载哪些程序集,或者您可以将它们全部放入特定的子目录中并加载该目录中的所有程序集,或者您可以仅检查与MyOwnServiceHost.exe位于同一位置的所有程序集,并检查是否存在所需的任何类型。

WCF配置未处理此部分 - 您需要按照最合理的方式自己完成此操作以找到哪个服务类型位于哪个程序集中。

// find currently executing assembly
Assembly curr = Assembly.GetExecutingAssembly();

// get the directory where this app is running in
string currentLocation = Path.GetDirectoryName(curr.Location);

// find all assemblies inside that directory
string[] assemblies = Directory.GetFiles(currentLocation, "*.dll");

// enumerate over those assemblies
foreach (string assemblyName in assemblies)
{
   // load assembly just for inspection
   Assembly assemblyToInspect = Assembly.ReflectionOnlyLoadFrom(assemblyName);

   if (assemblyToInspect != null)
   {
      // find all types
      Type[] types = assemblyToInspect.GetTypes();

      // enumerate types and determine if this assembly contains any types of interest
      // you could e.g. put a "marker" interface on those (service implementation)
      // types of interest, or you could use a specific naming convention (all types
      // like "SomeThingOrAnotherService" - ending in "Service" - are your services)
      // or some kind of a lookup table (e.g. the list of types you need to find from
      // parsing the app.config file)
      foreach(Type ty in types)
      {
         // do something here
      }
   }
}

我不明白这与我链接的问题有何不同。我错过了什么吗?我目前的主要问题是Type.GetType(string name)返回null。我已经能够从app.config文件中找到类型名称。 - Travis

1

您在问题链接中正确地找到了答案,@marc_s的回答也给出了正确的方法。实际问题是,您需要动态获取程序集的Type实例,而该程序集可能仅通过配置文件引用,因此可能未加载到当前AppDomain中。

查看这个博客文章以动态引用程序集。尽管该文章是专门针对ASP.NET应用程序编写的,但一般的方法在自托管场景中应该也适用。其思想是将Type.GetType(string)调用替换为一个私有方法调用,该方法会动态加载所请求的程序集(如有必要)并返回Type对象。您发送给该方法的参数仍将是元素名称,并且您需要确定要加载哪个程序集。简单的基于约定的程序集命名方案应该可以解决问题。例如,如果服务类型是:

MyNamespace.MyService.MyServiceImpl

那么假设这个汇编代码是:

MyNamespace.MyService


我意识到实际问题,这就是为什么我在我的问题描述中包含它的原因。你提到的博客文章很有趣且信息量丰富,但似乎有点过于武断。根据命名空间动态加载程序集的建议似乎是合理的。更深入地思考后,我想知道是否可以在app.config文件中放置一个程序集限定类型名称(Type.AssemblyQualifiedName,包括程序集引用),而不是默认的软类型名称。 - Travis
希望能够奏效。如果不行,可以通过使用配置运行时元素来设置应用程序的dependentAssembly元素从配置文件中加载程序集。这将负责将程序集加载到AppDomain中。您只需要使用反射获取要启动的服务的Type实例即可。 - Sixto Saez
当.Open()失败时,抛出了什么异常? - Sixto Saez
根据MSDN文档中对GetType的描述,除非类型位于执行程序集或mscorlib中,否则需要提供AssemblyQualifiedName。[http://msdn.microsoft.com/en-us/library/w3f99sx1.aspx] - Travis
抱歉,这些建议似乎没有帮助。从异常信息来看,由于全名的问题,WCF 无法自动配置服务。您可以尝试使用 Appconfig 存储所需的服务配置信息,然后在将其添加到 ServiceHost 构造函数之前,在代码中使用这些值来配置服务。但这种方法并不是很正规。 - Sixto Saez
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接