マイクロソフトの通信用ライブラリである Windows Communication Foundation (以下、WCFと略します)について説明していきます。
WCFは従来のソケット通信に比べて、より質の高い通信が行えるライブラリです。WCFの基本的な概念ABC(アドレス、バインディング、コントラクト)については、色んなサイトで紹介されているので、ここでは省略し、具体的にどのようにしてアプリ内へ組み込むのかをサンプルを交えて紹介したいと思います。
アグリモの製品では、ひかりFAX電話CTI、ひかり電話CTI、日報検索データベースの3つの製品で、サーバーとクライアント間の通信用として利用しています。主な用途は、ファイル転送とDB参照です。
サーバーとなるパソコンには、WCFをサービスとして、アプリとは独立してインストールして使用しています。このようにする理由は、WCFは管理者モードでないと起動できない点にあります。アプリを起動する度に管理者権限の要求画面が表示されては、アプリとしては少々問題です。
1.以下は、ひかりFAX電話CTIのソリューションです。赤枠内は、Windowsサービスアプリケーション(SmartConnectService_FAX)とWCFサービスライブラリ(SmartConnectServiceLibrary_FAX)、それにセットアッププロジェクト(Setup_SmartConnectService)です。
2.まず最初にWCFサービスライブラリを作成します。
3.新規のソリューションにWCFサービスライブラリのプロジェクトが追加されていますが、重要なのは、App.config、IService1.cs、Service1.cs の3つのファイルです。
App.config は、右クリックして「WCF構成の編集」で開くことができます。
設定すべきところは、「サービス」の中の「ホスト」と、その下の「エンドポイント」です。
「ホスト」は、「ベースアドレス」を編集します。
初期値は、http://localhost:8733/Design_Time_Addresses/WcfServiceLibrary1/Service1/ となっていますので、「編集」をクリックして、自身のシステムに合うように変更します。
「エンドポイント」は、「(空白の名前)」というのが2つありますが、上の方(Addressがmexでない方)を設定します。ここでは、バインディング方式とコントラクトを自身のシステムに合うように変更します。
4.IService1.cs、Service1.cs については、前者がサービスのプロトタイプ宣言のようなもの、後者がサービスの中身といった感じです。説明するより見た方が早いので、実際のソースを貼り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace SmartConnectService_FAX_Library { // メモ: [リファクター] メニューの [名前の変更] コマンドを使用すると、コードと config ファイルの両方で同時にインターフェイス名 "IService1" を変更できます。 [ServiceContract] public interface ISmartConnectService_FAXLibrary { [OperationContract] Stream GetStream(string filePath); [OperationContract] void SetString(string filePath); [OperationContract] bool Del_File(string filePath); [OperationContract] string SetStream(Stream stream); [OperationContract] string[] get_fileList(string path, string pattern); [OperationContract] Responce_DB get_DataTable_from_select_DB(string sql); [OperationContract] int exec_nonquery(string name); [OperationContract] ItemInfo GetItem(); [OperationContract] void SetItem(ItemInfo item); [OperationContract] RemoteFileInfo DownloadFile(DownloadRequest request); [OperationContract] void UploadFile(RemoteFileInfo request); } [DataContract] public class ItemInfo { private string _username = ""; private string _mydocument = ""; private string _appdata = ""; private string _machinename = ""; [DataMember] public string UserName { get { return _username; } set { _username = value; } } [DataMember] public string MyDocument { get { return _mydocument; } set { _mydocument = value; } } [DataMember] public string AppData { get { return _appdata; } set { _appdata = value; } } [DataMember] public string MachineName { get { return _machinename; } set { _machinename = value; } } } [DataContract] public class Responce_DB { private string _err_message = ""; private DataTable _data_table = null; [DataMember] public string Err_Message { get { return _err_message; } set { _err_message = value; } } [DataMember] public DataTable Data_Table { get { return _data_table; } set { _data_table = value; } } } [MessageContract] public class DownloadRequest { [MessageBodyMember] public string FileName; } [MessageContract] public class RemoteFileInfo : IDisposable { [MessageHeader(MustUnderstand = true)] public string FileName; [MessageHeader(MustUnderstand = true)] public long Length; [MessageBodyMember(Order = 1)] public System.IO.Stream FileByteStream; public void Dispose() { if (FileByteStream != null) { FileByteStream.Close(); FileByteStream = null; } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
using System; using System.Collections.Generic; using System.Data; using System.Data.SqlServerCe; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Activation; using System.Text; namespace SmartConnectService_FAX_Library { // メモ: [リファクター] メニューの [名前の変更] コマンドを使用すると、コードと config ファイルの両方で同時にクラス名 "Service1" を変更できます。 [ServiceBehavior(IncludeExceptionDetailInFaults = true)] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class SmartConnectService_FAXLibrary : ISmartConnectService_FAXLibrary { public Stream GetStream(string filePath) { try { FileStream imageFile = File.OpenRead(filePath); return imageFile; } catch (IOException ex) { throw ex; } } public bool Del_File(string filePath) { try { File.Delete(filePath); return true; } catch { return false; } } static string _filePath = ""; public void SetString(string filePath) { _filePath = filePath; } public string SetStream(Stream stream) { FileStream outstream = File.Open(_filePath, FileMode.Create, FileAccess.Write); CopyStream(stream, outstream); outstream.Close(); stream.Close(); return _filePath; } static void CopyStream(System.IO.Stream instream, System.IO.Stream outstream) { const int bufferLen = 4096; byte[] buffer = new byte[bufferLen]; int count = 0; int bytecount = 0; while ((count = instream.Read(buffer, 0, bufferLen)) > 0) { outstream.Write(buffer, 0, count); Console.Write("."); bytecount += count; } } public string[] get_fileList(string path, string pattern) { return Directory.GetFiles(path, pattern); } public int exec_nonquery(string sql) { int ret_value = -1; SqlCeConnection conn = new SqlCeConnection("Data Source=" + info.AppData + "\\AGRIMO.JP\\ひかりFAX電話CTI\\hd_fax_cti.sdf"); conn.Open(); SqlCeTransaction tx = conn.BeginTransaction(); SqlCeCommand cmd = conn.CreateCommand(); cmd.Transaction = tx; try { cmd.CommandText = sql; ret_value = cmd.ExecuteNonQuery(); tx.Commit(); } catch { tx.Rollback(); } finally { conn.Close(); } return ret_value; } public Responce_DB get_DataTable_from_select_DB(string sql) { SqlCeConnection conn = new SqlCeConnection("Data Source=" + info.AppData + "\\AGRIMO.JP\\ひかりFAX電話CTI\\hd_fax_cti.sdf"); conn.Open(); DataSet wDs = new DataSet(); try { using (SqlCeDataAdapter da = new SqlCeDataAdapter(sql, conn)) { da.Fill(wDs); da.Dispose(); } } catch (Exception ex) { return new Responce_DB { Data_Table = null, Err_Message = ex.Message }; } finally { conn.Close(); } return new Responce_DB { Data_Table = wDs.Tables[0], Err_Message = "" }; } static ItemInfo info = new ItemInfo(); public void SetItem(ItemInfo item) { info = item; } public ItemInfo GetItem() { return info; } public RemoteFileInfo DownloadFile(DownloadRequest request) { RemoteFileInfo result = new RemoteFileInfo(); try { // get some info about the input file string filePath = System.IO.Path.Combine(info.MyDocument + "\\Hikari FAX Denwa CTI\\", request.FileName); System.IO.FileInfo fileInfo = new System.IO.FileInfo(filePath); // check if exists if (!fileInfo.Exists) throw new System.IO.FileNotFoundException("File not found", request.FileName); // open stream System.IO.FileStream stream = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); // return result result.FileName = request.FileName; result.Length = fileInfo.Length; result.FileByteStream = stream; } catch { } return result; } public void UploadFile(RemoteFileInfo request) { FileStream targetStream = null; Stream sourceStream = request.FileByteStream; string uploadFolder = info.MyDocument + "\\Hikari FAX Denwa CTI\\"; string filePath = Path.Combine(uploadFolder, request.FileName); using (targetStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) { //read from the input stream in 6K chunks //and save to output stream const int bufferLen = 65000; byte[] buffer = new byte[bufferLen]; int count = 0; while ((count = sourceStream.Read(buffer, 0, bufferLen)) > 0) { targetStream.Write(buffer, 0, count); } targetStream.Close(); sourceStream.Close(); } } } } |
5.次にソリューション内に「Windowsサービス(.NET Framework)」プロジェクトを追加します。
6.プロジェクト内の Service1.cs を開いて、右クリックして「インストーラーの追加」を選択します。
↓
7.「serviceProcessInstaller1」のプロパティを開いて、「Account」を「LocalSystem」に変更します。
8.「serviceInstaller1」のプロパティを開いて、「DelayedAutoStart」を「True」、「StartType」を「Automatic」にそれぞれ変更します。
9.App.config を削除し、新たにWCFサービスライブラリ(例の場合は、WcfServiceLibrary1)プロジェクト内のApp.configへのリンクを追加します。
↓ 元のApp.config を削除
↓ WCFサービスライブラリ(例の場合は、WcfServiceLibrary1)プロジェクト内のApp.config を
↓ リンクとして追加します。
10.Service1.cs をコードで開いて、以下のサンプルのように変更します。(※サンプルは、ひかりFAX電話CTIスマートコネクトのものです。サービスが開始する時には、使用するポートをファイヤーウォール設定でオープンにし、サービスがストップする時にはクローズしています。※COMの NetFwTypeLib.dll 参照が必要です。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
using System; using System.ServiceProcess; using System.ServiceModel; using SmartConnectService_FAX_Library; using NetFwTypeLib; namespace SmartConnectService_FAX { public partial class SmartConnectService : ServiceBase { ServiceHost m_host = null; public SmartConnectService() { InitializeComponent(); ServiceName = "SmartConnectService_FAX"; } protected override void OnStart(string[] args) { OnStop(); m_host = new ServiceHost(typeof(SmartConnectService_FAXLibrary)); if (null != m_host) { m_host.Open(); } //ファイヤーウォール設定にルールを追加する add_rule(); } protected override void OnStop() { if (null != m_host) { m_host.Close(); m_host = null; } //ファイヤーウォール設定のルールを削除する remove_rule(); } //ファイヤーウォール設定にルールを追加する private void add_rule() { // Create the FwPolicy2 object. Type NetFwPolicy2Type = Type.GetTypeFromProgID("HNetCfg.FwPolicy2", false); INetFwPolicy2 fwPolicy2 = (INetFwPolicy2)Activator.CreateInstance(NetFwPolicy2Type); // Get the Rules object INetFwRules RulesObject = fwPolicy2.Rules; int CurrentProfiles = fwPolicy2.CurrentProfileTypes; // Create a Rule Object. Type NetFwRuleType = Type.GetTypeFromProgID("HNetCfg.FWRule", false); INetFwRule NewRule = (INetFwRule)Activator.CreateInstance(NetFwRuleType); NewRule.Name = "ひかりFAX電話CTIスマートコネクト"; NewRule.Description = "ひかりFAX電話CTIスマートコネクト"; //NewRule.ApplicationName = System.Windows.Forms.Application.ExecutablePath; NewRule.Protocol = (int)NET_FW_IP_PROTOCOL_.NET_FW_IP_PROTOCOL_TCP; NewRule.LocalPorts = "55315"; NewRule.Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN; NewRule.Enabled = true; NewRule.Grouping = "@firewallapi.dll,-23255"; NewRule.Profiles = CurrentProfiles; NewRule.Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW; //同一のルール名があれば削除する RulesObject.Remove(NewRule.Name); //新しくルールを追加する RulesObject.Add(NewRule); } //ファイヤーウォール設定のルールを削除する private void remove_rule() { // Create the FwPolicy2 object. Type NetFwPolicy2Type = Type.GetTypeFromProgID("HNetCfg.FwPolicy2", false); INetFwPolicy2 fwPolicy2 = (INetFwPolicy2)Activator.CreateInstance(NetFwPolicy2Type); // Get the Rules object INetFwRules RulesObject = fwPolicy2.Rules; //ルール名で削除する RulesObject.Remove("ひかりFAX電話CTIスマートコネクト"); } } } |
11.ProjectInstaller.cs をコードで開いて、以下のサンプルのように変更します。(※サンプルは、ひかりFAX電話CTIスマートコネクトのものです。インストール直後にサービスを自動開始します。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System.Collections; using System.ComponentModel; namespace SmartConnectService_FAX { [RunInstaller(true)] public partial class ProjectInstaller : System.Configuration.Install.Installer { public ProjectInstaller() { InitializeComponent(); } public override void Commit(IDictionary mySavedState) { base.Commit(mySavedState); //インストール直後自動開始 System.ServiceProcess.ServiceController sc = new System.ServiceProcess.ServiceController(); sc.ServiceName = "SmartConnectService_FAX"; sc.Start(); } } } |
12.以上が出来たら、セットアッププロジェクトを作成して完成です。(※以下は、ひかりFAX電話CTIスマートコネクトの画面です。)
ファイルシステムの設定を追加します。
カスタム動作の設定を追加します。
13.クライアントからWCFサービスを通じて、サーバーのDBにアクセスするサンプルプログラムを以下に示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
using System; using System.Data; using System.ServiceModel; using System.Windows.Forms; namespace Hikari_FAX_Denwa_CTI_Client { class CtiServerDB { public SmartConnectServiceReference.ISmartConnectService_FAXLibraryChannel proxy = null; public CtiServerDB() { //WCFサービスに接続する EndpointAddress endPoint = new EndpointAddress( "http://" + svr_addr + ":55315/SmartConnectService_FAX_Library.SmartConnectService_FAXLibrary"); BasicHttpBinding httpBind = new BasicHttpBinding(); httpBind.MaxReceivedMessageSize = 2147483647; httpBind.SendTimeout = TimeSpan.Parse("00:10:00"); httpBind.TransferMode = TransferMode.Streamed; proxy = ChannelFactory<SmartConnectServiceReference.ISmartConnectService_FAXLibraryChannel>.CreateChannel(httpBind, endPoint); } public int exec_nonquery(string sql) { return proxy.exec_nonquery(sql); } public DataTable query(string sql) { SmartConnectServiceReference.Responce_DB rdb = proxy.get_DataTable_from_select_DB(sql); return rdb.Data_Table; } public void close() { proxy.Close(); } } } |