Example #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 template  = CombineRoute(routeAttribute?.Template, attribute?.Template ?? method.GetCustomAttribute <RouteAttribute>()?.Template);

                var methodModel = new MethodModel
                {
                    MethodInfo   = method,
                    RoutePattern = template == null ? null : RoutePatternFactory.Parse(template)
                };

                // Add all attributes as metadata
                foreach (var metadata in method.GetCustomAttributes(inherit: true))
                {
                    methodModel.Metadata.Add(metadata);
                }

                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);
        }
Example #2
0
        public static HttpModel FromType(Type type)
        {
            var model = new HttpModel(type);

            var routeAttributeType        = type.BaseType.Assembly.GetType(typeof(RouteAttribute).FullName);
            var httpMethodAttributeType   = type.BaseType.Assembly.GetType(typeof(HttpMethodAttribute).FullName);
            var fromQueryAttributeType    = type.BaseType.Assembly.GetType(typeof(FromQueryAttribute).FullName);
            var fromHeaderAttributeType   = type.BaseType.Assembly.GetType(typeof(FromHeaderAttribute).FullName);
            var fromFormAttributeType     = type.BaseType.Assembly.GetType(typeof(FromFormAttribute).FullName);
            var fromBodyAttributeType     = type.BaseType.Assembly.GetType(typeof(FromBodyAttribute).FullName);
            var fromRouteAttributeType    = type.BaseType.Assembly.GetType(typeof(FromRouteAttribute).FullName);
            var fromCookieAttributeType   = type.BaseType.Assembly.GetType(typeof(FromCookieAttribute).FullName);
            var fromServicesAttributeType = type.BaseType.Assembly.GetType(typeof(FromServicesAttribute).FullName);

            var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);

            var routeAttribute = type.GetCustomAttributeData(routeAttributeType);

            foreach (var method in methods)
            {
                var attribute = method.GetCustomAttributeData(httpMethodAttributeType);
                var template  = CombineRoute(routeAttribute?.GetConstructorArgument <string>(0), attribute?.GetConstructorArgument <string>(0) ?? method.GetCustomAttributeData(routeAttributeType)?.GetConstructorArgument <string>(0));

                var methodModel = new MethodModel
                {
                    MethodInfo   = method,
                    RoutePattern = template
                };

                // Add all attributes as metadata
                //foreach (var metadata in method.GetCustomAttributes(inherit: true))
                //{
                //    methodModel.Metadata.Add(metadata);
                //}
                foreach (var metadata in method.CustomAttributes)
                {
                    if (metadata.AttributeType.Namespace == "System.Runtime.CompilerServices" ||
                        metadata.AttributeType.Name == "DebuggerStepThroughAttribute")
                    {
                        continue;
                    }
                    methodModel.Metadata.Add(metadata);
                }

                foreach (var parameter in method.GetParameters())
                {
                    var fromQuery   = parameter.GetCustomAttributeData(fromQueryAttributeType);
                    var fromHeader  = parameter.GetCustomAttributeData(fromHeaderAttributeType);
                    var fromForm    = parameter.GetCustomAttributeData(fromFormAttributeType);
                    var fromBody    = parameter.GetCustomAttributeData(fromBodyAttributeType);
                    var fromRoute   = parameter.GetCustomAttributeData(fromRouteAttributeType);
                    var fromCookie  = parameter.GetCustomAttributeData(fromCookieAttributeType);
                    var fromService = parameter.GetCustomAttributeData(fromServicesAttributeType);

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

                model.Methods.Add(methodModel);
            }

            return(model);
        }
        // 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 (CustomAttributeData item in method.Metadata)
                    {
                        var attr = item.Constructor.Invoke(item.ConstructorArguments.Select(a => a.Value).ToArray());
                        b.Metadata.Add(attr);
                    }
                });
            }
        }