Skip to content

Commit

Permalink
v3.27.0 (#125)
Browse files Browse the repository at this point in the history
- *Fixed:* The `ValueContentResult.TryCreateValueContentResult` would return `NotModified` where the request `ETag` was `null`; this has been corrected to return `OK` with the resulting `value`.
- *Fixed:* The `ValueContentResult.TryCreateValueContentResult` now returns `ExtendedStatusCodeResult` versus `StatusCodeResult` as this offers additional capabilities where required.
- *Enhancement:* The `ExtendedStatusCodeResult` and `ExtendedContentResult` now implement `IExtendedActionResult` to standardize access to the `BeforeExtension` and `AfterExtension` functions.
- *Enhancement:* Added `WebApiParam.CreateActionResult` helper methods to enable execution of the underlying `ValueContentResult.CreateValueContentResult` (which is no longer public as this was always intended as internal only).
- *Fixed:* `PostgresDatabase.OnDbException` corrected to use `PostgresException.MessageText` versus `Message` as it does not include the `SQLSTATE` code.
- *Enhancement:* Improve debugging insights by adding `ILogger.LogDebug` start/stop/elapsed for the `InvokerArgs`.
- *Fixed*: Updated `System.Text.Json` package depenedency to latest (including related); resolve [Microsoft Security Advisory CVE-2024-43485](GHSA-8g4q-xg66-9fp4).
  • Loading branch information
chullybun authored Oct 11, 2024
1 parent 4821c40 commit 6738a92
Show file tree
Hide file tree
Showing 44 changed files with 614 additions and 184 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

Represents the **NuGet** versions.

## v3.27.0
- *Fixed:* The `ValueContentResult.TryCreateValueContentResult` would return `NotModified` where the request `ETag` was `null`; this has been corrected to return `OK` with the resulting `value`.
- *Fixed:* The `ValueContentResult.TryCreateValueContentResult` now returns `ExtendedStatusCodeResult` versus `StatusCodeResult` as this offers additional capabilities where required.
- *Enhancement:* The `ExtendedStatusCodeResult` and `ExtendedContentResult` now implement `IExtendedActionResult` to standardize access to the `BeforeExtension` and `AfterExtension` functions.
- *Enhancement:* Added `WebApiParam.CreateActionResult` helper methods to enable execution of the underlying `ValueContentResult.CreateValueContentResult` (which is no longer public as this was always intended as internal only).
- *Fixed:* `PostgresDatabase.OnDbException` corrected to use `PostgresException.MessageText` versus `Message` as it does not include the `SQLSTATE` code.
- *Enhancement:* Improve debugging insights by adding `ILogger.LogDebug` start/stop/elapsed for the `InvokerArgs`.
- *Fixed*: Updated `System.Text.Json` package depenedency to latest (including related); resolve [Microsoft Security Advisory CVE-2024-43485](https://github.com/advisories/GHSA-8g4q-xg66-9fp4).

## v3.26.0
- *Enhancement:* Enable JSON serialization of database parameter values; added `DatabaseParameterCollection.AddJsonParameter` method and associated `JsonParam`, `JsonParamWhen` and `JsonParamWith` extension methods.
- *Enhancement:* Updated (simplified) `EventOutboxEnqueueBase` to pass events to the underlying stored procedures as JSON versus existing TVP removing database dependency on a UDT (user-defined type).
Expand Down
2 changes: 1 addition & 1 deletion Common.targets
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.26.0</Version>
<Version>3.27.0</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
2 changes: 1 addition & 1 deletion samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="DbEx.SqlServer" Version="2.5.9" />
<PackageReference Include="DbEx.SqlServer" Version="2.6.1" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion samples/My.Hr/My.Hr.Functions/My.Hr.Functions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.OpenApi" Version="1.5.1" />
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.4.0" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.4.1" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions samples/My.Hr/My.Hr.UnitTest/My.Hr.UnitTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
2 changes: 1 addition & 1 deletion src/CoreEx.AspNetCore/CoreEx.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<Import Project="..\..\Common.targets" />

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.1" />
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 3 additions & 7 deletions src/CoreEx.AspNetCore/WebApis/ExtendedContentResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,13 @@ namespace CoreEx.AspNetCore.WebApis
/// <summary>
/// Represents an extended <see cref="ContentResult"/> that enables customization of the <see cref="HttpResponse"/>.
/// </summary>
public class ExtendedContentResult : ContentResult
public class ExtendedContentResult : ContentResult, IExtendedActionResult
{
/// <summary>
/// Gets or sets the function to perform the extended <see cref="HttpResponse"/> customization.
/// </summary>
/// <inheritdoc/>
[JsonIgnore]
public Func<HttpResponse, Task>? BeforeExtension { get; set; }

/// <summary>
/// Gets or sets the function to perform the extended <see cref="HttpResponse"/> customization.
/// </summary>
/// <inheritdoc/>
[JsonIgnore]
public Func<HttpResponse, Task>? AfterExtension { get; set; }

Expand Down
18 changes: 10 additions & 8 deletions src/CoreEx.AspNetCore/WebApis/ExtendedStatusCodeResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@ namespace CoreEx.AspNetCore.WebApis
/// <summary>
/// Represents an extended <see cref="StatusCodeResult"/> that enables customization of the <see cref="HttpResponse"/>.
/// </summary>
/// <param name="statusCode">The <see cref="HttpStatusCode"/>.</param>
public class ExtendedStatusCodeResult(HttpStatusCode statusCode) : StatusCodeResult((int)statusCode)
/// <param name="statusCode">The status code value.</param>
public class ExtendedStatusCodeResult(int statusCode) : StatusCodeResult(statusCode), IExtendedActionResult
{
/// <summary>
/// Gets or sets the <see cref="Microsoft.AspNetCore.Http.Headers.ResponseHeaders.Location"/> <see cref="Uri"/>.
/// Initializes a new instance of the <see cref="ExtendedStatusCodeResult"/> class with the specified <see cref="HttpStatusCode"/>.
/// </summary>
public Uri? Location { get; set; }
/// <param name="statusCode">The <see cref="HttpStatusCode"/>.</param>
public ExtendedStatusCodeResult(HttpStatusCode statusCode) : this((int)statusCode) { }

/// <summary>
/// Gets or sets the function to perform the extended <see cref="HttpResponse"/> customization.
/// Gets or sets the <see cref="Microsoft.AspNetCore.Http.Headers.ResponseHeaders.Location"/> <see cref="Uri"/>.
/// </summary>
public Uri? Location { get; set; }

/// <inheritdoc/>
[JsonIgnore]
public Func<HttpResponse, Task>? BeforeExtension { get; set; }

/// <summary>
/// Gets or sets the function to perform the extended <see cref="HttpResponse"/> customization.
/// </summary>
/// <inheritdoc/>
[JsonIgnore]
public Func<HttpResponse, Task>? AfterExtension { get; set; }

Expand Down
28 changes: 28 additions & 0 deletions src/CoreEx.AspNetCore/WebApis/IExtendedActionResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace CoreEx.AspNetCore.WebApis
{
/// <summary>
/// Extends an <see cref="IActionResult"/> to enable customization of the resulting <see cref="HttpResponse"/> using the <see cref="BeforeExtension"/> and <see cref="AfterExtension"/> functions.
/// </summary>
public interface IExtendedActionResult : IActionResult
{
/// <summary>
/// Gets or sets the function to perform the extended <see cref="HttpResponse"/> customization.
/// </summary>
[JsonIgnore]
Func<HttpResponse, Task>? BeforeExtension { get; set; }

/// <summary>
/// Gets or sets the function to perform the extended <see cref="HttpResponse"/> customization.
/// </summary>
[JsonIgnore]
Func<HttpResponse, Task>? AfterExtension { get; set; }
}
}
12 changes: 6 additions & 6 deletions src/CoreEx.AspNetCore/WebApis/ValueContentResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public override Task ExecuteResultAsync(ActionContext context)
}

/// <summary>
/// Creates the <see cref="IActionResult"/> as either <see cref="ValueContentResult"/> or <see cref="StatusCodeResult"/> as per <see cref="TryCreateValueContentResult"/>; unless <paramref name="value"/> is an instance of <see cref="IActionResult"/> which will return as-is.
/// Creates the <see cref="IActionResult"/> as either <see cref="ValueContentResult"/> or <see cref="ExtendedStatusCodeResult"/> as per <see cref="TryCreateValueContentResult"/>; unless <paramref name="value"/> is an instance of <see cref="IActionResult"/> which will return as-is.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="statusCode">The primary status code where there is a value.</param>
Expand All @@ -91,11 +91,11 @@ public override Task ExecuteResultAsync(ActionContext context)
/// <param name="checkForNotModified">Indicates whether to check for <see cref="HttpStatusCode.NotModified"/> by comparing request and response <see cref="IETag.ETag"/> values.</param>
/// <param name="location">The <see cref="Microsoft.AspNetCore.Http.Headers.ResponseHeaders.Location"/> <see cref="Uri"/>.</param>
/// <returns>The <see cref="IActionResult"/>.</returns>
public static IActionResult CreateResult<T>(T value, HttpStatusCode statusCode, HttpStatusCode? alternateStatusCode, IJsonSerializer jsonSerializer, WebApiRequestOptions requestOptions, bool checkForNotModified, Uri? location)
internal static IActionResult CreateResult<T>(T value, HttpStatusCode statusCode, HttpStatusCode? alternateStatusCode, IJsonSerializer jsonSerializer, WebApiRequestOptions requestOptions, bool checkForNotModified, Uri? location)
=> TryCreateValueContentResult(value, statusCode, alternateStatusCode, jsonSerializer, requestOptions, checkForNotModified, location, out var pr, out var ar) ? pr! : ar!;

/// <summary>
/// Try and create an <see cref="IActionResult"/> as either <see cref="ValueContentResult"/> or <see cref="StatusCodeResult"/> as per <see cref="TryCreateValueContentResult"/>; unless <paramref name="value"/> is an instance of <see cref="IActionResult"/> which will return as-is.
/// Try and create an <see cref="IActionResult"/> as either <see cref="ValueContentResult"/> or <see cref="ExtendedStatusCodeResult"/> as per <see cref="TryCreateValueContentResult"/>; unless <paramref name="value"/> is an instance of <see cref="IActionResult"/> which will return as-is.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="statusCode">The primary status code where there is a value.</param>
Expand All @@ -107,7 +107,7 @@ public static IActionResult CreateResult<T>(T value, HttpStatusCode statusCode,
/// <param name="primaryResult">The <see cref="IActionResult"/> where created.</param>
/// <param name="alternateResult">The alternate result where no <paramref name="primaryResult"/>.</param>
/// <returns><c>true</c> indicates that the <paramref name="primaryResult"/> was created; otherwise, <c>false</c> for <paramref name="alternateResult"/> creation.</returns>
public static bool TryCreateValueContentResult<T>(T value, HttpStatusCode statusCode, HttpStatusCode? alternateStatusCode, IJsonSerializer jsonSerializer, WebApiRequestOptions requestOptions, bool checkForNotModified, Uri? location, out IActionResult? primaryResult, out IActionResult? alternateResult)
internal static bool TryCreateValueContentResult<T>(T value, HttpStatusCode statusCode, HttpStatusCode? alternateStatusCode, IJsonSerializer jsonSerializer, WebApiRequestOptions requestOptions, bool checkForNotModified, Uri? location, out IActionResult? primaryResult, out IActionResult? alternateResult)
{
if (value is Results.IResult)
throw new ArgumentException($"The {nameof(value)} must not implement {nameof(Results.IResult)}; the underlying {nameof(Results.IResult.Value)} must be unwrapped before invoking.", nameof(value));
Expand Down Expand Up @@ -182,10 +182,10 @@ public static bool TryCreateValueContentResult<T>(T value, HttpStatusCode status
ExecutionContext.Current.IsTextSerializationEnabled = isTextSerializationEnabled;

// Check for not-modified and return status accordingly.
if (checkForNotModified && result.etag == requestOptions.ETag)
if (checkForNotModified && requestOptions.ETag is not null && result.etag == requestOptions.ETag)
{
primaryResult = null;
alternateResult = new StatusCodeResult((int)HttpStatusCode.NotModified);
alternateResult = new ExtendedStatusCodeResult(HttpStatusCode.NotModified);
return false;
}

Expand Down
8 changes: 5 additions & 3 deletions src/CoreEx.AspNetCore/WebApis/WebApiBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ public abstract class WebApiBase(ExecutionContext executionContext, SettingsBase
/// Gets or sets the list of secondary correlation identifier names.
/// </summary>
/// <remarks>Searches the <see cref="HttpRequest.Headers"/> for <see cref="HttpConsts.CorrelationIdHeaderName"/> or one of the other <see cref="SecondaryCorrelationIdNames"/> to determine the <see cref="ExecutionContext.CorrelationId"/> (uses first value found in sequence).</remarks>
public IEnumerable<string> SecondaryCorrelationIdNames { get; set; } = new string[] { "x-ms-client-tracking-id" };
public IEnumerable<string> SecondaryCorrelationIdNames { get; set; } = ["x-ms-client-tracking-id"];

/// <summary>
/// Gets the list of correlation identifier names, being <see cref="HttpConsts.CorrelationIdHeaderName"/> and <see cref="SecondaryCorrelationIdNames"/> (inclusive).
/// </summary>
/// <returns>The list of correlation identifier names.</returns>
public virtual IEnumerable<string> GetCorrelationIdNames()
{
var list = new List<string>(new string[] { HttpConsts.CorrelationIdHeaderName });
var list = new List<string>([HttpConsts.CorrelationIdHeaderName]);
list.AddRange(SecondaryCorrelationIdNames);
return list;
}
Expand Down Expand Up @@ -173,7 +173,9 @@ public static async Task<IActionResult> CreateActionResultFromExceptionAsync(Web
if (owner is not null && !owner.Invoker.CatchAndHandleExceptions)
throw exception;

logger.LogDebug("WebApi error: {Error} [{Type}]", exception.Message, exception.GetType().Name);
// Also check for an inner IExtendedException where the outer is an AggregateException; if so, then use.
if (exception is AggregateException aex && aex.InnerException is not null && aex.InnerException is IExtendedException)
exception = aex.InnerException;

IActionResult? ar = null;
if (exception is IExtendedException eex)
Expand Down
12 changes: 0 additions & 12 deletions src/CoreEx.AspNetCore/WebApis/WebApiInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -55,9 +54,6 @@ protected async override Task<TResult> OnInvokeAsync<TResult>(InvokeArgs invokeA
// Start logging scope and begin work.
using (owner.Logger.BeginScope(new Dictionary<string, object>() { { HttpConsts.CorrelationIdHeaderName, owner.ExecutionContext.CorrelationId } }))
{
owner.Logger.LogDebug("WebApi started.");
var stopwatch = owner.Logger.IsEnabled(LogLevel.Debug) ? Stopwatch.StartNew() : null;

try
{
return await func(invokeArgs, cancellationToken).ConfigureAwait(false);
Expand All @@ -71,14 +67,6 @@ protected async override Task<TResult> OnInvokeAsync<TResult>(InvokeArgs invokeA
owner.Logger.LogDebug("WebApi unhandled exception: {Error} [{Type}]", ex.Message, ex.GetType().Name);
throw;
}
finally
{
if (stopwatch is not null)
{
stopwatch.Stop();
owner.Logger.LogDebug("WebApi elapsed: {Elapsed}ms.", stopwatch.Elapsed.TotalMilliseconds);
}
}
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/CoreEx.AspNetCore/WebApis/WebApiParam.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

using CoreEx.Entities;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Net;

namespace CoreEx.AspNetCore.WebApis
{
Expand All @@ -12,6 +14,7 @@ namespace CoreEx.AspNetCore.WebApis
/// <param name="webApi">The parent <see cref="WebApiBase"/> instance.</param>
/// <param name="requestOptions">The <see cref="WebApiRequestOptions"/>.</param>
/// <param name="operationType">The <see cref="CoreEx.OperationType"/>.</param>
/// <remarks>This enables access to the corresponding <see cref="WebApi"/>, <see cref="Request"/>, <see cref="RequestOptions"/>, etc.</remarks>
public class WebApiParam(WebApiBase webApi, WebApiRequestOptions requestOptions, OperationType operationType = OperationType.Unspecified)
{
/// <summary>
Expand Down Expand Up @@ -55,5 +58,23 @@ public T InspectValue<T>(T value)

return value;
}

/// <summary>
/// Creates the <see cref="IActionResult"/> as either <see cref="ValueContentResult"/> or <see cref="ExtendedStatusCodeResult"/> (<see cref="IExtendedActionResult"/>) as per <see cref="ValueContentResult.TryCreateValueContentResult"/>; unless <paramref name="value"/> is an instance of <see cref="IActionResult"/> which will return as-is.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="statusCode">The primary status code where there is a value.</param>
/// <param name="alternateStatusCode">The alternate status code where there is not a value (i.e. <c>null</c>).</param>
/// <param name="checkForNotModified">Indicates whether to check for <see cref="HttpStatusCode.NotModified"/> by comparing request and response <see cref="IETag.ETag"/> values.</param>
/// <param name="location">The <see cref="Microsoft.AspNetCore.Http.Headers.ResponseHeaders.Location"/> <see cref="Uri"/>.</param>
/// <returns>The <see cref="IActionResult"/>.</returns>
public IActionResult CreateActionResult<T>(T value, HttpStatusCode statusCode, HttpStatusCode? alternateStatusCode = null, bool checkForNotModified = true, Uri? location = null)
=> ValueContentResult.CreateResult(value, statusCode, alternateStatusCode, WebApi.JsonSerializer, RequestOptions, checkForNotModified, location);

/// <summary>
/// Creates the <see cref="IActionResult"/> as a <see cref="ExtendedStatusCodeResult"/> (<see cref="IExtendedActionResult"/>).
/// </summary>
/// <param name="statusCode">The status code.</param>
public static IActionResult CreateActionResult(HttpStatusCode statusCode) => new ExtendedStatusCodeResult(statusCode);
}
}
1 change: 1 addition & 0 deletions src/CoreEx.AspNetCore/WebApis/WebApiParamT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace CoreEx.AspNetCore.WebApis
/// <summary>
/// Represents a <see cref="WebApi"/> parameter with a request <see cref="Value"/>.
/// </summary>
/// <remarks>This enables access to the corresponding <see cref="WebApiParam.WebApi"/>, <see cref="WebApiParam.Request"/>, <see cref="WebApiParam.RequestOptions"/>, deserialized <see cref="Value"/>, etc.</remarks>
public class WebApiParam<T> : WebApiParam
{
/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions src/CoreEx.Azure/CoreEx.Azure.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@

<ItemGroup>
<PackageReference Include="Azure.Data.Tables" Version="12.9.1" />
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.18.1" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.22.0" />
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.18.2" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.22.1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.22.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.2.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="5.16.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureAppConfiguration" Version="7.3.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureAppConfiguration" Version="8.0.0" />
</ItemGroup>

<Import Project="..\..\Common.targets" />
Expand Down
2 changes: 1 addition & 1 deletion src/CoreEx.Cosmos/CoreEx.Cosmos.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<Import Project="..\..\Common.targets" />

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.43.1" />
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.44.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit 6738a92

Please sign in to comment.