WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

.NET 6.0 で Windows Forms アプリ作成

by WebSurfer 4. December 2021 14:22

2021 年 11 月にリリースされた Visual Studio Community 2022 のテンプレートを使って .NET 6.0 版の Windows Forms アプリを作ってみました。Visual Studio のバージョンはこの記事を書いた時点で最新版の 17.0.2、SDK は 6.0.100、ランタイムは 6.0.0 です。

.NET 6.0 版の Windows Forms アプリ

まず一つ問題があって、プロジェクト作成直後はデザイン画面が表示されません。下の画像のようにソリューションエクスプローラーに表示されるアイコンは C# のクラスファイスのもので、ここからデザイナ画面を表示することもできません。また、Form1.resx ファイルも存在しません。

初期画面

Developer Community に VS 2022 - WinForms .NET Designer does not automatically show the design surface when creating a new project という報告が上がっていて年内には改善されるとのことです。その記事に workaround が紹介されていますが自分の環境ではダメでした。

自分が試した限りですが、自動生成された Form1.cs を削除し、ソリューションエクスプローラーを操作して「新しい項目の追加」ダイアログから[フォーム (Windows フォーム)]を選んで新たにフォームを追加すればデザイナ画面は表示されました。Form1.resx も生成されました。

ただし、それでだけで全て OK かどうかは分かりません。実際、他の問題として、v17.0.1 の時 Form1.resx に画像を含めると開かないということがありました (v17.0.2 で改善されたのかその問題は出なくなりましたが)。年内に fix すると言っているので、待てるのであればそれを待った方が良いかもしれません。

以下は、自動生成された Form1.cs を削除してから新たに追加するという方法でデザイナ画面の表示と Form1.resx の生成を行ってから進めた時の話です。

SQL Server データベースから Visual Studio 2022 のウィザードを利用して型付 DataSet / DataTable + TableAdapter を作り、テーブルのレコード一覧を DataGridView に表示し、ユーザーが DataGridView に表示されたデータを編集してからデーターベースに編集結果を反映するという Windows Forms 定番のアプリを作ります。

Microsoft のドキュメント「新しいデータ ソースの追加」には ".NET Core 開発ではサポートされていません" と書いてありますが、型付 DataSet / DataTable + TableAdapter を作ることは可能です。ただし、その記事に書いてあるように[データ ソース]ウィンドウからドラッグ&ドロップしてコードを自動生成させることはできませんので、そこは自力でコードを書くことになります。

まず、型付 DataSet / DataTable + TableAdapter を作ります。ソリューションエクスプローラーでプロジェクトを右クリック ⇒[追加(D)]⇒[新しい項目(W)...]で表示されるメニューの中に DataSet という項目がありますのでそれを使います。(注: 上に紹介した Microsoft のドキュメントのように[プロジェクト(P)]⇒[新しいデータソースの追加(N)...]と操作するとサポートされてないというメッセージが出てその先に進めません)

SQL Server サンプルデータベース Northwind の Products, Catagories, Suppliers テーブルから NorthwindDataSet.xsd という名前で DataSet を作ってみました。この記事で使うのは下の画像の中の Products + ProductsTableAdapter です。

型付 DataSet / DataTable + TableAdapter

ここまでは Windows Forms プロジェクトを作成したデフォルトの状態で進めることができます。しかし、ビルドしようとすると SqlConnection, SqlCommand, SqlDataAdapter, SqlTransaction 等が System.Data.SqlClient に見つからないというエラーになって失敗します。

SqlConnection, SqlCommand 等は .NET Core 3.1 でも .NET 5.0 でもデフォルトでは含まれておらず NuGet で System.Data.SqlClient をインストールする必要がありました。.NET 6.0 でも同じことのようです。

NuGet パッケージのインストール

上の画像の System.Data.Common の方は無くてもビルドは通りますが、SystemDBNull とか System.Data.DbType 等が含まれているとのことなので念のため追加しておきました。

この後、.NET Framework であれば作成した DataTable を[データ ソース]ウィンドウからフォームにドラッグ&ドロップするだけで完全な Windows Forms アプリが自動生成されますが、.NET Core 3.1 や .NET 6.0 ではここから先は自力でコードを書くことになります。

一番実装が面倒そうなのが BindingNavigator 周りだと思いますが、それをデザイン画面で実装しようとしても、そもそもツールボックスに BindingNavigator がありません。

どうも特定の OS に依存するアプリを .NET Core 3.1 や .NET 6.0 で作る場合のサポートは限られているということのようです。Windows OS に依存する Windows Forms アプリなら最初から .NET Framework ベースで作るのが正解のようです。

しかし、ツールボックスに BindingNavigator が無いからと言って .NET 6.0 アプリで使えないというわけではないようです。Visual Studio のデザイナが対応してないだけで、.NET Framework ベースで作ったアプリと同じコードを書けば同じように動くはずです。

ということで、既存の .NET Framework ベースで作ったアプリから BindingNavigator 他のコードを移植してみました。

まず、BindingNavigator が使う .png 画像を既存のアプリから取得して Form1.resx にコピーします。

BindingNavigator が使う .png 画像

.NET 5.0 以前では画像は base64 形式に変換されて .resx ファイル内に埋め込まれたのですが、.NET 6.0 ではプロジェクトルート直下に Resources という名前のフォルダが作られ、その中に画像ファイルが格納される点が異なります。

Resources フォルダの画像ファイル

その後、既存の .NET Framework アプリからコードを移植します。以下がその例で、この記事の一番上の画像がその実行結果です。

namespace WinFormsApp2
{
    public partial class Form1 : Form
    {
        private NorthwindDataSet northwindDataSet;
        private System.Windows.Forms.BindingSource productsBindingSource;
        private NorthwindDataSetTableAdapters.ProductsTableAdapter productsTableAdapter;
        private NorthwindDataSetTableAdapters.TableAdapterManager tableAdapterManager;
        private System.Windows.Forms.BindingNavigator productsBindingNavigator;
        private System.Windows.Forms.ToolStripButton bindingNavigatorAddNewItem;
        private System.Windows.Forms.ToolStripLabel bindingNavigatorCountItem;
        private System.Windows.Forms.ToolStripButton bindingNavigatorDeleteItem;
        private System.Windows.Forms.ToolStripButton bindingNavigatorMoveFirstItem;
        private System.Windows.Forms.ToolStripButton bindingNavigatorMovePreviousItem;
        private System.Windows.Forms.ToolStripSeparator bindingNavigatorSeparator;
        private System.Windows.Forms.ToolStripTextBox bindingNavigatorPositionItem;
        private System.Windows.Forms.ToolStripSeparator bindingNavigatorSeparator1;
        private System.Windows.Forms.ToolStripButton bindingNavigatorMoveNextItem;
        private System.Windows.Forms.ToolStripButton bindingNavigatorMoveLastItem;
        private System.Windows.Forms.ToolStripSeparator bindingNavigatorSeparator2;
        private System.Windows.Forms.ToolStripButton productsBindingNavigatorSaveItem;
        private System.Windows.Forms.DataGridView productsDataGridView;

