You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Framework/Connected.Services/Service.cs

157 lines
4.1 KiB

2 years ago
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using Connected.Middleware;
using Connected.Security.Authorization;
using Connected.ServiceModel;
using Connected.ServiceModel.Transactions;
using Connected.Services.Middleware;
using Connected.Validation;
namespace Connected.Services;
public abstract class Service : IService, IDisposable
{
protected Service(IContext context)
{
Context = context;
if (Context.GetService<IMiddlewareService>() is not IMiddlewareService middleware)
throw new NullReferenceException(nameof(IMiddlewareService));
Middleware = middleware;
}
protected IContext Context { get; }
private IMiddlewareService Middleware { get; }
protected bool IsDisposed { get; private set; }
protected async Task<TReturnValue> Invoke<TArgs, TReturnValue>(IFunction<TArgs, TReturnValue> function, TArgs args, [CallerMemberName] string? method = null)
where TArgs : IDto
{
var ctx = await Prepare(function, args, method);
var result = await function.Invoke(args);
var middleware = await Middleware.Query<IFunctionMiddleware<TArgs, TReturnValue>>(ctx);
if (!middleware.IsEmpty)
{
foreach (var m in middleware)
result = await m.Invoke(args, result);
}
return await Authorize(ctx, args, result);
}
protected async Task<TReturnValue?> Invoke<TArgs, TReturnValue>(INullableFunction<TArgs, TReturnValue> function, TArgs args, [CallerMemberName] string? method = null)
where TArgs : IDto
{
var ctx = await Prepare(function, args, method);
var result = await function.Invoke(args);
var middleware = await Middleware.Query<IFunctionMiddleware<TArgs, TReturnValue>>(ctx);
if (!middleware.IsEmpty)
{
foreach (var m in middleware)
result = await m.Invoke(args, result);
}
return await Authorize(ctx, args, result);
}
protected async Task Invoke<TArgs>(IAction<TArgs> action, TArgs args, [CallerMemberName] string? method = null)
where TArgs : IDto
{
var ctx = await Prepare(action, args, method);
await action.Invoke(args);
var middleware = await Middleware.Query<IActionMiddleware<TArgs>>(ctx);
if (!middleware.IsEmpty)
{
foreach (var m in middleware)
await m.Invoke(args);
}
}
private async Task<ICallerContext> Prepare<TArgs>(IServiceOperation<TArgs> operation, TArgs args, [CallerMemberName] string? method = null)
where TArgs : IDto
{
if (operation is ITransactionClient client && Context.GetService<ITransactionContext>() is ITransactionContext transaction)
transaction.Register(client);
var ctx = new CallerContext(this, method);
await ProvideArgumentValues(args);
Validate(ctx, args);
await Authorize(ctx, args);
return ctx;
}
public TOperation GetOperation<TOperation>([CallerMemberName] string? method = null)
{
return Context.GetService<TOperation>();
}
private void Validate<TArgs>(ICallerContext context, TArgs args)
where TArgs : IDto
{
if (Context.GetService<IValidationContext>() is not IValidationContext validationContext)
return;
validationContext.Validate(context, args);
}
private async Task Authorize<TArgs>(ICallerContext context, TArgs args)
where TArgs : IDto
{
if (Context.GetService<IAuthorizationContext>() is not IAuthorizationContext authorization)
return;
await authorization.Authorize(context, args);
}
private async Task<TResult> Authorize<TArgs, TResult>(ICallerContext context, TArgs args, TResult result)
where TArgs : IDto
{
/*
* TODO: call data protection middleware and authorize each entity in the result.
* Will need to implement iterator which resolves all entities in the result set.
*/
await Task.CompletedTask;
return result;
}
private async Task ProvideArgumentValues<TArgs>(TArgs args)
where TArgs : IDto
{
if (await Middleware.Query<IArgumentValueProvider<TArgs>>() is not ImmutableList<IArgumentValueProvider<TArgs>> middleware || middleware.IsEmpty)
return;
foreach (var m in middleware)
await m.Invoke(args);
}
private void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
OnDisposing();
IsDisposed = true;
}
}
protected virtual void OnDisposing()
{
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}