-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #241 from max-ieremenko/examples/MessagePackMarsha…
…ller.AOT MessagePackMarshaller.AOT example
- Loading branch information
Showing
31 changed files
with
1,144 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<OutputType>Exe</OutputType> | ||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> | ||
|
||
<InvariantGlobalization>true</InvariantGlobalization> | ||
<PublishAot>true</PublishAot> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="ServiceModel.Grpc.Client.DependencyInjection" /> | ||
<PackageReference Include="ServiceModel.Grpc.DesignTime" /> | ||
<PackageReference Include="ServiceModel.Grpc.MessagePackMarshaller" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging.Console" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Contract\Contract.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<!-- Trimming hint: DataContractMarshallerFactory can be removed --> | ||
<RuntimeHostConfigurationOption Include="ServiceModel.Grpc.DisableDataContractMarshallerFactory" Value="true" Trim="true" /> | ||
</ItemGroup> | ||
|
||
</Project> |
68 changes: 68 additions & 0 deletions
68
Examples/MessagePackMarshaller.AOT/Client/ClientTestFixture.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
using Contract; | ||
using System.Threading.Tasks; | ||
using System.Threading; | ||
using Client.Tools; | ||
|
||
namespace Client; | ||
|
||
internal sealed class ClientTestFixture | ||
{ | ||
private readonly ICalculator _calculator; | ||
|
||
public ClientTestFixture(ICalculator calculator) | ||
{ | ||
_calculator = calculator; | ||
} | ||
|
||
public async Task RunAsync(CancellationToken cancellationToken = default) | ||
{ | ||
await TestCreateRectangleAsync(cancellationToken); | ||
await TestCreateInvalidRectangleAsync(cancellationToken); | ||
await TestShiftAsync(cancellationToken); | ||
await TestGetVerticesAsync(cancellationToken); | ||
await TestGetNumbersAsync(cancellationToken); | ||
} | ||
|
||
private async Task TestCreateRectangleAsync(CancellationToken cancellationToken) | ||
{ | ||
var actual = await _calculator.CreateRectangleAsync((0, 2), (2, 2), (2, 0), (0, 0), cancellationToken); | ||
|
||
actual.ShouldBe(new Rectangle((0, 2), 2, 2), RectangleComparer.Default); | ||
} | ||
|
||
private async Task TestCreateInvalidRectangleAsync(CancellationToken cancellationToken) | ||
{ | ||
var ex = await Should.ThrowAsync<InvalidRectangleException>( | ||
() => _calculator.CreateRectangleAsync((0, 2), (2, 1), (2, 0), (0, 0), cancellationToken)); | ||
|
||
ex.Message.ShouldContain("invalid rectangle"); | ||
ex.Points.ShouldBe([(0, 2), (2, 1), (2, 0), (0, 0)], PointComparer.Default); | ||
} | ||
|
||
private async Task TestShiftAsync(CancellationToken cancellationToken) | ||
{ | ||
var points = AsyncEnumerable.AsAsync<Point>((0, 2), (2, 2)); | ||
|
||
var actual = await _calculator.ShiftAsync(points, 1, 2, cancellationToken); | ||
|
||
actual.ShouldBe([(1, 4), (3, 4)], PointComparer.Default); | ||
} | ||
|
||
private async Task TestGetVerticesAsync(CancellationToken cancellationToken) | ||
{ | ||
var actual = await _calculator.GetVerticesAsync(new Rectangle((0, 2), 2, 2), cancellationToken); | ||
|
||
actual.Count.ShouldBe(4); | ||
var points = await actual.Points.ToArrayAsync(cancellationToken); | ||
points.ShouldBe([(0, 2), (2, 2), (2, 0), (0, 0)], PointComparer.Default); | ||
} | ||
|
||
private async Task TestGetNumbersAsync(CancellationToken cancellationToken) | ||
{ | ||
var points = AsyncEnumerable.AsAsync<Point>((0, 2), (2, 2)); | ||
|
||
var actual = await _calculator.GetNumbersAsync(points, cancellationToken).ToArrayAsync(cancellationToken); | ||
|
||
actual.ShouldBe([0, 2, 2, 2], NumberComparer.Default); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using Client.Services; | ||
using Contract; | ||
using Grpc.Net.Client; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
using ServiceModel.Grpc.Client.DependencyInjection; | ||
using ServiceModel.Grpc.Configuration; | ||
using ServiceModel.Grpc.Interceptors; | ||
|
||
namespace Client; | ||
|
||
public static class Program | ||
{ | ||
public static async Task Main() | ||
{ | ||
var services = new ServiceCollection(); | ||
services.AddLogging(builder => builder.AddSimpleConsole()); | ||
services.AddTransient<ClientTestFixture>(); | ||
|
||
AddApplicationServices(services, new Uri("http://localhost:5000")); | ||
|
||
await using var provider = services.BuildServiceProvider(); | ||
|
||
await provider.GetRequiredService<ClientTestFixture>().RunAsync(); | ||
} | ||
|
||
private static void AddApplicationServices(IServiceCollection services, Uri serverAddress) | ||
{ | ||
services.AddTransient<LoggingClientFilter>(); | ||
|
||
services | ||
.AddServiceModelGrpcClientFactory((options, provider) => | ||
{ | ||
// set MessagePackMarshaller with generated formatters as default Marshaller | ||
options.MarshallerFactory = new MessagePackMarshallerFactory(MessagePackSerializerHelper.CreateApplicationOptions()); | ||
// Filters: log gRPC calls | ||
options.Filters.Add(1, provider.GetRequiredService<LoggingClientFilter>()); | ||
// Error handling: activate ServerErrorHandler | ||
options.ErrorHandler = new ClientErrorHandlerCollection(new ClientErrorHandler()); | ||
// Error handling: AOT compatible marshalling of InvalidRectangleError | ||
options.ErrorDetailDeserializer = new ClientFaultDetailDeserializer(); | ||
}) | ||
.ConfigureDefaultChannel(ChannelProviderFactory.Singleton(GrpcChannel.ForAddress(serverAddress))) | ||
.AddAllGrpcServices(); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
Examples/MessagePackMarshaller.AOT/Client/Services/ClientErrorHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using Contract; | ||
using ServiceModel.Grpc.Interceptors; | ||
|
||
namespace Client.Services; | ||
|
||
/// <summary> | ||
/// Error handling: raise <see cref="InvalidRectangleException"/> with details from <see cref="InvalidRectangleError"/> | ||
/// </summary> | ||
internal sealed class ClientErrorHandler : IClientErrorHandler | ||
{ | ||
public void ThrowOrIgnore(ClientCallInterceptorContext context, ClientFaultDetail detail) | ||
{ | ||
if (detail.Detail is InvalidRectangleError error) | ||
{ | ||
throw new InvalidRectangleException(error.Message, detail.OriginalError, error.Points); | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
Examples/MessagePackMarshaller.AOT/Client/Services/ClientFaultDetailDeserializer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
using System; | ||
using Contract; | ||
using ServiceModel.Grpc.Configuration; | ||
using ServiceModel.Grpc.Interceptors; | ||
|
||
namespace Client.Services; | ||
|
||
/// <summary> | ||
/// Error handling: AOT compatible deserialization of <see cref="InvalidRectangleError"/> | ||
/// </summary> | ||
internal sealed class ClientFaultDetailDeserializer : IClientFaultDetailDeserializer | ||
{ | ||
public Type DeserializeDetailType(string typePayload) | ||
{ | ||
if (typePayload == nameof(InvalidRectangleError)) | ||
{ | ||
return typeof(InvalidRectangleError); | ||
} | ||
|
||
throw new NotSupportedException(); | ||
} | ||
|
||
public object DeserializeDetail(IMarshallerFactory marshallerFactory, Type detailType, byte[] detailPayload) | ||
{ | ||
if (detailType != typeof(InvalidRectangleError)) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
return MarshallerExtensions.Deserialize(marshallerFactory.CreateMarshaller<InvalidRectangleError>(), detailPayload); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
Examples/MessagePackMarshaller.AOT/Client/Services/GrpcServices.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using Contract; | ||
using ServiceModel.Grpc.Client.DependencyInjection; | ||
using ServiceModel.Grpc.DesignTime; | ||
|
||
namespace Client.Services; | ||
|
||
[ImportGrpcService(typeof(ICalculator), GenerateDependencyInjectionExtensions = true)]// instruct ServiceModel.Grpc.DesignTime to generate required code during the build process | ||
[MessagePackDesignTimeExtension] // instruct ServiceModel.Grpc.MessagePackMarshaller to generate required code during the build process | ||
internal static partial class GrpcServices | ||
{ | ||
public static void AddAllGrpcServices(this IClientFactoryBuilder factoryBuilder) | ||
{ | ||
// map generated ICalculator client | ||
factoryBuilder.AddCalculatorClient(); | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
Examples/MessagePackMarshaller.AOT/Client/Services/LoggingClientFilter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
using ServiceModel.Grpc.Filters; | ||
|
||
namespace Client.Services; | ||
|
||
/// <summary> | ||
/// Filters: log all gRPC calls | ||
/// </summary> | ||
internal sealed class LoggingClientFilter : IClientFilter | ||
{ | ||
private readonly ILogger _logger; | ||
|
||
public LoggingClientFilter(ILoggerFactory loggerFactory) | ||
{ | ||
_logger = loggerFactory.CreateLogger("ClientFilter"); | ||
} | ||
|
||
public void Invoke(IClientFilterContext context, Action next) | ||
{ | ||
OnRequest(context); | ||
try | ||
{ | ||
next(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
OnException(context, ex); | ||
throw; | ||
} | ||
|
||
OnResponse(context); | ||
} | ||
|
||
public async ValueTask InvokeAsync(IClientFilterContext context, Func<ValueTask> next) | ||
{ | ||
OnRequest(context); | ||
try | ||
{ | ||
await next(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
OnException(context, ex); | ||
throw; | ||
} | ||
|
||
OnResponse(context); | ||
} | ||
|
||
private void OnRequest(IClientFilterContext context) => | ||
_logger.LogInformation("request {signature}", GetSignature(context.ContractMethodInfo, context.Request)); | ||
|
||
private void OnResponse(IClientFilterContext context) => | ||
_logger.LogInformation("response {signature}", GetSignature(context.ContractMethodInfo, context.Response)); | ||
|
||
private void OnException(IClientFilterContext context, Exception ex) => | ||
_logger.LogError(ex, $"{context.ContractMethodInfo.DeclaringType?.Name}.{context.ContractMethodInfo.Name}"); | ||
|
||
private static string GetSignature(MethodInfo method, IEnumerable<KeyValuePair<string, object?>> parameters) | ||
{ | ||
var message = new StringBuilder() | ||
.Append($"{method.DeclaringType?.Name}.{method.Name}") | ||
.Append(' '); | ||
|
||
var comma = false; | ||
foreach (var entry in parameters) | ||
{ | ||
if (comma) | ||
{ | ||
message.Append(", "); | ||
} | ||
|
||
comma = true; | ||
message | ||
.Append(entry.Key) | ||
.Append(':') | ||
.Append(entry.Value); | ||
} | ||
|
||
return message.ToString(); | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
Examples/MessagePackMarshaller.AOT/Client/Tools/AsyncEnumerable.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Client.Tools; | ||
|
||
internal static class AsyncEnumerable | ||
{ | ||
public static async IAsyncEnumerable<T> AsAsync<T>(params T[] values) | ||
{ | ||
await Task.CompletedTask; | ||
|
||
foreach (var value in values) | ||
{ | ||
yield return value; | ||
} | ||
} | ||
|
||
public static async Task<T[]> ToArrayAsync<T>(this IAsyncEnumerable<T> enumerable, CancellationToken cancellationToken = default) | ||
{ | ||
var result = new List<T>(); | ||
await foreach (var item in enumerable.WithCancellation(cancellationToken)) | ||
{ | ||
result.Add(item); | ||
} | ||
|
||
return result.ToArray(); | ||
} | ||
} |
Oops, something went wrong.