Beispiel #1
0
        public static HttpModel FromType(Type type)
        {
            var model   = new HttpModel();
            var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);

            var routeAttribute = type.GetCustomAttribute <RouteAttribute>();

            foreach (var method in methods)
            {
                var attribute  = method.GetCustomAttribute <HttpMethodAttribute>();
                var httpMethod = attribute?.Method;
                var template   = CombineRoute(routeAttribute?.Template, attribute?.Template ?? method.GetCustomAttribute <RouteAttribute>()?.Template);

                var methodModel = new MethodModel
                {
                    MethodInfo = method,
                    ReturnType = method.ReturnType,
                    HttpMethod = httpMethod,
                };

                if (template != null)
                {
                    methodModel.Route(template);
                }

                foreach (var parameter in method.GetParameters())
                {
                    var fromQuery   = parameter.GetCustomAttribute <FromQueryAttribute>();
                    var fromHeader  = parameter.GetCustomAttribute <FromHeaderAttribute>();
                    var fromForm    = parameter.GetCustomAttribute <FromFormAttribute>();
                    var fromBody    = parameter.GetCustomAttribute <FromBodyAttribute>();
                    var fromRoute   = parameter.GetCustomAttribute <FromRouteAttribute>();
                    var fromCookie  = parameter.GetCustomAttribute <FromCookieAttribute>();
                    var fromService = parameter.GetCustomAttribute <FromServicesAttribute>();

                    methodModel.Parameters.Add(new ParameterModel
                    {
                        Name          = parameter.Name,
                        ParameterType = parameter.ParameterType,
                        FromQuery     = fromQuery == null ? null : fromQuery?.Name ?? parameter.Name,
                        FromHeader    = fromHeader == null ? null : fromHeader?.Name ?? parameter.Name,
                        FromForm      = fromForm == null ? null : fromForm?.Name ?? parameter.Name,
                        FromRoute     = fromRoute == null ? null : fromRoute?.Name ?? parameter.Name,
                        FromCookie    = fromCookie == null ? null : fromCookie?.Name,
                        FromBody      = fromBody != null,
                        FromServices  = fromService != null
                    });
                }

                model.Methods.Add(methodModel);
            }

            return(model);
        }
Beispiel #2
0
 public static MethodModel Method(this HttpModel model, string method)
 {
     return(model.Methods.First(m => m.MethodInfo.Name == method));
 }
