Classic ASP.NETでのRESTfulサービスの実装

この記事では、既存のクラシックASP.NETアプリケーションにRESTful APIを迅速に実装する方法を説明しています。

MVCライブラリの機能を最大限に活用する方法。



使用するツール



1. System.Web.Routing。 mysite.ru/rest/client/0などのリンクを受信するためのRouteTableおよびIRouteHandler

2. System.Web.Mvc。 DefaultModelBinderは、リクエストからモデルへのデータの書き込みを回避します

3. System.Web。 受信したリクエストをCRUD操作の1つに変換するIHttpHandler



動作原理



RouteTableにRouteを追加し、テンプレートに従って、要求を必要なHttpHandlerにリダイレクトします。

CRUD操作用とパラメーター検索操作用の2つのルートを登録します。

HttpHandlerは、要求メソッドと渡されたパラメーターによって目的の操作を選択します。

取得リクエストであり、クエリパラメータが存在する場合、パラメータによる検索操作が選択されます。

これが書き込み操作(作成、更新、削除)の場合、DefaultModelBinderの子孫を使用して目的のモデルを作成またはロードし、要求から受け取ったデータがそれに適用されます。

読み取り操作中に、idパラメーターが渡された場合、1つのモデルが選択され、idが渡されなかった場合、モデルのコレクション全体が返されます。

最後のステップは、モデルをJSONオブジェクトに変換することです。



応答では、キャッシュが30秒間構成されます。

コードが乱雑にならないように、構成を実装しませんでした。



ソリューションを構成するとき、2つの問題が発生する可能性があります。

1. 404エラー-IISのファイル存在チェックを無効にすることで処理されます

(web.config設定のここまたは以下を参照)

2.セッションのオブジェクトが存在しない-セッションモジュールを再登録することで処理されます

(web.config設定のここまたは以下を参照)



アプリケーションのソースはここからダウンロードできます



クライアントモデルのサービス実装例



クラスClientRestHttpHandler

public class ClientRestHttpHandler : RestHttpHandler<Client, ClientModelBinder> { protected override IEnumerable<Client> GetAll() { return ClientService.GetAll(); } protected override Client GetBy(int id) { return ClientService.GetById(id); } protected override IEnumerable<Client> GetBy(NameValueCollection query) { var result = ClientService.GetAll(); var contains = query["contains"]; if (contains != null) { result = from item in result where item.FirstName.Contains(contains) || item.LastName.Contains(contains) select item; } return result; } protected override void Create(Client entity) { ClientService.Create(entity); } protected override void Update(Client entity) { ClientService.Update(entity); } protected override void Delete(Client entity) { ClientService.Delete(entity); } protected override object ToJson(Client entity) { return new { entity.Id, entity.FirstName, entity.LastName }; } }
      
      







クラスClientModelBinder

 public class ClientModelBinder : DefaultModelBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType) { var value = bindingContext.ValueProvider.GetValue("id"); if (value == null) return ClientService.New(); var result = (int)value.ConvertTo(typeof(int)); return ClientService.GetById(result); } }
      
      







Global.asaxの名前

  void Application_Start(object sender, EventArgs e) { RestRouteHandler<ClientRestHttpHandler>.Register("client", "clients"); }
      
      







web.configの構成

 <configuration> <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <!-- fix for empty session on RESTful requests. see http://stackoverflow.com/questions/218057/httpcontext-current-session-is-null-when-routing-requests --> <remove name="Session" /> <add name="Session" type="System.Web.SessionState.SessionStateModule"/> </modules> <handlers> <add name="WildCard" path="*" verb="*" resourceType="Unspecified" /> </handlers> </system.webServer> </configuration>
      
      







それだけです。クライアントモデルは、当社のWebサイトからREST APIを介して利用できます。



