Skip to content

Instantly share code, notes, and snippets.

@dbones
Created February 9, 2024 08:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dbones/15f3f809d7c4acfd7fc095cd8b648e8f to your computer and use it in GitHub Desktop.
Save dbones/15f3f809d7c4acfd7fc095cd8b648e8f to your computer and use it in GitHub Desktop.
Mimicking a minimal Api
using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;
using Microsoft.Extensions.DependencyInjection;
public delegate Task MinimalCommand<T>(IServiceProvider scope, T instnace);
public class AppBuilder
{
readonly IServiceProvider _serviceProvider;
readonly Dictionary<Type, object> _commands = new();
public AppBuilder(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Map<T>(Delegate impl2)
{
var handler = CompileHandler<T>(impl2);
_commands.Add(typeof(T), handler);
}
static MinimalCommand<T> CompileHandler<T>(Delegate impl)
{
var scopeParameter = Expression.Parameter(typeof(IServiceProvider), "scope");
var instanceParameter = Expression.Parameter(typeof(T), "instance");
var methodInfo = impl.Method;
var targetExpression = impl.Target != null ?
Expression.Constant(impl.Target) : null;
var parameters = new List<Expression>();
foreach (var parameter in methodInfo.GetParameters())
{
if (parameter.ParameterType == typeof(T))
{
parameters.Add(instanceParameter);
}
else
{
var resolveParamExpr = Expression.Call(
typeof(ServiceProviderServiceExtensions), nameof(ServiceProviderServiceExtensions.GetRequiredService),
null, scopeParameter, Expression.Constant(parameter.ParameterType));
var resolveParam = Expression.Convert(resolveParamExpr, parameter.ParameterType);
parameters.Add(resolveParam);
}
}
Expression methodCall;
if (impl.Target != null)
{
methodCall = Expression.Call(
Expression.Constant(impl.Target),
methodInfo,
parameters);
}
else
{
methodCall = Expression.Call(
methodInfo,
parameters);
}
var methodTaskResultType = methodInfo.ReturnType;
if (methodTaskResultType != typeof(Task))
{
methodCall = Expression.Call(
typeof(Task),
nameof(Task.FromResult),
new[] { methodTaskResultType },
methodCall);
}
var body = Expression.Block(methodCall);
var lambda = Expression.Lambda<MinimalCommand<T>>(body, scopeParameter, instanceParameter);
return lambda.Compile();
}
public async Task Execute<T>(T payload)
{
if (!_commands.TryGetValue(typeof(T), out var commandBoxed))
{
throw new NotSupportedException($"no minimal Api for {typeof(T).FullName}");
}
var command = (MinimalCommand<T>)commandBoxed;
await command(_serviceProvider, payload);
}
}
//this is how we can inject all on all the params of a delegate....
var app = new AppBuilder(serviceProvider);
app.Map<Hello>(async (Hello message, ILogger<Program> logger) =>
{
logger.LogInformation("hi {Name}", message.Name);
return Task.CompletedTask;
});
app.Execute(new Hello() { Name = "Dave" });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment