Last active
September 9, 2015 02:22
-
-
Save composite/61b256e71d65e6e7d349 to your computer and use it in GitHub Desktop.
ASP.NET 4 Routing : Attribute based HTTP Handler Routing with Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Data.SqlClient; | |
| using System.Linq; | |
| using System.Transactions; | |
| using System.Web; | |
| using System.Web.Routing; | |
| namespace Composite.Handlers | |
| { | |
| [RequestMapping("Example/{MyType}")] | |
| public class ExampleHandler : AttributeRouteHandler | |
| { | |
| private string rvalue = null; | |
| public ExampleHandler(RequestMappingAttribute attr) : base(attr) { } | |
| public override RouteValueDictionary RouteDefaults { get { return new RouteValueDictionary() { { "MyType", "Default" } }; } } | |
| public override IHttpHandler GetHttpHandler(RequestContext requestContext) | |
| { | |
| rvalue = requestContext.RouteData.Values["MyType"].ToString(); | |
| return base.GetHttpHandler(requestContext); | |
| } | |
| public override void ProcessRequest(HttpContext context) | |
| { | |
| context.Response.Write("It works! You requested my route handler with " + rvalue); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Web.Routing; | |
| namespace Composite | |
| { | |
| public class Global : System.Web.HttpApplication | |
| { | |
| protected void Application_Start(object sender, EventArgs e) | |
| { | |
| RouteConfig.RegisterRoutes(RouteTable.Routes); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Linq; | |
| using System.Reflection; | |
| using System.Web; | |
| using System.Web.Routing; | |
| using System.Web.UI; | |
| namespace Composite.Routing | |
| { | |
| /// <summary> | |
| /// | |
| /// </summary> | |
| public class RouteConfig | |
| { | |
| /// <summary> | |
| /// | |
| /// </summary> | |
| /// <param name="routes"></param> | |
| public static void RegisterRoutes(RouteCollection routes) | |
| { | |
| var maphandlers = Assembly.GetAssembly(typeof (RouteConfig)) | |
| .GetTypes() | |
| .AsEnumerable() | |
| .Where(type => type.GetCustomAttributes(typeof (RequestMappingAttribute), false).Length == 1 && typeof (IHttpHandler).IsAssignableFrom(type)); | |
| foreach (var handlerType in maphandlers) | |
| { | |
| var defaultsProperty = handlerType.GetProperty("RouteDefaults", BindingFlags.Static); | |
| var defaults = defaultsProperty != null ? defaultsProperty.GetValue(null, null) as RouteValueDictionary : null; | |
| var constraintsProperty = handlerType.GetProperty("RouteConstraints", BindingFlags.Static); | |
| var constraints = constraintsProperty != null ? constraintsProperty.GetValue(null, null) as RouteValueDictionary : null; | |
| var dataTokensProperty = handlerType.GetProperty("RouteDataTokens", BindingFlags.Static); | |
| var dataTokens = dataTokensProperty != null ? dataTokensProperty.GetValue(null, null) as RouteValueDictionary : null; | |
| var routeAttribute = handlerType.GetCustomAttributes(typeof(RequestMappingAttribute), false)[0] as RequestMappingAttribute; | |
| if (string.IsNullOrEmpty(routeAttribute.Url)) | |
| throw new NullReferenceException("Route Url property cannot be null or empty."); | |
| routes.Add(MakeRouter(routeAttribute, defaults, constraints, dataTokens, handlerType)); | |
| } | |
| } | |
| /// <summary> | |
| /// | |
| /// </summary> | |
| /// <param name="attr"></param> | |
| /// <param name="defaults"></param> | |
| /// <param name="constraints"></param> | |
| /// <param name="datatokens"></param> | |
| /// <param name="type"></param> | |
| /// <returns></returns> | |
| public static Route MakeRouter(RequestMappingAttribute attr, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary datatokens, Type type) | |
| { | |
| if (!typeof (IHttpHandler).IsAssignableFrom(type)) | |
| throw new TypeLoadException(type.FullName + " is not interface of " + typeof (IHttpHandler).FullName); | |
| object target; | |
| if (typeof (AttributeRouteHandler).IsAssignableFrom(type)) | |
| { | |
| var handler = (AttributeRouteHandler) Activator.CreateInstance(type, attr); | |
| defaults = handler.RouteDefaults ?? defaults; | |
| constraints = handler.RouteConstraints ?? constraints; | |
| datatokens = handler.RouteDataTokens ?? datatokens; | |
| target = handler; | |
| } | |
| else target = Activator.CreateInstance(typeof (StaticHandlerRouter<>).MakeGenericType(type)); | |
| return (Route) Activator.CreateInstance(typeof (Route), attr.Url, defaults, constraints, datatokens, target); | |
| } | |
| } | |
| /// <summary> | |
| /// 요청 매핑 | |
| /// </summary> | |
| public class RequestMappingAttribute : Attribute | |
| { | |
| /// <summary> | |
| /// 새로운 요청 매핑을 정의 | |
| /// (주의 : url은 ASP.NET Routing 규칙을 따라야 함. / 문자나 ~ 문자로 시작하면 안 되며, ? 문자가 포함되면 안 됨.) | |
| /// </summary> | |
| /// <param name="url"></param> | |
| public RequestMappingAttribute(string url) | |
| { | |
| this.Url = url; | |
| } | |
| /// <summary> | |
| /// 요청을 매핑할 URL | |
| /// (주의 : url은 ASP.NET Routing 규칙을 따라야 함. / 문자나 ~ 문자로 시작하면 안 되며, ? 문자가 포함되면 안 됨.) | |
| /// </summary> | |
| public string Url { get; set; } | |
| /// <summary> | |
| /// 요청 매핑에서 허용되는 HTTP 처리방식 | |
| /// ALL을 제외한 복수 첨부 가능 (enum 복수지정 방식과 동일) | |
| /// </summary> | |
| public HttpMethod Verb { get; set; } | |
| } | |
| /// <summary> | |
| /// 정의된 HTTP 동사 | |
| /// (IIS에 따라 지원되지 않는 동사가 있을 수 있음.) | |
| /// </summary> | |
| public enum HttpMethod | |
| { | |
| /// <summary> | |
| /// 모든 요청을 받아들임. 기본값 | |
| /// </summary> | |
| ALL = 0, | |
| /// <summary> | |
| /// GET, 기본적인 요청 | |
| /// </summary> | |
| GET = 1, | |
| /// <summary> | |
| /// POST, 폼 전송을 위한 요청 | |
| /// </summary> | |
| POST = 2, | |
| /// <summary> | |
| /// PUT, 데이터 삽입을 위한 요청 | |
| /// </summary> | |
| PUT = 4, | |
| /// <summary> | |
| /// DELETE, 데이터 삭제를 위한 요청 | |
| /// </summary> | |
| DELETE = 8, | |
| /// <summary> | |
| /// OPTIONS | |
| /// </summary> | |
| OPTIONS = 16, | |
| /// <summary> | |
| /// HEAD, 헤더만 전달할 요청 | |
| /// </summary> | |
| HEAD = 32, | |
| /// <summary> | |
| /// TRACE | |
| /// </summary> | |
| TRACE = 64 | |
| } | |
| /// <summary> | |
| /// | |
| /// </summary> | |
| /// <typeparam name="T"></typeparam> | |
| public class StaticHandlerRouter<T> : IRouteHandler where T : IHttpHandler | |
| { | |
| /// <summary> | |
| /// | |
| /// </summary> | |
| /// <param name="requestContext"></param> | |
| /// <returns></returns> | |
| public IHttpHandler GetHttpHandler(RequestContext requestContext) | |
| { | |
| var type = typeof (T); | |
| var constructor = type.GetConstructors().FirstOrDefault() ?? type.GetConstructors().FirstOrDefault(); | |
| if (constructor == null) throw new NotSupportedException("There is not public constructor of " + typeof(T).FullName); | |
| var paras = constructor.GetParameters(); | |
| switch (paras.Count()) | |
| { | |
| case 1: | |
| if (!typeof(RequestContext).IsAssignableFrom(paras[0].ParameterType)) | |
| throw new NotSupportedException("a parameter type is not " + typeof(RequestContext).FullName + " in type " + typeof(T).FullName); | |
| return (IHttpHandler)Activator.CreateInstance(type, requestContext); | |
| case 0: | |
| return (IHttpHandler)Activator.CreateInstance(type); | |
| default: | |
| throw new NotSupportedException(typeof(T).FullName + " is not have constructor with no argument or a " + typeof(RequestContext).FullName + " argument."); | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// 특성 기반의 라우팅과 HTTP 처리기를 통합한 추상 클래스. 성능상 이거 추천. | |
| /// </summary> | |
| public abstract class AttributeRouteHandler : IRouteHandler, IHttpHandler | |
| { | |
| /// <summary> | |
| /// <see cref="HttpContext.Items"/>에 참조 가능한 라우팅 값들 | |
| /// 캐스팅 가능 타입 : <see cref="System.Web.Routing.RouteValueDictionary"/>, <see cref="System.Collections.Generic.IDictionary{TKey,TValue}"/>... | |
| /// </summary> | |
| protected const string ROUTE_VALUES = "AttributeRouteHandler_RouteValues"; | |
| /// <summary> | |
| /// <see cref="HttpContext.Items"/>에 참조 가능한 라우팅 부가 데이터들 | |
| /// 캐스팅 가능 타입 : <see cref="System.Web.Routing.RouteValueDictionary"/>, <see cref="System.Collections.Generic.IDictionary{TKey,TValue}"/>... | |
| /// </summary> | |
| protected const string ROUTE_TOKENS = "AttributeRouteHandler_RouteTokens"; | |
| /// <summary> | |
| /// 요청 특성 | |
| /// </summary> | |
| protected RequestMappingAttribute RequestMapping { get; private set; } | |
| /// <summary> | |
| /// 요청 특성을 사용하여 새 인스턴스 생성하는데 반드시 정의해야 함. | |
| /// </summary> | |
| /// <param name="attr"></param> | |
| protected AttributeRouteHandler(RequestMappingAttribute attr) | |
| { | |
| this.RequestMapping = attr; | |
| } | |
| /// <summary> | |
| /// 라우팅 속성을 참고하여 HTTP 처리기를 반환하는데 특별한 처리 불필요 시 재정의 필요 없음. | |
| /// </summary> | |
| /// <param name="requestContext">라우팅 속성이 첨가된 요청 컨텍스트</param> | |
| /// <returns></returns> | |
| public virtual IHttpHandler GetHttpHandler(RequestContext requestContext) | |
| { | |
| requestContext.HttpContext.Items[ROUTE_VALUES] = requestContext.RouteData.Values; | |
| requestContext.HttpContext.Items[ROUTE_TOKENS] = requestContext.RouteData.DataTokens; | |
| return this; | |
| } | |
| /// <summary> | |
| /// HTTP 처리기를 구현하는 메소드는 반드시 정의해야 함. | |
| /// </summary> | |
| /// <param name="context">HTTP 컨텍스트</param> | |
| public abstract void ProcessRequest(HttpContext context); | |
| /// <summary> | |
| /// 라우팅 경로변수 기본값 설정 | |
| /// </summary> | |
| public virtual RouteValueDictionary RouteDefaults { get { return null; } } | |
| /// <summary> | |
| /// 라우팅 경로변수 매칭 조건에 대한 상수 정의 | |
| /// </summary> | |
| public virtual RouteValueDictionary RouteConstraints { get { return null; } } | |
| /// <summary> | |
| /// 라우팅 데이터에 없는 부가적인 추가 데이터 | |
| /// </summary> | |
| public virtual RouteValueDictionary RouteDataTokens { get { return null; } } | |
| /// <summary> | |
| /// 인스턴스를 Pool에 넣어 재사용할 지, 가비지 대상으로 매 요청 시 따로 관리할 것인지? | |
| /// 기본적으로 Pool 관리를 수행하나, ASP.NET 특성상 싱글톤이 아님에 유의. | |
| /// 관리되는 코드를 이용하지 않을 경우 재정의하여 false 로 바꿔 성능 향상을 꾀할 수 있음. | |
| /// </summary> | |
| public virtual bool IsReusable { get { return true; } } | |
| } | |
| /// <summary> | |
| /// | |
| /// </summary> | |
| public static class PageExtensions | |
| { | |
| /// <summary> | |
| /// | |
| /// </summary> | |
| /// <param name="thisPage"></param> | |
| /// <param name="targetPageType"></param> | |
| /// <param name="routeParameters"></param> | |
| /// <returns></returns> | |
| public static string GetMappedRouteUrl(this Page thisPage, Type targetPageType, object routeParameters) | |
| { | |
| return thisPage.GetRouteUrl(targetPageType.FullName, routeParameters); | |
| } | |
| /// <summary> | |
| /// | |
| /// </summary> | |
| /// <param name="thisPage"></param> | |
| /// <param name="targetPageType"></param> | |
| /// <param name="routeParameters"></param> | |
| /// <returns></returns> | |
| public static string GetMappedRouteUrl(this Page thisPage, Type targetPageType, RouteValueDictionary routeParameters) | |
| { | |
| return thisPage.GetRouteUrl(targetPageType.FullName, routeParameters); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment