public OperationHandler(ILightNodeOptions options, Type classType, MethodInfo methodInfo)
{
this.ClassName = classType.Name;
this.MethodName = methodInfo.Name;
this.Arguments = methodInfo.GetParameters()
.Select(x => new ParameterInfoSlim(x))
.ToArray();
this.ParameterNames = Arguments.Select(x => x.Name).ToList().AsReadOnly();
this.ReturnType = methodInfo.ReturnType;
this.filters = options.Filters
.Concat(classType.GetCustomAttributes<LightNodeFilterAttribute>(true))
.Concat(methodInfo.GetCustomAttributes<LightNodeFilterAttribute>(true))
.OrderBy(x => x.Order)
.ToArray();
var operationOption = methodInfo.GetCustomAttributes<OperationOptionAttribute>(true).FirstOrDefault()
?? classType.GetCustomAttributes<OperationOptionAttribute>(true).FirstOrDefault();
this.AcceptVerb = (operationOption != null && operationOption.AcceptVerbs != null)
? operationOption.AcceptVerbs.Value
: options.DefaultAcceptVerb;
var verbSpecifiedAttr = methodInfo.GetCustomAttributes<HttpVerbAttribtue>(true);
if (verbSpecifiedAttr.Any())
{
this.AcceptVerb = verbSpecifiedAttr.Aggregate((AcceptVerbs)0, (x, y) => x | y.AcceptVerbs);
}
this.ForceUseFormatter = (operationOption != null && operationOption.ContentFormatter != null)
? operationOption.ContentFormatter
: null;
var formatterChoiceBase = new[] { options.DefaultFormatter }.Concat(options.SpecifiedFormatters).Where(x => x != null).ToArray();
this.optionFormatters = formatterChoiceBase;
this.formatterByExt = formatterChoiceBase.SelectMany(x => (x.Ext ?? "").Split('|'), (fmt, ext) => new { fmt, ext }).ToLookup(x => x.ext, x => x.fmt, StringComparer.OrdinalIgnoreCase);
this.formatterByMediaType = formatterChoiceBase.ToLookup(x => x.MediaType, StringComparer.OrdinalIgnoreCase);
this.formatterByContentEncoding = formatterChoiceBase.ToLookup(x => x.ContentEncoding, StringComparer.OrdinalIgnoreCase);
this.AttributeLookup = classType.GetTypeInfo().GetCustomAttributes(true)
.Concat(methodInfo.GetCustomAttributes(true))
.Cast<Attribute>()
.ToLookup(x => x.GetType());
foreach (var argument in this.Arguments)
{
if (!TypeBinder.IsAllowType(argument.ParameterType))
{
throw new InvalidOperationException(string.Format("parameter is not allowed, class:{0} method:{1} paramName:{2} paramType:{3}",
classType.Name, methodInfo.Name, argument.Name, argument.ParameterType.FullName));
}
}
// prepare lambda parameters
var envArg = Expression.Parameter(typeof(HttpContext), "context");
var envBind = Expression.Bind(typeof(LightNodeContract).GetProperty("Context"), envArg);
var args = Expression.Parameter(typeof(object[]), "args");
var parameters = methodInfo.GetParameters()
.Select((x, i) => Expression.Convert(Expression.ArrayIndex(args, Expression.Constant(i)), x.ParameterType))
.ToArray();
// Task or Task<T>
if (typeof(Task).IsAssignableFrom(this.ReturnType))
{
// (object[] args) => new X().M((T1)args[0], (T2)args[1])...
var lambda = Expression.Lambda<Func<HttpContext, object[], Task>>(
Expression.Call(
Expression.MemberInit(Expression.New(classType), envBind),
methodInfo,
parameters),
envArg, args);
if (this.ReturnType.GetTypeInfo().IsGenericType && this.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
{
this.handlerBodyType = HandlerBodyType.AsyncFunc;
this.methodAsyncFuncBody = lambda.Compile();
lock (taskResultExtractors)
{
if (!taskResultExtractors.ContainsKey(this.ReturnType))
{
// (object task) => (object)((Task<>).Result)
var taskParameter = Expression.Parameter(typeof(object), "task");
var resultLambda = Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Property(
Expression.Convert(taskParameter, this.ReturnType),
"Result"),
typeof(object)),
taskParameter);
var compiledResultLambda = resultLambda.Compile();
taskResultExtractors[this.ReturnType] = compiledResultLambda;
}
}
}
else
{
this.handlerBodyType = HandlerBodyType.AsyncAction;
this.methodAsyncActionBody = lambda.Compile();
}
}
else if (this.ReturnType == typeof(void)) // of course void
{
// (object[] args) => { new X().M((T1)args[0], (T2)args[1])... }
var lambda = Expression.Lambda<Action<HttpContext, object[]>>(
Expression.Call(
Expression.MemberInit(Expression.New(classType), envBind),
methodInfo,
parameters),
envArg, args);
this.handlerBodyType = HandlerBodyType.Action;
this.methodActionBody = lambda.Compile();
}
else // return T
{
// (object[] args) => (object)new X().M((T1)args[0], (T2)args[1])...
var lambda = Expression.Lambda<Func<HttpContext, object[], object>>(
Expression.Convert(
Expression.Call(
Expression.MemberInit(Expression.New(classType), envBind),
methodInfo,
parameters)
, typeof(object)),
envArg, args);
this.handlerBodyType = HandlerBodyType.Func;
this.methodFuncBody = lambda.Compile();
}
}