0

ここの例 (http://stackoverflow.com/questions/835839/client-configuration-to-consume-wcf-json-web-service) は単純明快です。単純なオブジェクトの配列を返す WCF RESTFUL JSON サービスを作成しました。JavaScript では、返されたものを問題なく処理できるため、Web クライアントは問題なく動作します。

このサービスを Web または C# クライアントから呼び出せるようにして、データを取得するコードを複製することなく、公開されたデータを複数のコンテキストで使用できるようにしたいと考えています。

私の C# コードには、プロキシ クライアント、インターフェイス コントラクト、構成ベースのバインディング定義があります。C# サービスと C# クライアント (テスト検証ハーネスとして使用する単純なコンソール アプリ) の間でインターフェイスとデータ コード モジュールを共有します。

C# クライアントでは、配列形式ではないデータが正常に返されます。サービスがオブジェクトの配列をそのまま返すか、単純なオブジェクトのラップされたプロパティとして返すとすぐに、クライアント側のシリアライザーはサイレントに失敗し、空の配列を返します。

トップレベルのクライアントコードは次のとおりです

 // for managed code to call our Ajax/Json incident service, we need to reuse the interface contract, 
 // and use the ServiceModel.Channels infra to hand-code a proxy client.
 public class IncidentClient : ClientBase<IIncidentServices.IGetActiveIncidents>, IIncidentServices.IGetActiveIncidents
 {
    public incidents GetActiveIncidents(string environmentAbbreviation)
    {
        return base.Channel.GetActiveIncidents(environmentAbbreviation);
    }
 }

 class Program
 {
    static void Main(string[] args)
    {


        IncidentClient client = new IncidentClient();

        incidents data = client.GetActiveIncidents("prod");
        Console.Write("Call to GetActiveIncidents returned ");
        if (null == data)
        {
            Console.WriteLine("no data (null)");
        }
        else
        {
            Console.WriteLine(data.incidentList.Count.ToString() + " rows of incident data.");
        }
        Console.WriteLine("\nPress any key to continue...");
        Console.ReadLine();
    }
 }
}

コードを実行すると、行がゼロであることが常に通知され、デバッガーは空の配列が返されることを示します。ログをアクティブにすることで、WCF トレース ツールと交換されているメッセージをトレースしました。データが戻ってくるのを確認できます (配列内の 100 要素)。

トリッキーな部分は、シリアライザーが静かにデータを破棄することです。そのため、WCF clientBase ベースのプロキシを放棄して生の HTTP get を使用し、別の JSON パーサーを使用してデータを処理する必要があるのではないかと考えています。

私のデータ コントラクトは次のようになります。

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Dan.Test.Incident.Data
{

[DataContract(Name="incidents", Namespace="Dan.Test.Incident.Data")]
public class incidents
{
    public incidents()
    {
            data = new List<incidentData>();
    }

    [DataMember(Name="incidentList")]
    private List<incidentData> data;

    [IgnoreDataMember]
    public List<incidentData> incidentList
    {
        get {
            if (null == data)
            {
                data = new List<incidentData>();
            }
            return data;
        }
    }
}

[DataContract(Name="incidentData", Namespace="Dan.Test.Incident.Data")]
[Serializable]
public class incidentData
{
    // define incident members and accessors for read-only get operations

    [DataMember(Name = "irNumber")]
    private string m_irNumber = null;       // the incident identifier as IR12345, etc.

    [DataMember(Name = "title")]
    private string m_title = null;          // the title of the incident

    [DataMember(Name = "devname")]
    private string m_devname = null;      // list of team members who were engaged

    [DataMember(Name = "description")]
    private string m_description = null;    // description of the incident

    [DataMember(Name = "startdate")]
    private DateTime m_startdate;

    [DataMember(Name = "priority")]
    private int m_priority = 0;

    [DataMember(Name = "environmentID")]
    private int m_environmentID = 0;

    [DataMember(Name = "status")]
    private string m_status;

    [DataMember(Name = "enddate")]
    private DateTime m_enddate;

    public incidentData()
    {
    }
}
}

私のインターフェース定義は

