Skip to content

Commit e683b4c

Browse files
authored
v3.11.0 (#90)
* v3.11.0 changes. * Further changes related to testing. * Final tweaks and tests. * Fix changelog text. * Fix failing test to be inconclusive.
1 parent e91ef00 commit e683b4c

File tree

86 files changed

+2858
-343
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+2858
-343
lines changed

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
Represents the **NuGet** versions.
44

5+
## v3.11.0
6+
- *Enhancement*: The `ITypedToResult` updated to correctly implement `IToResult` as the simple `ToResult` where required.
7+
- *Enhancement*: Added `Result.AsTask()` and `Result<T>.AsTask` to simplify the conversion to a completed `Task<Result>` or `Task<Result<T>>` where applicable.
8+
- *Enhancement*: Added `IResult.IsFailureOfType<TException>` to indicate whether the result is in a failure state and the underlying error is of the specified `TException` type.
9+
- *Enhancement*: Added `EventTemplate` property to the `WebApiPublisherArgs` and `WebApiPublisherCollectionArgs` to define an `EventData` template.
10+
- *Enhancement*: Added `SubscriberBase<T>` constructor overload to enable specification of `valueValidator` and `ValueIsRequired` parameters versus setting properties directly simplifying usage.
11+
- *Enhancement:* Enum renames to improve understanding of intent for event subscribing logic: `ErrorHandling.None` is now `ErrorHandling.HandleByHost` and `ErrorHandling.Handle` is now `ErrorHandling.HandleBySubscriber`.
12+
- *Enhancement:* Simplified the `ServiceBusSubscriber.Receive` methods by removing the `afterReceive` parameter which served no real purpose; also, reversed the `validator` and `valueIsRequired` parameters (order as stated) as the `validator` is more likely to be specified than `valueIsRequired` which defaults to `true`.
13+
- *Enhancement*: Added `CoreEx.Hosting.Work` namespace which includes light-weight/simple foundational capabilities to track and orchestrate work; intended for the likes of [_asynchronous request-response_](https://learn.microsoft.com/en-us/azure/architecture/patterns/async-request-reply) scenarios.
14+
- Added `IWorkStatePersistence` to enable flexible/pluggable persistence of the `WorkState` and resulting data; includes `InMemoryWorkStatePersistence` for testing, `FileWorkStatePersistence` for file-based, and `TableWorkStatePersistence` leveraging Azure table storage.
15+
- Added `WorkStateOrchestrator` support to `EventSubscriberBase`, including corresponding `ServiceBusSubscriber` and `ServiceBusOrchestratedSubscriber` using the `ServiceBusMessage.MessageId` as the corresponding `WorkState.Id`.
16+
- Extended `EventSubscriberArgs` to support a new `SetWorkStateDataAsync` operation to enable the setting of the underlying `WorkState` data is a consistent manner where using the event subscriber capabilities.
17+
518
## v3.10.0
619
- *Enhancement*: The `WebApiPublisher` publishing methods have been simplified (breaking change), primarily through the use of a new _argument_ that encapsulates the various related options. This will enable the addition of further options in the future without resulting in breaking changes or adding unneccessary complexities. The related [`README`](./src/CoreEx.AspNetCore/WebApis/README.md) has been updated to document.
720
- *Enhancement*: Added `ValidationUseJsonNames` to `SettingsBase` (defaults to `true`) to allow setting `ValidationArgs.DefaultUseJsonNames` to be configurable.

Common.targets

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>3.10.0</Version>
3+
<Version>3.11.0</Version>
44
<LangVersion>preview</LangVersion>
55
<Authors>Avanade</Authors>
66
<Company>Avanade</Company>

samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
<ItemGroup>
1515
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
16-
<PackageReference Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.0.0" />
16+
<PackageReference Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.1.0" />
1717
</ItemGroup>
1818

1919
<ItemGroup>

samples/My.Hr/My.Hr.Api/Startup.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public void ConfigureServices(IServiceCollection services)
3232
.AddAzureServiceBusPurger()
3333
.AddAzureServiceBusClient(connectionName: nameof(HrSettings.ServiceBusConnection), configure: (o, sp) => o.RetryOptions.MaxRetries = 3)
3434
.AddJsonMergePatch()
35-
.AddWebApi(c => c.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? WebApiBase.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
35+
.AddWebApi((_, c) => c.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? WebApiBase.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
3636
.AddReferenceDataContentWebApi()
3737
.AddRequestCache();
3838

samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</ItemGroup>
2222

2323
<ItemGroup>
24-
<PackageReference Include="DbEx.SqlServer" Version="2.3.15" />
24+
<PackageReference Include="DbEx.SqlServer" Version="2.4.0" />
2525
</ItemGroup>
2626

2727
<ItemGroup>

samples/My.Hr/My.Hr.Functions/My.Hr.Functions.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<ItemGroup>
88
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.OpenApi" Version="1.5.1" />
99
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
10-
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.2.0" />
10+
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.3.0" />
1111
</ItemGroup>
1212

1313
<ItemGroup>

samples/My.Hr/My.Hr.Functions/Startup.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public override void Configure(IFunctionsHostBuilder builder)
4141
.AddEventDataFormatter()
4242
.AddEventPublisher()
4343
.AddAzureServiceBusSender()
44-
.AddWebApi(c => c.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? WebApiBase.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
44+
.AddWebApi((_, c) => c.UnhandledExceptionAsync = (ex, _, _) => Task.FromResult(ex is DbUpdateConcurrencyException efex ? WebApiBase.CreateActionResultFromExtendedException(new ConcurrencyException()) : null))
4545
.AddJsonMergePatch()
4646
.AddWebApiPublisher()
4747
.AddAzureServiceBusSubscriber()

samples/My.Hr/My.Hr.UnitTest/EmployeeControllerTest.cs

+1
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ public void C110_Create_Success()
184184
.AssertCreated()
185185
.AssertValue(e, "Id", "ETag")
186186
.AssertLocationHeader<Employee>(v => new Uri($"api/employees/{v!.Id}", UriKind.Relative))
187+
.AssertLocationHeaderContains("api/employees") // Just for kicks testing both types work
187188
.GetValue<Employee>();
188189

189190
// Do a GET to make sure it is in the database and all fields equal.

samples/My.Hr/My.Hr.UnitTest/My.Hr.UnitTest.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<ItemGroup>
2323
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
2424
<PackageReference Include="NUnit" Version="4.0.1" />
25-
<PackageReference Include="NUnit.Analyzers" Version="3.10.0">
25+
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
2626
<PrivateAssets>all</PrivateAssets>
2727
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2828
</PackageReference>

src/CoreEx.AspNetCore/Abstractions/AspNetCoreServiceCollectionExtensions.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ public static class AspNetCoreServiceCollectionExtensions
2727
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
2828
/// <param name="configure">The action to enable the <see cref="WebApi"/> to be further configured.</param>
2929
/// <returns>The <see cref="IServiceCollection"/>.</returns>
30-
public static IServiceCollection AddWebApi(this IServiceCollection services, Action<WebApi>? configure = null) => CheckServices(services).AddScoped(sp =>
30+
public static IServiceCollection AddWebApi(this IServiceCollection services, Action<IServiceProvider, WebApi>? configure = null) => CheckServices(services).AddScoped(sp =>
3131
{
3232
var wa = new WebApi(sp.GetRequiredService<ExecutionContext>(), sp.GetRequiredService<SettingsBase>(), sp.GetRequiredService<IJsonSerializer>(), sp.GetRequiredService<ILogger<WebApi>>(), sp.GetService<WebApiInvoker>(), sp.GetService<IJsonMergePatch>());
33-
configure?.Invoke(wa);
33+
configure?.Invoke(sp, wa);
3434
return wa;
3535
});
3636

@@ -40,10 +40,10 @@ public static IServiceCollection AddWebApi(this IServiceCollection services, Act
4040
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
4141
/// <param name="configure">The action to enable the <see cref="WebApi"/> to be further configured.</param>
4242
/// <returns>The <see cref="IServiceCollection"/>.</returns>
43-
public static IServiceCollection AddReferenceDataContentWebApi(this IServiceCollection services, Action<ReferenceDataContentWebApi>? configure = null) => CheckServices(services).AddScoped(sp =>
43+
public static IServiceCollection AddReferenceDataContentWebApi(this IServiceCollection services, Action<IServiceProvider, ReferenceDataContentWebApi>? configure = null) => CheckServices(services).AddScoped(sp =>
4444
{
4545
var wa = new ReferenceDataContentWebApi(sp.GetRequiredService<ExecutionContext>(), sp.GetRequiredService<SettingsBase>(), sp.GetRequiredService<IReferenceDataContentJsonSerializer>(), sp.GetRequiredService<ILogger<WebApi>>(), sp.GetService<WebApiInvoker>(), sp.GetService<IJsonMergePatch>());
46-
configure?.Invoke(wa);
46+
configure?.Invoke(sp, wa);
4747
return wa;
4848
});
4949

@@ -53,10 +53,10 @@ public static IServiceCollection AddReferenceDataContentWebApi(this IServiceColl
5353
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
5454
/// <param name="configure">The action to enable the <see cref="WebApi"/> to be further configured.</param>
5555
/// <returns>The <see cref="IServiceCollection"/>.</returns>
56-
public static IServiceCollection AddWebApiPublisher(this IServiceCollection services, Action<WebApiPublisher>? configure = null) => CheckServices(services).AddScoped(sp =>
56+
public static IServiceCollection AddWebApiPublisher(this IServiceCollection services, Action<IServiceProvider, WebApiPublisher>? configure = null) => CheckServices(services).AddScoped(sp =>
5757
{
5858
var wap = new WebApiPublisher(sp.GetRequiredService<IEventPublisher>(), sp.GetRequiredService<ExecutionContext>(), sp.GetRequiredService<SettingsBase>(), sp.GetRequiredService<IJsonSerializer>(), sp.GetRequiredService<ILogger<WebApiPublisher>>(), sp.GetService<WebApiInvoker>());
59-
configure?.Invoke(wap);
59+
configure?.Invoke(sp,wap);
6060
return wap;
6161
});
6262
}

src/CoreEx.AspNetCore/README.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -203,24 +203,25 @@ Depending on the overload used (as defined above), an optional _argument_ can be
203203

204204
The following argurment types are supported:
205205
- [`WebApiPublisherArgs<TValue>`](./WebApis/WebApiPublisherArgsT.cs) - single message with no mapping.
206-
- [`WebApiPublisherArgs<TValue, TEventValue>`](./WebApis/WebApiPublisherArgsT2.cs) - single message _with_ mapping.
206+
- [`WebApiPublisherArgs<TValue, TEventValue>`](./WebApis/WebApiPublisherArgsT2.cs) - single message _with_ [mapping](https://github.com/Avanade/CoreEx/tree/main/src/CoreEx/Mapping).
207207
- [`WebApiPublisherCollectionArgs<TColl, TItem>`](./WebApis/WebApiPublisherCollectionArgsT.cs) - collection of messages with no mapping.
208-
- [`WebApiPublisherCollectionArgs<TColl, TItem, TEventItem>`](./WebApis/WebApiPublisherCollectionArgsT2.cs) - collection of messages _with_ mapping.
208+
- [`WebApiPublisherCollectionArgs<TColl, TItem, TEventItem>`](./WebApis/WebApiPublisherCollectionArgsT2.cs) - collection of messages _with_ [mapping](https://github.com/Avanade/CoreEx/tree/main/src/CoreEx/Mapping).
209209
210210
The arguments will have the following properties depending on the supported functionality. The sequence defines the order in which each of the properties is enacted (orchestrated) internally. Where a failure or exception occurs then the execution will be aborted and the corresponding `IActionResult` returned (including the likes of logging etc. where applicable).
211211

212212
Property | Description | Sequence
213213
-|-
214214
`EventName` | The event destintion name (e.g. Queue or Topic name) where applicable. | N/A
215+
`EventTemplate` | The [`EventData`](../CoreEx/Events/EventData.cs) template to be used to create the message/event. | N/A
215216
`StatusCode` | The resulting status code where successful. Defaults to `204-Accepted`. | N/A
216217
`OperationType` | The [`OperationType`](../CoreEx/OperationType.cs). Defaults to `OperationType.Unspecified`. | N/A
217-
`MaxCollectionSize` | The maximum collection size allowed/supported. | 1
218+
`MaxCollectionSize` | The maximum collection size allowed/supported (where applicable). | 1
218219
`OnBeforeValidateAsync` | The function to be invoked before the request value is validated; opportunity to modify contents. | 2
219220
`Validator` | The `IValidator<T>` to validate the request value. | 3
220221
`OnBeforeEventAsync` | The function to be invoked after validation / before event; opportunity to modify contents. | 4
221-
`Mapper` | The `IMapper<TSource, TDestination>` override. | 5
222+
`Mapper` | The `IMapper<TSource, TDestination>` override (where applicable). | 5
222223
`OnEvent` | The action to be invoked once converted to an [`EventData`](../CoreEx/Events/EventData.cs); opportunity to modify contents. | 6
223-
`CreateSuccessResult` | The function to be invoked to create/override the success `IActionResult`. | 7
224+
`CreateSuccessResult` | The function to be invoked to create/override the success `IActionResult`. Defaults to returning specified `StatusCode`. | 7
224225

225226
<br/>
226227

src/CoreEx.AspNetCore/WebApis/IWebApiPublisherArgs.cs

+36-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx
22

33
using CoreEx.Events;
4+
using CoreEx.Hosting.Work;
45
using CoreEx.Mapping;
56
using CoreEx.Results;
67
using CoreEx.Validation;
@@ -30,14 +31,25 @@ public interface IWebApiPublisherArgs<TValue, TEventValue>
3031
/// <remarks>Will leverage either <see cref="IEventPublisher.Publish(EventData[])"/> or <see cref="IEventPublisher.PublishNamed(string, EventData[])"/> depending on whether name is specified or not.</remarks>
3132
string? EventName { get; }
3233

34+
/// <summary>
35+
/// Gets or sets the optional <see cref="EventData"/> to use as a template when instantiating the <see cref="EventData"/> for publishing.
36+
/// </summary>
37+
/// <remarks>Will use the <see cref="EventData(EventDataBase)"/> constructor to copy from the template.</remarks>
38+
EventData? EventTemplate { get; }
39+
3340
/// <summary>
3441
/// Gets or sets the <see cref="HttpStatusCode"/> where successful.
3542
/// </summary>
3643
/// <remarks>Defaults to <see cref="HttpStatusCode.Accepted"/>.</remarks>
3744
HttpStatusCode StatusCode { get; }
3845

3946
/// <summary>
40-
/// Gets or sets the optional validator.
47+
/// Indicates whether the <typeparamref name="TValue"/> is required.
48+
/// </summary>
49+
bool ValueIsRequired { get; }
50+
51+
/// <summary>
52+
/// Gets or sets the optional <typeparamref name="TValue"/> validator.
4153
/// </summary>
4254
IValidator<TValue>? Validator { get; }
4355

@@ -48,21 +60,21 @@ public interface IWebApiPublisherArgs<TValue, TEventValue>
4860
OperationType OperationType { get; }
4961

5062
/// <summary>
51-
/// Gets or sets the on before validation <typeparamref name="TValue"/> modifier function.
63+
/// Gets or sets the on before validation <typeparamref name="TValue"/> function.
5264
/// </summary>
53-
/// <remarks>Enables the value to be modified before validation. The <see cref="Result"/> will allow failures and alike to be returned where applicable.</remarks>
65+
/// <remarks>Enables the likes of security, value modification, etc., before validation. The <see cref="Result"/> will allow failures and alike to be returned where applicable.</remarks>
5466
Func<WebApiParam<TValue>, CancellationToken, Task<Result>>? OnBeforeValidationAsync { get; }
5567

5668
/// <summary>
57-
/// Gets or sets the after validation / on before event <typeparamref name="TValue"/> modifier function.
69+
/// Gets or sets the after validation / on before event <typeparamref name="TValue"/> function.
5870
/// </summary>
59-
/// <remarks>Enables the value to be modified after validation. The <see cref="Result"/> will allow failures and alike to be returned where applicable.</remarks>
71+
/// <remarks>Enables the likes of security, value modification, etc., after validation. The <see cref="Result"/> will allow failures and alike to be returned where applicable.</remarks>
6072
Func<WebApiParam<TValue>, CancellationToken, Task<Result>>? OnBeforeEventAsync { get; }
6173

6274
/// <summary>
6375
/// Gets or sets the <see cref="EventData"/> modifier function.
6476
/// </summary>
65-
/// <remarks>Enables the corresponding <see cref="EventData"/> to be modified prior to publish.</remarks>
77+
/// <remarks>Enables the corresponding <see cref="EventData"/> to be modified prior to publish beyond the <see cref="EventTemplate"/> application.</remarks>
6678
Action<EventData>? OnEvent { get; }
6779

6880
/// <summary>
@@ -75,6 +87,23 @@ public interface IWebApiPublisherArgs<TValue, TEventValue>
7587
/// Gets or sets the function to override the creation of the success <see cref="IActionResult"/>.
7688
/// </summary>
7789
/// <remarks>Defaults to a <see cref="ExtendedStatusCodeResult"/> using the defined <see cref="StatusCode"/>.</remarks>
78-
Func<IActionResult>? CreateSuccessResult { get; }
90+
Func<Task<IActionResult>>? CreateSuccessResultAsync { get; }
91+
92+
/// <summary>
93+
/// Gets or sets the function to create the <see cref="Uri"/> for the <see cref="Microsoft.AspNetCore.Http.Headers.ResponseHeaders.Location"/>.
94+
/// </summary>
95+
/// <remarks>
96+
/// Where enabling the likes of the <i>asynchronous request-response</i> pattern then the <see cref="EventDataBase.Id"/> represents the <see cref="WorkState.Id"/> which is the unique identifier for the work instance and is therefore required.
97+
/// <para>This will not be invoked automatically where the <see cref="CreateSuccessResultAsync"/> is overridden.</para></remarks>
98+
Func<WebApiParam<TValue>, EventData, Uri>? CreateLocation { get; }
99+
100+
/// <summary>
101+
/// Gets or sets the function to create the <see cref="WorkStateArgs"/>.
102+
/// </summary>
103+
/// <remarks><para>The <see cref="WorkStateArgs.Id"/> and <see cref="WorkStateArgs.CorrelationId"/> will be overridden by the <see cref="EventData"/> equivalents after creation to ensure consistencey; therefore, these properties need
104+
/// not be set during create. The <see cref="WorkStateArgs.Key"/> will be set to the <see cref="EventDataBase.Key"/> where <c>null</c>, so also does not need to be explicitly set.</para>
105+
/// An <see cref="InvalidOperationException"/> will occur where this is set and the corresponding <see cref="WebApiPublisher.WorkStateOrchestrator"/> is <c>null</c>. The combination of the two enables
106+
/// the automatic create (<see cref="WorkStateOrchestrator.CreateAsync(WorkStateArgs, CancellationToken)"/>) of <see cref="WorkState"/> tracking to enable the likes of the <i>asynchronous request-response</i> pattern.</remarks>
107+
Func<WorkStateArgs>? CreateWorkStateArgs { get; }
79108
}
80109
}

src/CoreEx.AspNetCore/WebApis/IWebApiPublisherCollectionArgs.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ public interface IWebApiPublisherCollectionArgs<TColl, TItem, TEventItem> where
3333
/// <remarks>Will leverage either <see cref="IEventPublisher.Publish(EventData[])"/> or <see cref="IEventPublisher.PublishNamed(string, EventData[])"/> depending on whether name is specified or not.</remarks>
3434
string? EventName { get; }
3535

36+
/// <summary>
37+
/// Gets or sets the optional <see cref="EventData"/> to use as a template when instantiating the <see cref="EventData"/> for publishing.
38+
/// </summary>
39+
/// <remarks>Will use the <see cref="EventData(EventDataBase)"/> constructor to copy from the template.</remarks>
40+
EventData? EventTemplate { get; }
41+
3642
/// <summary>
3743
/// Gets or sets the <see cref="HttpStatusCode"/> where successful.
3844
/// </summary>
@@ -84,6 +90,6 @@ public interface IWebApiPublisherCollectionArgs<TColl, TItem, TEventItem> where
8490
/// Gets or sets the function to override the creation of the success <see cref="IActionResult"/>.
8591
/// </summary>
8692
/// <remarks>Defaults to a <see cref="ExtendedStatusCodeResult"/> using the defined <see cref="StatusCode"/>.</remarks>
87-
Func<IActionResult>? CreateSuccessResult { get; }
93+
Func<Task<IActionResult>>? CreateSuccessResultAsync { get; }
8894
}
8995
}

0 commit comments

Comments
 (0)