        public Form1()
        {
            InitializeComponent();

            this.components = new System.ComponentModel.Container();
            System.ComponentModel.ComponentResourceManager resources = 
                new System.ComponentModel.ComponentResourceManager(typeof(Form1));
            this.northwindDataSet = new NorthwindDataSet();
            this.productsBindingSource = new System.Windows.Forms.BindingSource(this.components);
            this.productsTableAdapter = new NorthwindDataSetTableAdapters.ProductsTableAdapter();
            this.tableAdapterManager = new NorthwindDataSetTableAdapters.TableAdapterManager();
            this.productsBindingNavigator = new System.Windows.Forms.BindingNavigator(this.components);
            this.bindingNavigatorAddNewItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorCountItem = new System.Windows.Forms.ToolStripLabel();
            this.bindingNavigatorDeleteItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorMoveFirstItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorMovePreviousItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorSeparator = new System.Windows.Forms.ToolStripSeparator();
            this.bindingNavigatorPositionItem = new System.Windows.Forms.ToolStripTextBox();
            this.bindingNavigatorSeparator1 = new System.Windows.Forms.ToolStripSeparator();
            this.bindingNavigatorMoveNextItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorMoveLastItem = new System.Windows.Forms.ToolStripButton();
            this.bindingNavigatorSeparator2 = new System.Windows.Forms.ToolStripSeparator();
            this.productsBindingNavigatorSaveItem = new System.Windows.Forms.ToolStripButton();
            this.productsDataGridView = new System.Windows.Forms.DataGridView();
            ((System.ComponentModel.ISupportInitialize)(this.northwindDataSet)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.productsBindingSource)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.productsBindingNavigator)).BeginInit();
            this.productsBindingNavigator.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this.productsDataGridView)).BeginInit();
            this.SuspendLayout();

            // 
            // northwindDataSet
            // 
            this.northwindDataSet.DataSetName = "NorthwindDataSet";
            this.northwindDataSet.SchemaSerializationMode = 
                System.Data.SchemaSerializationMode.IncludeSchema;
            // 
            // productsBindingSource
            // 
            this.productsBindingSource.DataMember = "Products";
            this.productsBindingSource.DataSource = this.northwindDataSet;
            // 
            // productsTableAdapter
            // 
            this.productsTableAdapter.ClearBeforeFill = true;
            // 
            // tableAdapterManager
            // 
            this.tableAdapterManager.BackupDataSetBeforeUpdate = false;
            this.tableAdapterManager.ProductsTableAdapter = this.productsTableAdapter;
            this.tableAdapterManager.UpdateOrder = 
                NorthwindDataSetTableAdapters.TableAdapterManager.UpdateOrderOption.InsertUpdateDelete;
            // 
            // productsBindingNavigator
            // 
            this.productsBindingNavigator.AddNewItem = this.bindingNavigatorAddNewItem;
            this.productsBindingNavigator.BindingSource = this.productsBindingSource;
            this.productsBindingNavigator.CountItem = this.bindingNavigatorCountItem;
            this.productsBindingNavigator.DeleteItem = this.bindingNavigatorDeleteItem;
            this.productsBindingNavigator.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.bindingNavigatorMoveFirstItem,
            this.bindingNavigatorMovePreviousItem,
            this.bindingNavigatorSeparator,
            this.bindingNavigatorPositionItem,
            this.bindingNavigatorCountItem,
            this.bindingNavigatorSeparator1,
            this.bindingNavigatorMoveNextItem,
            this.bindingNavigatorMoveLastItem,
            this.bindingNavigatorSeparator2,
            this.bindingNavigatorAddNewItem,
            this.bindingNavigatorDeleteItem,
            this.productsBindingNavigatorSaveItem});
            this.productsBindingNavigator.Location = new System.Drawing.Point(0, 0);
            this.productsBindingNavigator.MoveFirstItem = this.bindingNavigatorMoveFirstItem;
            this.productsBindingNavigator.MoveLastItem = this.bindingNavigatorMoveLastItem;
            this.productsBindingNavigator.MoveNextItem = this.bindingNavigatorMoveNextItem;
            this.productsBindingNavigator.MovePreviousItem = this.bindingNavigatorMovePreviousItem;
            this.productsBindingNavigator.Name = "productsBindingNavigator";
            this.productsBindingNavigator.PositionItem = this.bindingNavigatorPositionItem;
            this.productsBindingNavigator.Size = new System.Drawing.Size(1136, 25);
            this.productsBindingNavigator.TabIndex = 0;
            this.productsBindingNavigator.Text = "bindingNavigator1";
            // 
            // bindingNavigatorAddNewItem
            //
