前回の DLR で名前付き引数を使うでは、メソッドに引数として「キーと値のペア」を渡す方法を考え、
動的言語ランタイム (DLR) を利用する方法を紹介しました。
しかし、同じ処理を複数の場所で呼び出す必要があるなど、再利用性を重視する場合は、
なるべく静的にシグネチャを解決する方法がよいでしょう。
そこで今回は、呼び出すサービスをインターフェイスとして定義し、透過プロキシ (transparent proxy) を利用してみます。
先にコードを示します。
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Reflection; | |
| using System.Runtime.Remoting.Messaging; | |
| using System.Runtime.Remoting.Proxies; | |
| namespace TransparentHttpConsole | |
| { | |
| public static class HttpProxy | |
| { | |
| public static IService CreateProxy<IService>() | |
| { | |
| return (IService)new HttpProxy<IService>().GetTransparentProxy(); | |
| } | |
| } | |
| public class HttpProxy<IService> : RealProxy | |
| { | |
| public string BaseUri { get; } | |
| public HttpProxy() : base(typeof(IService)) | |
| { | |
| var serviceType = typeof(IService); | |
| if (!serviceType.IsInterface) throw new InvalidOperationException("IService must be an interface."); | |
| var baseUriAttribute = serviceType.GetCustomAttribute<BaseUriAttribute>(true); | |
| if (baseUriAttribute == null) throw new InvalidOperationException("IService must have a BaseUriAttribute."); | |
| BaseUri = baseUriAttribute.BaseUri; | |
| } | |
| public override IMessage Invoke(IMessage msg) | |
| { | |
| var methodCall = (IMethodCallMessage)msg; | |
| if (methodCall.MethodBase.DeclaringType == typeof(object)) | |
| { | |
| var result = | |
| methodCall.MethodName == "GetType" ? typeof(IService) : | |
| methodCall.MethodBase.Invoke(this, methodCall.InArgs); | |
| return CreateReturnMessage(result, methodCall); | |
| } | |
| var query = Enumerable.Range(0, methodCall.InArgCount) | |
| .ToDictionary(methodCall.GetInArgName, methodCall.GetInArg); | |
| switch (methodCall.MethodName) | |
| { | |
| case "Get": | |
| return CreateReturnMessage(HttpHelper.Get(BaseUri, query), methodCall); | |
| default: | |
| throw new InvalidOperationException($"The method \"{methodCall.MethodName}\" is not available."); | |
| } | |
| } | |
| static IMethodReturnMessage CreateReturnMessage(object returnValue, IMethodCallMessage methodCall) | |
| { | |
| return new ReturnMessage(returnValue, new object[0], 0, methodCall.LogicalCallContext, methodCall); | |
| } | |
| #region Methods of Object class | |
| // GetType method can not be overridden. | |
| public override int GetHashCode() => base.GetHashCode(); | |
| public override bool Equals(object obj) => ReferenceEquals(GetTransparentProxy(), obj); | |
| public override string ToString() => typeof(IService).ToString(); | |
| #endregion | |
| } | |
| [AttributeUsage(AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] | |
| public sealed class BaseUriAttribute : Attribute | |
| { | |
| public string BaseUri { get; } | |
| public BaseUriAttribute(string baseUri) | |
| { | |
| BaseUri = baseUri; | |
| } | |
| } | |
| } |
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| namespace TransparentHttpConsole | |
| { | |
| class Program | |
| { | |
| static void Main(string[] args) | |
| { | |
| var cgis = HttpProxy.CreateProxy<ICgisService>(); | |
| Console.WriteLine(cgis.Get("6048301")); | |
| Console.WriteLine(cgis.Get("501", 1)); | |
| } | |
| } | |
| // http://zip.cgis.biz/ | |
| [BaseUri("http://zip.cgis.biz/xml/zip.php")] | |
| public interface ICgisService | |
| { | |
| string Get(string zn); | |
| string Get(string zn, int ver); | |
| } | |
| } |
このコードでは、RealProxy クラスを継承した HttpProxy<IService> クラスを作成しています。
RealProxy はプロキシの実体となるものであり、
その外層である透過プロキシを RealProxy.GetTransparentProxy メソッドで取得できます。
一方、利用する側の Program.cs では、ICgisService インターフェイスを定義しておきます。
その透過プロキシを生成すれば、ICgisService インターフェイスを実装するクラスが存在しなくてもメソッドを呼び出すことができ、
実体は HttpProxy<IService> クラスの Invoke メソッドとなります。
また、BaseUriAttribute クラスを定義しており、
呼び出される Web API のベースとなる URI を属性で指定できるようにしています。
WCF における契約プログラミングでは、クライアント側とサーバー側で同一のインターフェイスを利用し、
クライアント側からのアクセスを上記のように透過プロキシで実装する方法があります。
方法 : ChannelFactory を使用する にある通り、ChannelFactory.CreateChannel メソッドで透過プロキシを生成します。
前回:DLR で名前付き引数を使う
次回:透過プロキシでアスペクト指向プログラミング (1)
作成したサンプル
TransparentHttpConsole (GitHub)
バージョン情報
C# 7.0
.NET Framework 4.5
