Compare commits
26 Commits
dcc0e8c806
...
6f5409f528
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f5409f528 | |||
| 03498275d7 | |||
| f8581deaf9 | |||
| 8ea7d47868 | |||
| 2bbfd96d62 | |||
| 4f364e3eb2 | |||
| 0c4d145e20 | |||
| 1757c0e055 | |||
| b1df50bc32 | |||
| a66a70fab3 | |||
| a84b5531b7 | |||
| 21e3171e11 | |||
| bbfff226de | |||
| 7a4885c86a | |||
| a46d97467d | |||
| 2f3a685e7d | |||
| d1e8f619f5 | |||
| 0ec913b95e | |||
| cb851a4ec6 | |||
| cd8f757c43 | |||
| 8aa43db909 | |||
| 2a9b52ebe1 | |||
| 988d48581b | |||
| 46ef5e0d02 | |||
| 5e4f113145 | |||
| 3e9edcd8af |
@ -11,7 +11,7 @@ public class ActionController(IMediator mediator) : ControllerBase
|
|||||||
[HttpPost("{profileId}")]
|
[HttpPost("{profileId}")]
|
||||||
public async Task<IActionResult> Invoke([FromRoute] int profileId)
|
public async Task<IActionResult> Invoke([FromRoute] int profileId)
|
||||||
{
|
{
|
||||||
await mediator.InvokeRecAction(profileId);
|
await mediator.InvokeBatchRecAction(profileId);
|
||||||
return Accepted();
|
return Accepted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,10 +4,13 @@ using ReC.Application;
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
var config = builder.Configuration;
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRecServices(options =>
|
builder.Services.AddRecServices(options =>
|
||||||
{
|
{
|
||||||
options.LuckyPennySoftwareLicenseKey = builder.Configuration["LuckyPennySoftwareLicenseKey"];
|
options.LuckyPennySoftwareLicenseKey = builder.Configuration["LuckyPennySoftwareLicenseKey"];
|
||||||
|
options.ConfigureRecActions(config.GetSection("RecAction"));
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddRecInfrastructure(options =>
|
builder.Services.AddRecInfrastructure(options =>
|
||||||
|
|||||||
@ -6,5 +6,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"MediatRLicense": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzg0ODUxMjAwIiwiaWF0IjoiMTc1MzM2MjQ5MSIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazB5NmV3MmQ4YTk4Mzg3aDJnbTRuOWswIiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ZqsFG7kv_-xGfxS6ACk3i0iuNiVUXX2AvPI8iAcZ6-z2170lGv__aO32tWpQccD9LCv5931lBNLWSblKS0MT3gOt-5he2TEftwiSQGFwoIBgtOHWsNRMinUrg2trceSp3IhyS3UaMwnxZDrCvx4-0O-kpOzVpizeHUAZNr5U7oSCWO34bpKdae6grtM5e3f93Z1vs7BW_iPgItd-aLvPwApbaG9VhmBTKlQ7b4Jh64y7UXJ9mKP7Qb_Oa97oEg0oY5DPHOWTZWeE1EzORgVr2qkK2DELSHuZ_EIUhODojkClPNAKtvEl_qEjpq0HZCIvGwfCCRlKlSkQqIeZdFkiXg"
|
"MediatRLicense": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzg0ODUxMjAwIiwiaWF0IjoiMTc1MzM2MjQ5MSIsImFjY291bnRfaWQiOiIwMTk4M2M1OWU0YjM3MjhlYmZkMzEwM2MyYTQ4NmU4NSIsImN1c3RvbWVyX2lkIjoiY3RtXzAxazB5NmV3MmQ4YTk4Mzg3aDJnbTRuOWswIiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.ZqsFG7kv_-xGfxS6ACk3i0iuNiVUXX2AvPI8iAcZ6-z2170lGv__aO32tWpQccD9LCv5931lBNLWSblKS0MT3gOt-5he2TEftwiSQGFwoIBgtOHWsNRMinUrg2trceSp3IhyS3UaMwnxZDrCvx4-0O-kpOzVpizeHUAZNr5U7oSCWO34bpKdae6grtM5e3f93Z1vs7BW_iPgItd-aLvPwApbaG9VhmBTKlQ7b4Jh64y7UXJ9mKP7Qb_Oa97oEg0oY5DPHOWTZWeE1EzORgVr2qkK2DELSHuZ_EIUhODojkClPNAKtvEl_qEjpq0HZCIvGwfCCRlKlSkQqIeZdFkiXg",
|
||||||
|
"RecAction": {
|
||||||
|
"MaxConcurrentInvocations": 5
|
||||||
|
}
|
||||||
}
|
}
|
||||||
21
src/ReC.Application/Common/Behaviors/BodyQueryBehavior.cs
Normal file
21
src/ReC.Application/Common/Behaviors/BodyQueryBehavior.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using MediatR;
|
||||||
|
using ReC.Application.Common.Dto;
|
||||||
|
using ReC.Application.Common.Interfaces;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ReC.Application.Common.Behaviors;
|
||||||
|
|
||||||
|
public class BodyQueryBehavior<TRecAction>(IRecDbContext dbContext) : IPipelineBehavior<TRecAction, Unit>
|
||||||
|
where TRecAction : RecActionDto
|
||||||
|
{
|
||||||
|
public async Task<Unit> Handle(TRecAction action, RequestHandlerDelegate<Unit> next, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
if (action.BodyQuery is null)
|
||||||
|
return await next(cancel);
|
||||||
|
|
||||||
|
var result = await dbContext.BodyQueryResults.FromSqlRaw(action.BodyQuery).FirstOrDefaultAsync(cancel);
|
||||||
|
action.Body = result?.RawBody;
|
||||||
|
|
||||||
|
return await next(cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/ReC.Application/Common/Behaviors/HeaderQueryBehavior.cs
Normal file
35
src/ReC.Application/Common/Behaviors/HeaderQueryBehavior.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ReC.Application.Common.Dto;
|
||||||
|
using ReC.Application.Common.Interfaces;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReC.Application.Common.Behaviors;
|
||||||
|
|
||||||
|
public class HeaderQueryBehavior<TRecAction>(IRecDbContext dbContext, ILogger<HeaderQueryBehavior<TRecAction>>? logger = null) : IPipelineBehavior<TRecAction, Unit>
|
||||||
|
where TRecAction : RecActionDto
|
||||||
|
{
|
||||||
|
public async Task<Unit> Handle(TRecAction action, RequestHandlerDelegate<Unit> next, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
if (action.HeaderQuery is null)
|
||||||
|
return await next(cancel);
|
||||||
|
|
||||||
|
var result = await dbContext.HeaderQueryResults.FromSqlRaw(action.HeaderQuery).FirstOrDefaultAsync(cancel);
|
||||||
|
|
||||||
|
if(result?.RawHeader is null)
|
||||||
|
return await next(cancel);
|
||||||
|
|
||||||
|
var headerDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(result.RawHeader);
|
||||||
|
|
||||||
|
if(headerDict is null)
|
||||||
|
{
|
||||||
|
logger?.LogWarning("Failed to deserialize header query result: {RawHeader}. Profile ID: {ProfileId}, Action ID: {ActionId}", result.RawHeader, action.ProfileId, action.ActionId);
|
||||||
|
return await next(cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
action.Headers = headerDict.ToDictionary(header => header.Key, kvp => kvp.Value.ToString());
|
||||||
|
|
||||||
|
return await next(cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using ReC.Domain.Entities;
|
using ReC.Domain.Entities;
|
||||||
|
|
||||||
namespace ReC.Application.Dto;
|
namespace ReC.Application.Common.Dto;
|
||||||
|
|
||||||
public class DtoMappingProfile : Profile
|
public class DtoMappingProfile : Profile
|
||||||
{
|
{
|
||||||
@ -1,6 +1,6 @@
|
|||||||
namespace ReC.Application.Dto;
|
namespace ReC.Application.Common.Dto;
|
||||||
|
|
||||||
public class RecActionDto
|
public record RecActionDto
|
||||||
{
|
{
|
||||||
public long? ActionId { get; init; }
|
public long? ActionId { get; init; }
|
||||||
|
|
||||||
@ -54,7 +54,23 @@ public class RecActionDto
|
|||||||
|
|
||||||
public string? HeaderQuery { get; init; }
|
public string? HeaderQuery { get; init; }
|
||||||
|
|
||||||
|
public Dictionary<string, string>? Headers { get; set; }
|
||||||
|
|
||||||
public string? BodyQuery { get; init; }
|
public string? BodyQuery { get; init; }
|
||||||
|
|
||||||
|
public string? Body { get; set; }
|
||||||
|
|
||||||
public string? PostprocessingQuery { get; init; }
|
public string? PostprocessingQuery { get; init; }
|
||||||
|
|
||||||
|
public UriBuilder ToEndpointUriBuilder()
|
||||||
|
{
|
||||||
|
var builder = EndpointUri is null ? new UriBuilder() : new UriBuilder(EndpointUri);
|
||||||
|
|
||||||
|
builder.Port = -1;
|
||||||
|
|
||||||
|
if (ProfileType is not null)
|
||||||
|
builder.Scheme = ProfileType;
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
26
src/ReC.Application/Common/HttpExtensions.cs
Normal file
26
src/ReC.Application/Common/HttpExtensions.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace ReC.Application.Common;
|
||||||
|
|
||||||
|
public static class HttpExtensions
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, HttpMethod> _methods = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["GET"] = HttpMethod.Get,
|
||||||
|
["POST"] = HttpMethod.Post,
|
||||||
|
["PUT"] = HttpMethod.Put,
|
||||||
|
["DELETE"] = HttpMethod.Delete,
|
||||||
|
["PATCH"] = HttpMethod.Patch,
|
||||||
|
["HEAD"] = HttpMethod.Head,
|
||||||
|
["OPTIONS"] = HttpMethod.Options,
|
||||||
|
["TRACE"] = HttpMethod.Trace,
|
||||||
|
["CONNECT"] = HttpMethod.Connect
|
||||||
|
};
|
||||||
|
|
||||||
|
public static HttpMethod ToHttpMethod(this string method) => _methods.TryGetValue(method, out var httpMethod)
|
||||||
|
? httpMethod
|
||||||
|
: new HttpMethod(method);
|
||||||
|
|
||||||
|
public static HttpRequestMessage ToHttpRequestMessage(this HttpMethod method, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri)
|
||||||
|
=> new(method, requestUri);
|
||||||
|
}
|
||||||
19
src/ReC.Application/Common/Interfaces/IRecDbContext.cs
Normal file
19
src/ReC.Application/Common/Interfaces/IRecDbContext.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using ReC.Domain.Entities;
|
||||||
|
|
||||||
|
namespace ReC.Application.Common.Interfaces;
|
||||||
|
|
||||||
|
public interface IRecDbContext
|
||||||
|
{
|
||||||
|
public DbSet<EndpointParam> EndpointParams { get; }
|
||||||
|
|
||||||
|
public DbSet<RecAction> Actions { get; }
|
||||||
|
|
||||||
|
public DbSet<OutRes> OutRes { get; }
|
||||||
|
|
||||||
|
public DbSet<HeaderQueryResult> HeaderQueryResults { get; }
|
||||||
|
|
||||||
|
public DbSet<BodyQueryResult> BodyQueryResults { get; }
|
||||||
|
|
||||||
|
public Task<int> SaveChangesAsync(CancellationToken cancel = default);
|
||||||
|
}
|
||||||
6
src/ReC.Application/Common/Options/RecActionOptions.cs
Normal file
6
src/ReC.Application/Common/Options/RecActionOptions.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace ReC.Application.Common.Options;
|
||||||
|
|
||||||
|
public class RecActionOptions
|
||||||
|
{
|
||||||
|
public int MaxConcurrentInvocations { get; set; } = 5;
|
||||||
|
}
|
||||||
@ -1,4 +1,7 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using ReC.Application.Common.Behaviors;
|
||||||
|
using ReC.Application.Common.Options;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace ReC.Application;
|
namespace ReC.Application;
|
||||||
@ -10,6 +13,10 @@ public static class DependencyInjection
|
|||||||
var configOpt = new ConfigurationOptions();
|
var configOpt = new ConfigurationOptions();
|
||||||
options.Invoke(configOpt);
|
options.Invoke(configOpt);
|
||||||
|
|
||||||
|
configOpt.EnsureRequiredServices();
|
||||||
|
|
||||||
|
configOpt.ApplyConfigurations(services);
|
||||||
|
|
||||||
services.AddAutoMapper(cfg =>
|
services.AddAutoMapper(cfg =>
|
||||||
{
|
{
|
||||||
cfg.AddMaps(Assembly.GetExecutingAssembly());
|
cfg.AddMaps(Assembly.GetExecutingAssembly());
|
||||||
@ -19,6 +26,7 @@ public static class DependencyInjection
|
|||||||
services.AddMediatR(cfg =>
|
services.AddMediatR(cfg =>
|
||||||
{
|
{
|
||||||
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
|
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
|
||||||
|
cfg.AddOpenBehaviors([typeof(BodyQueryBehavior<>), typeof(HeaderQueryBehavior<>)]);
|
||||||
cfg.LicenseKey = configOpt.LuckyPennySoftwareLicenseKey;
|
cfg.LicenseKey = configOpt.LuckyPennySoftwareLicenseKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -29,6 +37,74 @@ public static class DependencyInjection
|
|||||||
|
|
||||||
public class ConfigurationOptions
|
public class ConfigurationOptions
|
||||||
{
|
{
|
||||||
public string? LuckyPennySoftwareLicenseKey { get; set; }
|
|
||||||
|
#region Required Services
|
||||||
|
private readonly Dictionary<string, bool> _requiredServices = new()
|
||||||
|
{
|
||||||
|
{ nameof(ConfigureRecActions), false },
|
||||||
|
{ nameof(LuckyPennySoftwareLicenseKey), false }
|
||||||
|
};
|
||||||
|
|
||||||
|
internal void EnsureRequiredServices()
|
||||||
|
{
|
||||||
|
var missingServices = _requiredServices
|
||||||
|
.Where(kvp => !kvp.Value)
|
||||||
|
.Select(kvp => kvp.Key.Replace("Configure", string.Empty));
|
||||||
|
if (missingServices.Any())
|
||||||
|
throw new InvalidOperationException($"The following required services were not configured: {string.Join(", ", missingServices)}");
|
||||||
|
}
|
||||||
|
#endregion Required Services
|
||||||
|
|
||||||
|
#region Configuration
|
||||||
|
private readonly Queue<Action<IServiceCollection>> _configActions = new();
|
||||||
|
|
||||||
|
internal void ApplyConfigurations(IServiceCollection services)
|
||||||
|
{
|
||||||
|
while (_configActions.Count > 0)
|
||||||
|
{
|
||||||
|
var action = _configActions.Dequeue();
|
||||||
|
action(services);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion Configuration
|
||||||
|
|
||||||
|
#region LuckyPennySoftwareLicenseKey
|
||||||
|
private string? _luckyPennySoftwareLicenseKey;
|
||||||
|
|
||||||
|
public string? LuckyPennySoftwareLicenseKey
|
||||||
|
{
|
||||||
|
get => _luckyPennySoftwareLicenseKey;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_luckyPennySoftwareLicenseKey = value;
|
||||||
|
if (value is not null)
|
||||||
|
_requiredServices[nameof(LuckyPennySoftwareLicenseKey)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion LuckyPennySoftwareLicenseKey
|
||||||
|
|
||||||
|
#region ConfigureRecActions
|
||||||
|
public ConfigurationOptions ConfigureRecActions(Action<RecActionOptions> configure)
|
||||||
|
{
|
||||||
|
_configActions.Enqueue(services => services.Configure(configure));
|
||||||
|
|
||||||
|
if(_requiredServices[nameof(ConfigureRecActions)])
|
||||||
|
throw new InvalidOperationException("RecActionOptions have already been configured.");
|
||||||
|
_requiredServices[nameof(ConfigureRecActions)] = true;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationOptions ConfigureRecActions(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configActions.Enqueue(services => services.Configure<RecActionOptions>(configuration));
|
||||||
|
|
||||||
|
if (_requiredServices[nameof(ConfigureRecActions)])
|
||||||
|
throw new InvalidOperationException("RecActionOptions have already been configured.");
|
||||||
|
_requiredServices[nameof(ConfigureRecActions)] = true;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
#endregion ConfigureRecActions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
|
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
|
||||||
<PackageReference Include="MediatR" Version="13.1.0" />
|
<PackageReference Include="MediatR" Version="13.1.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.11" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ReC.Application.RecActions.Queries;
|
||||||
|
|
||||||
|
namespace ReC.Application.RecActions.Commands;
|
||||||
|
|
||||||
|
public record InvokeBatchRecActionsCommand : ReadRecActionQueryBase, IRequest;
|
||||||
|
|
||||||
|
public static class InvokeBatchRecActionsCommandExtensions
|
||||||
|
{
|
||||||
|
public static Task InvokeBatchRecAction(this ISender sender, int profileId)
|
||||||
|
=> sender.Send(new InvokeBatchRecActionsCommand { ProfileId = profileId });
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InvokeRecActionsCommandHandler(ISender sender, IHttpClientFactory clientFactory, ILogger<InvokeRecActionsCommandHandler>? logger = null) : IRequestHandler<InvokeBatchRecActionsCommand>
|
||||||
|
{
|
||||||
|
public async Task Handle(InvokeBatchRecActionsCommand request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
var actions = await sender.Send(request.ToReadQuery(), cancel);
|
||||||
|
|
||||||
|
var http = clientFactory.CreateClient();
|
||||||
|
|
||||||
|
using var semaphore = new SemaphoreSlim(5);
|
||||||
|
|
||||||
|
var tasks = actions.Select(async action =>
|
||||||
|
{
|
||||||
|
await semaphore.WaitAsync(cancel);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await sender.Send(action.ToInvokeCommand(), cancel);
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
logger?.LogError(
|
||||||
|
ex,
|
||||||
|
"Error invoking Rec action. ProfileId: {ProfileId}, ActionId: {ActionId}",
|
||||||
|
action.ProfileId,
|
||||||
|
action.ActionId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,57 +1,47 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using ReC.Application.RecActions.Queries;
|
using ReC.Application.Common;
|
||||||
|
using ReC.Application.Common.Dto;
|
||||||
|
|
||||||
namespace ReC.Application.RecActions.Commands;
|
namespace ReC.Application.RecActions.Commands;
|
||||||
|
|
||||||
public class InvokeRecActionCommand : ReadRecActionQuery, IRequest
|
public record InvokeRecActionCommand : RecActionDto, IRequest
|
||||||
{
|
{
|
||||||
|
public InvokeRecActionCommand(RecActionDto root) : base(root) { }
|
||||||
|
|
||||||
|
public InvokeRecActionCommand() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class InvokeRecActionCommandExtensions
|
public static class InvokeRecActionCommandExtensions
|
||||||
{
|
{
|
||||||
public static Task InvokeRecAction(this ISender sender, int profileId)
|
public static InvokeRecActionCommand ToInvokeCommand(this RecActionDto dto) => new(dto);
|
||||||
=> sender.Send(new InvokeRecActionCommand { ProfileId = profileId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class InvokeRecActionCommandHandler(ISender sender, IHttpClientFactory clientFactory, ILogger<InvokeRecActionCommandHandler>? logger = null) : IRequestHandler<InvokeRecActionCommand>
|
public class InvokeRecActionCommandHandler(
|
||||||
|
IHttpClientFactory clientFactory,
|
||||||
|
ILogger<InvokeRecActionsCommandHandler>? logger = null
|
||||||
|
) : IRequestHandler<InvokeRecActionCommand>
|
||||||
{
|
{
|
||||||
public async Task Handle(InvokeRecActionCommand request, CancellationToken cancel)
|
public async Task Handle(InvokeRecActionCommand request, CancellationToken cancel)
|
||||||
{
|
{
|
||||||
var actions = await sender.Send(request as ReadRecActionQuery, cancel);
|
using var http = clientFactory.CreateClient();
|
||||||
|
|
||||||
var http = clientFactory.CreateClient();
|
if (request.RestType is null)
|
||||||
|
|
||||||
using var semaphore = new SemaphoreSlim(5);
|
|
||||||
|
|
||||||
var tasks = actions.Select(async action =>
|
|
||||||
{
|
{
|
||||||
await semaphore.WaitAsync(cancel);
|
logger?.LogWarning(
|
||||||
try
|
"Rec action could not be invoked because the RestType value is null. ProfileId: {ProfileId}, ActionId: {ActionId}",
|
||||||
{
|
request.ProfileId,
|
||||||
if (action.RestType is null)
|
request.ActionId
|
||||||
{
|
);
|
||||||
logger?.LogWarning(
|
return;
|
||||||
"Rec action could not be invoked because the RestType value is null. ProfileId: {ProfileId}, ActionId: {ActionId}",
|
}
|
||||||
action.ProfileId,
|
|
||||||
action.ActionId
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var method = new HttpMethod(action.RestType.ToUpper());
|
using var httpReq = request.RestType
|
||||||
var msg = new HttpRequestMessage(method, action.EndpointUri);
|
.ToHttpMethod()
|
||||||
|
.ToHttpRequestMessage(request.EndpointUri);
|
||||||
|
|
||||||
var response = await http.SendAsync(msg, cancel);
|
using var response = await http.SendAsync(httpReq, cancel);
|
||||||
var body = await response.Content.ReadAsStringAsync(cancel);
|
var body = await response.Content.ReadAsStringAsync(cancel);
|
||||||
var headers = response.Headers.ToDictionary();
|
var headers = response.Headers.ToDictionary();
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
semaphore.Release();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,18 +1,22 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using ReC.Application.Dto;
|
|
||||||
using DigitalData.Core.Abstraction.Application.Repository;
|
using DigitalData.Core.Abstraction.Application.Repository;
|
||||||
using ReC.Domain.Entities;
|
using ReC.Domain.Entities;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using DigitalData.Core.Exceptions;
|
using DigitalData.Core.Exceptions;
|
||||||
|
using ReC.Application.Common.Dto;
|
||||||
|
|
||||||
namespace ReC.Application.RecActions.Queries;
|
namespace ReC.Application.RecActions.Queries;
|
||||||
|
|
||||||
public class ReadRecActionQuery : IRequest<IEnumerable<RecActionDto>>
|
public record ReadRecActionQueryBase
|
||||||
{
|
{
|
||||||
public int ProfileId { get; init; }
|
public int ProfileId { get; init; }
|
||||||
|
|
||||||
|
public ReadRecActionQuery ToReadQuery() => new(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record ReadRecActionQuery(ReadRecActionQueryBase Root) : ReadRecActionQueryBase(Root), IRequest<IEnumerable<RecActionDto>>;
|
||||||
|
|
||||||
public class ReadRecActionQueryHandler(IRepository<RecAction> repo, IMapper mapper) : IRequestHandler<ReadRecActionQuery, IEnumerable<RecActionDto>>
|
public class ReadRecActionQueryHandler(IRepository<RecAction> repo, IMapper mapper) : IRequestHandler<ReadRecActionQuery, IEnumerable<RecActionDto>>
|
||||||
{
|
{
|
||||||
public async Task<IEnumerable<RecActionDto>> Handle(ReadRecActionQuery request, CancellationToken cancel)
|
public async Task<IEnumerable<RecActionDto>> Handle(ReadRecActionQuery request, CancellationToken cancel)
|
||||||
|
|||||||
9
src/ReC.Domain/Entities/BodyQueryResult.cs.cs
Normal file
9
src/ReC.Domain/Entities/BodyQueryResult.cs.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace ReC.Domain.Entities;
|
||||||
|
|
||||||
|
public class BodyQueryResult
|
||||||
|
{
|
||||||
|
[Column("REQUEST_BODY")]
|
||||||
|
public string? RawBody { get; init; }
|
||||||
|
}
|
||||||
9
src/ReC.Domain/Entities/HeaderQueryResult.cs
Normal file
9
src/ReC.Domain/Entities/HeaderQueryResult.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace ReC.Domain.Entities;
|
||||||
|
|
||||||
|
public class HeaderQueryResult
|
||||||
|
{
|
||||||
|
[Column("REQUEST_HEADER")]
|
||||||
|
public string? RawHeader { get; init; }
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
using DigitalData.Core.Infrastructure;
|
using DigitalData.Core.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using ReC.Application.Common.Interfaces;
|
||||||
using ReC.Domain.Entities;
|
using ReC.Domain.Entities;
|
||||||
|
|
||||||
namespace ReC.Infrastructure;
|
namespace ReC.Infrastructure;
|
||||||
@ -16,7 +17,9 @@ public static class DependencyInjection
|
|||||||
if(configOpt.DbContextOptionsAction is null)
|
if(configOpt.DbContextOptionsAction is null)
|
||||||
throw new InvalidOperationException("DbContextOptionsAction must be configured.");
|
throw new InvalidOperationException("DbContextOptionsAction must be configured.");
|
||||||
|
|
||||||
services.AddDbContext<RecDbContext>(configOpt.DbContextOptionsAction);
|
services.AddDbContext<TRecDbContext>(configOpt.DbContextOptionsAction);
|
||||||
|
|
||||||
|
services.AddScoped<IRecDbContext>(provider => provider.GetRequiredService<TRecDbContext>());
|
||||||
|
|
||||||
services.AddDbRepository(opt => opt.RegisterFromAssembly<TRecDbContext>(typeof(RecAction).Assembly));
|
services.AddDbRepository(opt => opt.RegisterFromAssembly<TRecDbContext>(typeof(RecAction).Assembly));
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ReC.Application\ReC.Application.csproj" />
|
||||||
<ProjectReference Include="..\ReC.Domain\ReC.Domain.csproj" />
|
<ProjectReference Include="..\ReC.Domain\ReC.Domain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using ReC.Application.Common.Interfaces;
|
||||||
using ReC.Domain.Entities;
|
using ReC.Domain.Entities;
|
||||||
|
|
||||||
namespace ReC.Infrastructure;
|
namespace ReC.Infrastructure;
|
||||||
|
|
||||||
public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(options)
|
public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(options), IRecDbContext
|
||||||
{
|
{
|
||||||
public DbSet<EndpointParam> EndpointParams { get; set; }
|
public DbSet<EndpointParam> EndpointParams { get; set; }
|
||||||
|
|
||||||
@ -11,10 +12,18 @@ public class RecDbContext(DbContextOptions<RecDbContext> options) : DbContext(op
|
|||||||
|
|
||||||
public DbSet<OutRes> OutRes { get; set; }
|
public DbSet<OutRes> OutRes { get; set; }
|
||||||
|
|
||||||
|
public DbSet<HeaderQueryResult> HeaderQueryResults { get; set; }
|
||||||
|
|
||||||
|
public DbSet<BodyQueryResult> BodyQueryResults { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
modelBuilder.Entity<RecAction>().HasNoKey();
|
modelBuilder.Entity<RecAction>().HasNoKey();
|
||||||
|
|
||||||
|
modelBuilder.Entity<HeaderQueryResult>().HasNoKey();
|
||||||
|
|
||||||
|
modelBuilder.Entity<BodyQueryResult>().HasNoKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user