サービス間でデータを通信するときにデータが既知の型ではない場合、例外が発生します。サービスメソッドの引数や返り値の型が派生型となる場合、既知の型としてサービスコントラクトやデータコントラクトに設定する必要があります。今回はサービスコントラクトで既知の型として登録するのに使用するServiceKnownTypeAttributeとデータコントラクトの基本型などに付与するKnownTypeAttributeを使用してみます。
参考リンク:既知のデータコントラクト型
http://msdn.microsoft.com/ja-jp/library/ms730167.aspx
確認環境
- Windows Vista Enterprise (WCFホスト,クライアントはスタンドアロン)
- 開発環境 Visual Studio 2008 Professional(英語版)
- .NET 3.5
- システムバインディングの種類:BasicHttpBinding
1.サンプルソリューションの作成
Visual Studioを起動し、空のソリューションWCFSample019を作成します。
2.WCFサービスの作成
WCF Service LibraryをプロジェクトテンプレートにしてプロジェクトWCFSample.CustomerServiceを作成します。
2.1 データコントラクトの作成
クラスファイルCustomer.csを作成し、基本クラスのCustomerと一般顧客を現す派生クラスPersonalCustomerと企業顧客を現すCompanyCustomerを作成します(以下参照)。
namespace WCFSample.CustomerService
{
[DataContract]
public class Customer
{
[DataMember]
public int CustomerID { get; set; }
}
[DataContract]
public class PersonalCustomer : Customer
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string FamilyName {get;set;}
}
[DataContract]
public class CompanyCustomer : Customer
{
[DataMember]
public string CompanyCode { get; set; }
[DataMember]
public string CompanyName { get; set; }
}
}
2.2 サービスコントラクトとサービスクラスの作成
サービスコントラクトとサービスクラスを作成します。Add New ItemダイアログでファイルタイプにWCF Serviceを選択、名前をCustomerServiceと入力し、ファイルを作成します。作成されたCustomerService.cs,ICustomerService.csを編集する。(以下参照)
ICustomerService.csは次の通り。
namespace WCFSample.CustomerService
{
[ServiceContract]
public interface ICustomerService
{
[OperationContract]
List<Customer> RetrieveCustomer();
}
}
CustomerService.csは次の通り。
namespace WCFSample.CustomerService
{
public class CustomerService : ICustomerService
{
#region ICustomerService Members
public List<Customer> RetrieveCustomer()
{
return _customers;
}
private static List<Customer> _customers;
static CustomerService()
{
_customers = new List<Customer>();
PersonalCustomer p = new PersonalCustomer();
p.CustomerID = 1;
p.FamilyName = "Abogado";
p.FirstName = "sencha";
_customers.Add(p);
CompanyCustomer c = new CompanyCustomer();
c.CustomerID = 2;
c.CompanyCode = "0001";
c.CompanyName = "Test Limited";
_customers.Add(c);
}
#endregion
}
}
既定で作成されていたApp.config,IService1.cs,Service1.csを削除して終了。
3. WCFホストの作成
既存のプロジェクトを追加で、WCFサンプル再作成(ブラッシュアップ) で作成したプロジェクトをコピーします。WCFSample.WPFHostプロジェクトをソリューションのフォルダーにコピーして既存のプロジェクトとしてソリューションに追加します。プロジェクトを右クリックAdd ReferenceでWCFSample.CustomerServiceプロジェクトへの参照を追加します。WCFSample.ProductServiceへの参照は削除します。
3.1 App.configとプログラムの編集
App.configを編集します。ProductService,IProductServiceとなっていた箇所をCustomerService,ICustomerServiceに置き換えます。また、connectionStringの構成は必要ないので削除します。編集後のApp.configは次の通り。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<system.serviceModel>
<services>
<service name="WCFSample.CustomerService.CustomerService" behaviorConfiguration="CustomerServiceBehavior">
<endpoint address="http://localhost:8056/CustomerService" binding="basicHttpBinding"
bindingConfiguration="" contract="WCFSample.CustomerService.ICustomerService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="CustomerServiceBehavior">
<serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:8056/CustomerService/Mex" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
ProductServiceHost.xaml.csを修正して、CustomerServiceサービスクラスのホストを行うように変更。
namespace WCFSample.WPFHost
{
/// <summary>
/// Interaction logic for ProductServiceHost.xaml
/// </summary>
public partial class ProductServiceHost : Window
{
ServiceHost _host = null;
public ProductServiceHost()
{
InitializeComponent();
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
_host = new ServiceHost(typeof(WCFSample.CustomerService.CustomerService));
_host.Open();
lblState.Content = "サービス中...";
btnStart.IsEnabled = false;
btnStop.IsEnabled = true;
}
private void btnStop_Click(object sender, RoutedEventArgs e)
{
_host.Close();
lblState.Content = "サービス停止";
btnStop.IsEnabled = false;
btnStart.IsEnabled = true;
}
}
}
最後にサービスに対する ポートのアクセス許可を実行ユーザに割り当てて(以下のコマンド参照)、WCFSample.WPFHostプロジェクトをスタートアッププロジェクトに設定 してデバッグ実行で動作することを確認します。
netsh http add urlacl url=http://+:8056/CustomerService user=マシン名\ユーザ名
-Vista より前のOSでのアクセス許可の設定 Configuring HTTP and HTTPS
http://msdn2.microsoft.com/en-us/library/ms733768.aspx
http://msdn.microsoft.com/ja-jp/library/ms733768.aspx (日本語)
4. WCFクライアントの作成
デバッグ実行した状態でVisual Studio 2008コンソールからコマンドを入力してプロキシクラスを作成します。
svcutil /namespace:*,WCFSample.Client.ServiceProxy http://localhost:8056/CustomerService/Mex /out:Proxy.cs /config:App.config

4.1 WCFクライアントプロジェクトの作成
プロジェクトテンプレートでコンソールアプリケーションを選択し、WCFSample.ConsoleClientプロジェクトを作成する。作成したProxy.csとoutput.configをApp.configをプロジェクトにコピーします。System.Runtime.Serialization.dllをプロジェクトの参照に追加します。App.configはデフォルト値が設定されている部分は削除します。(編集結果参照)。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_ICustomerService" >
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:8056/CustomerService" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_ICustomerService" contract="WCFSample.Client.ServiceProxy.ICustomerService"
name="BasicHttpBinding_ICustomerService" />
</client>
</system.serviceModel>
</configuration>
4.2 Program.csの編集
既定で作成されるProgram.csを次のように編集します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WCFSample.Client.ServiceProxy;
using System.ServiceModel;
namespace WCFSample.ConsoleClient
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("サービスが起動したらEnterを押してください");
Console.ReadLine();
try
{
CustomerServiceClient proxy = new CustomerServiceClient("BasicHttpBinding_ICustomerService");
Customer[] customers = proxy.RetrieveCustomer();
for (int i = 0; i < customers.Length; ++i)
{
Console.WriteLine(customers[i].CustomerID);
}
proxy.Close();
}
catch(FaultException fe){
Console.WriteLine(fe.Message);
}
catch (CommunicationException ce)
{
Console.WriteLine(ce.Message);
}
Console.WriteLine("クライアントを終了します。Enterを押してください");
Console.ReadLine();
}
}
}
4.3 結果確認(例外発生)
ソリューションのプロパティでWCFSample.WPFHost,WCFSample.ConsoleClientをMultiple Startup Projectsに設定してデバッグ実行してみると、CommunicationExceptionが発生します。

これはサービスオペレーションで返されるリストがCusotomer型ではなく、その派生型のPersonCustomerとCompanyCustomerのクラスのインスタンスのため、シリアライズでエラーが発生しているためです。エラーがでることを確認したあとに、ServiceKnownTypeとKnownTypeAttributeを使って、プログラムが動作することを確認します。
5 既知の型を使用する
5.1 ServiceKnownTypeを使う場合
WCFSample.CustomerServiceプロジェクトのサービスコントラクトにServiceKnownTypeAttributeを付与してリビルドします。属性はサービスオペレーションではなく、サービスコントラクト自身に付与することもできます。
[ServiceContract]
// ここに付与することも可能
//[ServiceKnownType(typeof(CompanyCustomer))]
//[ServiceKnownType(typeof(PersonalCustomer))]
public interface ICustomerService
{
[OperationContract]
[ServiceKnownType(typeof(CompanyCustomer))]
[ServiceKnownType(typeof(PersonalCustomer))]
List<Customer> RetrieveCustomer();
}
プロキシを生成し直して、動作することを確認します。
5.2 KnownTypeAttributeを使う場合
ServiceKnownTypeAttributeを削除して、今度はDataContractクラスにたいしてKnownTypeAttributeを付与してみます。
namespace WCFSample.CustomerService
{
[DataContract]
[KnownType(typeof(PersonalCustomer))]
[KnownType(typeof(CompanyCustomer))]
public class Customer
{
[DataMember]
public int CustomerID { get; set; }
}
[DataContract]
public class PersonalCustomer : Customer
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string FamilyName {get;set;}
}
[DataContract]
public class CompanyCustomer : Customer
{
[DataMember]
public string CompanyCode { get; set; }
[DataMember]
public string CompanyName { get; set; }
}
}
変種後、ソリューションをビルドし、プロキシクラスを再作成してプログラムが動作することを確認します。
5.3 既知の型を返すメソッドを使用する
KnownTypeAttribute,ServiceKnownAttributeともに、既知の型が多くある場合、型を直接付与するだけでなく、既知の型をリストにするメソッドを指定して既知の型を指定することができます。(以下例)
namespace WCFSample.CustomerService
{
[DataContract]
[KnownType("GetKnownTypes")]
public class Customer
{
[DataMember]
public int CustomerID { get; set; }
public static Type[] GetKnownTypes()
{
Type[] t = new Type[2];
t[0] = typeof(PersonalCustomer);
t[1] = typeof(CompanyCustomer);
return t;
}
}
[DataContract]
public class PersonalCustomer : Customer
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string FamilyName {get;set;}
}
[DataContract]
public class CompanyCustomer : Customer
{
[DataMember]
public string CompanyCode { get; set; }
[DataMember]
public string CompanyName { get; set; }
}
}
ジェネリック型のクラスを既知の型として列挙するときに使用します。(参考リンク参照)
参考リンク参照:既知のデータコントラクト型
http://msdn.microsoft.com/ja-jp/library/ms730167.aspx
以上で終了です。間違い指摘等があれば連絡くださいませ。
さんのコメント: さんのコメント: