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.12.0 #91

Merged
merged 6 commits into from
Feb 26, 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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

Represents the **NuGet** versions.

## v3.12.0
- *Enhancement*: Added new `CoreEx.Database.Postgres` project/package to support [PostgreSQL](https://www.postgresql.org/) database capabilities. Primarily encapsulates the open-source [`Npqsql`](https://www.npgsql.org/) .NET ADO database provider for PostgreSQL.
- Added `EncodedStringToUInt32Converter` to support PostgreSQL `xmin` column encoding as the row version/etag.
- *Enhancement*: Migrated sentence case logic from inside `PropertyExpression` into `CoreEx.Text.SentenceCase` to improve discoverablity and reuse opportunities.
- *Fixed:* The `IServiceCollection.AddAzureServiceBusClient` extension method as been removed; the `ServiceBusClient` will need to be instantiated prior to usage. Standard approach is for consumers to create client instances independently.
- *Fixed*: The `WorkOrchestrator.GetAsync<T>()` and `WorkOrchestrator.GetAsync(string type, ..)` methods were not automatically cancelling where expired.
- *Fixed*: The `InvokerArgs` activity tracing updated to correctly capture the `Exception.Message` where an `Exception` has been thrown.
- *Internal*:
- All `throw new ArgumentNullException` checking migrated to the `xxx.ThrowIfNull` extension method equivalent.
- All _Run Code Analysis_ issues resolved.

## v3.11.0
- *Enhancement*: The `ITypedToResult` updated to correctly implement `IToResult` as the simple `ToResult` where required.
- *Enhancement*: Added `Result.AsTask()` and `Result<T>.AsTask` to simplify the conversion to a completed `Task<Result>` or `Task<Result<T>>` where applicable.
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.11.0</Version>
<Version>3.12.0</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
11 changes: 9 additions & 2 deletions CoreEx.sln
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreEx.UnitTesting", "src\C
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreEx.UnitTesting.NUnit", "src\CoreEx.UnitTesting.NUnit\CoreEx.UnitTesting.NUnit.csproj", "{91910971-4B1A-4791-9BB4-65FAB3ED3C76}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreEx.TestFunctionIso", "tests\CoreEx.TestFunctionIso\CoreEx.TestFunctionIso.csproj", "{6F7B4F1E-3C3A-4CD7-A9BF-973A5053C1C8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreEx.TestFunctionIso", "tests\CoreEx.TestFunctionIso\CoreEx.TestFunctionIso.csproj", "{6F7B4F1E-3C3A-4CD7-A9BF-973A5053C1C8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreEx.Test2", "tests\CoreEx.Test2\CoreEx.Test2.csproj", "{910B5894-46BC-4427-95D6-2804F06458E3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreEx.Test2", "tests\CoreEx.Test2\CoreEx.Test2.csproj", "{910B5894-46BC-4427-95D6-2804F06458E3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreEx.Database.Postgres", "src\CoreEx.Database.Postgres\CoreEx.Database.Postgres.csproj", "{C042AC2A-415D-432E-83FA-B911FD9ED378}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -209,6 +211,10 @@ Global
{910B5894-46BC-4427-95D6-2804F06458E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{910B5894-46BC-4427-95D6-2804F06458E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{910B5894-46BC-4427-95D6-2804F06458E3}.Release|Any CPU.Build.0 = Release|Any CPU
{C042AC2A-415D-432E-83FA-B911FD9ED378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C042AC2A-415D-432E-83FA-B911FD9ED378}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C042AC2A-415D-432E-83FA-B911FD9ED378}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C042AC2A-415D-432E-83FA-B911FD9ED378}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -245,6 +251,7 @@ Global
{91910971-4B1A-4791-9BB4-65FAB3ED3C76} = {D2C61D4A-2A6D-4284-BF9D-09F51BA735B8}
{6F7B4F1E-3C3A-4CD7-A9BF-973A5053C1C8} = {3145DCB9-98FB-4519-BCC0-75A22A252EDC}
{910B5894-46BC-4427-95D6-2804F06458E3} = {3145DCB9-98FB-4519-BCC0-75A22A252EDC}
{C042AC2A-415D-432E-83FA-B911FD9ED378} = {4B6BC31E-93B1-42B0-AE09-AD85AC4DB657}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B4566D2-9B22-4E27-9654-402BDBA6C744}
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ Package | Status | Source & documentation
`CoreEx.Azure` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Azure.svg)](https://badge.fury.io/nu/CoreEx.Azure) | [Link](./src/CoreEx.Azure)
`CoreEx.Cosmos` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Cosmos.svg)](https://badge.fury.io/nu/CoreEx.Cosmos) | [Link](./src/CoreEx.Cosmos)
`CoreEx.Database` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Database.svg)](https://badge.fury.io/nu/CoreEx.Database) | [Link](./src/CoreEx.Database)
`CoreEx.Database.SqlServer` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Database.SqlServer.svg)](https://badge.fury.io/nu/CoreEx.Database.SqlServer) | [Link](./src/CoreEx.Database.SqlServer)
`CoreEx.Database.MySql` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Database.MySql.svg)](https://badge.fury.io/nu/CoreEx.Database.MySql) | [Link](./src/CoreEx.Database.MySql)
`CoreEx.Database.Postgres` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Database.Postgres.svg)](https://badge.fury.io/nu/CoreEx.Database.Postgres) | [Link](./src/CoreEx.Database.Postgres)
`CoreEx.Database.SqlServer` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Database.SqlServer.svg)](https://badge.fury.io/nu/CoreEx.Database.SqlServer) | [Link](./src/CoreEx.Database.SqlServer)
`CoreEx.EntityFrameworkCore` | [![NuGet version](https://badge.fury.io/nu/CoreEx.EntityFrameworkCore.svg)](https://badge.fury.io/nu/CoreEx.EntityFrameworkCore) | [Link](./src/CoreEx.EntityFrameworkCore)
`CoreEx.FluentValidation` | [![NuGet version](https://badge.fury.io/nu/CoreEx.FluentValidation.svg)](https://badge.fury.io/nu/CoreEx.FluentValidation) | [Link](./src/CoreEx.FluentValidation)
`CoreEx.Newtonsoft` | [![NuGet version](https://badge.fury.io/nu/CoreEx.Newtonsoft.svg)](https://badge.fury.io/nu/CoreEx.Newtonsoft) | [Link](./src/CoreEx.Newtonsoft)
Expand Down Expand Up @@ -59,9 +60,9 @@ These other _Avanade_ repositories leverage _CoreEx_:

Repo | Description
-|-
[Beef](https://github.com/Avanade/beef) | Code-generation capabilities to support the industrialization of API development leveraging `CoreEx` as the primary runtime framework (_Beef_ version `v5+`).
[DbEx](https://github.com/Avanade/dbex) | Provides database extensions for DbUp-inspired database migrations.
[NTangle](https://github.com/Avanade/ntangle) | Change Data Capture (CDC) code generation tool and runtime.
[*Beef*](https://github.com/Avanade/beef) | Code-generation capabilities to support the industrialization of API development leveraging `CoreEx` as the primary runtime framework (_Beef_ version `v5+`).
[*DbEx*](https://github.com/Avanade/dbex) | Provides database extensions for DbUp-inspired database migrations.
[*NTangle*](https://github.com/Avanade/ntangle) | Change Data Capture (CDC) code generation tool and runtime.

<br/>

Expand Down
1 change: 1 addition & 0 deletions nuget-publish.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ param(
"src\CoreEx.Database",
"src\CoreEx.Database.SqlServer",
"src\CoreEx.Database.MySql",
"src\CoreEx.Database.Postgres",
"src\CoreEx.EntityFrameworkCore",
"src\CoreEx.Cosmos",
"src\CoreEx.OData",
Expand Down
4 changes: 2 additions & 2 deletions samples/My.Hr/My.Hr.Api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Azure.Monitor.OpenTelemetry.AspNetCore;
using OpenTelemetry.Instrumentation.AspNetCore;
using OpenTelemetry.Trace;
using Az = Azure.Messaging.ServiceBus;

namespace My.Hr.Api;

Expand All @@ -28,9 +28,9 @@ public void ConfigureServices(IServiceCollection services)
.AddEventDataSerializer()
.AddEventDataFormatter()
.AddEventPublisher()
.AddSingleton(sp => new Az.ServiceBusClient(sp.GetRequiredService<HrSettings>().ServiceBusConnection__fullyQualifiedNamespace))
.AddAzureServiceBusSender()
.AddAzureServiceBusPurger()
.AddAzureServiceBusClient(connectionName: nameof(HrSettings.ServiceBusConnection), configure: (o, sp) => o.RetryOptions.MaxRetries = 3)
.AddJsonMergePatch()
.AddWebApi((_, c) => c.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? WebApiBase.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
.AddReferenceDataContentWebApi()
Expand Down
7 changes: 3 additions & 4 deletions samples/My.Hr/My.Hr.Functions/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@
using CoreEx.Database;
using CoreEx.DataBase.HealthChecks;
using CoreEx.HealthChecks;
using CoreEx.RefData;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using My.Hr.Business;
using My.Hr.Business.Data;
using My.Hr.Business.External;
using My.Hr.Business.Services;
using Az = Azure.Messaging.ServiceBus;

[assembly: FunctionsStartup(typeof(My.Hr.Functions.Startup))]

Expand All @@ -40,12 +39,12 @@ public override void Configure(IFunctionsHostBuilder builder)
.AddEventDataSerializer()
.AddEventDataFormatter()
.AddEventPublisher()
.AddSingleton(sp => new Az.ServiceBusClient(sp.GetRequiredService<HrSettings>().ServiceBusConnection__fullyQualifiedNamespace))
.AddAzureServiceBusSender()
.AddWebApi((_, c) => c.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? WebApiBase.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
.AddJsonMergePatch()
.AddWebApiPublisher()
.AddAzureServiceBusSubscriber()
.AddAzureServiceBusClient(connectionName: nameof(HrSettings.ServiceBusConnection));
.AddAzureServiceBusSubscriber();

// Register the health checks.
builder.Services
Expand Down
2 changes: 1 addition & 1 deletion samples/My.Hr/My.Hr.UnitTest/appsettings.unittest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"DOTNET_ENVIRONMENT": "Development",
"VerificationResultsQueueName": "verificationResults",
"VerificationQueueName": "pendingVerifications",
"ServiceBusConnection__fullyQualifiedNamespace": "topsecret",
"ServiceBusConnection__fullyQualifiedNamespace": "Endpoint=sb://top-secret.servicebus.windows.net/;SharedAccessKeyName=top-secret;SharedAccessKey=top-encrypted-secret;",
"AgifyApiEndpointUri": "https://api.agify.mock.io",
"NationalizeApiClientApiEndpointUri": "https://api.nationalize.mock.io",
"GenderizeApiClientApiEndpointUri": "https://api.genderize.mock.io",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static Task<ReferenceDataMultiDictionary> GetNamedAsync(this ReferenceDat
/// <summary>
/// Perform a further split of the string values.
/// </summary>
private static IEnumerable<string> SplitStringValues(IEnumerable<string> values)
private static List<string> SplitStringValues(IEnumerable<string> values)
{
var list = new List<string>();
foreach (var value in values)
Expand Down
2 changes: 1 addition & 1 deletion src/CoreEx.AspNetCore/HealthChecks/HealthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public async Task<IActionResult> RunAsync()
/// <summary>
/// Builds the health report response.
/// </summary>
private IActionResult BuildResponse(HealthReport healthReport)
private ContentResult BuildResponse(HealthReport healthReport)
{
var code = healthReport.Status == HealthStatus.Healthy ? HttpStatusCode.OK : HttpStatusCode.ServiceUnavailable;

Expand Down
5 changes: 2 additions & 3 deletions src/CoreEx.AspNetCore/WebApis/ValueContentResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using CoreEx.AspNetCore.Http;
using CoreEx.Entities;
using CoreEx.Json;
using CoreEx.Results;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
Expand Down Expand Up @@ -159,12 +158,12 @@ public static bool TryCreateValueContentResult<T>(T value, HttpStatusCode status
bool hasETag = TryGetETag(val, out var etag);

Action<IJsonPreFilterInspector>? inspector;
if (requestOptions.IncludeFields != null && requestOptions.IncludeFields.Any())
if (requestOptions.IncludeFields != null && requestOptions.IncludeFields.Length > 0)
{
inspector = hasETag ? null : fi => etag = GenerateETag(requestOptions, val, fi.ToJsonString(), jsonSerializer);
jsonSerializer.TryApplyFilter(val, requestOptions.IncludeFields, out json, JsonPropertyFilter.Include, preFilterInspector: inspector);
}
else if (requestOptions.ExcludeFields != null && requestOptions.ExcludeFields.Any())
else if (requestOptions.ExcludeFields != null && requestOptions.ExcludeFields.Length > 0)
{
inspector = hasETag ? null : fi => etag = GenerateETag(requestOptions, val, fi.ToJsonString(), jsonSerializer);
jsonSerializer.TryApplyFilter(val, requestOptions.ExcludeFields, out json, JsonPropertyFilter.Exclude, preFilterInspector: inspector);
Expand Down
6 changes: 3 additions & 3 deletions src/CoreEx.AspNetCore/WebApis/WebApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,7 @@ private async Task<IActionResult> PutInternalAsync<TValue>(HttpRequest request,

// Get the current value before we perform the update; also performing a concurrency match.
var cvalue = await get(wap, ct).ConfigureAwait(false);
var ex = cvalue == null ? new NotFoundException() : ConcurrencyETagMatching(wap, cvalue, wapv!.Value, simulatedConcurrency);
var ex = cvalue == null ? (Exception)new NotFoundException() : ConcurrencyETagMatching(wap, cvalue, wapv!.Value, simulatedConcurrency);
if (ex is not null)
return await CreateActionResultFromExceptionAsync(this, request.HttpContext, ex, Settings, Logger, OnUnhandledExceptionAsync, cancellationToken).ConfigureAwait(false);

Expand All @@ -708,7 +708,7 @@ private async Task<IActionResult> PutInternalAsync<TValue>(HttpRequest request,
/// <summary>
/// Where etags are supported or automatic concurrency then we need to make sure one was provided up-front and match.
/// </summary>
private Exception? ConcurrencyETagMatching<TValue>(WebApiParam wap, TValue getValue, TValue putValue, bool autoConcurrency)
private ConcurrencyException? ConcurrencyETagMatching<TValue>(WebApiParam wap, TValue getValue, TValue putValue, bool autoConcurrency)
{
var et = putValue as IETag;
if (et != null || autoConcurrency)
Expand Down Expand Up @@ -844,7 +844,7 @@ public async Task<IActionResult> PatchAsync<TValue>(HttpRequest request, Func<We
{
// Get the current value and perform a concurrency match before we perform the merge.
var value = await get(wap, ct2).ConfigureAwait(false);
var ex = value is null ? new NotFoundException() : ConcurrencyETagMatching(wap, value, jpv, simulatedConcurrency);
var ex = value is null ? (Exception)new NotFoundException() : ConcurrencyETagMatching(wap, value, jpv, simulatedConcurrency);
if (ex is not null)
throw ex;

Expand Down
2 changes: 1 addition & 1 deletion src/CoreEx.AspNetCore/WebApis/WebApiBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public static IActionResult CreateActionResultFromExtendedException(IExtendedExc
/// <summary>
/// Creates an <see cref="IActionResult"/> from an unexpected <paramref name="exception"/>.
/// </summary>
private static IActionResult CreateActionResultForUnexpectedResult(Exception exception, bool includeExceptionInResult) => includeExceptionInResult
private static ContentResult CreateActionResultForUnexpectedResult(Exception exception, bool includeExceptionInResult) => includeExceptionInResult
? new ContentResult { StatusCode = (int)HttpStatusCode.InternalServerError, ContentType = MediaTypeNames.Text.Plain, Content = $"An unexpected internal server error has occurred. CorrelationId={ExecutionContext.Current.CorrelationId} Exception={exception}" }
: new ContentResult { StatusCode = (int)HttpStatusCode.InternalServerError, ContentType = MediaTypeNames.Text.Plain, Content = $"An unexpected internal server error has occurred. CorrelationId={ExecutionContext.Current.CorrelationId}" };
}
Expand Down
11 changes: 3 additions & 8 deletions src/CoreEx.AutoMapper/AutoMapperConverterWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,18 @@ namespace CoreEx.Mapping.Converters
/// <typeparam name="TDestination">The destination <see cref="Type"/>.</typeparam>
/// <typeparam name="TConverter">The <see cref="IConverter{TSource, TDestination}"/> <see cref="Type"/>.</typeparam>
/// <typeparam name="TSelf">The declaring <see cref="Type"/> itself to enable <see cref="Default"/>.</typeparam>
public abstract class AutoMapperConverterWrapper<TSource, TDestination, TConverter, TSelf>
/// <param name="create">The optional function to create the <typeparamref name="TConverter"/> instance.</param>
public abstract class AutoMapperConverterWrapper<TSource, TDestination, TConverter, TSelf>(Func<TConverter>? create = null)
where TConverter : IConverter<TSource, TDestination>, new()
where TSelf : AutoMapperConverterWrapper<TSource, TDestination, TConverter, TSelf>, new()
{
private readonly Func<TConverter> _create;
private readonly Func<TConverter> _create = create ?? (() => new TConverter());

/// <summary>
/// Gets or sets the default (singleton) instance.
/// </summary>
public static TSelf Default { get; set; } = new();

/// <summary>
/// Initializes a new instance of the <see cref="AutoMapperConverterWrapper{TSource, TDestination, TConverter, TSelf}"/> class.
/// </summary>
/// <param name="create">The optional function to create the <typeparamref name="TConverter"/> instance.</param>
public AutoMapperConverterWrapper(Func<TConverter>? create = null) => _create = create ?? (() => new TConverter());

/// <summary>
/// Gets the source to destination <see cref="AutoMapper.IValueConverter{TSource, TDestination}"/>.
/// </summary>
Expand Down
Loading
Loading