簡體   English   中英

.NET 4.5 XmlSerializer,xsi:nillable屬性和字符串值的問題

[英]Problems with .NET 4.5 XmlSerializer, xsi:nillable attribute, and string values

對於StackOverflow的大人物,請聽我的請求:

我正在編寫.NET 4.5庫代碼以與Oracle SalesCloud服務進行通信,並且我遇到了在C#對象上具有空字符串值的SOAP請求的問題。

屬性的XSD指定如下:

<xsd:element minOccurs="0" name="OneOfTheStringProperties" nillable="true" type="xsd:string" />

使用VS2013中的“添加服務引用...”實用程序並在我正在更新OneOfTheStringProperties之外的其他內容時寫入請求,輸出為

<OneOfTheStringProperties xsi:nil="true></OneOfTheStringProperties>

在服務器端,這會導致兩個問題。 首先,由於也以這種方式指定了只讀屬性,因此服務器拒絕整個請求。 其次,這意味着我可能無意中消除了我想要保留的值(除非我每次都發回每個屬性......效率低下且編碼很難。)

我的Google-fu在這一方面很弱,而且我不想深入研究自定義XmlSerializer(以及隨之而來的所有測試/邊緣情況),除非它是最好的路線。

到目前為止,我能找到的最好的是遵循[Property] Specified模式。 這樣做,對於每個可用的字符串屬性意味着我必須將以下內容添加到Reference.cs中的定義

[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool OneOfTheStringPropertiesSpecified 
{
    get { return !String.IsNullOrEmpty(OneOfTheStringProperties); }
    set { }
}

這是很多打字,但它的工作原理,SOAP消息的日志跟蹤是正確的。

我希望就三種途徑之一提供建議:

  1. 配置開關,特定XmlSerializer覆蓋或其他一些修復將禁止.NET 4.5 XmlSerializer輸出為空字符串

  2. 像“ <OneOfTheStringProperties xsi:nil="true" />那樣會產生”正確“XML的相同秘密公式

  3. 有針對性的教程來創建擴展(或現有的VS2013擴展),這將允許我右鍵單擊字符串屬性並插入以下模式:

     [System.Xml.Serialization.XmlIgnoreAttribute()] public bool [$StringProperty]Specified { get { return !String.isNullOrEmpty([$StringProperty]); } set { } } 

我也對任何其他建議持開放態度。 如果只是使用正確的搜索條件(顯然,我不是),那也將非常感激。

為了促進這一要求,知識的守護者,我提供了這種犧牲的山羊

添加澄清

為了確保,我不是在尋找一鍵魔術子彈。 作為一名開發人員,尤其是那些在基礎結構經常因需求而發生變化的團隊中工作的人,我知道要做好准備需要做很多工作。

但是,我正在尋找的是每次我必須對結構進行刷新時合理地減少工作量(對於其他人來說,這是一個簡化的配方來實現同樣的目的。)例如,使用* Specified意味着輸入對於給定的示例,大約165個字符。 對於包含45個字符串字段的合同,這意味着每次模型更改時我必須輸入超過7,425個字符 - 這是一個服務對象! 有大約10-20個服務對象可供爭奪。

右鍵單擊的想法會將其減少到45次右鍵單擊操作......更好。

放在類上的自定義屬性會更好,因為每次刷新只需要完成一次。

理想情況下,app.config中的運行時設置將是一勞永逸的 - 無論第一次實現多么困難,因為它進入庫。

我認為真正的答案是比一個近7500個字符/類更好的地方,可能不如簡單的app.config設置好,但它要么在那里,要么我相信它可以制作。

這不是完美的解決方案,但沿着45右鍵單擊行,您可以使用T4文本模板在與生成的Web服務代碼分離的部分類聲明中生成XXXSpecified屬性。

然后,單擊右鍵單擊 - >運行自定義工具,以在更新服務引用時重新生成XXXSpecified代碼。

這是一個示例模板,它為給定命名空間中的類的所有字符串屬性生成代碼:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="$(SolutionDir)<Path to assembly containing service objects>" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Reflection" #>
<#@ output extension=".cs" #>

<#
    string serviceObjectNamespace = "<Namespace containing service objects>";
#>

namespace <#= serviceObjectNamespace #> {

<#
        foreach (Type type in AppDomain.CurrentDomain.GetAssemblies()
                       .SelectMany(t => t.GetTypes())
                       .Where(t => t.IsClass && t.Namespace == serviceObjectNamespace)) {

        var properties = type.GetProperties().Where(p => p.PropertyType == typeof(string));
        if (properties.Count() > 0) {
#>

    public partial class <#= type.Name #> {

    <#
        foreach (PropertyInfo prop in properties) {
    #>

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public bool <#= prop.Name#>Specified 
        {
            get { return <#= prop.Name#> != null; }
            set { }
        }

    <#
        } 
    #>

    }

<#
    } }
#>

}