#nullable disable
            this.bindingNavigatorAddNewItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorAddNewItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorAddNewItem.Image")));
            this.bindingNavigatorAddNewItem.Name = "bindingNavigatorAddNewItem";
            this.bindingNavigatorAddNewItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorAddNewItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorAddNewItem.Text = "新規追加";
            // 
            // bindingNavigatorCountItem
            // 
            this.bindingNavigatorCountItem.Name = "bindingNavigatorCountItem";
            this.bindingNavigatorCountItem.Size = new System.Drawing.Size(29, 22);
            this.bindingNavigatorCountItem.Text = "/ {0}";
            this.bindingNavigatorCountItem.ToolTipText = "項目の総数";
            // 
            // bindingNavigatorDeleteItem
            // 
            this.bindingNavigatorDeleteItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorDeleteItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorDeleteItem.Image")));
            this.bindingNavigatorDeleteItem.Name = "bindingNavigatorDeleteItem";
            this.bindingNavigatorDeleteItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorDeleteItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorDeleteItem.Text = "削除";
            // 
            // bindingNavigatorMoveFirstItem
            // 
            this.bindingNavigatorMoveFirstItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorMoveFirstItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorMoveFirstItem.Image")));
            this.bindingNavigatorMoveFirstItem.Name = "bindingNavigatorMoveFirstItem";
            this.bindingNavigatorMoveFirstItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorMoveFirstItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorMoveFirstItem.Text = "最初に移動";
            // 
            // bindingNavigatorMovePreviousItem
            // 
            this.bindingNavigatorMovePreviousItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorMovePreviousItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorMovePreviousItem.Image")));
            this.bindingNavigatorMovePreviousItem.Name = "bindingNavigatorMovePreviousItem";
            this.bindingNavigatorMovePreviousItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorMovePreviousItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorMovePreviousItem.Text = "前に戻る";
            // 
            // bindingNavigatorSeparator
            // 
            this.bindingNavigatorSeparator.Name = "bindingNavigatorSeparator";
            this.bindingNavigatorSeparator.Size = new System.Drawing.Size(6, 25);
            // 
            // bindingNavigatorPositionItem
            // 
            this.bindingNavigatorPositionItem.AccessibleName = "位置";
            this.bindingNavigatorPositionItem.AutoSize = false;
            this.bindingNavigatorPositionItem.Font = new System.Drawing.Font("Yu Gothic UI", 9F);
            this.bindingNavigatorPositionItem.Name = "bindingNavigatorPositionItem";
            this.bindingNavigatorPositionItem.Size = new System.Drawing.Size(50, 23);
            this.bindingNavigatorPositionItem.Text = "0";
            this.bindingNavigatorPositionItem.ToolTipText = "現在の場所";
            // 
            // bindingNavigatorSeparator1
            // 
            this.bindingNavigatorSeparator1.Name = "bindingNavigatorSeparator1";
            this.bindingNavigatorSeparator1.Size = new System.Drawing.Size(6, 25);
            // 
            // bindingNavigatorMoveNextItem
            // 
            this.bindingNavigatorMoveNextItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorMoveNextItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorMoveNextItem.Image")));
            this.bindingNavigatorMoveNextItem.Name = "bindingNavigatorMoveNextItem";
            this.bindingNavigatorMoveNextItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorMoveNextItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorMoveNextItem.Text = "次に移動";
            // 
            // bindingNavigatorMoveLastItem
            // 
            this.bindingNavigatorMoveLastItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.bindingNavigatorMoveLastItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("bindingNavigatorMoveLastItem.Image")));
            this.bindingNavigatorMoveLastItem.Name = "bindingNavigatorMoveLastItem";
            this.bindingNavigatorMoveLastItem.RightToLeftAutoMirrorImage = true;
            this.bindingNavigatorMoveLastItem.Size = new System.Drawing.Size(23, 22);
            this.bindingNavigatorMoveLastItem.Text = "最後に移動";
            // 
            // bindingNavigatorSeparator2
            // 
            this.bindingNavigatorSeparator2.Name = "bindingNavigatorSeparator2";
            this.bindingNavigatorSeparator2.Size = new System.Drawing.Size(6, 25);
            // 
            // productsBindingNavigatorSaveItem
            // 
            this.productsBindingNavigatorSaveItem.DisplayStyle = 
                System.Windows.Forms.ToolStripItemDisplayStyle.Image;
            this.productsBindingNavigatorSaveItem.Image = 
                ((System.Drawing.Image)(resources.GetObject("productsBindingNavigatorSaveItem.Image")));
            this.productsBindingNavigatorSaveItem.Name = "productsBindingNavigatorSaveItem";
            this.productsBindingNavigatorSaveItem.Size = new System.Drawing.Size(23, 22);
            this.productsBindingNavigatorSaveItem.Text = "データの保存";
            this.productsBindingNavigatorSaveItem.Click += ProductsBindingNavigatorSaveItem_Click;
#nullable enable
            // 
            // productsDataGridView
            // 
            //this.productsDataGridView.AutoGenerateColumns = false;
            this.productsDataGridView.ColumnHeadersHeightSizeMode = 
                System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.productsDataGridView.DataSource = this.productsBindingSource;
            this.productsDataGridView.Dock = System.Windows.Forms.DockStyle.Fill;
            this.productsDataGridView.Location = new System.Drawing.Point(0, 25);
            this.productsDataGridView.Name = "productsDataGridView";
            this.productsDataGridView.RowTemplate.Height = 21;
            //this.productsDataGridView.Size = new System.Drawing.Size(1136, 643);
            this.productsDataGridView.TabIndex = 1;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.Controls.Add(this.productsDataGridView);
            this.Controls.Add(this.productsBindingNavigator);
            this.Name = "Form1";
            this.Text = "Northwind";
            this.Load += Form1_Load;
            ((System.ComponentModel.ISupportInitialize)(this.northwindDataSet)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.productsBindingSource)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.productsBindingNavigator)).EndInit();
            this.productsBindingNavigator.ResumeLayout(false);
            this.productsBindingNavigator.PerformLayout();
            ((System.ComponentModel.ISupportInitialize)(this.productsDataGridView)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        private void Form1_Load(object? sender, EventArgs e)
        {
            this.productsTableAdapter.Fill(this.northwindDataSet.Products);
        }

        private void ProductsBindingNavigatorSaveItem_Click(object? sender, EventArgs e)
        {
            this.Validate();
            this.productsBindingSource.EndEdit();
            this.tableAdapterManager.UpdateAll(this.northwindDataSet);
        }
    }
}

既存の .NET Framework アプリのコードと違うところは、DataGridView の AutoGenerateColumns プロパティをデフォルトの true として各列を DataTable から自動生成させていることろです。ひょっとしたらそれによる予期せぬ副作用があるかもしれません。

もう一つ、接続文字列が TableAdapter のコードの中にハードコーディングされているところが .NET Framework アプリと違います。 .NET Framework アプリの場合、接続文字列は設定ファイルに格納され、TableAdapter はそれから読んでくるのですが、.NET 6.0 版はそこのところにも手を加える必要があります。

こんな手間をかけるなら、最初から .NET Framework 版で作ってコードはすべて Visual Studio のデザイナで自動生成されればいいと言われそうですが、確かにその通りだと思いました。

Tags: , ,

CORE

WCF サービス経由で非接続型データアクセス

by WebSurfer 4. March 2018 14:41

型付 DataSet + TableAdapter を用いて非接続型データアクセ���を行う Windows Forms アプリケーションで、WCF サービス経由で SQL Server のデータの取得更新を行う例の紹介です。(Windows Forms アプリから直接 SQL Server に接続するのではなく)

DataGridView で表示

接続先を Web サービスとした場合の例は先の記事「Web サービス経由で非接続型データアクセス」に書きました。

Microsoft によると、Web サービスは "Legacy Technology" なので Windows Communication Foundation (WCF) を使うようにとのことです。なので、この記事では、Web サービスに代えて WCF サービスを使い、さらにサービス参照を作成する際に生成される非同期版メソッドを使って、非同期にアクセスするサンプルを作ってみました。

Visual Studio のデータソース構成ウィザードを使うと、MSDN ライブラリの Windows フォーム アプリケーションでのデータへの接続 の図のような構成の Windows アプリケーションを作成できます。具体的な作成手順例は、チュートリアル「10 行でズバリ !! 非接続型のデータ アクセス」を見てください。

上に紹介したチュートリアルの例では Windows アプリから直接 SQL Server に接続していますが、セキュリティ上の問題などで、Windows アプリ ⇔ サーバー ⇔ SQL Server というようにサーバーを介してアクセスしたいということがあると思います。

Web サーバー (IIS) 上で動いている ASP.NET Web Forms アプリケーションに WCF サービスを実装し、Windows Forms アプリケーションとの間で型付 DataSet をやりとりすることで Windows アプリ ⇔ Web サーバー ⇔ SQL Server という構成を実現できます。

(1) WCF サービス

まず WCF サービスを IIS 上で動いている既存の ASP.NET Web Forms アプリに実装します。Visual Studio の「ソリューションエクスプローラー」から[追加(D)]⇒[新しい項目の追加(W)...]で表示される「新しい項目の追加」ダイアログで[WCF サービス]を選択して追加します。

WCF サービスの追加

ここでは Web サイトプロジェクトのアプリケーションルート直下に WCF サービスを追加し、その名前を ProductsService.svc とするという前提で話を進めますので留意してください。

追加すると、IProductsService.cs, ProductsService.cs が App_Code フォルダに、アプリケーションルート直下に ProductsService.svc が生成されます。(Web アプリケーションプロジェクトでは生成されるファイルの場所と名前が違いますが基本は同じです)

次に、Visual Studio のデータソース構成ウィザードを使って、SQL Server のサンプルデータベース Northwind の Products テーブルをベースに、型付 DataSet + TableAdapter + TableAdapterManager を生成します。ここでは、名前を ProductsDataSet.xsd としています。

その手順は割愛しますが、一つだけ注意点を書くと、Web サイトプロジェクトの場合、.xsd ファイルは App_Code フォルダに作るのではなく、同一ソリューション内に別プロジェクトを作ってそこに作るようにした方がよさそうです。Temporary ASP.NET Files フォルダに生成される .Designer.cs ファイルのコードが読めないという不都合のほか、自分の環境ではなぜか TableAdapterManager が生成されないという問題がありましたので。

以上が完了したら、(a) SQL Server から型付 DataSet にデータを取得して Windows Forms アプリケーション渡すメソッド、(b) ユーザーが編集済みの型付 DataSet を受け取って SQL Server のレコードを更新するメソッドを ProductsService.cs に実装します。

具体例は以下のようになります。GetDataSet メソッドが上記 (a)、Update メソッドが上記 (b) に該当します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Activation;
using NorthwindProducts;
using NorthwindProducts.ProductsDataSetTableAdapters;

[AspNetCompatibilityRequirements(RequirementsMode =
            AspNetCompatibilityRequirementsMode.Allowed)]
public class ProductsService : IProductsService
{
  public ProductsDataSet GetDataSet()
  {
    // 非同期実行を確認するため追加(実環境では不要)
    System.Threading.Thread.Sleep(3000);

    ProductsDataSet dataset = new ProductsDataSet();
    ProductsTableAdapter adapter = new ProductsTableAdapter();
    adapter.Fill(dataset.Products);
    return dataset;
  }

  public int Update(ProductsDataSet dataset)
  {
    // 非同期実行を確認するため追加(実環境では不要)
    System.Threading.Thread.Sleep(3000);

    TableAdapterManager manager = new TableAdapterManager();
    manager.ProductsTableAdapter = new ProductsTableAdapter();
    return manager.UpdateAll(dataset);
  }
}

上記コードの ProductsDataSet, ProductsTableAdapter, TableAdapterManager は、Visual Studio のデータソース構成ウィザードを使って自動生成させた .xsd ファイル下の .Designer.cs に含まれる型付 DataSet + TableAdapter + TableAdapterManager です。

自動生成された IProductsService.cs ファイルに含まれるインターフェイス IProductsService の定義も上記に合わせて書き換えます。具体的には以下の通りです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using NorthwindProducts;

[ServiceContract]
public interface IProductsService
{
    [OperationContract]
    ProductsDataSet GetDataSet();

    [OperationContract]
    int Update(ProductsDataSet dataset);
}

(2) Windows Formsアプリケーション

上記の Web サービスを Windows Forms アプリケーションでは以下のように利用できます。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace UpdateUsingWcfService
{
  public partial class Form2 : Form
  {
    private ProductsServiceReference.
                         ProductsServiceClient client;
    private ProductsServiceReference.
                         ProductsDataSet productsDataSet;

    public Form2()
    {
      InitializeComponent();
      InitializeComponent2();
      this.client = new ProductsServiceReference.
                              ProductsServiceClient();
    }

    // 初期画面で Products テーブルのレコード一覧を表示
    private async void Form2_Load(object sender, EventArgs e)
    {
      // WCF サービスより型付 DataSet を取得

      // 非同期版メソッドを利用。戻り値は同期版と異なり:
      // Task<ProductsServiceReference.GetDataSetResponse>
      ProductsServiceReference.GetDataSetResponse response =
                           await this.client.GetDataSetAsync();

      // 目的の型付 DataSet は以下のようにして取得できる
      this.productsDataSet = response.GetDataSetResult;

      // 結果を DataGridView に表示
      this.productsBindingSource.DataSource = 
                                         this.productsDataSet;
    }

    //[データの保存]クリックでユーザーの編集結果を DB に保存
    private async void productsBindingNavigatorSaveItem_Click(
                                    object sender, EventArgs e)
    {
      this.Validate();
      this.productsBindingSource.EndEdit();

      // Web サービスに渡すのはユーザーが編集した行のみで可
      ProductsServiceReference.ProductsDataSet ds =
                 (ProductsServiceReference.ProductsDataSet)
                             this.productsDataSet.GetChanges();

      // 編集済みの型付 DataSet を Web サービスに渡して更新

      // 非同期版メソッドを利用。戻り値は同期版と異なり:
      // Task<ProductsServiceReference.UpdateResponse>
      ProductsServiceReference.UpdateResponse response = 
                             await this.client.UpdateAsync(ds);

      // 目的の戻り値(更新レコード数)は以下のようにして取得
      int n = response.UpdateResult;

      // 更新後の型付 DataSet を取得し DataGridView 書き換え

      // 非同期版メソッドを利用。戻り値は同期版と異なり:
      // Task<ProductsServiceReference.GetDataSetResponse>
      ProductsServiceReference.GetDataSetResponse response2 = 
                            await this.client.GetDataSetAsync();

      // 目的の型付 DataSet は以下のようにして取得できる
      this.productsDataSet = response2.GetDataSetResult;

      // 結果を DataGridView に表示
      this.productsBindingSource.DataSource = 
                                     this.productsDataSet;
    }

    // Form1.Designer.cs のコードにあるフォーム上の各コン
    // トロールの変数宣言をコピー。ProductsDataSet はサー
    // ビス参照の定義を使うのでその部分はコメントアウト。
    // TableAdapter と TableAdapterManager は不要(WCF サ
    // ービスを使う)なのでコメントアウト

    //private ProductsDataSet productsDataSet;
    private System.Windows.Forms.
                         BindingSource productsBindingSource;
    // ・・・以下省略・・・

    // Form1.Designer.cs のコードにある InitializeComponent()
    // メソッドのコードをコピー。メソッド名がダブらないように変
    // 更し、DataSet, TableAdapter, TableAdapterManager 関係の
    // コードをコメントアウト。Form.Text, Form.Name が "Form1"
    //  になっているので必要に応じて修正 
    private void InitializeComponent2()
    {
      this.components = new System.ComponentModel.Container();

      // BindingNavigator が使う画像が Form1.resx にしかないので、
      // ここは Form1 のままにしておく
      System.ComponentModel.ComponentResourceManager resources = 
         new System.ComponentModel.
                        ComponentResourceManager(typeof(Form1));
      // ・・・以下省略・・・

    }
  }
}

まず、Visual Studio のデータソース構成ウィザードを使って、上記 (1) WCF サービスを作った時と全く同様に、SQL Server のサンプルデータベース Northwind の Products テーブルをベースに、型付 DataSet + TableAdapter + TableAdapterManager を生成します。

生成できると「データソース」ウィンドウに Products テーブルが表示されているはずですので、それをデサイン画面で Form1 にドラッグ&ドロップすると DataGridView, BindingSource, BindingNavigatorなどのコードが自動生成され、直接 SQL Server とやり取りする Windows Forms アプリが完成するはずです。その段階で Form1 が期待通り動くことを確認してください。

確認出来たら、次に空の Form2 を追加し、それに上記 (1) WCF サービスを経由して SQL Server にアクセスして Form1 と同等の操作ができるコードを実装します。その結果が上記のコードです。具体的な手順は以下の通りです。

Visual Studio のサービス参照の追加ウィザードを利用して、上記 (1) で作成した Web サービスを参照し、サービスプロキシを自動生成させます。上記のコード例の ProductsWebServiceClient, ProductsDataSet, GetDataSetAsync, UpdateAsync 等は自動生成された Reference.cs ファイルに定義されています。

次に、Form1.Designer.cs にあるコントロールの定義と初期化のコードで必要なものを From2.cs にコピーします。上のサンプルで書いたように一式コピーして不要な部分をコメントアウトするのが間違いなくて良いと思います。

最後に、Form1.cs のイベントハンドラのコードを Form2.cs にコピーし、中身を上のサンプルのように書き換えます。

非同期版メソッド xxxAsync を利用する場合はその戻り値に注意してください。

同期版のメソッド GetDataSet、Update の場合、戻り値は (1) WCF サービスで定義した通り、それぞれ ProductsDataSet 型、int 型の値が直接返されます。

しかしながら、非同期版メソッド GetDataSetAsync、UpdateAsync の場合、戻り値はそれぞれ Task<ProductsServiceReference.GetDataSetResponse>、Task<ProductsServiceReference.UpdateResponse> となります。

即ち、Task<T> の T は ProductsDataSet 型、int 型ではなく、<operationName>Response という型のオブジェクトになります。そのクラス定義は、サービス参照を追加すると自動生成される Reference.cs というファイルに含まれています。

<operationName>Response オブジェクトから目的のオブジェクトを取得するのは、その中の public フィールド <operationName>Result にアクセスして可能です。

何故そういうことになるのかは調べ切れてません。また、ホントにその方法で良いのかと言われるとかなり不安があります。(汗) とりあえずそうすれば取得できるし非同期で動くということで上の記事は書きましたが、もう少し調べてみます。

Tags: , ,

.NET Framework

Web サービス経由で非接続型データアクセス

by WebSurfer 23. August 2014 16:50
2018/3/4 追記:
Microsoft によると Web サービスは "Legacy Technology" なので WCF を使えとのことゆえ、接続先を WCF サービスに変更し、非同期にアクセスするサンプルを作ってみました。興味がありましたら記事「WCF サービス経由で非接続型データアクセス」を見てください。

DataGridView と型付 DataSet + TableAdapter を用いて非接続型データアクセスを行う Windows アプリケーションで、Web サービス経由で SQL Server のデータの取得更新を行う例の紹介です。(Windows アプリから直接 SQL Server に接続するのではなく)

DataGridView

Visual Studio のデータソース構成ウィザードを使うと、MSDN ライブラリの Windows フォーム アプリケーションでのデータへの接続 の図のような構成の Windows アプリケーションを作成できます。具体的な作成手順例は、チュートリアル「10 行でズバリ !! 非接続型のデータ アクセス」を見てください。

上に紹介したチュートリアルの例では Windows アプリから直接 SQL Server に接続していますが、セキュリティ上の問題などで、Windows アプリ ⇔ サーバー ⇔ SQL Server というようにサーバーを介してアクセスしたいということがあると思います。

Web サーバー (IIS) を利用し、それに Web サービス(.asmx)を実装して、Windows アプリケーションとの間で型付 DataSet をやりとりすることで Windows アプリ ⇔ サーバー ⇔ SQL Server という構成を実現できます。

(1) Web サービス

まず Web サービスですが、(a) SQL Server から型付 DataSet にデータを取得して Windows アプリケーション渡すメソッド、(b) ユーザーが編集済みの型付 DataSet を受け取って SQL Server のレコードを更新するメソッドを実装します。

具体例は以下のようになります。GetDataSet メソッドが上記 (a)、Update メソッドが上記 (b) に該当します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;

namespace WebApplication1
{
  using ProductsDataSetTableAdapters;
    
  [WebService(Namespace = "http://tempuri.org/")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  public class ProductsWebService : WebService
  {
    [WebMethod]
    public ProductsDataSet GetDataSet()
    {
      ProductsDataSet dataset = new ProductsDataSet();
      ProductsTableAdapter adapter = 
                          new ProductsTableAdapter();            
      adapter.Fill(dataset.Products);
      return dataset;
    }

    [WebMethod]
    public int Update(ProductsDataSet dataset)
    {
      TableAdapterManager manager = new TableAdapterManager();
      manager.ProductsTableAdapter = new ProductsTableAdapter();
      return manager.UpdateAll(dataset);
    }
  }
}

SQL Server データベースには、Microsoft が無償で提供しているサンプルデータベース Northwind の Products テーブルを使っています。

ProductsDataSet, ProductsTableAdapter, TableAdapterManager は、 Visual Studio のデータソース構成ウィザードを使って自動生成させた .xsd ファイル下の .Designer.cs に含まれる型付 DataSet + TableAdapter + TableAdapterManager です。

(2) クライアント・アプリケーション

上記の Web サービスをクライアント・アプリケーションでは以下のように利用できます。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace UpdateUsingWebService
{
  public partial class Form2 : Form
  {
    private ProductsServiceReference.
                        ProductsWebServiceSoapClient client;
    private ProductsServiceReference.
                        ProductsDataSet productsDataSet;

    public Form2()
    {
      InitializeComponent();
      InitializeComponent2();
      this.client = new ProductsServiceReference.
                            ProductsWebServiceSoapClient();
    }

    private void productsBindingNavigatorSaveItem_Click(
        object sender, EventArgs e)
    {
      this.Validate();
      this.productsBindingSource.EndEdit();
            
      // Web サービスに渡すのはユーザーが編集した行のみで可
      ProductsServiceReference.ProductsDataSet ds =
                  (ProductsServiceReference.ProductsDataSet)
                           this.productsDataSet.GetChanges();

      // 編集済みの型付 DataSet を Web サービスに渡し、更新
      // をかける。戻り値 n は更新されたレコード数
      int n = this.client.Update(ds);

      // 更新後の型付 DataSet を取得。DataGridView 書き換え
      this.productsDataSet = this.client.GetDataSet();
      this.productsBindingSource.DataSource = 
                                    this.productsDataSet;
    }

    private void Form2_Load(object sender, EventArgs e)
    {
      // Web サービスより型付 DataSet を取得
      this.productsDataSet = this.client.GetDataSet();
      this.productsBindingSource.DataSource = 
                                     this.productsDataSet;
    }

    // ・・・中略・・・

    private void InitializeComponent2()
    {
      // ・・・中略・・・
    }

  }
}

まず、Visual Studio のサービス参照の追加ウィザードを利用して、上記 (1) で作成した Web サービスを参照し、プロキシクラスと型付 DataSet(上記のコード例では ProductsWebServiceSoapClient と ProductsDataSet)を自動生成させます。

次に、Visual Studio のデータソース構成ウィザードを使って、上記 (1) で作成した型付 DataSet + TableAdapter + TableAdapterManager と全く同じものを自動生成させます。

その上で、Visual Studio のデザイン画面で、「データソース」ウィンドウの項目を Form 画面にドラッグ&ドロップすると、DataGridView, BindingSource, BindingNavigatorなどのコードが自動生成されます。

それらを組み合わせれば容易に実装できるはずです。

Tags: , ,

.NET Framework

About this blog

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

Calendar

<<  January 2022  >>
MoTuWeThFrSaSu
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar