web-dev-qa-db-ja.com

XMLではモデルは常にnull POST

現在、システム間の統合に取り組んでおり、WebApiを使用することにしましたが、問題が発生しています...

モデルがあるとしましょう:

public class TestModel
{
    public string Output { get; set; }
}

そしてPOSTメソッドは:

public string Post(TestModel model)
{
    return model.Output;
}

Fiddlerからヘッダー付きのリクエストを作成します。

User-Agent: Fiddler
Content-Type: "application/xml"
Accept: "application/xml"
Host: localhost:8616
Content-Length: 57

と体:

<TestModel><Output>Sito</Output></TestModel>

メソッドmodelPostパラメーターは常にnullであり、理由はわかりません。誰にも手がかりがありますか?

42
Wowca

2つのこと:

  1. コンテンツタイプを引用符""で囲む必要はなく、Fiddlerでヘッダー値を受け入れます。

    User-Agent: Fiddler
    Content-Type: application/xml
    Accept: application/xml
    
  2. Web APIは、デフォルトでxmlシリアル化にDataContractSerializerを使用します。したがって、タイプの名前空間をxmlに含める必要があります。

    <TestModel 
    xmlns="http://schemas.datacontract.org/2004/07/YourMvcApp.YourNameSpace"> 
        <Output>Sito</Output>
    </TestModel> 
    

    または、WebApiConfig.RegisterXmlSerializerを使用するようにWeb APIを構成できます。

    config.Formatters.XmlFormatter.UseXmlSerializer = true;
    

    その場合、XMLデータに名前空間は必要ありません。

     <TestModel><Output>Sito</Output></TestModel>
    
68
nemesv

答えはすでに与えられていますが、検討する価値のある他の詳細をいくつか見つけました。

XML投稿の最も基本的な例は、Visual Studioによって新しいWebAPIプロジェクトの一部として自動的に生成されますが、この例では入力パラメーターとして文字列を使用します。

Visual Studioによって生成された簡易サンプルWebAPIコントローラー

using System.Web.Http;
namespace webAPI_Test.Controllers
{
    public class ValuesController : ApiController
    {
        // POST api/values
        public void Post([FromBody]string value)
        {
        }
    }
}

これは、手元の質問に対応していないため、あまり役に立ちません。ほとんどのPOST Webサービスには、パラメーターとしてかなり複雑なタイプがあり、おそらく応答として複雑なタイプがあります。上記の例を拡張して、複雑な要求と複雑な応答を含めます...

サンプルは簡略化されていますが、複合型が追加されています

using System.Web.Http;
namespace webAPI_Test.Controllers
{
    public class ValuesController : ApiController
    {
        // POST api/values
        public MyResponse Post([FromBody] MyRequest value)
        {
            var response = new MyResponse();
            response.Name = value.Name;
            response.Age = value.Age;
            return response;
        }
    }

    public class MyRequest
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class MyResponse
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

この時点で、フィドラーで呼び出すことができます。

フィドラーリクエストの詳細

リクエストヘッダー:

User-Agent: Fiddler
Host: localhost:54842
Content-Length: 63

リクエスト本文:

<MyRequest>
   <Age>99</Age>
   <Name>MyName</Name>
</MyRequest>

...コントローラーにブレークポイントを配置すると、リクエストオブジェクトがnullであることがわかります。これはいくつかの要因によるものです...

  • WebAPIのデフォルトでは、DataContractSerializerが使用されます
  • Fiddlerリクエストでコンテンツタイプまたは文字セットが指定されていません
  • リクエストの本文にXML宣言が含まれていません
  • リクエストの本文には名前空間の定義は含まれません。

Webサービスコントローラーに変更を加えることなく、fiddlerのリクエストを修正して機能させることができます。 xml POSTリクエスト本文のネームスペース定義に注意してください。また、リクエストヘッダーに一致する正しいUTF設定でXML宣言が含まれていることを確認してください。

複雑なデータ型で動作するようにFiddlerリクエストボディを修正

リクエストヘッダー:

User-Agent: Fiddler
Host: localhost:54842
Content-Length: 276
Content-Type: application/xml; charset=utf-16

リクエスト本文:

<?xml version="1.0" encoding="utf-16"?>
   <MyRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/webAPI_Test.Controllers">
      <Age>99</Age>
      <Name>MyName</Name>
   </MyRequest>

リクエストの名前空間が、C#コントローラークラス(の種類)の同じ名前空間をどのように参照しているかに注目してください。 DataContractSerializer以外のシリアライザーを使用するようにこのプロジェクトを変更しておらず、特定の名前空間でモデル(クラスMyRequestまたはMyResponse)を装飾していないため、WebAPIコントローラー自体と同じ名前空間を想定しています。これはあまり明確ではなく、非常に紛らわしいです。より良いアプローチは、特定の名前空間を定義することです。

特定の名前空間を定義するには、コントローラーモデルを変更します。この機能を使用するには、System.Runtime.Serializationへの参照を追加する必要があります。

モデルにネームスペースを追加

using System.Runtime.Serialization;
using System.Web.Http;
namespace webAPI_Test.Controllers
{
    public class ValuesController : ApiController
    {
        // POST api/values
        public MyResponse Post([FromBody] MyRequest value)
        {
            var response = new MyResponse();
            response.Name = value.Name;
            response.Age = value.Age;
            return response;
        }
    }

    [DataContract(Namespace = "MyCustomNamespace")]
    public class MyRequest
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public int Age { get; set; }
    }

    [DataContract(Namespace = "MyCustomNamespace")]
    public class MyResponse
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public int Age { get; set; }
    }
}

この名前空間を使用するようにFiddlerリクエストを更新します...

カスタム名前空間を持つフィドラーリクエスト

<?xml version="1.0" encoding="utf-16"?>
   <MyRequest xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="MyCustomNamespace">
      <Age>99</Age>
      <Name>MyName</Name>
   </MyRequest>

この考えをさらに進めることができます。モデルのネームスペースとして空の文字列が指定されている場合、フィドラーリクエストにネームスペースは必要ありません。

空の文字列名前空間を持つコントローラー

using System.Runtime.Serialization;
using System.Web.Http;

namespace webAPI_Test.Controllers
{
    public class ValuesController : ApiController
    {
        // POST api/values
        public MyResponse Post([FromBody] MyRequest value)
        {
            var response = new MyResponse();
            response.Name = value.Name;
            response.Age = value.Age;
            return response;
        }
    }

    [DataContract(Namespace = "")]
    public class MyRequest
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public int Age { get; set; }
    }

    [DataContract(Namespace = "")]
    public class MyResponse
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public int Age { get; set; }
    }
}

名前空間が宣言されていないフィドラーリクエスト

<?xml version="1.0" encoding="utf-16"?>
   <MyRequest>
      <Age>99</Age>
      <Name>MyName</Name>
   </MyRequest>

その他の落とし穴

DataContractSerializerは、XMLペイロードの要素がデフォルトでアルファベット順に並べられることを期待していることに注意してください。 XMLペイロードの順序が正しくない場合、一部の要素がnullであることがわかります(または、データ型が整数の場合はデフォルトでゼロになり、ブールの場合はデフォルトでfalseになります)。たとえば、順序が指定されておらず、次のxmlが送信された場合...

要素の順序が正しくないXML本体

<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
   <Name>MyName</Name>
   <Age>99</Age>
</MyRequest>  

... Ageの値はデフォルトでゼロになります。ほぼ同一のxmlが送信された場合...

要素の正しい順序のXML本体

<?xml version="1.0" encoding="utf-16"?>
<MyRequest>
   <Age>99</Age>
   <Name>MyName</Name>
</MyRequest>  

その後、WebAPIコントローラーはAgeパラメーターを正しくシリアル化し、入力します。 XMLを特定の順序で送信できるようにデフォルトの順序を変更する場合は、DataMember属性に「Order」要素を追加します。

プロパティの順序を指定する例

using System.Runtime.Serialization;
using System.Web.Http;

namespace webAPI_Test.Controllers
{
    public class ValuesController : ApiController
    {
        // POST api/values
        public MyResponse Post([FromBody] MyRequest value)
        {
            var response = new MyResponse();
            response.Name = value.Name;
            response.Age = value.Age;
            return response;
        }
    }

    [DataContract(Namespace = "")]
    public class MyRequest
    {
        [DataMember(Order = 1)]
        public string Name { get; set; }

        [DataMember(Order = 2)]
        public int Age { get; set; }
    }

    [DataContract(Namespace = "")]
    public class MyResponse
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public int Age { get; set; }
    }
}

この例では、XML本体は、Age要素の前にName要素を指定して正しく入力する必要があります。

結論

不正な形式または不完全なPOSTリクエストボディ(DataContractSerializerの観点から)はエラーをスローせず、単にランタイムの問題を引き起こすだけです。DataContractSerializerを使用する場合、満たす必要があります。シリアル化ツール(特に名前空間周辺)。テストツールを使用するのが良い方法であることがわかりました-DataContractSerializerを使用してXMLを逆シリアル化する関数にXML文字列を渡します。逆シリアル化が発生しない場合にエラーをスローします。 DataContractSerializerを使用したXML文字列(これを実装する場合は、System.Runtime.Serializationへの参照を追加する必要があることを思い出してください)。

DataContractSerializer逆シリアル化の評価用テストコードの例

public MyRequest Deserialize(string inboundXML)
{
    var ms = new MemoryStream(Encoding.Unicode.GetBytes(inboundXML));
    var serializer = new DataContractSerializer(typeof(MyRequest));
    var request = new MyRequest();
    request = (MyRequest)serializer.ReadObject(ms);

    return request;
}

オプション

他の人が指摘したように、DataContractSerializerはXMLを使用するWebAPIプロジェクトのデフォルトですが、他のXMLシリアライザーもあります。 DataContractSerializerを削除して、代わりにXmlSerializerを使用できます。 XmlSerializerは、誤った形式の名前空間のものに対してより寛容です。

別のオプションは、リクエストをXMLの代わりにJSONの使用に制限することです。 JSONの逆シリアル化中にDataContractSerializerが使用されているかどうか、およびJSONの相互作用がモデルを装飾するためにDataContract属性を必要とするかどうかを判断する分析を実行していません。

55
barrypicker

これを2日間解決しようとしていました。最終的に、外側のタグは変数名ではなく型名である必要があることがわかりました。事実上、POSTメソッドとして

public string Post([FromBody]TestModel model)
{
    return model.Output;
}

私は体を提供していました

<model><Output>Sito</Output></model>

の代わりに

<TestModel><Output>Sito</Output></TestModel>
2
Dave the Sax

Content-Typeヘッダーをapplication/xmlに設定し、config.Formatters.XmlFormatter.UseXmlSerializer = true;RegisterメソッドでWebApiConfig.csを設定したことを確認したら、それが重要ですXMLドキュメントの上部にバージョン管理やエンコードは必要ありません。

この最後の作品が私を動かしませんでした。これが誰かを助け、あなたの時間を節約することを願っています。

2
zekedaddy17