ServiceComposer.AspNetCore

Contract-less composition requests handlers

Available starting version 4.2.0-beta.1

Contract-less composition handlers allow writing composition handlers using a syntax like the following:

[CompositionHandler]
class SampleCompositionHandler
{
    [HttpGet("/sample/{id}")]
    public Task SampleMethod(int id, [FromQuery(Name = "c")]string aValue, [FromBody]ComplexType ct)
    {
        return Task.CompletedTask;
    }
}

snippet source | anchor

Compared to what is available today, when using classes that implement ICompositionRequestsHandler, contract-less composition request handlers allow grouping handlers belonging to the same logical context, rather than being forced to artificially split them into multiple classes due to the need to implement an interface.

The syntax is nonetheless similar to ASP.NET controller actions. At compilation time, ServiceComposer identifies contract-less composition request handlers by the presence of the [CompositionHandler] attribute on the class:

Known limitations

Source generation

Given a class like the above one, a C# source generator will create a class like the following:

// <auto-generated/>
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using ServiceComposer.AspNetCore;
using System;
using System.ComponentModel;
using System.Threading.Tasks;

#pragma warning disable SC0001
namespace YourNamespace.Generated
{
    [EditorBrowsable(EditorBrowsableState.Never)]
    class SampleCompositionHandler_SampleMethod_int_id_string_aValue_ComplexType_ct(SampleCompositionHandler userHandler)
         : ICompositionRequestsHandler
    {
        [HttpGetAttribute("/sample/{id}")]
        [BindFromRoute<Int32>("id")]
        [BindFromQuery<String>("c")]
        [BindFromBody<ComplexType>()]
        public Task Handle(HttpRequest request)
        {
            var ctx = HttpRequestExtensions.GetCompositionContext(request);
            var arguments = ctx.GetArguments(this);
            var p0_id = ModelBindingArgumentExtensions.Argument<Int32>(arguments, "id", BindingSource.Path);
            var p1_c = ModelBindingArgumentExtensions.Argument<String>(arguments, "c", BindingSource.Query);
            var p2_ct = ModelBindingArgumentExtensions.Argument<ComplexType>(arguments, BindingSource.Body);

            return userHandler.SampleMethod(p0_id, p1_c, p2_ct);
        }
    }
}
#pragma warning restore SC0001

Both classes will be registered in DI, allowing the injection of dependencies into the user contract-less composition handler. If the user contract-less composition handler defines more than one method matching the above requirements, one class for each method will be generated. The generated class name will guarantee uniqueness.

Services injection via [FromServices]

Parameters decorated with [FromServices] are resolved from the DI container at request time. For example:

[CompositionHandler]
class SampleCompositionHandlerWithServices
{
    [HttpGet("/sample/{id}")]
    public Task SampleMethod(int id, [FromServices] IMyService myService)
    {
        return Task.CompletedTask;
    }
}

snippet source | anchor

The source generator will emit a [BindFromServices<T>] attribute for the parameter and resolve it via the DI container when the request is handled:

// <auto-generated/>
#pragma warning disable SC0001
[EditorBrowsable(EditorBrowsableState.Never)]
class SampleCompositionHandlerWithServices_SampleMethod_int_id_IMyService_myService(
    SampleCompositionHandlerWithServices userHandler) : ICompositionRequestsHandler
{
    [HttpGetAttribute("/sample/{id}")]
    [BindFromRoute<int>("id")]
    [BindFromServices<IMyService>("myService")]
    public Task Handle(HttpRequest request)
    {
        var ctx = HttpRequestExtensions.GetCompositionContext(request);
        var arguments = ctx.GetArguments(this);
        var p0_id = ModelBindingArgumentExtensions.Argument<int>(arguments, "id", BindingSource.Path);
        var p1_myService = ModelBindingArgumentExtensions.Argument<IMyService>(arguments, "myService", BindingSource.Services);

        return userHandler.SampleMethod(p0_id, p1_myService);
    }
}
#pragma warning restore SC0001

snippet source | anchor