4

私のユースケース:

  • 既に ASP.NET アプリケーションが動作しています
  • そのアプリケーションの一部として新しい Web サービスを実装したい
  • ASP.NET Web サービス (*.asmx)ではなく、WCF サービス (*.svc) を使用することになっています。
  • GetInterface()サービスには、インターフェイスのインスタンスを返す1 つの操作が必要です。このインスタンスはサーバー上に存在する必要があり、クライアントにシリアライズされません。そのインターフェイスで呼び出されるメソッドは、サーバー上で実行する必要があります。

これが私が試したことです(どこが間違っていたか教えてください):

  • これをテストするために、新しいASP.NET Web アプリケーションプロジェクトを作成しましたServiceSide

  • そのプロジェクト内で、「追加→新しい項目」を使用してWCF サービスを追加しました。私はそれを呼んだMainService。これにより、MainServiceクラスとIMainServiceインターフェイスの両方が作成されました。

  • ここで、クライアントとサーバー間で共有されるインターフェイス宣言のみを含む、新しいクラス ライブラリプロジェクトを作成しました。ServiceWorkLibrary

    [ServiceContract]
    public interface IWorkInterface
    {
        [OperationContract]
        int GetInt();
    }
    
  • に戻って、クラスでの実装だけでなくインターフェイスServiceSideのデフォルトDoWork()メソッドも置き換え、共有の単純な実装も追加しました。それらは次のようになります。IMainServiceMainServiceIWorkInterface

    [ServiceContract]
    public interface IMainService
    {
        [OperationContract]
        IWorkInterface GetInterface();
    }
    
    public class MainService : IMainService
    {
        public IWorkInterface GetInterface()
        {
            return new WorkInterfaceImpl();
        }
    }
    
    public class WorkInterfaceImpl : MarshalByRefObject, IWorkInterface
    {
        public int GetInt() { return 47; }
    }
    

    このアプリケーションを実行すると、ブラウザに次のようなデフォルトの Web サービス ページが表示されるという意味で「動作」します。

    サービスを作成しました。

    このサービスをテストするには、クライアントを作成し、それを使用してサービスを呼び出す必要があります。これは、コマンド ラインから次の構文で svcutil.exe ツールを使用して行うことができます。

    svcutil.exe http://localhost:59958/MainService.svc?wsdl
    

    これにより、クライアント クラスを含む構成ファイルとコード ファイルが生成されます。2 つのファイルをクライアント アプリケーションに追加し、生成されたクライアント クラスを使用してサービスを呼び出します。例えば:

  • それではクライアントに。別の Visual Studio で、新しいソリューションで呼び出される新しいコンソール アプリケーションプロジェクトを作成しました。プロジェクトを追加し、からの参照を追加しましClientSideた。ServiceWorkLibraryClientSide

  • 次に、上記のsvcutil.exe呼び出しを実行しました。これにより と が生成さMainService.csoutput.config、プロジェクトに追加しましたClientSide

  • 最後に、次のコードをMainメソッドに追加しました。

    using (var client = new MainServiceClient())
    {
        var workInterface = client.GetInterface();
        Console.WriteLine(workInterface.GetType().FullName);
    }
    
  • これは、コンストラクター呼び出しで不可解な例外で既に失敗しています。output.configに名前を変更することで、これを修正できましたApp.config

  • の戻り値の型がではなくGetInterface()isであることに気付きました。理由を知っている人はいますか?しかし、先に進みましょう...objectIWorkInterface

  • これを実行するとCommunicationException、呼び出し時に次のようになりGetInterface()ます。

    基になる接続が閉じられました: 接続が予期せず閉じられました。

これを修正して、IWorkInterface期待どおりの透過プロキシを取得するにはどうすればよいですか?

