7

複数の WCF サービスをホストする必要があるアプリケーションを作成しています。WCF の強みの 1 つは、app.config ファイルで設定を指定することにより、再コンパイルせずにサービスを構成できることです。

セルフホスティングの場合、app.config ファイルにあるサービスを自動的にホストするすぐに使える方法はないようです。実行時に app.config にリストされているサービスを動的に列挙し、それぞれに対して ServiceHost を作成するという可能な解決策について言及しているこの質問を見つけました。

ただし、私のサービス、コントラクト、およびホスティング アプリケーションはすべて別のアセンブリにあります。これにより、別のアセンブリで定義されているためType.GetType(string name)、サービス タイプ (戻り値) を見つけることができません。null

app.config ファイルにリストされているすべてのサービスを動的に確実にホストするにはどうすればよいですか (つまり、new ServiceHost(typeof(MyService))自己ホスティング アプリケーションでハードコーディングする必要はありませんか?

注: 私の app.config は、Visual Studio 2010 の「WCF 構成エディター」を使用して生成されました。

注: 私の主な目標は、これを app.config ファイルによって駆動することであり、これにより単一の構成ポイントが存在します。これを別の場所で構成する必要はありません。

編集: app.config ファイルを読み取ることができます (こちらを参照) が、さまざまなアセンブリの型を解決できる必要があります。

編集: 以下の回答の 1 つにより、基本的な型名だけでなく、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

例外の詳細:

Service 'SO.Example.MyService' has zero application (non-infrastructure) endpoints. This might be because no configuration file was found for your application, or because no service element matching the service name could be found in the configuration file, or because no endpoints were defined in the service element.

app.config ファイルを内部で解析するときに、WCF がサービス名のリテラル文字列を照合しようとしていると思います。

編集/回答:私がやったことは、基本的に以下の回答にあったものでした。使用する代わりに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);
4

3 に答える 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 内のサービスが検索されます。

于 2012-10-21T16:25:35.603 に答える
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
      }
   }
}
于 2011-05-13T15:11:33.793 に答える
1

質問リンクで問題に対する答えを正しく特定しました。@marc_s の答えも正しいアプローチを提供します。あなたが抱えている実際の問題は、現在の AppDomain にロードされない可能性があるため、構成ファイルを介してのみ参照されるアセンブリの Type インスタンスを動的に取得する必要があることです。

コード内でアセンブリを動的に参照する方法については、このブログ投稿を参照してください。投稿は特に ASP.NET アプリケーション向けですが、一般的なアプローチは自己ホスト型のシナリオでも機能するはずです。Type.GetType(string) 呼び出しを、必要に応じて要求されたアセンブリを動的に読み込み、Type オブジェクトを返すプライベート メソッド呼び出しに置き換えるというアイデアです。このメソッドに送信するパラメーターは引き続き element.Name であり、読み込む正しいアセンブリを特定する必要があります。単純な規則ベースのアセンブリ命名スキームが機能するはずです。たとえば、サービス タイプが次の場合:

MyNamespace.MyService.MyServiceImpl

次に、アセンブリを次のように仮定します。

MyNamespace.MyService

于 2011-05-13T16:20:17.510 に答える