Updated InvokeRecActionViewCommandHandler to throw a RecActionException with action Id and ProfileId when an error occurs and ErrorAction is set to Stop. This change enhances error traceability by providing more contextual information in exceptions.
191 lines
8.0 KiB
C#
191 lines
8.0 KiB
C#
using MediatR;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Options;
|
|
using ReC.Application.Common.Constants;
|
|
using ReC.Application.Common.Dto;
|
|
using ReC.Application.Common.Exceptions;
|
|
using ReC.Application.Common.Options;
|
|
using ReC.Application.Results.Commands;
|
|
using ReC.Domain.Constants;
|
|
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
|
|
namespace ReC.Application.RecActions.Commands;
|
|
|
|
public record InvokeRecActionViewCommand : IRequest
|
|
{
|
|
public RecActionViewDto Action { get; set; } = null!;
|
|
}
|
|
|
|
public class InvokeRecActionViewCommandHandler(
|
|
IOptions<RecActionOptions> options,
|
|
ISender sender,
|
|
IHttpClientFactory clientFactory,
|
|
IConfiguration? config = null
|
|
) : IRequestHandler<InvokeRecActionViewCommand>
|
|
{
|
|
private readonly RecActionOptions _options = options.Value;
|
|
|
|
public async Task Handle(InvokeRecActionViewCommand request, CancellationToken cancel)
|
|
{
|
|
var action = request.Action;
|
|
HttpClient? ntlmClient = null;
|
|
|
|
try
|
|
{
|
|
if (action.RestType is not RestType restType)
|
|
throw new DataIntegrityException(
|
|
$"Rec action could not be invoked because the RestType value is null. " +
|
|
$"ProfileId: {action.ProfileId}, " +
|
|
$"Id: {action.Id}"
|
|
);
|
|
|
|
using var httpReq = CreateHttpRequestMessage(restType, action.EndpointUri);
|
|
|
|
if (action.Body is not null)
|
|
httpReq.Content = new StringContent(action.Body);
|
|
|
|
if (action.Headers is not null)
|
|
foreach (var header in action.Headers)
|
|
httpReq.Headers.Add(header.Key, header.Value);
|
|
|
|
switch (action.EndpointAuthType)
|
|
{
|
|
case EndpointAuthType.NoAuth:
|
|
break;
|
|
|
|
case EndpointAuthType.ApiKey:
|
|
if (action.EndpointAuthApiKey is string apiKey && action.EndpointAuthApiValue is string apiValue)
|
|
{
|
|
switch (action.EndpointAuthApiKeyAddTo)
|
|
{
|
|
case ApiKeyLocation.Header:
|
|
httpReq.Headers.Add(apiKey, apiValue);
|
|
break;
|
|
case ApiKeyLocation.Query:
|
|
var uriBuilder = new UriBuilder(httpReq.RequestUri!);
|
|
var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query);
|
|
query[apiKey] = apiValue;
|
|
uriBuilder.Query = query.ToString();
|
|
httpReq.RequestUri = uriBuilder.Uri;
|
|
break;
|
|
default:
|
|
throw new DataIntegrityException(
|
|
$"The API key location '{action.EndpointAuthApiKeyAddTo}' is not supported. " +
|
|
$"ProfileId: {action.ProfileId}, " +
|
|
$"Id: {action.Id}"
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EndpointAuthType.BearerToken:
|
|
case EndpointAuthType.JwtBearer:
|
|
case EndpointAuthType.OAuth2:
|
|
if (action.EndpointAuthToken is string authToken)
|
|
httpReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
|
|
break;
|
|
|
|
case EndpointAuthType.BasicAuth:
|
|
if (action.EndpointAuthUsername is string authUsername && action.EndpointAuthPassword is string authPassword)
|
|
{
|
|
var basicAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{authUsername}:{authPassword}"));
|
|
httpReq.Headers.Authorization = new AuthenticationHeaderValue("Basic", basicAuth);
|
|
}
|
|
break;
|
|
|
|
case EndpointAuthType.NtlmAuth:
|
|
if (!string.IsNullOrWhiteSpace(action.EndpointAuthUsername))
|
|
{
|
|
if (_options.UseHttp1ForNtlm)
|
|
{
|
|
httpReq.Version = HttpVersion.Version11;
|
|
httpReq.VersionPolicy = HttpVersionPolicy.RequestVersionExact;
|
|
}
|
|
var endpointAuthPassword = action.EndpointAuthPassword
|
|
#if DEBUG
|
|
?.Replace("%NTLM_PW%", config?.GetValue<string>("%NTLM_PW%"))
|
|
#endif
|
|
;
|
|
var credentials = new NetworkCredential(
|
|
action.EndpointAuthUsername,
|
|
endpointAuthPassword,
|
|
action.EndpointAuthDomain);
|
|
var credentialCache = new CredentialCache { { httpReq.RequestUri!, "NTLM", credentials } };
|
|
var ntlmHandler = new HttpClientHandler
|
|
{
|
|
Credentials = credentialCache,
|
|
UseDefaultCredentials = false
|
|
};
|
|
ntlmClient = new HttpClient(ntlmHandler, disposeHandler: true);
|
|
}
|
|
break;
|
|
|
|
case EndpointAuthType.DigestAuth:
|
|
case EndpointAuthType.OAuth1:
|
|
case EndpointAuthType.AwsSignature:
|
|
// These authentication methods require more complex implementations,
|
|
// often involving multi-step handshakes or specialized libraries.
|
|
// They are left as placeholders for future implementation.
|
|
default:
|
|
throw new NotImplementedException(
|
|
$"The authentication type '{action.EndpointAuthType}' is not supported yet. " +
|
|
$"ProfileId: {action.ProfileId}, " +
|
|
$"Id: {action.Id}"
|
|
);
|
|
}
|
|
|
|
var http = ntlmClient ?? clientFactory.CreateClient(Http.ClientName);
|
|
using var response = await http.SendAsync(httpReq, cancel);
|
|
var resBody = await response.Content.ReadAsStringAsync(cancel);
|
|
var resHeaders = response.Headers.ToDictionary();
|
|
|
|
var statusCode = (short)response.StatusCode;
|
|
|
|
await sender.Send(new InsertResultCommand()
|
|
{
|
|
StatusId = statusCode,
|
|
ActionId = action.Id,
|
|
Header = JsonSerializer.Serialize(resHeaders, options: new() { WriteIndented = false }),
|
|
Body = resBody
|
|
}, cancel);
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
await sender.Send(new InsertResultCommand()
|
|
{
|
|
ActionId = action.Id,
|
|
Error = ex.ToString()
|
|
}, cancel);
|
|
|
|
if (action.ErrorAction == ErrorAction.Stop)
|
|
throw new RecActionException(action.Id, action.ProfileId, ex);
|
|
}
|
|
finally
|
|
{
|
|
ntlmClient?.Dispose();
|
|
}
|
|
}
|
|
|
|
private static HttpRequestMessage CreateHttpRequestMessage(RestType restType, string? endpointUri)
|
|
{
|
|
var method = restType switch
|
|
{
|
|
RestType.Get => HttpMethod.Get,
|
|
RestType.Post => HttpMethod.Post,
|
|
RestType.Put => HttpMethod.Put,
|
|
RestType.Delete => HttpMethod.Delete,
|
|
RestType.Patch => HttpMethod.Patch,
|
|
RestType.Head => HttpMethod.Head,
|
|
RestType.Options => HttpMethod.Options,
|
|
RestType.Trace => HttpMethod.Trace,
|
|
RestType.Connect => HttpMethod.Connect,
|
|
RestType.None => throw new ArgumentOutOfRangeException(nameof(restType), $"The RestType value '{restType}' is not valid."),
|
|
_ => new HttpMethod(restType.ToString().ToUpperInvariant())
|
|
};
|
|
|
|
return new HttpRequestMessage(method, endpointUri);
|
|
}
|
|
} |