Skip to content

Instantly share code, notes, and snippets.

@composite
Last active September 9, 2015 02:22
Show Gist options
  • Select an option

  • Save composite/61b256e71d65e6e7d349 to your computer and use it in GitHub Desktop.

Select an option

Save composite/61b256e71d65e6e7d349 to your computer and use it in GitHub Desktop.
ASP.NET 4 Routing : Attribute based HTTP Handler Routing with Example
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);
}
}
}
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);
}
}
}
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