{"id":25091353,"url":"https://github.com/barclayadam/blueprint","last_synced_at":"2025-04-15T23:10:03.555Z","repository":{"id":37773145,"uuid":"207297886","full_name":"barclayadam/blueprint","owner":"barclayadam","description":"Blueprint is a framework for building high-performance HTTP apis, console apps \u0026 background processors","archived":false,"fork":false,"pushed_at":"2023-05-19T12:07:52.000Z","size":17921,"stargazers_count":7,"open_issues_count":17,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-15T23:09:47.410Z","etag":null,"topics":["console","cqrs","dotnet","http","roslyn"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/barclayadam.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-09T11:55:03.000Z","updated_at":"2022-11-22T10:03:58.000Z","dependencies_parsed_at":"2025-02-07T13:21:44.765Z","dependency_job_id":"368a158f-bee8-4991-b665-3aa476eea4aa","html_url":"https://github.com/barclayadam/blueprint","commit_stats":null,"previous_names":[],"tags_count":58,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barclayadam%2Fblueprint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barclayadam%2Fblueprint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barclayadam%2Fblueprint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barclayadam%2Fblueprint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/barclayadam","download_url":"https://codeload.github.com/barclayadam/blueprint/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249167445,"owners_count":21223506,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["console","cqrs","dotnet","http","roslyn"],"created_at":"2025-02-07T13:19:17.456Z","updated_at":"2025-04-15T23:10:03.536Z","avatar_url":"https://github.com/barclayadam.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status][build-shield]][build-url]\n[![Coverage][coverage-shield]][coverage-url]\n[![Quality][quality-shield]][quality-url]\n[![Contributors][contributors-shield]][contributors-url]\n[![Stargazers][stars-shield]][stars-url]\n[![Issues][issues-shield]][issues-url]\n[![Apache 2.0 License][license-shield]][license-url]\n[![Gitpod][gitpod-shield]][gitpod-url]\n\n### Blueprint\n\nBlueprint provides a framework to create HTTP APIs, background task processors and command line apps that\nare built using a simple operation + handler architecture with a pipeline of middlewares that perform\ncross-cutting concerns such as auditing, authorisation and error handling.\n\nBlueprint uses runtime code compilation using [Rosyln](https://github.com/dotnet/roslyn) to generate efficient executors for each individual \noperation at startup.\n\n[Report Bug](https://github.com/barclayadam/blueprint/issues)\n·\n[Request Features](https://github.com/barclayadam/blueprint/issues)\n\n## Table of Contents\n\n* [About the Project](#about-the-project)\n  * [Built With](#built-with)\n* [Installation](#installation)\n* [Roadmap](#roadmap)\n* [Contributing](#contributing)\n* [License](#license)\n* [Contact](#contact)\n* [Acknowledgements](#acknowledgements)\n\n\u003c!-- ABOUT THE PROJECT --\u003e\n## About The Project\n\n````c#\nnamespace Blueprint.Sample.WebApi.Api\n{\n    [RootLink(\"forecast\")]\n    public class WeatherForecastQuery : IQuery\u003cIEnumerable\u003cWeatherForecast\u003e\u003e\n    {\n        [Required]\n        public string City { get; set; }\n\n        [FromHeader(\"X-Header-Key\")]\n        public string MyHeader { get; set; }\n\n        [FromCookie]\n        public string MyCookie { get; set; }\n\n        [FromCookie(\"a-different-cookie-name\")]\n        public int MyCookieNumber { get; set; }\n\n        public IEnumerable\u003cWeatherForecast\u003e Invoke(IWeatherDataSource weatherDataSource)\n        {\n            return weatherDataSource.Get(this.City);\n        }\n    }\n}\n````\n\nBlueprint provides a runtime-compiled pipeline runner of operations that can be used in multiple contexts, enabling a codebase\nto have a homogeneous way of organising command and queries whilst having a common means of adding cross-cutting concerns.\n\nBlueprint compiles a class per operation, with middleware builders contributing cross-cutting concerns. Because we\nbuild the pipeline dynamically per type builders are able to eliminate unused code per type, remove reflection over property\ntypes and with DI integration potentially eliminate DI calls and replace them with direct constructor calls in some cases.\n\nGiven the `WeatherForecastQuery` class above Blueprint will generate a class similar to the one below that will\nexecute the query when the `/forecast?city=London` URL is hit.\n\n````c#\npublic class WeatherForecastQueryExecutorPipeline : Blueprint.Api.IOperationExecutorPipeline\n{\n    private readonly Microsoft.Extensions.Logging.ILogger _logger;\n    private readonly Blueprint.Sample.WebApi.Data.IWeatherDataSource _weatherDataSource;\n    private readonly Blueprint.Api.IApiLinkGenerator _apiLinkGenerator;\n    private readonly Blueprint.Core.Errors.IErrorLogger _errorLogger;\n\n    public WeatherForecastQueryExecutorPipeline(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Blueprint.Sample.WebApi.Data.IWeatherDataSource weatherDataSource, Blueprint.Api.IApiLinkGenerator apiLinkGenerator, Blueprint.Core.Errors.IErrorLogger errorLogger)\n    {\n        _logger = loggerFactory.CreateLogger(\"Blueprint.Sample.WebApi.Api.WeatherForecastQueryExecutorPipeline\");\n        _weatherDataSource = weatherDataSource;\n        _apiLinkGenerator = apiLinkGenerator;\n        _errorLogger = errorLogger;\n    }\n\n    public async System.Threading.Tasks.Task\u003cBlueprint.Api.OperationResult\u003e ExecuteAsync(Blueprint.Api.ApiOperationContext context)\n    {\n        var stopwatch = System.Diagnostics.Stopwatch.StartNew();\n        var weatherForecastQuery = (Blueprint.Sample.WebApi.Api.WeatherForecastQuery) context.Operation;\n        var httpContext = Blueprint.Api.Http.ApiOperationContextHttpExtensions.GetHttpContext(context);\n        var requestTelemetry = httpContext.Features.Get\u003cMicrosoft.ApplicationInsights.DataContracts.RequestTelemetry\u003e();\n\n        try\n        {\n            // ApplicationInsightsMiddleware\n            if (requestTelemetry != null)\n            {\n                requestTelemetry.Name = \"WeatherForecastInline\";\n            }\n\n            // MessagePopulationMiddlewareBuilder\n             var fromCookieMyCookie = httpContext.Request.Cookies[\"MyCookie\"];\n            weatherForecastQuery.MyCookie = fromCookieMyCookie != null ? fromCookieMyCookie.ToString() : weatherForecastQuery.MyCookie;\n\n            var fromCookieMyCookieNumber = httpContext.Request.Cookies[\"a-different-cookie-name\"];\n            weatherForecastQuery.MyCookieNumber = fromCookieMyCookieNumber != null ? int.Parse(fromCookieMyCookieNumber.ToString()) : weatherForecastQuery.MyCookieNumber;\n\n            var fromHeaderMyHeader = httpContext.Request.Headers[\"X-Header-Key\"];\n            weatherForecastQuery.MyHeader = fromHeaderMyHeader != Microsoft.Extensions.Primitives.StringValues.Empty ? fromHeaderMyHeader.ToString() : weatherForecastQuery.MyHeader;\n\n            var fromQueryCity = httpContext.Request.Query[\"City\"];\n            weatherForecastQuery.City = fromQueryCity != Microsoft.Extensions.Primitives.StringValues.Empty ? fromQueryCity.ToString() : weatherForecastQuery.City;\n\n            var fromQueryDays = httpContext.Request.Query[\"Days\"] == Microsoft.Extensions.Primitives.StringValues.Empty ? httpContext.Request.Query[\"Days[]\"] : httpContext.Request.Query[\"Days\"];\n            weatherForecastQuery.Days = fromQueryDays != Microsoft.Extensions.Primitives.StringValues.Empty ? (System.String[]) Blueprint.Api.Http.MessagePopulation.HttpPartMessagePopulationSource.ConvertValue(\"Days\", fromQueryDays, typeof(System.String[])) : weatherForecastQuery.Days;\n\n            // ValidationMiddlewareBuilder\n            var validationFailures = new Blueprint.Api.Validation.ValidationFailures();\n            var validationContext = new System.ComponentModel.DataAnnotations.ValidationContext(weatherForecastQuery);\n            validationContext.MemberName = \"City\";\n            validationContext.DisplayName = \"City\";\n\n            // context.Descriptor.Properties[0] == WeatherForecastQuery.City\n            foreach (var attribute in context.Descriptor.PropertyAttributes[0])\n            {\n                if (attribute is System.ComponentModel.DataAnnotations.ValidationAttribute x)\n                {\n                    var result =  x.GetValidationResult(weatherForecastQuery.City, validationContext);\n                    if (result != System.ComponentModel.DataAnnotations.ValidationResult.Success)\n                    {\n                        validationFailures.AddFailure(result);\n                    }\n                }\n            }\n\n            if (validationFailures.Count \u003e 0)\n            {\n                var validationFailedOperationResult = new Blueprint.Api.Middleware.ValidationFailedOperationResult(validationFailures);\n                return validationFailedOperationResult;\n            }\n\n            // OperationExecutorMiddlewareBuilder\n            _logger.Log(Microsoft.Extensions.Logging.LogLevel.Debug, \"Executing API operation. handler_type=WeatherForecastQuery\");\n            var handlerResult = weatherForecastQuery.Invoke(_weatherDataSource);\n            Blueprint.Api.OperationResult operationResult = handlerResult;\n\n            // LinkGeneratorMiddlewareBuilder\n            var resourceLinkGeneratorIEnumerable = context.ServiceProvider.GetRequiredService\u003cSystem.Collections.Generic.IEnumerable\u003cBlueprint.Api.IResourceLinkGenerator\u003e\u003e();\n            await Blueprint.Api.Middleware.LinkGeneratorHandler.AddLinksAsync(_apiLinkGenerator, resourceLinkGeneratorIEnumerable, context, operationResult);\n\n            // BackgroundTaskRunnerMiddleware\n            var backgroundTaskScheduler = context.ServiceProvider.GetRequiredService\u003cBlueprint.Tasks.IBackgroundTaskScheduler\u003e();\n            await backgroundTaskScheduler.RunNowAsync();\n\n            // ReturnFrameMiddlewareBuilder\n            return operationResult;\n        }\n        catch (Blueprint.Api.Validation.ValidationException e)\n        {\n            var validationFailedOperationResult = new Blueprint.Api.Middleware.ValidationFailedOperationResult(e.ValidationResults);\n            return validationFailedOperationResult;\n        }\n        catch (System.ComponentModel.DataAnnotations.ValidationException e)\n        {\n            var validationFailedOperationResult = Blueprint.Api.Middleware.ValidationMiddlewareBuilder.ToValidationFailedOperationResult(e);\n            return validationFailedOperationResult;\n        }\n        catch (System.Exception e)\n        {\n            var userAuthorisationContext = context.UserAuthorisationContext;\n            var identifier = new Blueprint.Core.Authorisation.UserExceptionIdentifier(userAuthorisationContext);\n\n            userAuthorisationContext?.PopulateMetadata((k, v) =\u003e e.Data[k] = v?.ToString());\n            e.Data[\"WeatherForecastQuery.City\"] = weatherForecastQuery.City?.ToString();\n            e.Data[\"WeatherForecastQuery.MyHeader\"] = weatherForecastQuery.MyHeader?.ToString();\n            e.Data[\"WeatherForecastQuery.MyCookie\"] = weatherForecastQuery.MyCookie?.ToString();\n            e.Data[\"WeatherForecastQuery.MyCookieNumber\"] = weatherForecastQuery.MyCookieNumber.ToString();\n\n            _errorLogger.Log(e, identifier);\n\n            if (requestTelemetry != null)\n            {\n                requestTelemetry.Success = false;\n            }\n\n            return new Blueprint.Api.UnhandledExceptionOperationResult(e);\n        }\n        finally\n        {\n            var userContext = context.UserAuthorisationContext;\n            if (requestTelemetry != null)\n            {\n                if (userContext != null \u0026\u0026 userContext.IsAnonymous == false)\n                {\n                    requestTelemetry.Context.User.AuthenticatedUserId = userContext.Id;\n                    requestTelemetry.Context.User.AccountId = userContext.AccountId;\n                }\n\n                requestTelemetry.Success = requestTelemetry.Success ?? true;\n            }\n\n            stopwatch.Stop();\n            _logger.Log(Microsoft.Extensions.Logging.LogLevel.Information, \"Operation {0} finished in {1}ms\", \"Blueprint.Sample.WebApi.Api.WeatherForecastQuery\", stopwatch.Elapsed.TotalMilliseconds);\n        }\n    }\n}\n````\n\n### Built With\n\n * [Roslyn](https://github.com/dotnet/roslyn) - For runtime code compilation\n * [Newtonsoft.Json](https://www.newtonsoft.com/json) - For JSON handling\n\n## Installation\n\nTo use Blueprint API in your ASP.NET app:\n\n 1.  Add Blueprint.Api to your project\n    ```sh\n    dotnet add package Blueprint.Api\n    dotnet add package Blueprint.Api.Http \n    ```\n 2. Add Blueprint.Api to `Startup.ConfigureServices`\n    ```c#\n            public void ConfigureServices(IServiceCollection services)\n            {\n                services.AddApplicationInsightsTelemetry();\n    \n                services.AddBlueprintApi(b =\u003e b\n                    .SetApplicationName(\"SampleWebApi\")\n                    .Operations(o =\u003e o.ScanForOperations(typeof(Startup).Assembly))\n                    .AddHttp()\n                    .AddApplicationInsights()\n                    .Pipeline(m =\u003e m\n                        .AddLogging()\n                        .AddValidation()\n                        .AddHateoasLinks()\n                    ));\n            }\n    ```\n3. Add Blueprint.Api to `Startup.Configure`\n    ```c#\n            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)\n            {\n                app.UseForwardedHeaders();\n    \n                if (env.IsDevelopment())\n                {\n                    app.UseDeveloperExceptionPage();\n                }\n                else\n                {\n                    app.UseExceptionHandler();\n                }\n    \n                app.UseBlueprintApi(\"api/\");\n            }\n    ```\n   \n### Compilation\n\nAt runtime Blueprint will, for every operation it finds, generate a class that is used for running an operation with\nall the configured middlewares.\n\nThe pipeline is fully customisable with many built-in middlewares such as APM integration, logging, auditing, link\ngeneration (HATEOAS) and validation. \n\n## Roadmap\n\nSee the [open issues](https://github.com/barclayadam/blueprint/issues) for a list of proposed features (and known issues).\n\n## Contributing\n\nContributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.\n\n1. Fork the Project\n2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)\n3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)\n4. Push to the Branch (`git push origin feature/AmazingFeature`)\n5. Open a Pull Request\n\n## License\n\nDistributed under the Apache 2.0 License. See `LICENSE.md` for more information.\n\n## Contact\n\nAdam Barclay - [@barclayadam](https://twitter.com/barclayadam)\n\n## Acknowledgements\n\n * [LamarCompiler/LamarCodeGeneration](https://github.com/JasperFx/lamar/tree/master/src/LamarCodeGeneration) Blueprint.Compiler is a modified version of LamarCompiler that\n was present in Lamar before being changed to using Expressions for compilation\n\n\u003c!-- MARKDOWN LINKS \u0026 IMAGES --\u003e\n[build-shield]: https://img.shields.io/github/actions/workflow/status/barclayadam/blueprint/blueprint-build.yml?style=flat\n[build-url]: https://github.com/barclayadam/blueprint/actions\n[coverage-shield]: https://img.shields.io/sonar/coverage/barclayadam_blueprint?server=https%3A%2F%2Fsonarcloud.io\n[coverage-url]: https://sonarcloud.io/component_measures?id=barclayadam_blueprint\u0026metric=Coverage\n[quality-shield]: https://sonarcloud.io/api/project_badges/measure?project=barclayadam_blueprint\u0026metric=alert_status\n[quality-url]: https://sonarcloud.io/dashboard?id=barclayadam_blueprint\n[contributors-shield]: https://img.shields.io/github/contributors/barclayadam/blueprint.svg?style=flat\n[contributors-url]: https://github.com/barclayadam/blueprint/graphs/contributors\n[stars-shield]: https://img.shields.io/github/stars/barclayadam/blueprint.svg?style=flat\n[stars-url]: https://github.com/barclayadam/blueprint/stargazers\n[issues-shield]: https://img.shields.io/github/issues/barclayadam/blueprint.svg?style=flat\n[issues-url]: https://github.com/barclayadam/blueprint/issues\n[license-shield]: https://img.shields.io/github/license/barclayadam/blueprint.svg?style=flat\n[license-url]: https://github.com/barclayadam/blueprint/blob/master/LICENSE.md\n[gitpod-url]: https://gitpod.io/#https://github.com/barclayadam/blueprint\n[gitpod-shield]: https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod?style=flat\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbarclayadam%2Fblueprint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbarclayadam%2Fblueprint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbarclayadam%2Fblueprint/lists"}