Beispiel #3
0
        private static List <Endpoint> BuildWithoutCache(Type handlerType, Action <HttpModel> configure)
        {
            var model = HttpModel.FromType(handlerType);

            configure?.Invoke(model);

            var endpoints = new List <Endpoint>();

            foreach (var method in model.Methods.Where(m => m.RoutePattern != null))
            {
                var needForm   = false;
                var httpMethod = method.HttpMethod;
                var template   = method.RoutePattern;

                // Non void return type

                // Task Invoke(HttpContext httpContext, RouteValueDictionary routeValues, RequestDelegate next)
                // {
                //     // The type is activated via DI if it has args
                //     return ExecuteResult(new THttpHandler(...).Method(..), httpContext);
                // }

                // void return type

                // Task Invoke(HttpContext httpContext, RouteValueDictionary routeValues, RequestDelegate next)
                // {
                //     new THttpHandler(...).Method(..)
                //     return Task.CompletedTask;
                // }

                var httpContextArg      = Expression.Parameter(typeof(HttpContext), "httpContext");
                var routeValuesArg      = Expression.Parameter(typeof(RouteValueDictionary), "routeValues");
                var nextArg             = Expression.Parameter(typeof(RequestDelegate), "next");
                var requestServicesExpr = Expression.Property(httpContextArg, nameof(HttpContext.RequestServices));

                // Fast path: We can skip the activator if there's only a default ctor with 0 args
                var ctors = handlerType.GetConstructors();

                Expression httpHandlerExpression = null;

                if (method.MethodInfo.IsStatic)
                {
                    // Do nothing
                }
                else if (ctors.Length == 1 && ctors[0].GetParameters().Length == 0)
                {
                    httpHandlerExpression = Expression.New(ctors[0]);
                }
                else
                {
                    // CreateInstance<THttpHandler>(context.RequestServices)
                    httpHandlerExpression = Expression.Call(ActivatorMethodInfo.MakeGenericMethod(handlerType), requestServicesExpr);
                }

                var args = new List <Expression>();

                var httpRequestExpr = Expression.Property(httpContextArg, nameof(HttpContext.Request));

                foreach (var parameter in method.Parameters)
                {
                    Expression paramterExpression = Expression.Default(parameter.ParameterType);

                    if (parameter.FromQuery != null)
                    {
                        var queryProperty = Expression.Property(httpRequestExpr, nameof(HttpRequest.Query));
                        paramterExpression = BindArgument(queryProperty, parameter, parameter.FromQuery);
                    }
                    else if (parameter.FromHeader != null)
                    {
                        var headersProperty = Expression.Property(httpRequestExpr, nameof(HttpRequest.Headers));
                        paramterExpression = BindArgument(headersProperty, parameter, parameter.FromHeader);
                    }
                    else if (parameter.FromRoute != null)
                    {
                        paramterExpression = BindArgument(routeValuesArg, parameter, parameter.FromRoute);
                    }
                    else if (parameter.FromCookie != null)
                    {
                        var cookiesProperty = Expression.Property(httpRequestExpr, nameof(HttpRequest.Cookies));
                        paramterExpression = BindArgument(cookiesProperty, parameter, parameter.FromCookie);
                    }
                    else if (parameter.FromServices)
                    {
                        paramterExpression = Expression.Call(GetRequiredServiceMethodInfo.MakeGenericMethod(parameter.ParameterType), requestServicesExpr);
                    }
                    else if (parameter.FromForm != null)
                    {
                        needForm = true;

                        var formProperty = Expression.Property(httpRequestExpr, nameof(HttpRequest.Form));
                        paramterExpression = BindArgument(formProperty, parameter, parameter.FromForm);
                    }
                    else if (parameter.FromBody)
                    {
                        var bodyProperty = Expression.Property(httpRequestExpr, nameof(HttpRequest.Body));
                        paramterExpression = BindBody(bodyProperty, parameter);
                    }
                    else
                    {
                        if (parameter.ParameterType == typeof(IFormCollection))
                        {
                            paramterExpression = Expression.Property(httpRequestExpr, nameof(HttpRequest.Form));
                        }
                        else if (parameter.ParameterType == typeof(HttpContext))
                        {
                            paramterExpression = httpContextArg;
                        }
                        else if (parameter.ParameterType == typeof(RequestDelegate))
                        {
                            paramterExpression = nextArg;
                        }
                        else if (parameter.ParameterType == typeof(IHeaderDictionary))
                        {
                            paramterExpression = Expression.Property(httpRequestExpr, nameof(HttpRequest.Headers));
                        }
                    }

                    args.Add(paramterExpression);
                }

                Expression body = null;

                if (method.ReturnType == typeof(void))
                {
                    var bodyExpressions = new List <Expression>
                    {
                        Expression.Call(httpHandlerExpression, method.MethodInfo, args),
                        Expression.Property(null, (PropertyInfo)CompletedTaskMemberInfo)
                    };

                    body = Expression.Block(bodyExpressions);
                }
                else
                {
                    var methodCall = Expression.Call(httpHandlerExpression, method.MethodInfo, args);

                    // Coerce Task<T> to Task<object>
                    if (method.ReturnType.IsGenericType &&
                        method.ReturnType.GetGenericTypeDefinition() == typeof(Task <>))
                    {
                        var typeArg = method.ReturnType.GetGenericArguments()[0];

                        // ExecuteTask<T>(handler.Method(..), httpContext);
                        body = Expression.Call(
                            ExecuteTaskOfTMethodInfo.MakeGenericMethod(typeArg),
                            methodCall,
                            httpContextArg);
                    }
                    else
                    {
                        // ExecuteResult(handler.Method(..), httpContext);
                        body = Expression.Call(ExecuteAsyncMethodInfo, methodCall, httpContextArg);
                    }
                }

                var lambda = Expression.Lambda <Func <HttpContext, RouteValueDictionary, RequestDelegate, Task> >(body, httpContextArg, routeValuesArg, nextArg);

                var routeTemplate = method.RoutePattern;

                var invoker = lambda.Compile();

                var routeEndpointModel = new RouteEndpointModel(
                    async httpContext =>
                {
                    // Generating async code would just be insane so if the method needs the form populate it here
                    // so the within the method it's cached
                    if (needForm)
                    {
                        await httpContext.Request.ReadFormAsync();
                    }

                    await invoker.Invoke(httpContext, httpContext.Request.RouteValues, (c) => Task.CompletedTask);
                },
                    routeTemplate,
                    0);
                routeEndpointModel.DisplayName = routeTemplate.RawText;

                if (!string.IsNullOrEmpty(method.HttpMethod))
                {
                    routeEndpointModel.Metadata.Add(new HttpMethodMetadata(new[] { method.HttpMethod }));
                }

                foreach (var attribute in method.MethodInfo.GetCustomAttributes(true))
                {
                    routeEndpointModel.Metadata.Add(attribute);
                }

                foreach (var convention in method.Conventions)
                {
                    convention(routeEndpointModel);
                }

                endpoints.Add(routeEndpointModel.Build());
            }

            return(endpoints);
        }
Beispiel #4
0
        // Expression tree impl
        internal static void Build(Type handlerType, IEndpointRouteBuilder routes)
        {
            var model = HttpModel.FromType(handlerType);

            ObjectFactory factory = null;

            // REVIEW: Should this be lazy?
            var httpRequestReader = routes.ServiceProvider.GetRequiredService <IHttpRequestReader>();

            foreach (var method in model.Methods)
            {
                // Nothing to route to
                if (method.RoutePattern == null)
                {
                    continue;
                }

                var  needForm = false;
                var  needBody = false;
                Type bodyType = null;
                // Non void return type

                // Task Invoke(HttpContext httpContext)
                // {
                //     // The type is activated via DI if it has args
                //     return ExecuteResultAsync(new THttpHandler(...).Method(..), httpContext);
                // }

                // void return type

                // Task Invoke(HttpContext httpContext)
                // {
                //     new THttpHandler(...).Method(..)
                //     return Task.CompletedTask;
                // }

                var httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext");
                // This argument represents the deserialized body returned from IHttpRequestReader
                // when the method has a FromBody attribute declared
                var deserializedBodyArg = Expression.Parameter(typeof(object), "bodyValue");

                var requestServicesExpr = Expression.Property(httpContextArg, nameof(HttpContext.RequestServices));

                // Fast path: We can skip the activator if there's only a default ctor with 0 args
                var ctors = handlerType.GetConstructors();

                Expression httpHandlerExpression = null;

                if (method.MethodInfo.IsStatic)
                {
                    // Do nothing
                }
                else if (ctors.Length == 1 && ctors[0].GetParameters().Length == 0)
                {
                    httpHandlerExpression = Expression.New(ctors[0]);
                }
                else
                {
                    // Create a factory lazily for this handlerType
                    if (factory == null)
                    {
                        factory = ActivatorUtilities.CreateFactory(handlerType, Type.EmptyTypes);
                    }

                    // This invokes the cached factory to create the instance then casts it to the target type
                    var invokeFactoryExpr = Expression.Invoke(Expression.Constant(factory), requestServicesExpr, Expression.Constant(null, typeof(object[])));
                    httpHandlerExpression = Expression.Convert(invokeFactoryExpr, handlerType);
                }

                var args = new List <Expression>();

                var httpRequestExpr = Expression.Property(httpContextArg, nameof(HttpContext.Request));
                foreach (var parameter in method.Parameters)
                {
                    Expression paramterExpression = Expression.Default(parameter.ParameterType);

                    if (parameter.FromQuery != null)
                    {
                        var queryProperty = Expression.Property(httpRequestExpr, nameof(HttpRequest.Query));
                        paramterExpression = BindArgument(queryProperty, parameter, parameter.FromQuery);
                    }
                    else if (parameter.FromHeader != null)
                    {
                        var headersProperty = Expression.Property(httpRequestExpr, nameof(HttpRequest.Headers));
                        paramterExpression = BindArgument(headersProperty, parameter, parameter.FromHeader);
                    }
                    else if (parameter.FromRoute != null)
                    {
                        var routeValuesProperty = Expression.Property(httpRequestExpr, nameof(HttpRequest.RouteValues));
                        paramterExpression = BindArgument(routeValuesProperty, parameter, parameter.FromRoute);
                    }
                    else if (parameter.FromCookie != null)
                    {
                        var cookiesProperty = Expression.Property(httpRequestExpr, nameof(HttpRequest.Cookies));
                        paramterExpression = BindArgument(cookiesProperty, parameter, parameter.FromCookie);
                    }
                    else if (parameter.FromServices)
                    {
                        paramterExpression = Expression.Call(GetRequiredServiceMethodInfo.MakeGenericMethod(parameter.ParameterType), requestServicesExpr);
                    }
                    else if (parameter.FromForm != null)
                    {
                        needForm = true;

                        var formProperty = Expression.Property(httpRequestExpr, nameof(HttpRequest.Form));
                        paramterExpression = BindArgument(formProperty, parameter, parameter.FromForm);
                    }
                    else if (parameter.FromBody)
                    {
                        if (needBody)
                        {
                            throw new InvalidOperationException(method.MethodInfo.Name + " cannot have more than one FromBody attribute.");
                        }

                        if (needForm)
                        {
                            throw new InvalidOperationException(method.MethodInfo.Name + " cannot mix FromBody and FromForm on the same method.");
                        }

                        needBody           = true;
                        bodyType           = parameter.ParameterType;
                        paramterExpression = Expression.Convert(deserializedBodyArg, bodyType);
                    }
                    else
                    {
                        if (parameter.ParameterType == typeof(IFormCollection))
                        {
                            needForm = true;

                            paramterExpression = Expression.Property(httpRequestExpr, nameof(HttpRequest.Form));
                        }
                        else if (parameter.ParameterType == typeof(HttpContext))
                        {
                            paramterExpression = httpContextArg;
                        }
                    }

                    args.Add(paramterExpression);
                }

                Expression body = null;

                var methodCall = Expression.Call(httpHandlerExpression, method.MethodInfo, args);

                // Exact request delegate match
                if (method.MethodInfo.ReturnType == typeof(void))
                {
                    var bodyExpressions = new List <Expression>
                    {
                        methodCall,
                        Expression.Property(null, (PropertyInfo)CompletedTaskMemberInfo)
                    };

                    body = Expression.Block(bodyExpressions);
                }
                else if (AwaitableInfo.IsTypeAwaitable(method.MethodInfo.ReturnType, out var info))
                {
                    if (method.MethodInfo.ReturnType == typeof(Task))
                    {
                        body = methodCall;
                    }
                    else if (method.MethodInfo.ReturnType.IsGenericType &&
                             method.MethodInfo.ReturnType.GetGenericTypeDefinition() == typeof(Task <>))
                    {
                        var typeArg = method.MethodInfo.ReturnType.GetGenericArguments()[0];

                        if (typeof(Result).IsAssignableFrom(typeArg))
                        {
                            body = Expression.Call(
                                ExecuteTaskResultOfTMethodInfo.MakeGenericMethod(typeArg),
                                methodCall,
                                httpContextArg);
                        }
                        else
                        {
                            // ExecuteTask<T>(handler.Method(..), httpContext);
                            body = Expression.Call(
                                ExecuteTaskOfTMethodInfo.MakeGenericMethod(typeArg),
                                methodCall,
                                httpContextArg);
                        }
                    }
                    else if (method.MethodInfo.ReturnType.IsGenericType &&
                             method.MethodInfo.ReturnType.GetGenericTypeDefinition() == typeof(ValueTask <>))
                    {
                        var typeArg = method.MethodInfo.ReturnType.GetGenericArguments()[0];

                        if (typeof(Result).IsAssignableFrom(typeArg))
                        {
                            body = Expression.Call(
                                ExecuteValueResultTaskOfTMethodInfo.MakeGenericMethod(typeArg),
                                methodCall,
                                httpContextArg);
                        }
                        else
                        {
                            // ExecuteTask<T>(handler.Method(..), httpContext);
                            body = Expression.Call(
                                ExecuteValueTaskOfTMethodInfo.MakeGenericMethod(typeArg),
                                methodCall,
                                httpContextArg);
                        }
                    }
                    else
                    {
                        // TODO: Handle custom awaitables
                        throw new NotSupportedException("Unsupported return type " + method.MethodInfo.ReturnType);
                    }
                }
                else if (typeof(Result).IsAssignableFrom(method.MethodInfo.ReturnType))
                {
                    body = Expression.Call(methodCall, ResultExecuteAsync, httpContextArg);
                }
                else
                {
                    var newObjectResult = Expression.New(ObjectResultCtor, methodCall);
                    body = Expression.Call(newObjectResult, ObjectResultExecuteAsync, httpContextArg);
                }

                RequestDelegate requestDelegate = null;

                if (needBody)
                {
                    // We need to generate the code for reading from the body before calling into the
                    // delegate
                    var lambda  = Expression.Lambda <Func <HttpContext, object, Task> >(body, httpContextArg, deserializedBodyArg);
                    var invoker = lambda.Compile();

                    requestDelegate = async httpContext =>
                    {
                        var bodyValue = await httpRequestReader.ReadAsync(httpContext, bodyType);

                        await invoker(httpContext, bodyValue);
                    };
                }
                else if (needForm)
                {
                    var lambda  = Expression.Lambda <RequestDelegate>(body, httpContextArg);
                    var invoker = lambda.Compile();

                    requestDelegate = async httpContext =>
                    {
                        // Generating async code would just be insane so if the method needs the form populate it here
                        // so the within the method it's cached
                        await httpContext.Request.ReadFormAsync();

                        await invoker(httpContext);
                    };
                }
                else
                {
                    var lambda  = Expression.Lambda <RequestDelegate>(body, httpContextArg);
                    var invoker = lambda.Compile();

                    requestDelegate = invoker;
                }

                var displayName = method.MethodInfo.DeclaringType.Name + "." + method.MethodInfo.Name;

                routes.Map(method.RoutePattern, requestDelegate).Add(b =>
                {
                    foreach (var item in method.Metadata)
                    {
                        b.Metadata.Add(item);
                    }
                });
            }
        }