public static void UseBlueprintApi( this IApplicationBuilder applicationBuilder, string basePath) { // Ensure ends with a slash, but only one basePath = basePath.TrimEnd('/') + '/'; var logger = applicationBuilder.ApplicationServices.GetRequiredService <ILoggerFactory>().CreateLogger("Blueprint.Compilation"); var routeBuilder = new RouteBuilder(applicationBuilder); var apiDataModel = applicationBuilder.ApplicationServices.GetRequiredService <ApiDataModel>(); var inlineConstraintResolver = applicationBuilder.ApplicationServices.GetRequiredService <IInlineConstraintResolver>(); var apmTool = applicationBuilder.ApplicationServices.GetRequiredService <IApmTool>(); var httpOptions = applicationBuilder.ApplicationServices.GetRequiredService <IOptions <BlueprintHttpOptions> >(); IRouter routeHandler; try { var apiOperationExecutor = applicationBuilder.ApplicationServices.GetRequiredService <IApiOperationExecutor>(); routeHandler = new BlueprintApiRouter( apiOperationExecutor, apmTool, applicationBuilder.ApplicationServices, applicationBuilder.ApplicationServices.GetRequiredService <ILogger <BlueprintApiRouter> >(), httpOptions, basePath); } catch (CompilationException e) { logger.LogCritical(e, "Blueprint pipeline compilation failed"); // When a compilation exception occurs (happens when getting IApiOperationExecutor for the first time as it is // registered as a singleton factory) we create a route handler that will throw a WrapperException. // // The WrapperException implements ICompilationException which means if we are using the developer exception // page (i.e. app.UseDeveloperExceptionPage()) we get a nice compilation error page that includes the error // message AND complete file source code to enable much better diagnostics of the compilation issue). // // The consequence of this is that the app is NOT prevented from starting up and only when hitting an API endpoint // would the exception be known to HTTP clients (the error is logged regardless) routeHandler = new RouteHandler(context => throw new CompilationWrapperException(e)); } catch (Exception e) when(e.InnerException is CompilationException ce) { logger.LogCritical(e.InnerException, "Blueprint pipeline compilation failed"); // Some DI projects wrap the CompilationException in their own (i.e. StructureMap, which throws a specific exception // when trying to create an instance). This catch block attempts to handle those custom exceptions on the assumption that // the inner exception would be the original CompilationException routeHandler = new RouteHandler(context => throw new CompilationWrapperException(ce)); } // Ordering by 'indexOf {' means we put those URLs which are not placeholders // first (e.g. /users/{id} and /users/me will put /users/me first) because those without would return -1 foreach (var link in apiDataModel.Links.OrderBy(l => l.UrlFormat.IndexOf('{'))) { var httpFeatureData = link.OperationDescriptor.GetFeatureData <HttpOperationFeatureData>(); routeBuilder.Routes.Add(new Route( target: routeHandler, routeName: httpFeatureData.HttpMethod + "-" + link.UrlFormat, routeTemplate: basePath + link.RoutingUrl, defaults: new RouteValueDictionary(new { operation = link.OperationDescriptor }), constraints: new Dictionary <string, object> { ["httpMethod"] = new HttpMethodRouteConstraint(httpFeatureData.HttpMethod), }, dataTokens: null, inlineConstraintResolver: inlineConstraintResolver)); } applicationBuilder.UseRouter(routeBuilder.Build()); }
/// <summary> /// Maps all Blueprint operation endpoints that have previously been registered with the dependency /// injection host using <see cref="ServiceCollectionExtensions.AddBlueprintApi" />. /// </summary> /// <remarks> /// Note that if the compilation of the pipelines fail this method does <b>not</b> throw that exception /// but instead will register handlers that expose the compilation exception, that when combined with /// the development exception page can present useful diagnostics for tracking down the compilation /// error. /// </remarks> /// <param name="endpoints">The endpoint builder to register with.</param> /// <param name="basePath">A base path to prepend to all routes.</param> /// <returns>A builder that allows adding metadata / conventions to all mapped endpoints.</returns> public static IEndpointConventionBuilder MapBlueprintApi( this IEndpointRouteBuilder endpoints, string basePath) { // Ensure ends with a slash, but only one basePath = basePath.TrimEnd('/') + '/'; var logger = endpoints.ServiceProvider.GetRequiredService <ILoggerFactory>().CreateLogger("Blueprint.Compilation"); var apiDataModel = endpoints.ServiceProvider.GetRequiredService <ApiDataModel>(); var httpOptions = endpoints.ServiceProvider.GetRequiredService <IOptions <BlueprintHttpOptions> >(); RequestDelegate requestDelegate; try { var apiOperationExecutor = endpoints.ServiceProvider.GetRequiredService <IApiOperationExecutor>(); requestDelegate = new BlueprintApiRouter( apiOperationExecutor, endpoints.ServiceProvider, endpoints.ServiceProvider.GetRequiredService <ILogger <BlueprintApiRouter> >(), httpOptions, basePath).RouteAsync; } catch (CompilationException e) { logger.LogCritical(e, "Blueprint pipeline compilation failed"); // When a compilation exception occurs (happens when getting IApiOperationExecutor for the first time as it is // registered as a singleton factory) we create a route handler that will throw a WrapperException. // // The WrapperException implements ICompilationException which means if we are using the developer exception // page (i.e. app.UseDeveloperExceptionPage()) we get a nice compilation error page that includes the error // message AND complete file source code to enable much better diagnostics of the compilation issue). // // The consequence of this is that the app is NOT prevented from starting up and only when hitting an API endpoint // would the exception be known to HTTP clients (the error is logged regardless) requestDelegate = _ => throw new CompilationWrapperException(e); } catch (Exception e) when(e.InnerException is CompilationException ce) { logger.LogCritical(e.InnerException, "Blueprint pipeline compilation failed"); // Some DI projects wrap the CompilationException in their own (i.e. StructureMap, which throws a specific exception // when trying to create an instance). This catch block attempts to handle those custom exceptions on the assumption that // the inner exception would be the original CompilationException requestDelegate = _ => throw new CompilationWrapperException(ce); } var builders = new List <IEndpointConventionBuilder>(); // Ordering by 'indexOf {' means we put those URLs which are not placeholders // first (e.g. /users/{id} and /users/me will put /users/me first) because those without would return -1 foreach (var link in apiDataModel.Links.OrderBy(l => l.UrlFormat.IndexOf('{'))) { var httpFeatureData = link.OperationDescriptor.GetFeatureData <HttpOperationFeatureData>(); var builder = endpoints.Map(RoutePatternFactory.Parse(basePath + link.RoutingUrl), requestDelegate); builder.WithDisplayName($"{httpFeatureData.HttpMethod} {link.OperationDescriptor.Name}"); builder.WithMetadata(new HttpMethodMetadata(new[] { httpFeatureData.HttpMethod })); builder.WithMetadata(link.OperationDescriptor); builders.Add(builder); } return(new BlueprintEndpointRouteBuilder(builders)); }