/// <summary> /// Ctor /// </summary> /// <param name="httpContext"></param> /// <param name="routeData"></param> /// <param name="methodContext"></param> public ServiceHandler(HttpContext httpContext, RouteData routeData, MethodInvocationContext methodContext) { HttpContext = httpContext; HttpRequest = httpContext.Request; HttpResponse = httpContext.Response; RouteData = routeData; MethodContext = methodContext; ServiceInstanceConfiguration = MethodContext.InstanceConfiguration; }
/// <summary> /// Hook up routed maps to service handlers. /// </summary> /// <param name="appBuilder"></param> /// <returns></returns> public static IApplicationBuilder UseServiceHandler( this IApplicationBuilder appBuilder) { var serviceConfig = ServiceHandlerConfiguration.Current; foreach (var serviceInstanceConfig in serviceConfig.Services) { // conditionally route to service handler based on RouteBasePath appBuilder.MapWhen( context => { var requestPath = context.Request.Path.ToString().ToLower(); var servicePath = serviceInstanceConfig.RouteBasePath.ToLower(); bool matched = requestPath == servicePath || requestPath.StartsWith(servicePath.Replace("//", "/") + "/"); return(matched); }, builder => { if (serviceConfig.Cors.UseCorsPolicy) { builder.UseCors(serviceConfig.Cors.CorsPolicyName); } // Build up route mapping builder.UseRouter(routeBuilder => { // Get Service interface = assuming first interface def is service interface var interfaces = serviceInstanceConfig.ServiceType.GetInterfaces(); if (interfaces.Length < 1) { throw new NotSupportedException(Resources.HostedServiceRequiresAnInterface); } // Loop through service methods and cache the method info, parameter info, and RestAttribute // in a MethodInvocationContext so we don't have to do this for each method call foreach (var method in serviceInstanceConfig.ServiceType.GetMethods( BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.DeclaredOnly)) { // find service contract var interfaceMethod = interfaces[0].GetMethod(method.Name); if (interfaceMethod == null) { continue; } var restAttribute = GetRestAttribute(interfaceMethod); if (restAttribute == null) { continue; } var relativeRoute = restAttribute.Route; if (relativeRoute == null) { // if no route assume we use the method name // Note: string.Empty is a valid route! relativeRoute = method.Name; } // figure out the full route we pass the ASP.NET Core Route Manager string fullRoute = (serviceInstanceConfig.RouteBasePath + "/" + relativeRoute).Replace("//", "/"); if (fullRoute.StartsWith("/")) { fullRoute = fullRoute.Substring(1); } // Cache reflection and context data var methodContext = new MethodInvocationContext(method, serviceConfig, serviceInstanceConfig); var roles = restAttribute.AuthorizationRoles; if (roles != null) { methodContext.AuthorizationRoles = roles.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); } // This code is what triggers the SERVICE METHOD EXECUTION // via a delegate that is called when the route is matched Func <HttpRequest, HttpResponse, RouteData, Task> exec = async(req, resp, routeData) => { // ReSharper disable once AccessToModifiedClosure var handler = new ServiceHandler(req.HttpContext, routeData, methodContext); await handler.ProcessRequest(); }; routeBuilder.MapVerb(restAttribute.Method.ToString(), fullRoute, exec); routeBuilder.MapVerb("OPTIONS", fullRoute, async(req, resp, route) => { resp.StatusCode = StatusCodes.Status204NoContent; }); } }); // TODO: Should move into a separate // builder.UseMiddleware<ServiceHandlerMiddleware>(); }); } return(appBuilder); }
/// <summary> /// Enabled CODE Framework service hosting /// </summary> /// <param name="appBuilder"></param> /// <returns></returns> public static IApplicationBuilder UseServiceHandler(this IApplicationBuilder appBuilder) { var serviceConfig = ServiceHandlerConfiguration.Current; if (serviceConfig.Cors.UseCorsPolicy) { appBuilder.UseCors(serviceConfig.Cors.CorsPolicyName); } // Endpoints require routing, so we make sure it is there appBuilder.UseRouting(); appBuilder.UseEndpoints(endpoints => { foreach (var serviceInstanceConfig in serviceConfig.Services) { // conditionally route to service handler based on RouteBasePath appBuilder.MapWhen( context => { var requestPath = context.Request.Path.ToString().ToLower(); if (SwaggerRoutes != null && SwaggerRoutes.Contains(requestPath)) { return(false); // We make sure we are not accidently eating up a configured swagger/openapi route } var servicePath = serviceInstanceConfig.RouteBasePath.ToLower(); var matched = requestPath == servicePath || requestPath.StartsWith(servicePath.Replace("//", "/") + "/"); return(matched); }, builder => { //if (serviceConfig.Cors.UseCorsPolicy) // builder.UseCors(serviceConfig.Cors.CorsPolicyName); // Build up route mapping builder.UseRouter(routeBuilder => { // Get Service interface = assuming first interface def is service interface var interfaces = serviceInstanceConfig.ServiceType.GetInterfaces(); if (interfaces.Length < 1) { throw new NotSupportedException(Resources.HostedServiceRequiresAnInterface); } // Loop through service methods and cache the propertyInfo info, parameter info, and RestAttribute // in a MethodInvocationContext so we don't have to do this for each propertyInfo call foreach (var method in serviceInstanceConfig.ServiceType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.DeclaredOnly)) { // find service contract var interfaceMethod = interfaces[0].GetMethod(method.Name); if (interfaceMethod == null) { continue; // Should never happen, but doesn't hurt to check } var restAttribute = GetRestAttribute(interfaceMethod); if (restAttribute == null) { continue; // This should never happen since GetRestAttribute() above returns a default attribute if none is attached } var relativeRoute = restAttribute.Route; if (relativeRoute == null) { // If no route is defined, we either build a route out of name and other attributes, or we use the propertyInfo name as the last resort. // Note: string.Empty is a valid route (and also a valid name). Only null values indicate that the setting has not been set! relativeRoute = restAttribute.Name ?? method.Name; // We also have to take a look at the parameter(s) - there should be only one - to build the route var parameters = method.GetParameters(); if (parameters.Length > 0) { var parameterType = parameters[0].ParameterType; var parameterProperties = parameterType.GetProperties(BindingFlags.Instance | BindingFlags.Public); var inlineParameters = GetSortedInlineParameterNames(parameterProperties); foreach (var inlineParameter in inlineParameters) { relativeRoute += $"/{{{inlineParameter}}}"; } } } if (relativeRoute.StartsWith("/")) { relativeRoute = relativeRoute.Substring(1); } // Figure out the full route we pass the ASP.NET Core Route Manager var fullRoute = (serviceInstanceConfig.RouteBasePath + "/" + relativeRoute).Replace("//", "/"); if (fullRoute.StartsWith("/")) { fullRoute = fullRoute.Substring(1); } // Cache reflection and context data var methodContext = new MethodInvocationContext(method, serviceConfig, serviceInstanceConfig); var roles = restAttribute.AuthorizationRoles; if (roles != null) { methodContext.AuthorizationRoles = roles.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); } // This code is what triggers the SERVICE METHOD EXECUTION via a delegate that is called when the route is matched Func <HttpRequest, HttpResponse, RouteData, Task> exec = async(req, resp, routeData) => { // ReSharper disable once AccessToModifiedClosure var handler = new ServiceHandler(req.HttpContext, routeData, methodContext); await handler.ProcessRequest(); }; routeBuilder.MapVerb("OPTIONS", fullRoute, async(req, resp, route) => { resp.StatusCode = StatusCodes.Status204NoContent; await Task.CompletedTask; }); routeBuilder.MapVerb(restAttribute.Method.ToString(), fullRoute, exec); } }); }); } }); return(appBuilder); }