基本クラスRestHttpHandlerおよびRestRouteHandlerのソース



 public abstract class RestHttpHandler : IHttpHandler, IReadOnlySessionState { public const string ParamKeyId = "id"; public const string ParamKeyQuery = "query"; /// <summary> /// RouteData property gives an access to request data provided by the router /// It has a setter to simplify instantiation from the RestRouteHandler class /// </summary> public RouteData RouteData { get; set; } protected bool HasId { get { return this.RouteData.Values[ParamKeyId] != null; } } protected bool HasQuery { get { return this.RouteData.Values[ParamKeyQuery] != null; } } protected int ParseId() { return int.Parse(this.RouteData.Values[ParamKeyId].ToString()); } protected NameValueCollection ParseQuery() { var regex = new Regex("(?<key>[a-zA-Z\\-]+)($|/)(?<value>[^/]+)?"); var matches = regex.Matches(this.RouteData.Values[ParamKeyQuery].ToString()); var result = new NameValueCollection(); foreach (Match match in matches) { result.Add(match.Groups["key"].Value, match.Groups["value"].Value); } return result; } public bool IsReusable { get { return false; } } public abstract void ProcessRequest(HttpContext context); }
      
      







 public abstract class RestHttpHandler<T, TBinder> : RestHttpHandler where T : class where TBinder : DefaultModelBinder, new() { /// <summary> /// ProcessRequest actually does request mapping to one of CRUD actions /// </summary> public override void ProcessRequest(HttpContext context) { var @params = new NameValueCollection { context.Request.Form, context.Request.QueryString }; foreach (var value in this.RouteData.Values) { @params.Add(value.Key, value.Value.ToString()); } RenderHeader(context); if (context.Request.HttpMethod == "GET") { if (this.HasQuery) { @params.Add(this.ParseQuery()); this.Render(context, this.GetBy(@params)); } else { if (this.HasId) { this.Render(context, this.GetBy(this.ParseId())); } else { this.Render(context, this.GetAll()); } } } else { var entity = BindModel(@params); switch (context.Request.HttpMethod) { case "POST": this.Create(entity); break; case "PUT": this.Update(entity); break; case "DELETE": this.Delete(entity); break; default: throw new NotSupportedException(); } this.Render(context, entity); } } protected abstract T GetBy(int id); protected abstract IEnumerable<T> GetBy(NameValueCollection query); protected abstract IEnumerable<T> GetAll(); protected abstract void Create(T entity); protected abstract void Update(T entity); protected abstract void Delete(T entity); protected abstract object ToJson(T entity); private object ToJson(IEnumerable<T> entities) { return ( from entity in entities select this.ToJson(entity)).ToArray(); } private static T BindModel(NameValueCollection @params) { return new TBinder().BindModel( new ControllerContext(), new ModelBindingContext { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(T)), ValueProvider = new NameValueCollectionValueProvider( @params, CultureInfo.InvariantCulture ) } ) as T; } private static void RenderHeader(HttpContext context) { context.Response.ClearHeaders(); context.Response.ClearContent(); context.Response.ContentType = "application/json"; context.Response.ContentEncoding = Encoding.UTF8; var cachePolicy = context.Response.Cache; cachePolicy.SetCacheability(HttpCacheability.Public); cachePolicy.SetMaxAge(TimeSpan.FromSeconds(30.0)); } private void Render(HttpContext context, IEnumerable<T> entities) { Render(context, RuntimeHelpers.GetObjectValue(this.ToJson(entities))); } private void Render(HttpContext context, T entity) { Render(context, RuntimeHelpers.GetObjectValue(this.ToJson(entity))); } private static void Render(HttpContext context, object result) { context.Response.Write( new JavaScriptSerializer().Serialize( RuntimeHelpers.GetObjectValue(result) ) ); } }
      
      







 public class RestRouteHandler<T> : IRouteHandler where T : RestHttpHandler, new() { IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) { return new T() { RouteData = requestContext.RouteData }; } public static void Register(string name, string pluralName) { RouteTable.Routes.Add( name, new Route( string.Format( "rest/{0}/{{{1}}}", name, RestHttpHandler.ParamKeyId ), new RestRouteHandler<T>() ) { Defaults = new RouteValueDictionary { { RestHttpHandler.ParamKeyId, null } }, Constraints = new RouteValueDictionary { { RestHttpHandler.ParamKeyId, "\\d*" } } } ); RouteTable.Routes.Add( pluralName, new Route( string.Format( "rest/{0}/{{*{1}}}", pluralName, RestHttpHandler.ParamKeyQuery ), new RestRouteHandler<T>()) { Defaults = new RouteValueDictionary { { RestHttpHandler.ParamKeyQuery, "" } } } ); } }
      
      





私の経験がお役に立てば幸いです。



All Articles