5

SOAP フィールド要素を空のままにすると、ネイティブ型のキャスト エラーが発生します。(残念ながら、クライアントの制約により xsi:nil="true" は使用できません)

WCF コントラクトのネイティブ型を nullable<> としてマークしても、次のエラーがクライアントに返されるのを止めるには十分ではないようです。

文字列 '' は有効なブール値ではありません。System.Xml.XmlConvert.ToBoolean(String s) で System.Xml.XmlConverter.ToBoolean(String 値) で System.FormatException

DataContractSerializer に空の要素を変換して null に逆シリアル化するように指示する最良の方法を知っている人はいますか?

私の例の WCF サービス コントラクト。

[ServiceContract()]
public interface IMyTest
{
    [OperationContract]
    string TestOperation(TestRequest request);
}

[ServiceBehavior()]
public class Settings : IMyTest
{
    public string TestOperation(TestRequest request)
    {
        if (request.TestDetail.TestBool.HasValue)
            return "Bool was specified";
        else
            return "Bool was null";
    }

}

[DataContract()]
public class TestRequest
{
    [DataMember(IsRequired = true)]
    public int ID { get; set; }

    [DataMember(IsRequired = true)]
    public TestDetail TestDetail { get; set; }
}

[DataContract()]
public class TestDetail
{
    [DataMember()]
    public bool? TestBool { get; set; }
}

WCF に次の送信を受け入れるようにするにはどうすればよいですか。

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ster="mynamespace">
   <soapenv:Header/>
   <soapenv:Body>
      <ster:TestOperation>
         <ster:request>
            <ster:ID>1</ster:ID>
            <ster:TestDetail>
               <ster:TestBool></ster:TestBool>
            </ster:TestDetail>
         </ster:request>
      </ster:TestOperation>
   </soapenv:Body>
</soapenv:Envelope>

クライアントは挿入する値を変更することしかでき<ster:TestBool>{here}</ster:TestBool>ないため、true false または何もしないことが唯一のオプションです。

4

1 に答える 1

4

IDispatchMessageFormatter でフォーマットする前に、Operation Behavior を使用して基になるメッセージを変更することで、これをクラックしたと思います。

次のコードは、WCF ファイルレス アクティベーションに基づくサービスに対するソリューションを提供します。

IOperationBehavior を Attribute クラスの形式でライブにしたかったのです。次に、各サービス操作を新しい属性で装飾するだけで、その操作の IOperationBehavior が開始されます。これは、エンド ユーザーにとって非常に便利でシンプルです。

重要な問題は、動作を適用する場所です。これは重要です。属性を介して動作を適用するときに WCF によって呼び出される操作動作の順序は、サービス ホストで適用する場合とは異なります。属性に基づく順序は次のとおりです。

  1. System.ServiceModel.Dispatcher.OperationInvokerBehavior
  2. MyOperationBehaviorAttribute
  3. System.ServiceModel.OperationBehaviorAttribute
  4. System.ServiceModel.Description.DataContractSerializerOperationBehavior
  5. System.ServiceModel.Description.DataContractSerializerOperationGenerator

何らかの理由で、DataContractSerializerOperationBehavior の前に操作動作 (属性を使用して適用された場合のみ) が呼び出されます。メッセージを調整した後 (コードを参照)、フォーマッタ内の DataContractSerializerOperationBehavior フォーマッタ (内部フォーマッタとして動作に渡される) に逆シリアル化を委任したいので、これは問題です。Microsoft が完全に優れたデシリアライザーを既に提供している場合、デシリアライゼーション ルーチンを書き直す必要はありません。最初のインスタンスで XML を修正するだけで、空白が XML 内で正しく表現される null に変換され、DataContractSerializer がそれらをサービス インターフェイスで null 許容型に結び付けることができます。

したがって、これは属性ベースの動作を意図したとおりに使用できないことを意味します。なぜなら、この現象の理由が見当たらないため、ここではかなり微妙な方法で WCF が壊れている可能性があるからです。そのため、IOperationBehavior を操作に追加することもできます。サービス ホストの作成段階で手動で割り当てる必要があるだけです。これは、IOperationBehavior が「正しい」シーケンスに挿入されるためです。つまり、DataContractSerializerOperationBehavior が作成された後です。内部フォーマッタへの参照を取得できますか。

 // This operation behaviour changes the formatter for a specific set of operations in a web service.

[System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false)]
public class NullifyEmptyElementsAttribute : Attribute
{
    // just a marker, does nothing
}

public class NullifyEmptyElementsBahavior : IOperationBehavior
{
    #region IOperationBehavior Members

    public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) 
    {

    }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) {  }

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        // we are the server, we need to accept client message that omit the xsi:nill on empty elements
        dispatchOperation.Formatter = new NullifyEmptyElementsFormatter(dispatchOperation.Formatter);

    }

    public void Validate(OperationDescription operationDescription) { }

    #endregion IOperationBehavior Members
}

/// <summary>
///  This customized formatter intercepts the deserialization process to perform extra processing.
/// </summary>
public class NullifyEmptyElementsFormatter : IDispatchMessageFormatter
{
    // Hold on to the original formatter so we can use it to return values for method calls we don't need.
    private IDispatchMessageFormatter _innerFormatter;

    public NullifyEmptyElementsFormatter(IDispatchMessageFormatter innerFormatter)
    {
        // Save the original formatter
        _innerFormatter = innerFormatter;
    }

    /// <summary>
    /// Check each node and add the xsi{namespace}:nil declaration if the inner text is blank
    /// </summary>
    public static void MakeNillable(XElement element)
    {
        XName _nillableAttributeName = "{http://www.w3.org/2001/XMLSchema-instance}nil"; // don't worry, the namespace is what matters, not the alias, it will work

        if (!element.HasElements) // only end nodes
        {
            var hasNillableAttribute = element.Attribute(_nillableAttributeName) != null;

            if (string.IsNullOrEmpty(element.Value))
            {
                if (!hasNillableAttribute)
                    element.Add(new XAttribute(_nillableAttributeName, true));
            }
            else
            {
                if (hasNillableAttribute)
                    element.Attribute(_nillableAttributeName).Remove();
            }
        }
    }

    public void DeserializeRequest(System.ServiceModel.Channels.Message message, object[] parameters)
    {


var buffer = message.CreateBufferedCopy(int.MaxValue);

        var messageSource = buffer.CreateMessage(); // don't affect the underlying stream

        XDocument doc = null;

        using (var messageReader = messageSource.GetReaderAtBodyContents())
        {
            doc = XDocument.Parse(messageReader.ReadOuterXml()); // few issues with encoding here (strange bytes at start of string), this technique resolves that
        }

        foreach (var element in doc.Descendants())
        {
            MakeNillable(element);
        }

        // create a new message with our corrected XML
        var messageTarget = Message.CreateMessage(messageSource.Version, null, doc.CreateReader());
        messageTarget.Headers.CopyHeadersFrom(messageSource.Headers);

        // now delegate the work to the inner formatter against our modified message, its the parameters were after
         _innerFormatter.DeserializeRequest(messageTarget, parameters);
    }

    public System.ServiceModel.Channels.Message SerializeReply(System.ServiceModel.Channels.MessageVersion messageVersion, object[] parameters, object result)
    {
        // Just delegate this to the inner formatter, we don't want to do anything with this.
        return _innerFormatter.SerializeReply(messageVersion, parameters, result);
    }
}


public class MyServiceHost : ServiceHost
{
    public MyServiceHost(Type serviceType, params Uri[] baseAddresses)
        : base(serviceType, baseAddresses) { }

     protected override void OnOpening()
    {
        base.OnOpening();

        foreach (var endpoint in this.Description.Endpoints)
        {
            foreach (var operation in endpoint.Contract.Operations)
            {
                if ((operation.BeginMethod != null && operation.BeginMethod.GetCustomAttributes(_NullifyEmptyElementsBahaviorAttributeType, false).Length > 0)
                       ||
                       (operation.SyncMethod != null && operation.SyncMethod.GetCustomAttributes(_NullifyEmptyElementsBahaviorAttributeType, false).Length > 0)
                       ||
                       (operation.EndMethod != null && operation.EndMethod.GetCustomAttributes(_NullifyEmptyElementsBahaviorAttributeType, false).Length > 0))
                {
                    operation.Behaviors.Add(new NullifyEmptyElementsBahavior());
                }
            }
        }
    }
}

おそらく、受信メッセージを変更しているだけなので、IDispatchMessageFormatter のアクティブ化順序への依存を削除する IDispatchMessageInspector を代わりに使用できます。しかし、これは今のところ機能します;)

使用法:

  1. オペレーションに追加
 [ServiceContract(Namespace = Namespaces.MyNamespace)]
 public interface IMyServiceContrct
 {
       [OperationContract]
       [NullifyEmptyElements]
       void MyDoSomthingMethod(string someIneteger);
 }
  1. サービスに結びつける

A. .svc がある場合は、単に MyServiceHost を参照してください

<%@ ServiceHost 
    Language="C#" 
    Debug="true" 
    Service="MyNameSpace.MyService"
    Factory="MyNameSpace.MyServiceHost"  %>

B. ファイルレス アクティベーション サービスを使用している場合は、これを web.config ファイルに追加します。

   <system.serviceModel>
        ... stuff
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" >

          <!-- WCF File-less service activation - there is no need to use .svc files anymore, WAS in IIS7 creates a host dynamically - less config needed-->
          <serviceActivations >  
            <!-- Full access to Internal services -->
            <add relativeAddress="MyService.svc" 
                 service="MyNameSpace.MyService" 
                 factory="MyNameSpace.MyServiceHost" />

          </serviceActivations>


        </serviceHostingEnvironment>
        ... stuff
    </system.serviceModel>
于 2013-10-30T11:44:18.550 に答える