gpt4 book ai didi

WCF - 将空元素转换为可为空的 native 类型

转载 作者:行者123 更新时间:2023-12-03 16:44:47 29 4
gpt4 key购买 nike

将 SOAP 字段元素留空会导致 native 类型的转换错误。 (遗憾的是,由于客户端限制,无法使用 xsi:nil="true")

将 WCF 契约(Contract) native 类型标记为 nullable<> 似乎不足以阻止将以下错误返回给客户端。

The string '' is not a valid Boolean value. at System.Xml.XmlConvert.ToBoolean(String s) at System.Xml.XmlConverter.ToBoolean(String value) System.FormatException



有谁知道指示 DataContractSerializer 将要反序列化的空元素转换为 null 的最佳方法?

我的示例 WCF 服务契约(Contract);
[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 or nothing 是唯一的选择。

最佳答案

好的,我相信我已经通过使用操作行为来修改底层消息,然后再通过 IDispatchMessageFormatter 对其进行格式化。

以下代码针对基于 WCF 无文件激活的服务提供了解决方案。

我想让我的 IOperationBehavior 以 Attribute 类的形式存在。然后我可以简单地用我的新属性装饰每个服务操作,这将激发该操作的 IOperationBehavior - 对最终用户来说非常好和简单。

关键问题是你在哪里应用行为,这很关键。通过属性应用行为时,WCF 调用的操作行为的顺序与在服务主机应用时不同。基于属性的顺序如下:

  • System.ServiceModel.Dispatcher.OperationInvokerBehavior
  • 我的操作行为属性
  • System.ServiceModel.OperationBehaviorAttribute
  • System.ServiceModel.Description.DataContractSerializerOperationBehavior
  • System.ServiceModel.Description.DataContractSerializerOperationGenerator

  • 由于某种原因,操作行为(仅当通过使用属性应用时)将被称为 之前 DataContractSerializerOperationBehavior。这是一个问题,因为在我的行为中,在我调整了消息(参见代码)之后,我想将反序列化委托(delegate)给我的格式化程序中的 DataContractSerializerOperationBehavior 格式化程序(作为内部格式化程序传递给我的行为)。当微软已经提供了一个非常好的反序列化器时,我不想重新编写反序列化例程。我只是在第一个实例中更正 XML,以便将空白转换为在 XML 中正确表示的空值,以便 DataContractSerializer 可以将它们绑定(bind)到服务接口(interface)中的可为空类型。

    因此,这意味着我们不能按预期使用基于属性的行为,因为 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());
    }
    }
    }
    }
    }

    也许因为我只是在修改传入的消息,我可以改用 IDispatchMessageInspector,这将消除对 IDispatchMessageFormatter 激活顺序的依赖。但这暂时有效;)

    用法:
  • 加入您的运营

  •  [ServiceContract(Namespace = Namespaces.MyNamespace)]
    public interface IMyServiceContrct
    {
    [OperationContract]
    [NullifyEmptyElements]
    void MyDoSomthingMethod(string someIneteger);
    }

  • 为您服务

  • 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>

    关于WCF - 将空元素转换为可为空的 native 类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19656691/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com