4 Commits

Author SHA1 Message Date
761fd208e5 Bump version to 2.2.1-beta
Updated Version, AssemblyVersion, and FileVersion in ReC.API.csproj from 2.2.0-beta/2.2.0.0 to 2.2.1-beta/2.2.1.0. No other changes made.
2026-04-17 00:34:40 +02:00
d149cbea3a Improve error handling in query behaviors for SQL execution
Wrap ExecuteScalarAsync in try-catch blocks in BodyQueryBehavior and HeaderQueryBehavior. Throw DataIntegrityException with detailed context if SQL execution fails, aiding in diagnosing malformed or problematic stored SQL queries.
2026-04-17 00:34:01 +02:00
bb2dd4d63b Stricter error handling in BodyQuery and HeaderQuery behaviors
Throw DataIntegrityException when body or header queries return
null, no result, or invalid JSON. Remove ILogger and related
logging from HeaderQueryBehavior for simplification. This change
ensures data integrity issues are surfaced immediately.
2026-04-17 00:23:36 +02:00
4bde1d090f Refactor query behaviors to process action collections
Refactored BodyQueryBehavior and HeaderQueryBehavior to operate on collections of RecActionViewDto instead of single instances. Moved SQL execution and property assignment logic into private helper methods using ADO.NET commands. Improved null checks and logging, and updated type constraints to reflect the new usage. Behaviors now return the modified collection after processing.
2026-04-17 00:15:52 +02:00
3 changed files with 88 additions and 39 deletions

View File

@@ -10,9 +10,9 @@
<Product>ReC.API</Product>
<PackageIcon>Assets\icon.ico</PackageIcon>
<PackageTags>digital data rest-caller rec api</PackageTags>
<Version>2.2.0-beta</Version>
<AssemblyVersion>2.2.0.0</AssemblyVersion>
<FileVersion>2.2.0.0</FileVersion>
<Version>2.2.1-beta</Version>
<AssemblyVersion>2.2.1.0</AssemblyVersion>
<FileVersion>2.2.1.0</FileVersion>
<InformationalVersion>2.2.0-beta</InformationalVersion>
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

View File

@@ -1,23 +1,54 @@
using MediatR;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Exceptions;
using ReC.Application.Common.Interfaces;
namespace ReC.Application.Common.Behaviors.Action;
public class BodyQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse>
where TRequest : RecActionViewDto
where TResponse : notnull
where TRequest : notnull
where TResponse : IEnumerable<RecActionViewDto>
{
public async Task<TResponse> Handle(TRequest action, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
{
if (action.BodyQuery is null)
return await next(cancel);
var actions = await next(cancel);
var result = await dbContext.BodyQueryResults.FromSqlRaw(action.BodyQuery).SingleOrDefaultAsync(cancel);
foreach (var action in actions)
await SetBody(action, cancel);
action.Body = result?.RawBody;
return actions;
}
return await next(cancel);
private async Task SetBody(RecActionViewDto action, CancellationToken cancel)
{
if (action.BodyQuery is not string bodyQuery)
return;
await using var command = dbContext.Database.GetDbConnection().CreateCommand();
command.CommandText = bodyQuery;
await dbContext.Database.OpenConnectionAsync(cancel);
try
{
object? scalar;
try
{
scalar = await command.ExecuteScalarAsync(cancel);
}
catch (Exception ex)
{
throw new DataIntegrityException(
$"Body query execution failed. The stored SQL may be malformed. ActionId: {action.Id}, ProfileId: {action.ProfileId}, Error: {ex.Message}");
}
action.Body = scalar as string
?? throw new DataIntegrityException(
$"Body query returned no result or a null value. ActionId: {action.Id}, ProfileId: {action.ProfileId}");
}
finally
{
await dbContext.Database.CloseConnectionAsync();
}
}
}

View File

@@ -1,43 +1,61 @@
using MediatR;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Exceptions;
using ReC.Application.Common.Interfaces;
using System.Text.Json;
namespace ReC.Application.Common.Behaviors.Action;
public class HeaderQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext, ILogger<HeaderQueryBehavior<TRequest, TResponse>>? logger = null) : IPipelineBehavior<TRequest, TResponse>
where TRequest : RecActionViewDto
where TResponse : notnull
public class HeaderQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
where TResponse : IEnumerable<RecActionViewDto>
{
public async Task<TResponse> Handle(TRequest action, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
{
if (action.HeaderQuery is null)
return await next(cancel);
var actions = await next(cancel);
var result = await dbContext.HeaderQueryResults.FromSqlRaw(action.HeaderQuery).SingleOrDefaultAsync(cancel);
foreach (var action in actions)
await SetHeader(action, cancel);
if (result?.RawHeader is null)
return actions;
}
private async Task SetHeader(RecActionViewDto action, CancellationToken cancel)
{
if (action.HeaderQuery is not string headerQuery)
return;
await using var command = dbContext.Database.GetDbConnection().CreateCommand();
command.CommandText = headerQuery;
await dbContext.Database.OpenConnectionAsync(cancel);
try
{
logger?.LogWarning("Header query did not return a result or returned a null REQUEST_HEADER. Profile ID: {ProfileId}, Action ID: {Id}", action.ProfileId, action.Id);
object? scalar;
try
{
scalar = await command.ExecuteScalarAsync(cancel);
}
catch (Exception ex)
{
throw new DataIntegrityException(
$"Header query execution failed. The stored SQL may be malformed. ActionId: {action.Id}, ProfileId: {action.ProfileId}, Error: {ex.Message}");
}
return await next(cancel);
if (scalar is not string rawHeader)
throw new DataIntegrityException(
$"Header query returned no result or a null value. ActionId: {action.Id}, ProfileId: {action.ProfileId}");
var headerDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(rawHeader)
?? throw new DataIntegrityException(
$"Header query returned invalid JSON. ActionId: {action.Id}, ProfileId: {action.ProfileId}");
action.Headers = headerDict.ToDictionary(header => header.Key, kvp => kvp.Value.ToString());
}
var headerDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(result.RawHeader);
if(headerDict is null)
finally
{
logger?.LogWarning(
"Header JSON deserialization returned null. RawHeader: {RawHeader}, ProfileId: {ProfileId}, Id: {Id}",
result.RawHeader, action.ProfileId, action.Id);
return await next(cancel);
await dbContext.Database.CloseConnectionAsync();
}
action.Headers = headerDict.ToDictionary(header => header.Key, kvp => kvp.Value.ToString());
return await next(cancel);
}
}