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); }
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); } }); } }