using Dan.Test.Incident.Data;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace IIncidentServices
{
[ServiceContract(Namespace = "Dan.Test.Incident.Data")]
public interface IGetActiveIncidents
{
    [OperationContract]
    [WebGet(ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
    incidents GetActiveIncidents(string environmentAbbreviation);
}
}

私の設定は次のように簡単です:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
    <sources>
        <source name="System.ServiceModel.MessageLogging" switchValue="Error,ActivityTracing">
            <listeners>
                <add type="System.Diagnostics.DefaultTraceListener" name="Default">
                    <filter type="" />
                </add>
                <add name="ServiceModelMessageLoggingListener">
                    <filter type="" />
                </add>
            </listeners>
        </source>
        <source propagateActivity="true" name="System.ServiceModel" switchValue="Verbose,ActivityTracing">
            <listeners>
                <add type="System.Diagnostics.DefaultTraceListener" name="Default">
                    <filter type="" />
                </add>
                <add name="ServiceModelTraceListener">
                    <filter type="" />
                </add>
            </listeners>
        </source>
    </sources>
    <sharedListeners>
        <add initializeData="c:\users\danro\documents\visual studio 2012\projects\gadgetactiveincidentservice\consoleapplication1\app_messages.svclog"
            type="System.Diagnostics.XmlWriterTraceListener, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
            name="ServiceModelMessageLoggingListener" traceOutputOptions="Timestamp">
            <filter type="" />
        </add>
        <add initializeData="c:\users\danro\documents\visual studio 2012\projects\gadgetactiveincidentservice\consoleapplication1\app_tracelog.svclog"
            type="System.Diagnostics.XmlWriterTraceListener, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
            name="ServiceModelTraceListener" traceOutputOptions="Timestamp">
            <filter type="" />
        </add>
    </sharedListeners>
</system.diagnostics>
<startup> 
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<diagnostics>
  <messageLogging logEntireMessage="true" logMalformedMessages="true"
    logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true"
    maxMessagesToLog="3000" maxSizeOfMessageToLog="100000" />
  <endToEndTracing messageFlowTracing="true" />
</diagnostics>
<bindings>
  <webHttpBinding>
    <binding name="NewBinding2" openTimeout="00:05:00" receiveTimeout="00:50:00"
      sendTimeout="00:05:00" hostNameComparisonMode="WeakWildcard"
      maxBufferSize="2000000" maxBufferPoolSize="2000000" maxReceivedMessageSize="2000000"
      useDefaultWebProxy="false" contentTypeMapper="">
      <readerQuotas maxDepth="32" maxStringContentLength="100000" maxArrayLength="10000"
        maxBytesPerRead="2000000" maxNameTableCharCount="100000" />
    </binding>
  </webHttpBinding>
</bindings>
<behaviors>
  <endpointBehaviors>
    <behavior name="webwcf">
      <webHttp defaultBodyStyle="WrappedRequest" defaultOutgoingResponseFormat="Json" 
        automaticFormatSelectionEnabled="true" faultExceptionEnabled="true" />
    </behavior>
  </endpointBehaviors>
</behaviors>
<client>
  <endpoint address="http://localhost/GadgetActiveIncidentService/ActiveIncidentService.svc"
    behaviorConfiguration="webwcf" binding="webHttpBinding" bindingConfiguration="NewBinding2"
    contract="IIncidentServices.IGetActiveIncidents" name="ActiveIncidentService" />
</client>
</system.serviceModel>
</configuration>

私のデータを見ると、これは戻ってきたものの短いスニペットです-そして、奇妙に見える唯一のものは、データの [d] __type 行です....

{"d":[{"__type":"incidentData:Dan.Test.Incident.Data",
"description":"My description","devname":"",
"enddate":"\/Date(1357995120000-0800)\/",
"environmentID":10,"irNumber":"IR742989","priority":1,
"startdate":"\/Date(1357973873643-0800)\/",
"status":"closed","title":"A subset of users "},
{"__type":"incidentData:Dan.Test.Incident.Data","description":"second description.",
"devname":"","enddate":"\/Date(1352871180000-0800)\/",
"environmentID":10,"irNumber":"IR595320","priority":2,
"startdate":"\/Date(1352758680000-0800)\/",
"status":"This incident has been downgraded.",
"title":"Users will be unable to upgrade"}]}

ここの誰かが、これを機能させるために私が何をする必要があるかを明らかにしてくれることを願っています:)

前もって感謝します

ダン

4

1 に答える 1

2

WebScriptEnablingBehaviorJSON データを有効にするためのWCF<enableWebScript/>からすぐに使用できる 2 つの動作があります。前者は、ASP.NET AJAX フレームワークを使用しているときに使用され、JS で、サービスと通信する方法を知っている「プロキシ」を提供します。後者は、より汎用的な JSON 通信に使用されます (Web スクリプトよりもオーバーヘッドが少ない)。WebHttpBehavior<webHttp/>

あなたのコメントに基づいて、あなたは最初のものを使用しています。その動作は、ASP.NET AJAX フレームワークの要件に基づいており、{"d":...}配列のある種の JS プロトタイプ ハイジャックを防ぐために、IIRC で応答をオブジェクト (表示されているもの) にラップする必要があります。したがって、そのようなエンドポイントからの JSON を使用したい場合は、応答を「ラップ解除」してその「d」を取り除くか、実際にそれを理解する動作を使用する必要があります。

「通常の」HTTP クライアントを使用してサービスを利用し、JSON シリアライザーを使用して応答をデシリアライズする場合、最も簡単な方法は、単純にラッピング クラスを作成し、そのクラスをルート タイプとして渡すことです。 Incidents クラスの代わりにシリアライザー。クラスが 1 つ (または少数) しかない場合は簡単な解決策ですが、多数ある場合はメンテナンスの問題になる可能性があります。

WCF ベースのクライアントを使用してサービスを使用する場合は、より一般的な WebHttpBehavior ではなく、サービスで使用されているものと同じ動作 (WebScriptEnablingBehavior) を使用する必要があります。それもうまくいきます。

サービスを所有している場合は、さらに別の選択肢があります。今度は WebHttpBehavior を使用して、別のエンドポイントを追加できます。これは、"d" をラップせずにデータを返します。これにより、HTTP クライアントとデシリアライザーを直接使用できるようになります。

以下のコードは、最初の 2 つの選択肢を示しています。

public class Post_e5cb2f0a_717d_4255_9142_7c9f7995fa4f
{
    [ServiceContract]
    public interface IGetActiveIncidents
    {
        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
        Incidents GetActiveIncidents(string environmentAbbreviation);
    }
    [CollectionDataContract]
    public class Incidents : List<IncidentData>
    {
        public Incidents() { }
        public Incidents(List<IncidentData> incidents) : base(incidents) { }
    }
    public class Service : IGetActiveIncidents
    {
        public Incidents GetActiveIncidents(string environmentAbbreviation)
        {
            Incidents incidents = new Incidents();
            incidents.Add(new IncidentData(
                "IR12345", "Title", "dev engaged", "description", DateTime.UtcNow, 1, 10, "status", DateTime.UtcNow + new TimeSpan(1, 0, 0, 0)));
            return incidents;
        }
    }
    [DataContract]
    public class IncidentData
    {
        public IncidentData(string irNumber, string title, string devName, string description, DateTime startDate, int priority, int envId, string status, DateTime endDate)
        {
            m_irNumber = irNumber;
            m_title = title;
            m_devname = devName;
            m_description = description;
            m_startdate = startDate;
            m_priority = priority;
            m_environmentID = envId;
            m_status = status;
            m_enddate = endDate;
        }

        [DataMember(Name = "irNumber")]
        private string m_irNumber = null;

        [DataMember(Name = "title")]
        private string m_title = null;

        [DataMember(Name = "devname")]
        private string m_devname = null;

        [DataMember(Name = "description")]
        private string m_description = null;

        [DataMember(Name = "startdate")]
        private DateTime m_startdate;

        [DataMember(Name = "priority")]
        private int m_priority = 0;

        [DataMember(Name = "environmentID")]
        private int m_environmentID = 0;

        [DataMember(Name = "status")]
        private string m_status;

        [DataMember(Name = "enddate")]
        private DateTime m_enddate;

        public IncidentData()
        {
        }
    }
    [DataContract]
    class IncidentWrapper
    {
        [DataMember(Name = "d")]
        public Incidents Incidents { get; set; }
    }
    public static void Test()
    {
        string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
        ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
        var endpoint = host.AddServiceEndpoint(typeof(IGetActiveIncidents), new WebHttpBinding(), "");
        endpoint.Behaviors.Add(new WebScriptEnablingBehavior());
        host.Open();
        Console.WriteLine("Host opened");

        //Using a "normal" HTTP client
        WebClient c = new WebClient();
        byte[] data = c.DownloadData(baseAddress + "/GetActiveIncidents?environmentAbbreviation=dd");
        MemoryStream ms = new MemoryStream(data);
        DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(IncidentWrapper));
        IncidentWrapper wrapper = (IncidentWrapper)dcjs.ReadObject(ms);
        Console.WriteLine("Using HttpClient/DCJS: {0}", wrapper.Incidents.Count);

        // Using a WCF client (with WebScriptEnablingBehavior
        ChannelFactory<IGetActiveIncidents> factory = new ChannelFactory<IGetActiveIncidents>(new WebHttpBinding(), new EndpointAddress(baseAddress));
        factory.Endpoint.Behaviors.Add(new WebScriptEnablingBehavior());
        IGetActiveIncidents proxy = factory.CreateChannel();

        Console.WriteLine("Using WCF client (with WSEB): {0}", proxy.GetActiveIncidents("dd").Count);
    }
}
于 2013-02-01T22:35:18.163 に答える