diff --git a/.github/workflows/run-markdownsnippets.yml b/.github/workflows/run-markdownsnippets.yml index 9a070dea..aeb3cd6f 100644 --- a/.github/workflows/run-markdownsnippets.yml +++ b/.github/workflows/run-markdownsnippets.yml @@ -9,7 +9,7 @@ jobs: - name: Run MarkdownSnippets run: | dotnet tool install --global MarkdownSnippets.Tool - mdsnippets ${GITHUB_WORKSPACE} + mdsnippets ${GITHUB_WORKSPACE} -c InPlaceOverwrite git add . shell: bash - name: Fix links diff --git a/README.md b/README.md index bda698da..a47a69fb 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,3 @@ - - # ServiceComposer diff --git a/README.source.md b/README.source.md deleted file mode 100644 index e788126f..00000000 --- a/README.source.md +++ /dev/null @@ -1,77 +0,0 @@ - - -# ServiceComposer - -ServiceComposer is a ViewModel Composition Gateway. - -Designing a UI when the back-end system consists of dozens (or more) of (micro)services is challenging. We have separation and autonomy on the back end, but this all needs to come back together on the front-end. ViewModel Composition stops it from turning into a mess of spaghetti code and prevents simple actions from causing an inefficient torrent of web requests. - -toc - -## Technical introduction - -For a technical introduction and an overview of the problem space, refer to the following presentation available on [YouTube](https://www.youtube.com/watch?v=AxWGAiIg7_0). - -## Getting Started - -Imagine an elementary e-commerce web page, where it's needed to display details about a selected product. These details are stored in two different services. The Sales service owns the product price, and the Marketing service owns the product name and description. ServiceComposer solves the problem of composing information coming from different services into one composed view model that can be later displayed or consumed by downstream clients. - -To start using ServiceComposer, follow the outlined steps: - -- Create, in an empty or existing solution, a .NET Core 3.x or later empty web application project named `CompositionGateway`. -- Add a package reference to the `ServiceComposer.AspNetCore` NuGet package and configure the `Startup` class as follows: - -snippet: net-core-3x-sample-startup - -> NOTE: To use a `Startup` class, Generic Host support is required. - -- Add a new .NET Core 3.x or later class library project, named `Sales.ViewModelComposition`. -- Add a package reference to the `ServiceComposer.AspNetCore` NuGet package. -- Add a new class to create a composition request handler. -- Define the class similar to the following: - -snippet: net-core-3x-basic-usage-sales-handler - -- Add another class library project, named `Marketing.ViewModelComposition`, and define a composition request handler like the following: - -snippet: net-core-3x-basic-usage-marketing-handler - -- Make so that the web application project created at the beginning can load both class library assemblies, e.g., by adding a reference to the class library projects -- Build and run the web application project -- Using a browser or a tool like Postman, issue an HTTP Get request to `/product/1` - -The HTTP response should be a JSON result containing the properties and values defined in the composition handler classes. - -> NOTE: ServiceComposer uses regular ASP.NET Core attribute routing to configure routes for which composition support is required. - -In this brief sample the view model instance returned by `GetComposedResponseModel()` is a C# `dynamic` object. `dynamic` objects are handy because they allow requests handlers to be fully independent from each other, they share nothing. ServiceComposer supports using strongly typed view models in case they are preferred. They come with the advantage of strong typing and compilers checks, and the disadvantage of a little coupling. Refer to the [view model factory documentation](docs/view-model-factory) for more information. - -## Documentation and supported platforms - -ServiceComposer is available for the following platforms: - -- ASP.NET Core 3.x and .NET 5: [documentation is available in the docs folder](docs) -- ASP.NET Core 2.x: [documentation is available in the docs/asp-net-core-2x folder](docs/asp-net-core-2x) (.NET Standard 2.0 compatible) - -> Note: Support for ASP.NET Core 2.x has been deprecated in version 1.8.0 and will be removed in 2.0.0. - -## Philosophy - -### Service boundaries - -When building systems based on SOA principles, service boundaries are key, if not THE key aspect. If we get service boundaries wrong, the end result has the risk to be, in the best case, a distributed monolith, and in the worst one, a complete failure. - -> Service boundaries identification is a challenge on its own; it requires a lot of business domain knowledge and a lot of confidence with high-level design techniques. Other than that, technical challenges might drive the solution design in the wrong direction due to the lack of technical solutions to problems foreseen while defining service boundaries. - -The transition from the user mental model, described by domain experts, to the service boundaries architectural model in the SOA space raises many different concerns. If domain entities, as defined by domain experts, are split among several services: - -- how can we then display to users what they need to visualize? -- when systems need to make decisions, how can they “query” data required to make that decision, stored in many different services? - -This type of question leads systems to be designed using rich events, and not thin ones, to share data between services and at the same to share data with cache-like things, such as Elastic Search, to satisfy UI query/visualization needs. - -This is the beginning of a road that can only lead to a distributed monolith, where data ownership is a lost concept and every change impacts and breaks the whole system. In such a scenario, it’s very easy to blame SOA and the toolset. - -ViewModel Composition techniques are designed to address all these concerns. ViewModel Composition brings the separation of concerns, designed at the back-end, to the front-end. - -For more details and the philosophy behind a Composition Gateway, refer to the [ViewModel Composition series](https://milestone.topics.it/categories/view-model-composition) of article available on [milestone.topics.it](https://milestone.topics.it/). diff --git a/docs/README.md b/docs/README.md index 932f3d39..61f5c3b0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,10 +1,3 @@ - - # ASP.NET Core 3.x and .NET 5 Starting ASP.NET Core 3.x ServiceComposer leverages the new Endpoints support to plugin into the request handling pipeline. diff --git a/docs/README.source.md b/docs/README.source.md deleted file mode 100644 index d887a5b2..00000000 --- a/docs/README.source.md +++ /dev/null @@ -1,22 +0,0 @@ -# ASP.NET Core 3.x and .NET 5 - -Starting ASP.NET Core 3.x ServiceComposer leverages the new Endpoints support to plugin into the request handling pipeline. -ServiceComposer can be added to existing or new ASP.NET Core projects, or it can be hosted in .NET Core console applications. - -## ASP.Net Model Binding - -When handling composition requests it possible to leverage the power of ASP.Net Model Binding to bind incoming forms, bodies, query string parameters, or route data to strongly typed C# models. For more information on model binding refer to the [Model Binding](model-binding.source.md) section. - -### ASP.Net MVC Action results - -MVC Action results support allow composition handlers to set custom response results for specific scenarios, like for example, handling bad requests or validation error thoat would nornmally require throwing an exception. For more information on action results refer to the [MVC Action results](action-results.source.md) section. - -## Authentication and Authorization - -By virtue of leveraging ASP.NET Core 3.x Endpoints ServiceComposer automatically supports authentication and authorization metadata attributes to express authentication and authorization requirements on routes. For more information refer to the [Authentication and Authorization](authentication-authorization.source.md) section - -## Serialization - -By default ServiceComposer serializes responses using the Newtonsoft JSON serializer. The built-in serialization support can be configured to seriazlie responses using a camel case or pascal case approach on a per request basis by adding to the request an `Accept-Casing` custom HTTP header. For more information refer to the [response serialization casing](response-serialization-casing.source.md) section. Or it's possible to take full control over the [response serialization settings on a case-by-case](custom-json-response-serialization-settings.source.md) by suppliying at configuration time a customization function. - -Starting with version 1.9.0, regular MVC Output Formatters can be used to serialize the response model, and honor the `Accept` HTTP header set by clients. When using output formatters the serialization casing is controlled by the formatter configuration and not by ServiceComposer. For more information on using output formatters refers to the [output formatters serialization section](output-formatters-serialization.source.md). diff --git a/docs/action-results.md b/docs/action-results.md index 92bd9d2c..ca015e49 100644 --- a/docs/action-results.md +++ b/docs/action-results.md @@ -1,10 +1,3 @@ - - # ASP.Net MVC Action results MVC Action results support allow composition handlers to set custom response results for specific scenarios, like for example, handling bad requests or validation error thoat would nornmally require throwing an exception. Setting a custom action result is done by using the `SetActionResult()` `HttpRequest` extension method: diff --git a/docs/action-results.source.md b/docs/action-results.source.md deleted file mode 100644 index 0d511e1e..00000000 --- a/docs/action-results.source.md +++ /dev/null @@ -1,11 +0,0 @@ -# ASP.Net MVC Action results - -MVC Action results support allow composition handlers to set custom response results for specific scenarios, like for example, handling bad requests or validation error thoat would nornmally require throwing an exception. Setting a custom action result is done by using the `SetActionResult()` `HttpRequest` extension method: - -snippet: net-core-3x-action-results - -Using MVC action results require enabling output formatters support: - -snippet: net-core-3x-action-results-required-config - -Note: ServiceComposer supports only one action result per request. If two or more composition handlers try to set action results, only the frst one will succeed and subsequent requests will be ignored. diff --git a/docs/asp-net-core-2x/README.md b/docs/asp-net-core-2x/README.md index d4a9f2f2..3261b9df 100644 --- a/docs/asp-net-core-2x/README.md +++ b/docs/asp-net-core-2x/README.md @@ -1,10 +1,3 @@ - - # ASP.NET Core 2.x _NOTE: As of v1.8.0 ASP.NET Core 2.x is legacy. It'll be removed in v2.0.0, consider moving to the new endpoint and attribute routing based approach available starting with ASP.NET Core 3.x._ diff --git a/docs/asp-net-core-2x/README.source.md b/docs/asp-net-core-2x/README.source.md deleted file mode 100644 index 1a09224f..00000000 --- a/docs/asp-net-core-2x/README.source.md +++ /dev/null @@ -1,36 +0,0 @@ -# ASP.NET Core 2.x - -_NOTE: As of v1.8.0 ASP.NET Core 2.x is legacy. It'll be removed in v2.0.0, consider moving to the new endpoint and attribute routing based approach available starting with ASP.NET Core 3.x._ - -Create a new .NET Core console project and add a reference to the following NuGet packages: - -* `Microsoft.AspNetCore` -* `Microsoft.AspNetCore.Routing` -* `ServiceComposer.AspNetCore` - -Configure the `Startup` class like follows: - -snippet: net-core-2x-sample-startup - -> Note: define routes so to match your project needs. `ServiceComposer` adds provides all the required `MapComposable*` `IRouteBuilder` extension methods to map routes for every HTTP supported Verb. - -Define one or more classes implementing either the `IHandleRequests` or the `ISubscribeToCompositionEvents` based on your needs. - -> Make sure the assemblies containing requests handlers and events subscribers are available to the composition gateway. By adding a reference or by simply dropping assemblies in the `bin` directory. - -More details on how to implement `IHandleRequests` and `ISubscribeToCompositionEvents` are available in the following articles: - -* [ViewModel Composition: show me the code!](https://milestone.topics.it/view-model-composition/2019/03/06/viewmodel-composition-show-me-the-code.html) -* [The ViewModels Lists Composition Dance](https://milestone.topics.it/view-model-composition/2019/03/21/the-viewmodels-lists-composition-dance.html) - -## Custom HTTP status codes in ASP.NET Core 2.x - -The response status code can be set in requests handlers and it'll be honored by the composition pipeline. To set a custom response status code the following snippet can be used: - -snippet: net-core-2x-sample-handler-with-custom-status-code - -NOTE: Requests handlers are executed in parallel in a non-deterministic way, setting the response code in more than one handler can have unpredictable effects. - -## MVC and Web API support - -For information on how to host the Composition Gateway in a ASP.Net Core MVC application, please, refer to the [`ServiceComposer.AspNetCore.Mvc` package](https://github.com/ServiceComposer/ServiceComposer.AspNetCore.Mvc). diff --git a/docs/authentication-authorization.md b/docs/authentication-authorization.md index cba2e7d8..9552650d 100644 --- a/docs/authentication-authorization.md +++ b/docs/authentication-authorization.md @@ -1,10 +1,3 @@ - - # Authentication and Authorization By virtue of leveraging ASP.NET Core 3.x Endpoints ServiceComposer automatically supports authentication and authorization metadata attributes to express authentication and authorization requirements on routes. For example, it's possible to use the `Authorize` attribute to specify that a handler requires authorization. The authorization process is the regular ASP.NET Core 3.x process and no special configuration is needed to plugin ServiceComposer: diff --git a/docs/authentication-authorization.source.md b/docs/authentication-authorization.source.md deleted file mode 100644 index eeea7212..00000000 --- a/docs/authentication-authorization.source.md +++ /dev/null @@ -1,5 +0,0 @@ -# Authentication and Authorization - -By virtue of leveraging ASP.NET Core 3.x Endpoints ServiceComposer automatically supports authentication and authorization metadata attributes to express authentication and authorization requirements on routes. For example, it's possible to use the `Authorize` attribute to specify that a handler requires authorization. The authorization process is the regular ASP.NET Core 3.x process and no special configuration is needed to plugin ServiceComposer: - -snippet: net-core-3x-sample-handler-with-authorization diff --git a/docs/composition-over-controllers.md b/docs/composition-over-controllers.md index b9e23e85..1fbc6492 100644 --- a/docs/composition-over-controllers.md +++ b/docs/composition-over-controllers.md @@ -1,10 +1,3 @@ - - # Composition over controllers ServiceComposer can be used to enhance a MVC web application by adding compostion support to Controllers. ServiceComposer can be configured to use a technique called "Composition over controllers": diff --git a/docs/composition-over-controllers.source.md b/docs/composition-over-controllers.source.md deleted file mode 100644 index 2f11c90f..00000000 --- a/docs/composition-over-controllers.source.md +++ /dev/null @@ -1,9 +0,0 @@ -# Composition over controllers - -ServiceComposer can be used to enhance a MVC web application by adding compostion support to Controllers. ServiceComposer can be configured to use a technique called "Composition over controllers": - -snippet: net-core-3x-enable-composition-over-controllers - -Once composition over controllers is enabled, ServiceComposer will inject a MVC filter to intercept all controllers invocations. If a route matches a regular controller and a set of composition handlers ServiceComposer will invoke the matching handlers after the controller and before the view is rendered. - -Composition over controllers can be used as a templating engine leveraging the excellent Razor engine. Optionally, it can be used to add ViewModel Composition support to MVC web application without introducing a separate composition gateway. diff --git a/docs/custom-http-status-codes.md b/docs/custom-http-status-codes.md index 5e33dbc4..ed6017e2 100644 --- a/docs/custom-http-status-codes.md +++ b/docs/custom-http-status-codes.md @@ -1,10 +1,3 @@ - - # Custom HTTP status codes in ASP.NET Core 3.x The response status code can be set in requests handlers and it'll be honored by the composition pipeline. To set a custom response status code the following snippet can be used: diff --git a/docs/custom-http-status-codes.source.md b/docs/custom-http-status-codes.source.md deleted file mode 100644 index 96bca960..00000000 --- a/docs/custom-http-status-codes.source.md +++ /dev/null @@ -1,7 +0,0 @@ -# Custom HTTP status codes in ASP.NET Core 3.x - -The response status code can be set in requests handlers and it'll be honored by the composition pipeline. To set a custom response status code the following snippet can be used: - -snippet: net-core-3x-sample-handler-with-custom-status-code - -NOTE: Requests handlers are executed in parallel in a non-deterministic way, setting the response code in more than one handler can have unpredictable effects. diff --git a/docs/custom-json-response-serialization-settings.md b/docs/custom-json-response-serialization-settings.md index e2cbeae2..e9050260 100644 --- a/docs/custom-json-response-serialization-settings.md +++ b/docs/custom-json-response-serialization-settings.md @@ -1,15 +1,8 @@ - - # Custom JSON response serialization settings _Available starting with v1.8.0_ -By default each response is serialized using [Json.Net](https://www.newtonsoft.com/json/help/html/Introduction.htm) and serialization settings (`JsonSerializerSettings`) are determined by the [requested response casing](response-serialization-casing.md). If the requested casing is camel casing, the default, the folowing serialization settings are applied to the response: +By default each response is serialized using [Json.Net](https://www.newtonsoft.com/json/help/html/Introduction.htm) and serialization settings (`JsonSerializerSettings`) are determined by the [requested response casing](response-serialization-casing.source.md). If the requested casing is camel casing, the default, the folowing serialization settings are applied to the response: diff --git a/docs/custom-json-response-serialization-settings.source.md b/docs/custom-json-response-serialization-settings.source.md deleted file mode 100644 index eedb0206..00000000 --- a/docs/custom-json-response-serialization-settings.source.md +++ /dev/null @@ -1,20 +0,0 @@ -# Custom JSON response serialization settings - -_Available starting with v1.8.0_ - -By default each response is serialized using [Json.Net](https://www.newtonsoft.com/json/help/html/Introduction.htm) and serialization settings (`JsonSerializerSettings`) are determined by the [requested response casing](response-serialization-casing.source.md). If the requested casing is camel casing, the default, the folowing serialization settings are applied to the response: - -snippet: net-core-3x-camel-serialization-settings - -If the requested case is pascal, the following settings are applied: - -snippet: net-core-3x-pascal-serialization-settings - -It's possible to customize the response serialization settings on a case-by-case using the following configuration: - -snippet: net-core-3x-custom-serialization-settings - -Each time ServiceComposer needs to serialize a response it'll invoke the supplied function. - -NOTE: -When customizing the serialization settings, it's responsibility of the function to configure the correct resolver for the requested casing diff --git a/docs/model-binding.md b/docs/model-binding.md index 1fd32b17..ee22264c 100644 --- a/docs/model-binding.md +++ b/docs/model-binding.md @@ -1,10 +1,3 @@ - - # Model Binding _Available starting with v1.9.0_ diff --git a/docs/model-binding.source.md b/docs/model-binding.source.md deleted file mode 100644 index 21b00d13..00000000 --- a/docs/model-binding.source.md +++ /dev/null @@ -1,47 +0,0 @@ -# Model Binding - -_Available starting with v1.9.0_ - -Composition handlers get access to the incoming `HttpRequest`. If the incoming request contains a body, the handler can access a body like the following: - -```json -{ - "AString": "Some value" -} -``` - -by using the following code: - -snippet: net-core-3x-model-binding-raw-body-usage - -Something similar applies to getting data from the route, or from the query string, or the incoming form. For example: - -snippet: net-core-3x-model-binding-raw-route-data-usage - -## Use model binding - -It's possible to leverage ASP.Net built-in support for model binding to avoid accessing raw values of the incoming `HttpRequest`, such as the body. - -To start using model binding configure the ASP.Net application to add MVC components: - -snippet: net-core-3x-model-binding-add-controllers - -> If, for reasons outside the scope of composing responses, there is the need to use MVC, or Razor Pages, or just controllers and views, any of the MVC configuration options will add support for model binding. - -THe first step is to define the models, for example in the above sample the body model will look like the following C# class: - -snippet: net-core-3x-model-binding-model - -> The class name is irrelevant - -Once we have a model for the body, a model that represent the incoming request is needed. In in a scenario in which there is the need to bind the body and the id from the route, the following request model can be used: - -snippet: net-core-3x-model-binding-request - -> The class name is irrelevant. The name of the properties marked as `[FromRoute]` or `[FromQueryString]` must match the route data names or query string keys names. The name for the body, or form, property is irrelevant. - -Once the models are defined they can be used as follows: - -snippet: net-core-3x-model-binding-bind-body-and-route-data - -For more information and options when using model binding refer to the [Microsoft official documentation](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-5.0). diff --git a/docs/output-formatters-serialization.md b/docs/output-formatters-serialization.md index 1df2b01f..79a64892 100644 --- a/docs/output-formatters-serialization.md +++ b/docs/output-formatters-serialization.md @@ -1,10 +1,3 @@ - - # Output formatters _Available starting with v1.9.0_ diff --git a/docs/output-formatters-serialization.source.md b/docs/output-formatters-serialization.source.md deleted file mode 100644 index 8931bd6d..00000000 --- a/docs/output-formatters-serialization.source.md +++ /dev/null @@ -1,20 +0,0 @@ -# Output formatters - -_Available starting with v1.9.0_ - -Enabling output formatters support is a metter of: - -snippet: net-core-3x-use-output-formatters - -The required steps are: - -- set `UseOutputFormatters` to `true` -- Set up the MVC components by calling one of the following: - - `AddControllers()` - - `AddControllersAndViews()` - - `AddMvc` - - `AddRazorPages()` - -The above configuration uses the new `System.Text.Json` serializer as the default json serializer to format json responses. The `System.Text.Json` serializer does not support serializing `dynamic` objects. It's possible to plug-in the Newtonsoft Json.Net serializer as output formatter by adding a package reference to the `Microsoft.AspNetCore.Mvc.NewtonsoftJson` package, and using the following configuration: - -snippet: net-core-3x-use-newtonsoft-output-formatters diff --git a/docs/response-serialization-casing.md b/docs/response-serialization-casing.md index 89603633..fe71458e 100644 --- a/docs/response-serialization-casing.md +++ b/docs/response-serialization-casing.md @@ -1,10 +1,3 @@ - - # Response serialization casing ServiceComposer serializes responses using a JSON serializer. By default responses are serialized using camel casing, a C# `SampleProperty` property is serialized as `sampleProperty`. Consumers can influence the response casing of a specific request by adding to the request an `Accept-Casing` custom HTTP header. Accepted values are `casing/camel` (default) and `casing/pascal`. diff --git a/docs/response-serialization-casing.source.md b/docs/response-serialization-casing.source.md deleted file mode 100644 index 339a3fb3..00000000 --- a/docs/response-serialization-casing.source.md +++ /dev/null @@ -1,13 +0,0 @@ -# Response serialization casing - -ServiceComposer serializes responses using a JSON serializer. By default responses are serialized using camel casing, a C# `SampleProperty` property is serialized as `sampleProperty`. Consumers can influence the response casing of a specific request by adding to the request an `Accept-Casing` custom HTTP header. Accepted values are `casing/camel` (default) and `casing/pascal`. - -## Default response serialization casing - -_Available starting with v1.8.0_ - -Default response serialization is camel casing. It's possible to configure a different default response serialization casing when configuring ServiceComposer: - -snippet: net-core-3x-default-casing - -Requests containing the `Accept-Casing` custom HTTP header will still be honored. diff --git a/docs/view-model-factory.md b/docs/view-model-factory.md index 32e2a771..56c3161a 100644 --- a/docs/view-model-factory.md +++ b/docs/view-model-factory.md @@ -1,10 +1,3 @@ - - # Strongly typed view models _Available starting with v1.8.0_ diff --git a/docs/view-model-factory.source.md b/docs/view-model-factory.source.md deleted file mode 100644 index 2c5753a1..00000000 --- a/docs/view-model-factory.source.md +++ /dev/null @@ -1,37 +0,0 @@ -# Strongly typed view models - -_Available starting with v1.8.0_ - -By default ServiceComposer uses C# `dynamic` object instances to serve view models to requests handler, when the `SalesProductInfo` handler, used in the getting started sample, requires the view model, the call to `GetComposedResponseModel()` returns a `dynamic` instance. - -The first step to use strongly typed view models is to define the view model class: - -snippet: view-model-factory-product-view-model - -View models have no requirements other than being serializable. They are POC objects. - -## Endpoint scoped view model factories - -The second step is to define a factory for the view model. Create a class the implements the `IEndpointScopedViewModelFactory` interface: - -snippet: view-model-factory-product-view-model-factory - -View model factories are scoped to endpoints, they need to be identified when handling routes and thus they need to be decorated with a routing attribute and a route template. Each rout that needs a strongly typed view model needs a different factory. - -Once the factory is defined it's automatically registered in the DI container at startup and will be used by ServiceComposer when handling the specified route. - -To use the strongly typed view model convert requests handlers to use the generic `GetComposedResponseModel()` method, where the `T` parameter is the view model of choice: - -snippet: view-model-factory-sales-handler - -## Global view model factories - -As already mentioned the default ServiceComposer behavior if no endpoint scoped factories are registered is to create a `dynamic` object instance. It's possible to replace this behavior by defining a class the implements the `IViewModelFactory` interface. If an `IViewModelFactory` type is defined it'll be used to create all view models not handled by endpoint scoped factories. - -## Resolution order - -View model factories are resolved, and used, in the following order: - -1. `IEndpointScopedViewModelFactory`: If an endpoint scoped factory is defined for the current route it'll be used to create a view model instance -2. `IViewModelFactory`: If no endpoint scoped factory exist bound to the current route and a global view model factory is registered it'll be used -3. If no global view model factory exist the default ServiceComposer factory creates a `dynamic` view model instance to use to serve the current request