私が試したこと

  • [KnownType(typeof(WorkInterfaceImpl))]の宣言に追加してみましたWorkInterfaceImpl。これを行うと、同じ場所で別の例外が発生します。これはNetDispatcherFaultException、次のメッセージ付きです。

    メッセージを逆シリアル化しようとしているときにフォーマッタが例外をスローしました: パラメータhttp://tempuri.org/:GetInterfaceResultを逆シリアル化しようとしているときにエラーが発生しました。InnerException メッセージは、「行 1 位置 491 のエラーです。要素 'http://tempuri.org/:GetInterfaceResult' には、名前 'http://schemas.datacontract.org/2004/07/ にマップされる型からのデータが含まれています。 ServiceSide:WorkInterfaceImpl'. デシリアライザーは、この名前にマップされる型を認識しません。DataContractResolver の使用を検討するか、'WorkInterfaceImpl' に対応する型を既知の型のリストに追加します。たとえば、KnownTypeAttribute 属性を使用するか、DataContractSerializer に渡される既知の型のリストに追加します。詳細については、InnerException を参照してください。

    InnerException言及されたのSerializationExceptionはメッセージ付きです:

    行 1 位置 491 のエラー。要素 'http://tempuri.org/:GetInterfaceResult' には、名前 'http://schemas.datacontract.org/2004/07/ServiceSide:WorkInterfaceImpl' にマップされるタイプのデータが含まれています。デシリアライザーは、この名前にマップされる型を認識しません。DataContractResolver の使用を検討するか、'WorkInterfaceImpl' に対応する型を既知の型のリストに追加します。たとえば、KnownTypeAttribute 属性を使用するか、DataContractSerializer に渡される既知の型のリストに追加します。

    これは、システムが型をシリアル化しようとしていることを示しているように見えることに注目してください。そうすべきではありません。代わりに透過プロキシを生成することになっています。シリアル化の試行を停止するにはどうすればよいですか?

  • クラス[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]に属性 を追加してみました。WorkInterfaceImpl無効。

  • インターフェイスの属性(共有ライブラリで宣言されている) を に[ServiceContract]変更してみました。また効果なし。IWorkInterfaceServiceWorkLibrary[ServiceContract(SessionMode = SessionMode.Required)]

  • system.diagnosticsまた、次の魔法の要素をWeb.configin に追加してみましたServerSide:

      <system.diagnostics>
        <!-- This logging is great when WCF does not work. -->
        <sources>
          <source name="System.ServiceModel" switchValue="Information, ActivityTracing" propagateActivity="true">
            <listeners>
              <add name="traceListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData="c:\traces.svclog"  />
            </listeners>
          </source>
        </sources>
      </system.diagnostics>
    

    これにより、約束どおりにファイルが生成されc:\traces.svclogますが、その内容を理解できるかどうかはわかりません。生成されたファイルを Pastebin here に投稿しました。を使用すると、この情報をより使いやすい UI で表示できますsvctraceviewer.exe。私はそうしましたが、率直に言って、それらすべてが私に何も教えてくれません...

私は何を間違っていますか?

4

3 に答える 3

1
于 2012-02-09T17:57:28.130 に答える
0

とはMainServiceClient()? これは、クライアント メッセージをサーバーにマーシャリングするクラスです。

WCF でパラメーターとしてインターフェイスを返すことに関する関連する SO 投稿を参照する必要があります。ServiceKnownTypeAttributeが役立つ場合があります。

セッションMarshalByRefは、.NET Remoting の動作に関連しているため、探しているものでもあります。

もう 1 つの方法 ( MSDN フォーラムで言及されているようにEndpointAddress) は、インターフェイス自体ではなく、サービス インターフェイスのを返すことです。

WCFは、バインドに関係なく、すべてをシリアル化します。同じシステム上のサービスと通信する必要がある場合に取るべき最善の方法は、IPC トランスポート バインディング( net.pipe ) を使用することです。

于 2012-02-07T15:20:02.773 に答える
0

あなたがやろうとしているのは、SOAの原則「サービスはクラスではなくスキーマとコントラクトを共有する」の直接的な違反です。つまり、実装コードをサービスからそのコンシューマーに実際に渡すのではなく、コントラクト自体で指定された戻り値だけを渡します。

一般に、WCF と SOA の主な焦点は相互運用性です。つまり、どのプラットフォームのクライアントからでもサービスにアクセスできる必要があります。Java または C++ の消費者は、あなたが設計しているこのサービスをどのように使用できますか? 簡単に言えば、できなかったということです。そのため、SOAP などのメッセージング標準でこのコードをシリアル化することは、不可能ではないにしても困難です。

このコードを構造化するより適切な方法は、IWorkerInterface の各実装を独自のサービスとしてホストし (サービス コントラクトとして定義されているため)、各サービスを異なるエンドポイントで公開することです。IWorkerInterface へのプロキシのリモート ファクトリとして動作する MainService の代わりに、セットアップしたさまざまなサービスへのエンドポイント ファクトリとして機能できます。エンドポイント メタデータは、IMainService によって簡単にシリアル化してクライアントに提供できます。次に、クライアントはそのメタデータを取得し、カスタム IServiceProxy 実装を介して、または WCF によって既に提供されているオブジェクト (ChannelFactory など) を介して、リモート実装へのプロキシを構築できます。

于 2012-02-07T15:57:08.600 に答える