以下是如何向WCF客戶端添加自定義行為,該行為可用於檢查消息並跳過屬性。

這是以下組合:

完整代碼:

void Main()
{
    var endpoint = new Uri("http://somewhere/");

    var behaviours = new List<IEndpointBehavior>()
    {
        new SkipConfiguredPropertiesBehaviour(),
    };

    var channel = Create<IRemoteService>(endpoint, GetBinding(endpoint), behaviours);
    channel.SendData(new Data()
    {
        SendThis = "This should appear in the HTTP request.",
        DoNotSendThis = "This should not appear in the HTTP request.",
    });
}

[ServiceContract]
public interface IRemoteService
{
    [OperationContract]
    int SendData(Data d);
}

public class Data
{
    public string SendThis { get; set; }
    public string DoNotSendThis { get; set; }
}

public class SkipConfiguredPropertiesBehaviour : IEndpointBehavior
{
   public void AddBindingParameters(
        ServiceEndpoint endpoint,
        BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(
        ServiceEndpoint endpoint, 
        ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new SkipConfiguredPropertiesInspector());
    }

    public void ApplyDispatchBehavior(
        ServiceEndpoint endpoint, 
        EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(
        ServiceEndpoint endpoint)
    {
    }
}

public class SkipConfiguredPropertiesInspector : IClientMessageInspector
{
    public void AfterReceiveReply(
        ref Message reply, 
        object correlationState)
    {
        Console.WriteLine("Received the following reply: '{0}'", reply.ToString());
    }

    public object BeforeSendRequest(
        ref Message request, 
        IClientChannel channel)
    {     
        Console.WriteLine("Was going to send the following request: '{0}'", request.ToString());

        request = TransformMessage(request);

        return null;
    }

    private Message TransformMessage(Message oldMessage)
    {
        Message newMessage = null;
        MessageBuffer msgbuf = oldMessage.CreateBufferedCopy(int.MaxValue);
        XPathNavigator nav = msgbuf.CreateNavigator();

        //load the old message into xmldocument
        var ms = new MemoryStream();
        using(var xw = XmlWriter.Create(ms))
        {
            nav.WriteSubtree(xw);
            xw.Flush();
            xw.Close();
        }

        ms.Position = 0;
        XDocument xdoc = XDocument.Load(XmlReader.Create(ms));

        //perform transformation
        var elementsToRemove = xdoc.Descendants().Where(d => d.Name.LocalName.Equals("DoNotSendThis")).ToArray();

        foreach(var e in elementsToRemove)
        {
            e.Remove();
        }

        // have a cheeky read...
        StreamReader sr = new StreamReader(ms);
        Console.WriteLine("We're really going to write out: " + xdoc.ToString());

        //create the new message           
        newMessage = Message.CreateMessage(xdoc.CreateReader(), int.MaxValue, oldMessage.Version);

        return newMessage;
    }    
}

 public static T Create<T>(Uri endpoint, Binding binding, List<IEndpointBehavior> behaviors = null)
{
    var factory = new ChannelFactory<T>(binding);

    if (behaviors != null)
    {
        behaviors.ForEach(factory.Endpoint.Behaviors.Add);
    }

    return factory.CreateChannel(new EndpointAddress(endpoint));
}

public static BasicHttpBinding GetBinding(Uri uri)
{
    var binding = new BasicHttpBinding()
    {
        MaxBufferPoolSize = 524288000, // 10MB
        MaxReceivedMessageSize = 524288000,
        MaxBufferSize = 524288000,
        MessageEncoding = WSMessageEncoding.Text,
        TransferMode = TransferMode.Buffered,
        Security = new BasicHttpSecurity()
        {
            Mode = uri.Scheme == "http" ? BasicHttpSecurityMode.None : BasicHttpSecurityMode.Transport,
        }
    };

    return binding;
}

這是LinqPad腳本的鏈接: http ://share.linqpad.net/kgg8st.linq

如果你運行它,輸出將是這樣的:

Was going to send the following request: '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IRemoteService/SendData</Action>
  </s:Header>
  <s:Body>
    <SendData xmlns="http://tempuri.org/">
      <d xmlns:d4p1="http://schemas.datacontract.org/2004/07/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:DoNotSendThis>This should not appear in the HTTP request.</d4p1:DoNotSendThis>
        <d4p1:SendThis>This should appear in the HTTP request.</d4p1:SendThis>
      </d>
    </SendData>
  </s:Body>
</s:Envelope>'
We're really going to write out: <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Action a:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none" xmlns:a="http://schemas.xmlsoap.org/soap/envelope/">http://tempuri.org/IRemoteService/SendData</Action>
  </s:Header>
  <s:Body>
    <SendData xmlns="http://tempuri.org/">
      <d xmlns:a="http://schemas.datacontract.org/2004/07/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <a:SendThis>This should appear in the HTTP request.</a:SendThis>
      </d>
    </SendData>
  </s:Body>
</s:Envelope>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM