WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

Web API に XML データを送信

by WebSurfer 9. August 2020 15:08

ASP.NET Web API 2 に XML 形式のデータを送信し、サーバー側のアクションメソッドで受け取る方法を書きます。(注: .NET Framework 版の話です。Core 版は未検証・未確認)

UTF-8 の場合

ASP.NET Web API で XML データを送信するというのは需要がなさそうで、あまり役には立たないかもしれませんが、せっかく考えたことなので整理して備忘録として残しておくことにしました。

(1) UTF-8 の場合

元の話は Teratail のスレッド「ASP.NET Web APIでPOSTされたXMLデータをXML形式で取得する方法」のものです。

サンプル XML データは以下の通りです。

<?xml version="1.0" encoding="utf-8"?>
<sample code="1.0">
  <data>
   <id>100</id>
  </data>
  <value>
    <name>日本語</name>
    <version>1.0.0</version>
  </value>
</sample>

この XML データを ASP.NET Web API に POST 送信し、サーバー側でアクションメソッド Post(SampleData value) の引数 value にモデルバインドするにはどうしたらよいかという話です。SampleData クラスの定義は以下の通り。属性を付与した理由は下に書きます。

namespace WebApi.Models
{
    [System.Xml.Serialization.XmlRoot("sample")]
    public class SampleData
    {
        [System.Xml.Serialization.XmlAttribute("code")]
        public string Code { get; set; }

        [System.Xml.Serialization.XmlElement("data")]
        public Data Data { get; set; }

        [System.Xml.Serialization.XmlElement("value")]
        public Value Value { get; set; }
    }

    public class Data
    {
        [System.Xml.Serialization.XmlElement("id")]
        public string Id { get; set; }
    }

    public class Value
    {
        [System.Xml.Serialization.XmlElement("name")]
        public string Name { get; set; }

        [System.Xml.Serialization.XmlElement("version")]
        public string Version { get; set; }
    }
}

XML データの文字コードが UTF-8 であれば以下の 3 つの設定でこの記事の一番上の画像の通りモデルバインドできます。

  1. 要求ヘッダに Content-Type: application/xml; charset=UTF-8 の設定

    Microsoft のドキュメント How WebAPI does Parameter Binding には content-type の設定だけで済みそうなことが書いてありますが、自分が試した限りでは下の 2, 3 も必要でした。
  2. App_Start/WebApiConfig.cs へ以下の設定の追加

    config.Formatters.XmlFormatter.UseXmlSerializer = true;  
  3. モデルのクラス、プロパティに XmlRoot, XmlAttribute, XmlElement 属性の付与

    xml コードの要素名とそれをバインドするクラス、プロパティ名を関連付けるために必要です。たとえ同名でも、上の例のように大文字小文字の違いがある場合は、プロパティにXmlElement 属性を付与してその引数に xml の要素名を設定しないとバインドされません。また、xml コードの属性値(この記事の例では code)をバインドする場合は、それに該当するプロパティへの XmlAttribute 属性の付与が必要です。

(2) Shift_JIS の場合

XML データの文字コードが Shift_JIS の場合はどうでしょう? (これも Teratail のスレッド「ASP.NET Web API POSTされた文字エンコードShift_JISのXMLデータをモデルバインドできない」の話です)

上の「(1) UTF-8 の場合」のような方法ではうまくいかないようです(null がバインドされる)。もちろん XML データを encoding="shift_jis" とし、要求ヘッダを Content-Type: application/xml; charset=shift_jis としたのですがダメでした。

検索するなどして調べてみましたが成果がなかったです。Shift_JIS ではヒットしないので UTF-16 に範囲を広げてググってみましたが、stackoverflow のスレッド Web API not able to bind model for POST with utf-16 encoded XML など、できなかったという記事しか見つかりませんでした。

そこを何とかするには、Microsoft のドキュメント How WebAPI does Parameter Binding の最初の方に書いてあるように、HttpRequestMessage クラスを引数に持つメソッドを使う他には手がなさそうな感じです。

HttpRequestMessage.Content プロパティで HttpContent オブジェクトを取得し、HttpContent.ReadAsStringAsync メソッドで POST されてきた XML 文字列を読んで、XDocument.Load メソッドで XDocument オブジェクトを作ってそれを操作するようにしてみました。

なお、この方法を取った場合、上の「UTF-8 の場合」で行った「2. App_Start/WebApiConfig.cs へ以下の設定の追加」と「3. モデルのクラス、プロパティに XmlRoot, XmlAttribute, XmlElement 属性の付与」は必要ありません。

検証は以下のようにしました。

まず Fiddler の Composer を使って、要求ヘッダに Content-Type: application/xml; charset=shift_jis を付与し、Request Body に XML データを設定して送信します。

Fiddler の Composer で送信

Fiddler の Inspectors で HexView を選択し、Request Body に設定して送信した XML データの 16 進数表現をチェックします。XML データの中の <name>日本語</name> の「日本語」の文字コードは 93 FA 96 7B 8C EA と Shift_JIS になっているのが分かります。

要求ヘッダとボディの 16 進数データ

Visual Studio のデバッガでアクションメソッドの Local 変数を確認します。「日本語」は文字化けせず期待通り取得できています。

アクションメソッドの Local 変数

ちょっと予想外だったのは、Fiddler の Composer の Request Body に書いた「日本語」の文字コードが Shift_JIS になることと、HttpContent.ReadAsStringAsync メソッドがそれを正しく「日本語」と読んでくれることです。そんなに都合よくできているというのが信じ難く、何か見落としがあるかも。(汗)

Tags: , , ,

Web API

try - catch を書かないでロールバック

by WebSurfer 29. July 2020 13:45

ADO.NET + SqlClient を使ってトランザクション処理を行う場合、明示的に Rollback メソッドを書かなくてもロールバックできるという話を書きます。

ロールバックを行うコードで自分がよく目にするのは、try - catch 文を使って try 句の中に処理をまとめ、catch 句に Rollback メソッドを書いて、処理の途中で例外が発生したらロールバックできるようにするというものです。

具体例は Microsoft のドキュメント「SqlTransaction.Rollback メソッド」にありま���ので見てください。

そのドキュメントにある catch 句で Rollback するというコードは、一見して例外発生でロールバックされるということが分かって良いと思うのですが、Exception を catch してそのままにしてしまっている所が気になります。

そこは、catch するのを SqlException のみにするとか Rollback した後で再 throw することで対応できるのですが、意地でも try - catch 文は書きたくないという場合もあるかもしれません。ないかもしれませんが。(笑)

Microsoft の Blog に書いてあったことですが(今はリンク切れで読めません)、未コミットのトランザクションは SqlConnection を Dispose(Close と同じ)する際ロールバックされるそうです。

ということは、catch 句に Rollback メソッドを書くというようなことはしなくても、try - finally または using 句を使って確実に SqlConnection が Dispose / Close されるようにしておけば例外発生でロールバックされるはずです。

実際「Microsoft Visual Studio 2005 による Web アプリケーション構築技法」という本にも Rollback メソッドを使わないコード例は紹介されていました。

具体例を以下の画像の SQL Server のテーブルと ADO.NET + SqlClient のコードを使って説明します。

SQL Server のテーブル

テーブルは「エラー発生時のトランザクションのロールバック - SET XACT_ABORT ON と TRY...CATCH」という記事から借用しました。フィールド StudentID には FK 制約が設定してあり 1 ~ 6 または NULL 以外を入力しようとするとエラーになります。

コード例は下の方にアップします。using 句を使って確実に SqlConnection が Dispose されるようにしています。Rollback メソッドは書いていません。例外が発生するとアプリは終了しますが、その前に using 句により SqlConnection が Dispose され、Commit メソッドは実行されませんのでロールバックされるはずです。

コード例では、上に紹介した記事にならって、3 つの UPDATE 文を実行しようとしています。最後の UPDATE 文で StudentID を 7 に設定し(1 ~ 6 または NULL 以外は FK 制約違反)そこで FK 制約違反となるようにして試してみました。エラーメッセージは以下の通りです。

ハンドルされていない例外: System.Data.SqlClient.SqlException: UPDATE ステートメントは FOREIGN KEY 制約 "FK__TestResul__Stude__22AA2996" と競合しています。競合が発生したのは、データベース "TestDatabase"、テーブル "dbo.Student", column 'StudentID' です。

static void Main(string[] args)
{
    string connString = @"接続文字列";
    string query = "UPDATE [TestDatabase].[dbo].[TestResult] " +
                   "SET [StudentID]=@StudentID " +
                   "WHERE [TestResultID]=@TestResultID";

    using (var connection = new SqlConnection(connString))
    {
        using (var command = new SqlCommand(query, connection))
        {
            command.Parameters.Add(
                new SqlParameter("@StudentID", SqlDbType.Int));
            command.Parameters.Add(
                new SqlParameter("@TestResultID", SqlDbType.Int));
            connection.Open();
            var sqltx = connection.BeginTransaction();
            command.Transaction = sqltx;

            command.Parameters["@StudentID"].Value = 5;
            command.Parameters["@TestResultID"].Value = 1;
            command.ExecuteNonQuery();

            command.Parameters["@StudentID"].Value = 6;
            command.Parameters["@TestResultID"].Value = 2;
            command.ExecuteNonQuery();

            // FK 制約違反
            command.Parameters["@StudentID"].Value = 7;
            command.Parameters["@TestResultID"].Value = 3;
            command.ExecuteNonQuery();

            sqltx.Commit();
        }
    }
}

結果、期待通りロールバックされて TestResult テーブルには 3 つの UPDATE 文のいずれも反映されないことを確認しました。

上に紹介した記事の Transact-SQL を使った場合の例では、SET XACT_ABORT ON を設定しない場合、TRY...CATCH を使って CATCH で明示的に ROLLBACK する必要があるそうです。しかし、ADO.NET + SqlClient では上のコード例のように SqlConnection を Dispose / Close すればロールバックされるようです。

Tags: , , ,

ADO.NET

ASP.NET アプリの Settings.settings

by WebSurfer 13. July 2020 16:44

ASP.NET Web アプリでも Web アプリケーションプロジェクトであれば Settings.settings を利用できます。ASP.NET Web アプリでこれを使うことはあまりなさそうですし、実際、自分は使ったことがないのですが、こういうこともできるということで備忘録に残しておきます。

Settings.settings の設定

上の画像は ASP.NET MVC5 アプリのプロジェクトの例です。プロジェクトルート直下にある Properties フォルダを右クリックして開き、[設定]タブを選択すると「このプロジェクトには既定の設定ファイルが含まれていません。ファイルを作成するにはここをクリックしてください。」と表示されるので、それをクリックして情報を設定できます。

Windows Forms アプリなどと同様に「種類」には文字列の他に bool, int, double, 接続文字列などを選択でき、Settings.Designer.cs に定義されている強く型付けされたプロパティを使って値を取得できます。その点では web.config の appSettings を使うよりメリットがあるかもしれません。

ただし「スコープ」はアプリケーションのみで、ユーザーは選択できません。複数のユーザーがアクセスしてくる ASP.NET Web アプリなので、当然と言えば当然なのですが。

情報の保存場所はどこになるかと言えば、.NET Framework 版の ASP.NET Web アプリでは当たり前かもしれませんが、アプリケーションルート直下の web.config になります。

web.config 内のどこにどのような形で設定情報が保存されるかと言うと、上の画像の設定例では以下のようになります。

<configuration>
  <configSections>
    <!-- 中略 -->
    <sectionGroup name="applicationSettings" 
      type="System.Configuration.ApplicationSettingsGroup, 
            System, Version=4.0.0.0, Culture=neutral, 
            PublicKeyToken=b77a5c561934e089">
      <section name="Mvc5App.Properties.Settings" 
        type="System.Configuration.ClientSettingsSection, 
              System, Version=4.0.0.0, Culture=neutral, 
              PublicKeyToken=b77a5c561934e089" 
              requirePermission="false"/>
    </sectionGroup>
  </configSections>

  <connectionStrings>
    <!-- 中略 -->
    <add name="Mvc5App.Properties.Settings.connString" 
         connectionString="Data Source=lpc:(local)\SQLEXPRESS;
                           Initial Catalog=ContosoUniversity;
                           Integrated Security=SSPI;" />
  </connectionStrings>

  <!-- 中略 -->

  <applicationSettings>
    <Mvc5App.Properties.Settings>
      <setting name="settingsInfo" serializeAs="String">
        <value>Settings.settings に追加した文字列</value>
      </setting>
      <setting name="intValue" serializeAs="String">
        <value>100</value>
      </setting>
    </Mvc5App.Properties.Settings>
  </applicationSettings>
</configuration>

configSections 要素で Settings.Designer.cs に定義された Settings クラスを利用できるように設定しています。接続文字列は connectionStrings 要素内に、int 型と string 型の値は applicationSettings 要素内に設定されています。

では、ASP.NET Web アプリが利用する別プロジェクトのクラスライブラリがあるとして、それが Settings.settings を使っている場合はどうなるでしょうか?

自動的に web.config に取り込んでくれるということはなさそうです。自分が試した限りですが、.dll.config という構成ファイルが .dll と一緒に bin フォルダに配置され、それが使われるようです。先の記事「構成ファイルの保存場所」に書いたのと同様な配置になるようです。

どのように試したかを以下に書いておきます。

MVC アプリのプロジェクトと同じソリューション内にクラスライブラリ LibraryA を追加し、Settings.settings ファイルに文字列情報を追加します。クラスファイル Class1.cs の中には設定した文字列を取得するコードを書きます。

クラスライブラリの Settings.settings

注: スコープは全て「アプリケーション」でなければなりません。一つでも「ユーザー」に設定されていると、MVC アプリから情報を取得する際、その項目が「アプリケーション」であっても ConfigurationErrorsException がスローされます。エラーメッセージは "現在の構成システムでは、ユーザーによってスコープされた設定はサポートされません" です。

MVC プロジェクトで LibraryA を参照に追加します。

参照の追加

ソリューションをビルドすると自動的に MVC プロジェクトの bin フォルダに LibraryA の .dll とともに .dll.config が コピーされます。

bin フォルダの .dll と .dll.config

その .dll.config に LibraryA の Settings.Settings ファイルに設定した情報が含まれています。MVC アプリから .dll のコード経由でその情報を取得できます。

Tags: , , ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  August 2020  >>
MoTuWeThFrSaSu
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar