Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3.27.0 #125

Merged
merged 1 commit into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading