Skip to content

Instantly share code, notes, and snippets.

@composite
Forked from puryfury/AttributeDriver.cs
Last active November 11, 2015 07:57
Show Gist options
  • Save composite/aac8858a04afe7a222f8 to your computer and use it in GitHub Desktop.
Save composite/aac8858a04afe7a222f8 to your computer and use it in GitHub Desktop.
Nancy Attrubute-Driven Module Example (Nancy 1.x or above, targeted as .NET 4.5 or later.)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Nancy
{
/// <summary>
/// Nancy Attribute Driver Module (Must inherit)
/// </summary>
public abstract class NancyAttributeModule : NancyModule
{
/// <summary>
/// Class Base Route Path
/// </summary>
protected readonly string basePath;
/// <summary>
/// Initializes a new instance of the <see cref="NancyAttributeModule"/> class.
/// </summary>
protected NancyAttributeModule()
: base()
{
this.basePath = string.Empty;
Init();
}
/// <summary>
/// Initializes a new instance of the <see cref="NancyAttributeModule"/> class.
/// </summary>
/// <param name="path">The path.</param>
protected NancyAttributeModule(string path)
: base(path)
{
this.basePath = path;
Init();
}
/// <summary>
/// Initializes this instance.
/// </summary>
/// <exception cref="InvalidCastException">Attribute-Driven Method cannot cast from <see cref="NancyModule.RouteBuilder"/>.</exception>
/// <exception cref="MethodAccessException">Attribute-Driven Method is not accessible from this class.</exception>
/// <exception cref="InvalidOperationException">Attribute-Driven Module Initialization failed.</exception>
private void Init()
{
Type type = this.GetType(), nct = typeof(NancyContext);
Type tattr = typeof(NancyRequestAttribute), battr = typeof(NancyBeforeFilterAttribute), aattr = typeof(NancyAfterFilterAttribute);
MethodInfo[] methods = type.GetMethods();
Dictionary<string, Func<NancyContext, bool>> conditions = methods.Where(m=> m.GetCustomAttribute<NancyRequestConditionAttribute>(false)!=null)
.ToDictionary(method => method.GetCustomAttribute<NancyRequestConditionAttribute>(false).Name ?? method.Name, method =>
{
if(method.GetParameters().Length != 1 || method.ReturnType != typeof(bool))
throw new InvalidCastException($"{type.FullName}.{method.Name} method must have a argument with Nancy.NancyContext and return type as System.Boolean.");
ParameterExpression pex = Expression.Parameter(typeof(NancyContext));
return Expression.Lambda<Func<NancyContext, bool>>(Expression.Call(Expression.Constant(this), method, pex), pex).Compile();
});
foreach (MethodInfo method in methods)
{
string strE = "'" + type.FullName + "." + method.Name + "' method {0}";
Exception arglenE = new TargetParameterCountException(string.Format(strE, "must have 1 argument in normal use. if use Task or async, you must 2 arguments with any type and System.Threading.CancellationToken.")),
ncE = new InvalidCastException(string.Format(strE, "must have 1 argument as Nancy.NancyContext!"));
if (method.IsDefined(tattr, false))
{
ParameterInfo[] ps = method.GetParameters();
if (method.ReturnType == typeof(void)) throw new InvalidCastException(string.Format(strE, "must have return any type!"));
foreach (NancyRequestAttribute attr in method.GetCustomAttributes(tattr, false))
{
if (attr == null) continue;
if (attr.Async)
{
if (ps.Length != 2) throw arglenE;
if (method.ReturnType.BaseType != typeof(Task)) throw new InvalidCastException(string.Format(strE, "must have async and System.Threading.Task<dynamic> type!"));
ParameterExpression pex = Expression.Parameter(typeof(object)), pcx = Expression.Parameter(typeof(CancellationToken));
var body = Expression.Lambda<Func<dynamic, CancellationToken, Task<dynamic>>>(Expression.Call(Expression.Constant(this), method, pex, pcx), pex, pcx).Compile();
if (!string.IsNullOrEmpty(attr.Condition))
{
switch (attr.Type)
{
case RequestType.Get: Get[attr.Path, conditions[attr.Condition], true] = body; break;
case RequestType.Post: Post[attr.Path, conditions[attr.Condition], true] = body; break;
case RequestType.Put: Put[attr.Path, conditions[attr.Condition], true] = body; break;
case RequestType.Delete: Delete[attr.Path, conditions[attr.Condition], true] = body; break;
case RequestType.Options: Options[attr.Path, conditions[attr.Condition], true] = body; break;
case RequestType.Patch: Patch[attr.Path, conditions[attr.Condition], true] = body; break;
case RequestType.Head: Head[attr.Path, conditions[attr.Condition], true] = body; break;
}
}
else
{
switch (attr.Type)
{
case RequestType.Get: Get[attr.Path, true] = body; break;
case RequestType.Post: Post[attr.Path, true] = body; break;
case RequestType.Put: Put[attr.Path, true] = body; break;
case RequestType.Delete: Delete[attr.Path, true] = body; break;
case RequestType.Options: Options[attr.Path, true] = body; break;
case RequestType.Patch: Patch[attr.Path, true] = body; break;
case RequestType.Head: Head[attr.Path, true] = body; break;
}
}
}
else
{
if (ps.Length != 1) throw arglenE;
ParameterExpression pex = Expression.Parameter(typeof(object));
var body = Expression.Lambda<Func<object, object>>(Expression.Call(Expression.Constant(this), method, pex), pex).Compile();
if (!string.IsNullOrEmpty(attr.Condition))
{
switch (attr.Type)
{
case RequestType.Get: Get[attr.Path, conditions[attr.Condition]] = body; break;
case RequestType.Post: Post[attr.Path, conditions[attr.Condition]] = body; break;
case RequestType.Put: Put[attr.Path, conditions[attr.Condition]] = body; break;
case RequestType.Delete: Delete[attr.Path, conditions[attr.Condition]] = body; break;
case RequestType.Options: Options[attr.Path, conditions[attr.Condition]] = body; break;
case RequestType.Patch: Patch[attr.Path, conditions[attr.Condition]] = body; break;
case RequestType.Head: Head[attr.Path, conditions[attr.Condition]] = body; break;
}
}
else
{
switch (attr.Type)
{
case RequestType.Get: Get[attr.Path] = body; break;
case RequestType.Post: Post[attr.Path] = body; break;
case RequestType.Put: Put[attr.Path] = body; break;
case RequestType.Delete: Delete[attr.Path] = body; break;
case RequestType.Options: Options[attr.Path] = body; break;
case RequestType.Patch: Patch[attr.Path] = body; break;
case RequestType.Head: Head[attr.Path] = body; break;
}
}
}
}
}
else if (method.IsDefined(battr, false))
{
ParameterInfo[] ps = method.GetParameters();
if (ps.Length != 1) throw arglenE;
if (ps.Any(param => param.ParameterType != nct)) throw ncE;
if (method.ReturnType != typeof(Response)) throw new MethodAccessException(string.Format(strE, "must return Nancy.Response!"));
ParameterExpression pex = Expression.Parameter(nct);
Func<NancyContext, Response> body = Expression.Lambda<Func<NancyContext, Response>>(Expression.Call(Expression.Constant(this), method, pex), pex).Compile();
Before += body;
}
else if (method.IsDefined(aattr, false))
{
ParameterInfo[] ps = method.GetParameters();
if (ps.Length != 1) throw arglenE;
if (ps.Any(param => param.ParameterType != nct)) throw ncE;
if (method.ReturnType != typeof(void)) throw new MethodAccessException(string.Format(strE, "must return NOTHING(void)!"));
ParameterExpression pex = Expression.Parameter(nct);
Action<NancyContext> body = Expression.Lambda<Action<NancyContext>>(Expression.Call(Expression.Constant(this), method, pex), pex).Compile();
After += body;
}
}
//DriverModule Helper ViewBag
After += cx =>
{
ViewBag.ClassName = this.GetType().Name;
ViewBag.BasePath = this.basePath;
};
}
}
/// <summary>
/// Nancy Request Attribute. you can define twice or more.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class NancyRequestAttribute : Attribute
{
/// <summary>
/// Defines Request for declared request type and route path.
/// </summary>
/// <param name="type"></param>
/// <param name="path"></param>
public NancyRequestAttribute(RequestType type, string path)
{
this.Type = type;
this.Path = path;
}
/// <summary>
/// Defines Request for GET type and route path.
/// </summary>
/// <param name="path"></param>
public NancyRequestAttribute(string path) : this(RequestType.Get, path) { }
/// <summary>
/// Gets the request type.
/// </summary>
/// <value>The type.</value>
public RequestType Type { get; }
/// <summary>
/// Gets the request path.
/// </summary>
/// <value>The path.</value>
public string Path { get; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="NancyRequestAttribute"/> is asynchronous.
/// </summary>
/// <value><c>true</c> if asynchronous; otherwise, <c>false</c>.</value>
public bool Async { get; set; }
/// <summary>
/// Gets or sets the condition name.
/// </summary>
/// <value>The condition.</value>
public string Condition { get; set; }
}
/// <summary>
/// The Nancy GET alias request attribute. This class cannot be inherited.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class GetAttribute : NancyRequestAttribute
{
public GetAttribute(string path) : base(path) { }
}
/// <summary>
/// The Nancy POST alias request attribute. This class cannot be inherited.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class PostAttribute : NancyRequestAttribute
{
public PostAttribute(string path) : base(RequestType.Post, path) { }
}
/// <summary>
/// The Nancy PUT alias request attribute. This class cannot be inherited.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class PutAttribute : NancyRequestAttribute
{
public PutAttribute(string path) : base(RequestType.Put, path) { }
}
/// <summary>
/// The Nancy DELETE alias request attribute. This class cannot be inherited.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class DeleteAttribute : NancyRequestAttribute
{
public DeleteAttribute(string path) : base(RequestType.Delete, path) { }
}
/// <summary>
/// The Nancy PATCH alias request attribute. This class cannot be inherited.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class PatchAttribute : NancyRequestAttribute
{
public PatchAttribute(string path) : base(RequestType.Patch, path) { }
}
/// <summary>
/// The Nancy OPTIONS alias request attribute. This class cannot be inherited.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class OptionsAttribute : NancyRequestAttribute
{
public OptionsAttribute(string path) : base(RequestType.Options, path) { }
}
/// <summary>
/// The Nancy HEAD alias request attribute. This class cannot be inherited.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class HeadAttribute : NancyRequestAttribute
{
public HeadAttribute(string path) : base(RequestType.Head, path) { }
}
/// <summary>
/// Nancy Request condition defination attribute.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class NancyRequestConditionAttribute : Attribute
{
/// <summary>
/// Gets the name. if null, method name will used.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
}
/// <summary>
/// Nancy Request condition defination attribute. This class cannot be inherited.
/// Alias class of <see cref="NancyRequestConditionAttribute"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class ConditionAttribute : NancyRequestConditionAttribute { }
/// <summary>
/// Nancy Before Filter Attribute
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class NancyBeforeFilterAttribute : Attribute { }
/// <summary>
/// Nancy Before Filter Attribute. This class cannot be inherited.
/// Alias class of <see cref="NancyBeforeFilterAttribute"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class BeforeAttribute : NancyBeforeFilterAttribute { }
/// <summary>
/// Nancy After Filter Attribute
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class NancyAfterFilterAttribute : Attribute { }
/// <summary>
/// Nancy After Filter Attribute. This class cannot be inherited.
/// Alias class of <see cref="NancyAfterFilterAttribute"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class AfterAttribute : NancyAfterFilterAttribute { }
/// <summary>
/// Nancy Request Types for Request Attribute
/// </summary>
public enum RequestType
{
/// <summary>
/// HTTP Verb : GET
/// </summary>
Get,
/// <summary>
/// HTTP Verb : POST
/// </summary>
Post,
/// <summary>
/// HTTP Verb : PUT
/// </summary>
Put,
/// <summary>
/// HTTP Verb : DELETE
/// </summary>
Delete,
/// <summary>
/// HTTP Verb : PATCH
/// </summary>
Patch,
/// <summary>
/// HTTP Verb : OPTIONS
/// </summary>
Options,
/// <summary>
/// HTTP Verb : HEAD
/// </summary>
Head
}
}
using Nancy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using CustomerWanso.Secutiry;
namespace CustomerWanso.Controller
{
public class MainModule : NancySecurityModule
{
[NancyRequest("/")]
public dynamic GetMain(dynamic route)
{
return View["Main.cshtml"];
}
[NancyRequest("/async", Async = true)]
public async Task<dynamic> AsyncMain(dynamic route)
{
return Response.AsText("<h1>It works!</h1> whooray!", "text/html;charset=utf-8");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Nancy;
using Nancy.Responses;
using CustomerWanso.DataModel;
namespace CustomerWanso.Secutiry
{
public class NancySecurityModule : NancyAttributeModule
{
public const string LOGINURL = "/Login";
public const string USERSESS = "USER";
public NancySecurityModule() : base() { }
public NancySecurityModule(string path) : base(path) { }
[NancyBeforeFilter]
public Response LoginFilter(NancyContext context)
{
Response res = null;
if (context.Request.Session[USERSESS] as UserDto == null)
return new RedirectResponse(LOGINURL);
return res;
}
[NancyAfterFilter]
public void UserInfoFilter(NancyContext context)
{
UserDto user = Session[USERSESS] as UserDto;
if (user != null)
{
ViewBag.UserId = user.UserId;
ViewBag.UserName = user.UserName;
ViewBag.Status = user.Status;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment