Skip to content

Commit

Permalink
Support multiple assemblies in Grpc extensions (#926)
Browse files Browse the repository at this point in the history
* Support multiple assemblies in Grpc extensions

Updated
- GrpcClientServiceCollectionExtensions
- EndpointRouteBuilderGrpcExtensions
- GrpcServerServiceCollectionExtensions
to support multiple assemblies for scanning data contracts and API contract attributes. Added new overloads for relevant methods with backwards compatibility

* Support multiple assemblies in Grpc extensions - CR fixes

---------

Co-authored-by: Václavek, Ondřej <[email protected]>
  • Loading branch information
vaclavek and Václavek, Ondřej authored Nov 5, 2024
1 parent 4dd744e commit 004408b
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 7 deletions.
51 changes: 47 additions & 4 deletions Havit.Blazor.Grpc.Client/GrpcClientServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System.Reflection;
using System.Runtime.InteropServices;
using Grpc.Net.Client.Web;
using Grpc.Net.ClientFactory;
using Havit.Blazor.Grpc.Client.HttpHeaders;
using Havit.Blazor.Grpc.Client.Infrastructure;
using Havit.Blazor.Grpc.Client.ServerExceptions;
using Havit.Blazor.Grpc.Core;
using Havit.ComponentModel;
using Havit.Diagnostics.Contracts;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand All @@ -25,16 +25,30 @@ public static class GrpcClientServiceCollectionExtensions
/// Adds the necessary infrastructure for gRPC clients.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="assemblyToScanForDataContracts">The assembly to scan for data contracts.</param>
/// <param name="assemblyToScanForDataContracts">Assembly to scan for data contracts.</param>
public static void AddGrpcClientInfrastructure(
this IServiceCollection services,
Assembly assemblyToScanForDataContracts)
{
AddGrpcClientInfrastructure(services, [assemblyToScanForDataContracts]);
}

/// <summary>
/// Adds the necessary infrastructure for gRPC clients.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="assembliesToScanForDataContracts">Assemblies to scan for data contracts.</param>
public static void AddGrpcClientInfrastructure(
this IServiceCollection services,
Assembly[] assembliesToScanForDataContracts)
{
services.AddTransient<ServerExceptionsGrpcClientInterceptor>();
services.AddSingleton<GlobalizationLocalizationGrpcClientInterceptor>();
services.AddScoped<ClientUriGrpcClientInterceptor>();
services.AddTransient<GrpcWebHandler>(provider => new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
services.AddSingleton<ClientFactory>(ClientFactory.Create(BinderConfiguration.Create(marshallerFactories: new[] { ProtoBufMarshallerFactory.Create(RuntimeTypeModel.Create().RegisterApplicationContracts(assemblyToScanForDataContracts)) }, binder: new ProtoBufServiceBinder())));
services.AddSingleton<ClientFactory>(ClientFactory.Create(BinderConfiguration.Create(
marshallerFactories: CreateMarshallerFactories(assembliesToScanForDataContracts),
binder: new ProtoBufServiceBinder())));

services.TryAddScoped<IGrpcClientClientUriResolver, NavigationManagerGrpcClientClientUriResolver>();
}
Expand All @@ -57,7 +71,31 @@ public static void AddGrpcClientsByApiContractAttributes(
Action<IHttpClientBuilder> configureGrpClientAll = null,
Action<IServiceProvider, GrpcClientFactoryOptions> configureGrpcClientFactory = null)
{
var interfacesAndAttributes = (from type in assemblyToScan.GetTypes()
AddGrpcClientsByApiContractAttributes(services, [assemblyToScan], configureGrpcClientWithAuthorization, configureGrpClientAll, configureGrpcClientFactory);
}

/// <summary>
/// Adds gRPC clients based on API contract attributes.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="assembliesToScan">The assembly to scan for API contract attributes.</param>
/// <param name="configureGrpcClientWithAuthorization">An optional action to configure gRPC clients with authorization.</param>
/// <param name="configureGrpClientAll">An optional action to configure all gRPC clients.</param>
/// <param name="configureGrpcClientFactory">
/// An optional action to configure the gRPC client factory.
/// If Not provided, <c>options.Address</c> (backend URL) will be configured from <c>NavigationManager.BaseUri</c>.
/// </param>
public static void AddGrpcClientsByApiContractAttributes(
this IServiceCollection services,
Assembly[] assembliesToScan,
Action<IHttpClientBuilder> configureGrpcClientWithAuthorization = null,
Action<IHttpClientBuilder> configureGrpClientAll = null,
Action<IServiceProvider, GrpcClientFactoryOptions> configureGrpcClientFactory = null)
{
Contract.Requires<ArgumentNullException>(assembliesToScan is not null);

var interfacesAndAttributes = (from assembly in assembliesToScan
from type in assembly.GetTypes()
from apiContractAttribute in type.GetCustomAttributes(typeof(ApiContractAttribute), false).Cast<ApiContractAttribute>()
select new { Interface = type, Attribute = apiContractAttribute }).ToArray();

Expand All @@ -77,6 +115,11 @@ from apiContractAttribute in type.GetCustomAttributes(typeof(ApiContractAttribut
}
}

private static List<MarshallerFactory> CreateMarshallerFactories(Assembly[] assembliesToScanForDataContracts) =>
assembliesToScanForDataContracts
.Select(assembly => ProtoBufMarshallerFactory.Create(RuntimeTypeModel.Create().RegisterApplicationContracts(assembly)))
.ToList();

private static void AddGrpcClientCore<TService>(
this IServiceCollection services,
Action<IHttpClientBuilder> configureGrpcClientWithAuthorization = null,
Expand Down
21 changes: 19 additions & 2 deletions Havit.Blazor.Grpc.Server/EndpointRouteBuilderGrpcExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,28 @@ public static void MapGrpcServicesByApiContractAttributes(
Assembly assemblyToScan,
Action<GrpcServiceEndpointConventionBuilder> configureEndpointWithAuthorization = null,
Action<GrpcServiceEndpointConventionBuilder> configureEndpointAll = null)
{
MapGrpcServicesByApiContractAttributes(builder, [assemblyToScan], configureEndpointWithAuthorization, configureEndpointAll);
}

/// <summary>
/// Maps gRPC services with <see cref="ApiContractAttribute"/> as route endpoints.
/// </summary>
/// <param name="builder">Endpoint route builder.</param>
/// <param name="assembliesToScan">Assemblies to scan for interfaces with <see cref="ApiContractAttribute"/>.</param>
/// <param name="configureEndpointWithAuthorization">Optional configuration for endpoints with authorization (all except <c>[ApiContract(RequireAuthorization = false)]</c>). Usually you want to setup <c>endpoint.RequireAuthorization(...)</c> here."/></param>
/// <param name="configureEndpointAll">Optional configuration for all endpoints.</param>
public static void MapGrpcServicesByApiContractAttributes(
this IEndpointRouteBuilder builder,
Assembly[] assembliesToScan,
Action<GrpcServiceEndpointConventionBuilder> configureEndpointWithAuthorization = null,
Action<GrpcServiceEndpointConventionBuilder> configureEndpointAll = null)
{
Contract.Requires<ArgumentNullException>(builder is not null, nameof(builder));
Contract.Requires<ArgumentNullException>(assemblyToScan is not null, nameof(assemblyToScan));
Contract.Requires<ArgumentNullException>(assembliesToScan is not null, nameof(assembliesToScan));

var interfacesAndAttributes = (from type in assemblyToScan.GetTypes()
var interfacesAndAttributes = (from assembly in assembliesToScan
from type in assembly.GetTypes()
from apiContractAttribute in type.GetCustomAttributes(typeof(ApiContractAttribute), false).Cast<ApiContractAttribute>()
select new { Interface = type, Attribute = apiContractAttribute }).ToArray();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Havit.Blazor.Grpc.Core;
using Havit.Blazor.Grpc.Server.GlobalizationLocalization;
using Havit.Blazor.Grpc.Server.ServerExceptions;
using Havit.Diagnostics.Contracts;
using Microsoft.Extensions.DependencyInjection;
using ProtoBuf.Grpc.Configuration;
using ProtoBuf.Grpc.Server;
Expand All @@ -12,14 +13,38 @@ namespace Havit.Blazor.Grpc.Server;

public static class GrpcServerServiceCollectionExtensions
{
/// <summary>
/// Adds the necessary infrastructure for gRPC servers.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="assembliesToScanForDataContracts">Assembly to scan for data contracts</param>
/// <param name="configureOptions">gRPC Service options</param>
public static void AddGrpcServerInfrastructure(
this IServiceCollection services,
Assembly assemblyToScanForDataContracts,
Action<GrpcServiceOptions> configureOptions = null)
{
AddGrpcServerInfrastructure(services, [assemblyToScanForDataContracts], configureOptions);
}

/// <summary>
/// Adds the necessary infrastructure for gRPC servers.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="assembliesToScanForDataContracts">Assemblies to scan for data contracts</param>
/// <param name="configureOptions">gRPC Service options</param>
public static void AddGrpcServerInfrastructure(
this IServiceCollection services,
Assembly[] assembliesToScanForDataContracts,
Action<GrpcServiceOptions> configureOptions = null)
{
Contract.Requires<ArgumentNullException>(assembliesToScanForDataContracts is not null);

services.AddSingleton<GlobalizationLocalizationGrpcServerInterceptor>();
services.AddSingleton<ServerExceptionsGrpcServerInterceptor>();
services.AddSingleton(BinderConfiguration.Create(marshallerFactories: new[] { ProtoBufMarshallerFactory.Create(RuntimeTypeModel.Create().RegisterApplicationContracts(assemblyToScanForDataContracts)) }, binder: new ServiceBinderWithServiceResolutionFromServiceCollection(services)));
services.AddSingleton(BinderConfiguration.Create(
marshallerFactories: CreateMarshallerFactories(assembliesToScanForDataContracts),
binder: new ServiceBinderWithServiceResolutionFromServiceCollection(services)));

services.AddCodeFirstGrpc(options =>
{
Expand All @@ -30,4 +55,13 @@ public static void AddGrpcServerInfrastructure(
configureOptions?.Invoke(options);
});
}

/// <summary>
/// Creates marshaller factories for the specified assemblies.
/// Each assembly has its own marshaller factory.
/// </summary>
private static List<MarshallerFactory> CreateMarshallerFactories(Assembly[] assembliesToScanForDataContracts) =>
assembliesToScanForDataContracts
.Select(assembly => ProtoBufMarshallerFactory.Create(RuntimeTypeModel.Create().RegisterApplicationContracts(assembly)))
.ToList();
}

0 comments on commit 004408b

Please sign in to comment.