GithubHelp home page GithubHelp logo

max-ieremenko / servicemodel.grpc Goto Github PK

View Code? Open in Web Editor NEW
88.0 12.0 11.0 14.32 MB

Code-first for gRPC

Home Page: https://max-ieremenko.github.io/ServiceModel.Grpc/

License: Apache License 2.0

C# 97.94% PowerShell 2.06%
grpc grpc-dotnet servicemodel wcf code-first csharp swagger

servicemodel.grpc's People

Contributors

jacob-morgan avatar max-ieremenko avatar qoniac-max avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

servicemodel.grpc's Issues

implement Hub in ServiceModel.Grpc

Hello
Can implement realtime server <--> client communication same Hub in Signnalr For ServiceModel.Grpc ?

MagicOnion has StreamingHub .

StreamingHub is a fully-typed realtime server <--> client communication framework.

For Example in MagicOnion

// Server -> Client definition
public interface IGamingHubReceiver
{
    // The method must have a return type of `void` and can have up to 15 parameters of any type.
    void OnJoin(Player player);
    void OnLeave(Player player);
    void OnMove(Player player);
}
 
// Client -> Server definition
// implements `IStreamingHub<TSelf, TReceiver>`  and share this type between server and client.
public interface IGamingHub : IStreamingHub<IGamingHub, IGamingHubReceiver>
{
    // The method must return `Task` or `Task<T>` and can have up to 15 parameters of any type.
    Task<Player[]> JoinAsync(string roomName, string userName, Vector3 position, Quaternion rotation);
    Task LeaveAsync();
    Task MoveAsync(Vector3 position, Quaternion rotation);
}
 
// for example, request object by MessagePack.
[MessagePackObject]
public class Player
{
    [Key(0)]
    public string Name { get; set; }
    [Key(1)]
    public Vector3 Position { get; set; }
    [Key(2)]
    public Quaternion Rotation { get; set; }
}
// Server implementation
// implements : StreamingHubBase<THub, TReceiver>, THub
public class GamingHub : StreamingHubBase<IGamingHub, IGamingHubReceiver>, IGamingHub
{
    // this class is instantiated per connected so fields are cache area of connection.
    IGroup room;
    Player self;
    IInMemoryStorage<Player> storage;

    public async Task<Player[]> JoinAsync(string roomName, string userName, Vector3 position, Quaternion rotation)
    {
        self = new Player() { Name = userName, Position = position, Rotation = rotation };

        // Group can bundle many connections and it has inmemory-storage so add any type per group. 
        (room, storage) = await Group.AddAsync(roomName, self);

        // Typed Server->Client broadcast.
        Broadcast(room).OnJoin(self);

        return storage.AllValues.ToArray();
    }

    public async Task LeaveAsync()
    {
        await room.RemoveAsync(this.Context);
        Broadcast(room).OnLeave(self);
    }

    public async Task MoveAsync(Vector3 position, Quaternion rotation)
    {
        self.Position = position;
        self.Rotation = rotation;
        Broadcast(room).OnMove(self);
    }

    // You can hook OnConnecting/OnDisconnected by override.
    protected override async ValueTask OnDisconnected()
    {
        // on disconnecting, if automatically removed this connection from group.
        return CompletedTask;
    }
}

You can write client like this.

public class GamingHubClient : IGamingHubReceiver
{
    Dictionary<string, GameObject> players = new Dictionary<string, GameObject>();
 
    IGamingHub client;
 
    public async Task<GameObject> ConnectAsync(ChannelBase grpcChannel, string roomName, string playerName)
    {
        this.client = await StreamingHubClient.ConnectAsync<IGamingHub, IGamingHubReceiver>(grpcChannel, this);
 
        var roomPlayers = await client.JoinAsync(roomName, playerName, Vector3.zero, Quaternion.identity);
        foreach (var player in roomPlayers)
        {
            (this as IGamingHubReceiver).OnJoin(player);
        }
 
        return players[playerName];
    }
 
    // methods send to server.
 
    public Task LeaveAsync()
    {
        return client.LeaveAsync();
    }
 
    public Task MoveAsync(Vector3 position, Quaternion rotation)
    {
        return client.MoveAsync(position, rotation);
    }
 
    // dispose client-connection before channel.ShutDownAsync is important!
    public Task DisposeAsync()
    {
        return client.DisposeAsync();
    }
 
    // You can watch connection state, use this for retry etc.
    public Task WaitForDisconnect()
    {
        return client.WaitForDisconnect();
    }
 
    // Receivers of message from server.
 
    void IGamingHubReceiver.OnJoin(Player player)
    {
        Debug.Log("Join Player:" + player.Name);
 
        var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.name = player.Name;
        cube.transform.SetPositionAndRotation(player.Position, player.Rotation);
        players[player.Name] = cube;
    }
 
    void IGamingHubReceiver.OnLeave(Player player)
    {
        Debug.Log("Leave Player:" + player.Name);
 
        if (players.TryGetValue(player.Name, out var cube))
        {
            GameObject.Destroy(cube);
        }
    }
 
    void IGamingHubReceiver.OnMove(Player player)
    {
        Debug.Log("Move Player:" + player.Name);
 
        if (players.TryGetValue(player.Name, out var cube))
        {
            cube.transform.SetPositionAndRotation(player.Position, player.Rotation);
        }
    }
}

Generation .proto file instead of swagger

Hi,
Can we generate proto file to distribute so third party clients can generate their client codes in their programming language? Having the swagger is good but other languages have client code generators from proto files.
Readme says:

helps to get around some limitations of gRPC protocol like "only reference types", "exact one input", "no nulls/value-types"

but serializers can handle these situations with some hacks like protobuf-net

Server filters

The concept is simple: server filter is a hook for service method invocation. It can work together with gRPC server interceptors, but it is not interceptor.

internal sealed class LoggingServerFilter : IServerFilter
{
    private readonly ILoggerFactory _loggerFactory;

    public LoggingServerFilter(ILoggerFactory loggerFactory)
    {
        _loggerFactory = loggerFactory;
    }

    public async ValueTask InvokeAsync(IServerFilterContext context, Func<ValueTask> next)
    {
        // create logger with a service name
        var logger = _loggerFactory.CreateLogger(context.ServiceInstance.GetType().Name);

        // before service method invocation log input
        logger.LogInformation("Begin {0}", context.ContractMethodInfo.Name);
        foreach (var entry in context.Request)
        {
            logger.LogInformation("Input {0} = {1}", entry.Key, entry.Value);
        }

        try
        {
            // invoke all other filters in the stack and the service method
            await next().ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            // log exception
            logger.LogError("Method {0} failed: {1}", context.ContractMethodInfo.Name, ex);
            throw;
        }

        // after service method is finished log output
        logger.LogInformation("End {0}", context.ContractMethodInfo.Name);
        foreach (var entry in context.Response)
        {
            logger.LogInformation("Output {0} = {1}", entry.Key, entry.Value);
        }
    }
}

How to register the filter.

Variant 1: register as global one for all services:

public void ConfigureServices(IServiceCollection services)
{
    // instance is transient
    services.AddTransient<LoggingServerFilter>();

    services.AddServiceModelGrpc(options =>
    {
        // register LoggingServerFilter
        options.Filters.Add(1, provider => provider.GetRequiredService<LoggingServerFilter>());
    });
}

Variant 2: register for a specific service only:

public void ConfigureServices(IServiceCollection services)
{
    // instance is transient
    services.AddTransient<LoggingServerFilter>();

    services.AddServiceModelGrpcServiceOptions<Calculator>(options =>
    {
        // register LoggingServerFilter
        options.Filters.Add(1, provider => provider.GetRequiredService<LoggingServerFilter>());
    });
}

Variant 3: mark service with attribute:

[ServerFilter(1, typeof(LoggingServerFilter))]
public sealed class Calculator : ICalculator
{
    public ValueTask<int> SumAsync(int x, int y, CancellationToken token)
    {
        return new ValueTask<int>(x + y);
    }
}

Variant 4: mark method with attribute:

public sealed class Calculator : ICalculator
{
    [ServerFilter(1, typeof(LoggingServerFilter))]
    public ValueTask<int> SumAsync(int x, int y, CancellationToken token)
    {
        return new ValueTask<int>(x + y);
    }
}

At runtime for SumAsync(1, 2) the log output will be

INFO Begin SumAsync
INFO Input x = 1
INFO Input y = 2
INFO End SumAsync
INFO Output result = 3

The filter has complete control over the method call.

[ServerFilter(100, typeof(SumAsyncServerFilter))]
public ValueTask<int> SumAsync(int x, int y, CancellationToken token)
{
    // the filter must handle the call
    throw new NotImplementedException();
}

internal sealed class SumAsyncServerFilter : IServerFilter
{
    public ValueTask InvokeAsync(IServerFilterContext context, Func<ValueTask> next)
    {
        // var x = (int)context.Request[0];
        var x = (int)context.Request["x"];

        // var y = (int)context.Request[1];
        var y = (int)context.Request["y"];

        // do not invoke the real SumAsync
        // await next().ConfigureAwait(false);

        // context.Response[0] = x + y;
        context.Response["result"] = x + y;
        
        return new ValueTask(Task.CompletedTask);
    }
}

// at runtime SumAsync(1 + 2) will be 3

Design Time Issue With Return Type of Task

Describe the bug

Task only return type causes build error with source code generation.

To Reproduce

[ServiceContract]
public interface IExampleService
{
    [OperationContract]
    Task Example(int number, CancellationToken token);
}
internal partial class ExampleService : IExampleService
{
    public async Task Example(int number, CancellationToken token)
    {
        await Task.CompletedTask;
    }
}
[ExportGrpcService(typeof(ExampleService), GenerateAspNetExtensions = true)]
internal static partial class Services { }
[ImportGrpcService(typeof(IExampleService)]
internal static partial class Services { }

Expected behavior:

Using Task as a return time should not cause error with design time code.

Environment

OS: Windows 11
.NET 6

Shared interface (contract)

Problem

[ServiceContract]
public interface IService
{
    // method: POST /IService/SomeOperation
    [OperationContract]
    Task SomeOperation();
}

[ServiceContract]
public interface IConcreteService1 : IService { /* skip other operations ... */ }

[ServiceContract]
public interface IConcreteService2 : IService { /* skip other operations ... */ }

class ConcreteService1 : IConcreteService1
{
    public Task SomeOperation() { /* do something in the context of ConcreteService1 */ }
}

class ConcreteService2 : IConcreteService2
{
    public Task SomeOperation() { /* do something else in the context of ConcreteService2 */ }
}

// register /IService/SomeOperation
endpoints.MapGrpcService<ConcreteService1>();

// /IService/SomeOperation is already registered by ConcreteService1
endpoints.MapGrpcService<ConcreteService2>();

Boths ConcreteService1 and ConcreteService2 try to register the same endpoint /IService/SomeOperation and it leads to a naming conflict. For details see ServiceModel.Grpc service and operation names.

Solution

Remove ServiceContract attribute from IService.

// remove [ServiceContract]
public interface IService
{
    [OperationContract]
    Task SomeOperation();
}

// register endpoint /IConcreteService1/SomeOperation
endpoints.MapGrpcService<ConcreteService1>();

// register endpoint /IConcreteService2/SomeOperation
endpoints.MapGrpcService<ConcreteService2>();

If an interface does not contain [ServiceContract], the service name for each defined operation comes from the root [ServiceContract] interface, in the provided example root interfaces are IConcreteService1 and IConcreteService2

Multiple Implementations of a Service Contract on Same Server

Is your feature request related to a problem? Please describe.
Problem:

Server cannot register multiple service classes implementing a common service contract interface on the same machine and same port.

Example:

[ServiceContract()] 
public interface IBootService
{
	...
	//Some operation contracts
	...
}

[ServiceContract()] 
public interface IDataFetchService
{
	...
	//Some operation contracts
	...
}

[ServiceContract()] 
public interface IDataStorageService
{
	...
	//Some operation contracts
	...
}

public DataFetchService: IBootService, IDataFetchService
{
	...
}

public DataStorageService: IBootService, IDataStorageService
{
	...
}

We currently have lot of WCF services running and all of them implements IBootService. IBootService contains some operations which are required for all of our Wcf Services.

When Both DataFetchService and DataStorageService is hosted on same server, we get System.ArgumentException: 'An item with the same
key has already been added' exception when second service is added to the server.
The problem arises due to the fact that each operation is getting identified by interface name and operation name, and because of it, it
shows that the operation is already added.

Describe the solution you'd like

Possible Solution:

We can add an optional parameter, for example a unique identifier string to the operations(method name) apart from interface name. This will allow us to add unique identifier while registering services to server and also while creating a client.

While generating Contract Description, append unique identifier string to service name in AnalyzeServiceAndinterfaces method and for properties ClientClassName, ContractClassName, ClientBuilder Class Name and EndpointClassName in Contract Description Class.

This solution will result in modifying CreateClient in following way:
public IContract CreateClient(ChannelBase channel, string uniqueIdentifierString="") in ClientFactory

Example:

var client1 = clientFactory.CreateClient<IBootService>(channel,"DatafetchServiceUniqueString");
var client2 = clientFactory.CreateClient<IBootService>(channel, "DataStorageServiceUniqueString");

And for Server Service Registration in following way:

public static Server.ServiceDefinitionCollection AddServiceModelSingleton<TService>(this Server,
 ServiceDefinitionCollection services, TService service, Action<ServiceModelGrpcServiceOptions>? configure=default,
 string uniqueIdentifierString="")

in ServiceDefinitionCollectionExtensions

Example:

server.Services.AddServiceModelSingleton(new DataFetchService(),null,"DataFetchServiceUniqueString"); 
server Services.AddServiceModelSingleton(new DataStorageService(),null,"DataStorageServiceUniqueString");

Describe alternatives you've considered
Alternatives we have considered are:

  • Do not decorate IBootService with [ServiceContract] and just with [OperationContracts] like how you described here. But we cannot use this, because we would like to get client for all of the services running using IBootService Interface to manage lifecycles of those servers.
  • Another alternative is to create a new server for each of the service implementation. But, this will require us to use too many ports as in my use case I have dozens of services. We would like to avoid doing that.

gRPC C# deprecation

As stated in readme, ServiceModelGrpc is based on Grpc C#.

Now gRPC C# will be deprecated, quote fron the project page: "We plan to deprecate it in the future in favor of the grpc-dotnet implementation".

So the project will upgrate to grpc-dotnet?

Thanks

Question: Best serialisation method for performance

Hi Max

A great project and sincerely thank you for it.

In your opinion is grpc-dotnet serialisation most likely to out perform other methods?

I read that Protobuf is highly regarded for speed but your benchmark results don't seem to reflect this.

Thanks.

Fig

Is't possible to suppress an exception?

I read about filter exception on server and client and rethrow it using DefaultErrorHandlerFactory / ErrorHandler.

But it's possible to suppress an exception and return null as grpc method return value without raising any exception on client side?

gRPC client throws the following exception when trying to invoke the proxy stub code.

I got the following exception using the design time client connecting to a self-hosting gRPC service,
It seems that the server didn't even connect and the client throws the following exception can you please let me know how to deal with it?

Grpc.Core.RpcException: Status(StatusCode="Unknown", Detail="Exception was thrown by handler.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1620792359.173000000","description":"Error received from peer ipv6:[::1]:8090","file":"......\src\core\lib\surface\call.cc","file_line":1068,"grpc_message":"Exception was thrown by handler.","grpc_status":2}")
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at ServiceModel.Grpc.Client.ClientChannelAdapter.<GetAsyncUnaryCallResult>d__11.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Squid.InstantMessage.DesignTime.Program.d__3.MoveNext() in C:\Users\danny\RiderProjects\TheSquid_Master\Squid.InstantMessage.DesignTime\Program.cs:line 60
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Squid.InstantMessage.DesignTime.Program.

d__1.MoveNext() in C:\Users\danny\RiderProjects\TheSquid_Master\Squid.InstantMessage.DesignTime\Program.cs:line 27
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Squid.InstantMessage.DesignTime.Program.()

ServiceModel.Grpc + RemoteLinq

In my project using ServiceModel.Grpc + RemoteLinq but with some complex Linq query i get this error when DataContractSerializer try to serialize IEnumerate<Acqua.Dynamic.DynamicObject>

I try with Protobuf marshaller but nothing to do, worst than with DataContractSerializer.

Error:
System.Runtime.Serialization.SerializationException: Type 'Aqua.Dynamic.DynamicObject' with data contract name 'DynamicObject:http://schemas.datacontract.org/2004/07/Aqua.Dynamic' is not expected. Add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiType(XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle objectTypeHandle, Type objectType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
at WritePropertyToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
at WritePropertySetToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract )
at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
at WriteDynamicObjectToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
at WriteArrayOfDynamicObjectToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , CollectionDataContract )
at System.Runtime.Serialization.CollectionDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType) at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiType(XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle objectTypeHandle, Type objectType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerialize(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.InternalSerializeReference(XmlWriterDelegator xmlWriter, Object obj, Boolean isDeclaredType, Boolean writeXsiType, Int32 declaredTypeID, RuntimeTypeHandle declaredTypeHandle)
at WritemToXml(XmlWriterDelegator , Object , XmlObjectSerializerWriteContext , ClassDataContract )
at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(Stream stream, Object graph)
at ServiceModel.Grpc.Configuration.DataContractMarshaller1.Serialize(T value, SerializationContext context) at Grpc.AspNetCore.Server.Internal.PipeExtensions.WriteMessageAsync[TResponse](PipeWriter pipeWriter, TResponse response, HttpContextServerCallContext serverCallContext, Action2 serializer, Boolean canFlush)
at Grpc.AspNetCore.Server.Internal.CallHandlers.UnaryServerCallHandler3.HandleCallAsyncCore(HttpContext httpContext, HttpContextServerCallContext serverCallContext) at Grpc.AspNetCore.Server.Internal.CallHandlers.ServerCallHandlerBase3.g__AwaitHandleCall|8_0(HttpContextServerCallContext serverCallContext, Method`2 method, Task handleCall)

Blazor + ProtobufMarshallerFactory

Describe the bug
When I try to use the combination Blazor (from the examples)+ ProtobufMarshallerFactory there is an error during the service call:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Status(StatusCode="Unknown", Detail="Exception was thrown by handler.")
Grpc.Core.RpcException: Status(StatusCode="Unknown", Detail="Exception was thrown by handler.")
at ServiceModel.Grpc.Client.ClientChannelAdapter.d__11[[System.Collections.Generic.IList1[[BlazorApp.Shared.WeatherForecast, BlazorApp.Shared, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
at BlazorApp.Client.Pages.FetchData.Reload() in C:\Application\Its-Hub\ServiceModel.Grpc-master\Examples\BlazorApp\BlazorApp\Client\Pages\FetchData.razor:line 53
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

To Reproduce
I modified the Blazor example
In the client program.cs I added:
services.AddSingleton(_ => new ClientFactory(new ServiceModelGrpcClientOptions
{
MarshallerFactory = ProtobufMarshallerFactory.Default
}));

In the server startup.cs I added:
services.AddServiceModelGrpc(options => options.DefaultMarshallerFactory = ProtobufMarshallerFactory.Default);

What could be the problem?
Thanks!

WriteClientStream hangs after the client streaming call completes.

Describe the bug
In some cases ClientChannelAdapter.WriteClientStream hangs after client streaming call is finished.

Expected behavior
ClientChannelAdapter.WriteClientStream is cancelled/finished.

Environment
Windows 11, net6

To Reproduce

[ServiceContract]
interface IService
{
    [OperationContract]
    Task ThrowException(IAsyncEnumerable<int> data);
}

class Service : IService
{
    Task ThrowException(IAsyncEnumerable<int> data) => throw new ApplicationException();
}

// run the following test under debugger
[Test]
async Task Test
{
    var client = IClientFactory.CreateClient<IService>();

    // the reader is empty and is not completed: no channel.Writer.Write and channel.Writer.Complete
    var channel = System.Threading.Channels.Channel.CreateUnbounded<int>();

    // as expected
    Assert.ThrowAsync<RpcException>(() => client.ThrowException(channel.Reader.ReadAllAsync()));
	
    // when the test reaches this line, "Break all" and check "Parallel Stacks" -> View Tasks
    // there will be active task "ClientChannelAdapter.WriteClientStream": the call is finished, but WriteClientStream is not
    await Task.Delay(TimeSpan.FromHours(1));
}

Question: which serialization to choose?

As i read in the documentation there are 3 types of builtin serialization we can use:

DataContract (default one, included in .net, useful to migrate from WCF)
ProtoBuf (gRPC standard serialization, useful to expose a wide standard communication protocol as gRPC is based on this one)
MessagePack (a performance oriented serialization protocol)

Now as i can understand, if i need a gRPC standard protocol i have to use ProtoBuf, if i migrate from WCF is a good choice to adopt DataContract, and if i have a performance critical application is reccomended MessagePack.

Now, switch from one serialization to another one is only related to set the ServiceModelGrpcServiceOptions.DefaultMarshallerFactory and change the contract attributes (DataContract / DataMember vs ProtoContract / ProtoMember vs MessagePackObject / Key) or there are other differences / limitations using one serialization or another one?

Thanks

Can Use FilterAttribute ?

Can Use FilterAttribute ?

For Example In MagicOnion

public class SampleFilterAttribute : MagicOnionFilterAttribute
{
    public override async ValueTask Invoke(ServiceContext context, Func<ServiceContext, ValueTask> next)
    {
        try
        {
            /* on before */
            await next(context); // next
            /* on after */
        }
        catch
        {
            /* on exception */
            throw;
        }
        finally
        {
            /* on finally */
        }
    }
}

Blazor File upload gives exception

Describe the bug
When I try to upload an image file with an IAsyncEnumerable<byte[]> call there is an "Unexpected end of data when reading base64 content" error message.

To Reproduce
See attachement, this is the Blazor example with code to upload a file in the index.razor.
test.zip

It seems that the async/await is not properly synchronized.
The upload was based upon an (partly) example of #18
Any help would be greatly appreciated!

deadlock in WPF

.net 5 wpf client

    private async Task<int> Test()
    {
        return await proxy.Sum(10, 5).ConfigureAwait(false); 
    }
    private  void Button_Click(object sender, RoutedEventArgs e)
    {          
        var ttt = Test().Result; // deadlock.
        MessageBox.Show(ttt.ToString());
    }

.net 5 grpc service
public Task Sum(int a, int b)
{
return Task.FromResult(a + b);
}

how to fix it?. thanks

Swagger problem with service functions without input parameters

Service Interface:
[OperationContract]
long Test();

Swagger Curl:
curl -X 'POST'
'http://localhost:5000/ICalculator/Test'
-H 'accept: application/json'
-d ''

Error Code 415 - Unsupported Media Type
content-length: 0
date: Wed,19 Jan 2022 16:56:00 GMT
grpc-message: Content-Type is missing from the request.
grpc-status: 13
server: Kestrel

Proposed Resolution:
SwaggerOperationProcessor.cs

    public bool Process(OperationProcessorContext context)
    {
        var description = (context as AspNetCoreOperationProcessorContext)?.ApiDescription;
        var descriptor = description?.ActionDescriptor as GrpcActionDescriptor;
        if (descriptor == null)
        {
            return true;
        }

        var operation = context.OperationDescription.Operation;

        if (operation.RequestBody == null)
        {
            operation.RequestBody = new OpenApiRequestBody();
            operation.RequestBody.Content.Add(Multipart, new OpenApiMediaType());
        }

        FixRequestType(operation);
        UpdateSummary(operation, descriptor);
        UpdateDescription(operation, descriptor);

        for (var i = 0; i < description!.SupportedResponseTypes.Count; i++)
        {
            AddResponseHeaders(operation, description.SupportedResponseTypes[i], context.SchemaResolver, context.SchemaGenerator);
        }

        return true;
    }

MessagePackSerializationException: Building dynamic formatter only allows public type

Describe the bug
ServiceModel.Grpc.DesignTime source code generator generates internal data contracts for more then 3 data parameters.
Internal types are not supported by MessagePackSerializer with default options.

To Reproduce
use ServiceModel.Grpc.DesignTime
define an operation contract with more then 3 data parameters:

[ServiceContract]
interface IContract
{
    [OperationContract]
    void Test(int p1, int p2, int p3, int p4);
}

at runtime on serializing Test`s input MessagePack throws "MessagePackSerializationException: Building dynamic formatter only allows public type."

Additional context
as a workaround ContractlessStandardResolverAllowPrivate can be used:

var options = MessagePackSerializer.DefaultOptions.WithResolver(ContractlessStandardResolverAllowPrivate.Instance);
.DefaultMarshallerFactory = new MessagePackMarshallerFactory(options);

No issues with "reflection emit" generator.

Update MessagePack to 2.4.35, support DateOnly and TimeOnly

ServiceModel.Grpc.MessagePackMarshaller

Unexpected msgpack code 132 (fixmap) encountered.\r\n at MessagePack.MessagePackReader.ThrowInvalidCode(Byte code)\r\n at MessagePack.MessagePackReader.ReadInt32()\r\n at MessagePack.Formatters.DateOnlyFormatter.Deserialize(MessagePackReader& reader, MessagePackSerializerOptions options)

How to handle JWT Authentication

Hi @max-ieremenko, It's possible to handle authentication with JWT token as done in ASP.NET Core controllers with [Authorize] attribute?

How to pass accessToken for each method call?

There is a way to set accessToken at service level avoiding to pass it as parameter for each method?

Thanks

Server and Duplex streaming out parameters alternative

Problem

gRPC protocol is message oriented and in server streaming calls does not allow to pass back to a caller external information.
The only way to achieve this is by using response headers.

As an example file download from server. Together with content i need also file creation date, owner information and file version:

[OperationContract]
Task<IAsyncEnumerable<byte[]>> DownloadFile(string fileName, CallContext context);

// server-side implementation
async Task<IAsyncEnumerable<byte[]>> DownloadFile(string fileName, CallContext context)
{
    DateTime fileCreationDate = ... // resolve file creation date
    string fileOwner = ... // resolve owner information
    string fileVersion = ... // resolve file version

    // pass external information in response headers
    ServerCallContext serverContext = context;
    await serverContext.WriteResponseHeadersAsync(new Metadata
    {
        { "creation-date", fileCreationDate.ToString() },
        { "owner", fileOwner },
        { "version", fileVersion  }
    });

    // pass file content via server streaming
    return ReadFileContent(fileName);
}
private IAsyncEnumerable<byte[]> ReadFileContent(string fileName)
{
    // convert file content to gRPC byte[] stream
    // the code is for demonstration purposes only !!!
    using (var stream = File.OpenRead(fileName))
    {
        var buffer = new byte[4 * 1204];
        while ((await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            yield return buffer;
        }
    }
}

// client-side implementation
var context = new CallContext();
var content = await DownloadFile("file name", context);

// extract external information from response headers
var fileCreationDate = context.ResponseHeaders.First(i => i.Key == "creation-date").Value;
var fileOwner = context.ResponseHeaders.First(i => i.Key == "owner").Value;
var fileVersion = context.ResponseHeaders.First(i => i.Key == "version").Value;

// download file content
await foreach(var buffer in content)
{
    // ...
}

Idea

The idea is to move "response headers" related implementation into ServiceModel.Grpc framework:

// no CallContext in the call, only CancellationToken
// pass external information in the method return type
[OperationContract]
Task<(DateTime CreationDate, string Owner, string Version, IAsyncEnumerable<byte[]> Content)> DownloadFile(string fileName, CancellationToken token);

// server-side implementation
async Task<(DateTime, string, string, IAsyncEnumerable<byte[]>)> DownloadFile(string fileName, CancellationToken token)
{
    DateTime fileCreationDate = ... // resolve file creation date
    string fileOwner = ... // resolve owner information
    string fileVersion = ... // resolve file version

    return (fileCreationDate, fileOwner, version, ReadFileContent(fileName));
}

// client-side implementation
var (fileCreationDate, fileOwner, fileVersion, content) = await DownloadFile("file name", CancellationToken);

// download file content
await foreach(var buffer in content)
{
    // ...
}

Error GrpcService with app.UseIdentityServer()

@max-ieremenko
When I Use [Authorize] Attribute And app.UseIdentityServer()

I get Error "Status(StatusCode="Unimplemented", Detail="Bad gRPC response. HTTP status code: 404")",

My Startup

        public void ConfigureServices(IServiceCollection services)
        {

            services.AddAuthentication(o =>
            {
                o.DefaultAuthenticateScheme = "Bearer";
                o.DefaultScheme = "Bearer";
                o.DefaultChallengeScheme = "Bearer";
            })
                .AddJwtBearer("Bearer", options =>
                {
                    options.Authority = "192.168.2.1";
                    options.RequireHttpsMetadata = false;
                    options.Audience = "client";
                });

            services.AddAuthorization();

            services.AddServiceModelGrpc(options =>
            {
                options.DefaultMarshallerFactory = MessagePackMarshallerFactory.Default;
                options.DefaultErrorHandlerFactory = serviceProvider => new UnexpectedExceptionServerHandler();
            });

            services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;
                // options.IssuerUri = authority;

            })
              .AddSigningCredential(new X509Certificate2(Path.Combine("Certs", "sso.pfx"), "aA123"))
              .AddResourceStore<AppResourceStore>()
              .AddClientStore<AppClientStore>()
              .AddAspNetIdentity<User>()
              .AddProfileService<ProfileService>()
              .AddExtensionGrantValidator<AppTotpGrantValidator>()
              .AddCorsPolicyService<AppSeptaCorsPolicyService>()
              .AddResourceOwnerValidator<AppResourceOwnerPasswordValidator<User>>();


        }


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.ApplicationServices.InitializeDb();
            app.UseRouting();
            app.UseIdentityServer();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGrpcService<TestApi>()
            });
        }

My Interface

    [ServiceContract]
    public interface ITestApi
    {
        [OperationContract]
        Task SampleAsync();
    }

My Class

    public class TestApi : ITestApi
    {
        [Authorize]
        public async Task SampleAsync()
        {
            await Task.CompletedTask;
        }
    }

Without Authorize Attribute It is Work Without app.UseIdentityServer() It is Work

But When I Use Authorize Attribute With app.UseIdentityServer()
I get Error "Status(StatusCode="Unimplemented", Detail="Bad gRPC response. HTTP status code: 404")",

Silent exception happen on the gRPC server side.

Hi

I've tried again today to run the GRPC service model. I noticed that it could be failing on the server side with the call stack below:

I am trying to implement and debug the problem and my server side implementation is like the following;
// I tried to add a wait loop here. but it never stop in the sleep mode and go directly to exit the function.
// Hence I believe that it is failing with a silent exception, can you help?

public async IAsyncEnumerable<IEnumerable<MessageDetail_RevCon>> GetMessageDetailAsyncEnumerable(DateTime start, DateTime end)
{
await foreach (var formatMessageItems in GetFormattedMessagesAsyncEnumerable(start, end))
{
var messageDetails = new List<MessageDetail_RevCon>();
foreach (var formatMessageItem in formatMessageItems)
{
var conversionItem = _conversionItemFactory.GetConversionItem(formatMessageItem);
if(conversionItem != null && conversionItem is MessageDetail_RevCon)
{
messageDetails.Add( (MessageDetail_RevCon)conversionItem);
}
}
yield return messageDetails.AsEnumerable();
}

// I tried to add a wait loop here. but it never stop in the sleep mode and go directly to exit the function.
// Hence I believe that it is failing with a silent exception, can you help?
while(true)
System.Threading.Thread.Sleep(500);
}

Grpc.Core.RpcException: Status(StatusCode="Unknown", Detail="Exception was thrown by handler.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1621284891.042000000","description":"Error received from peer ipv6:[::1]:8090","file":"......\src\core\lib\surface\call.cc","file_line":1068,"grpc_message":"Exception was thrown by handler.","grpc_status":2}")
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
at Grpc.Core.Internal.ClientResponseStream2.<MoveNext>d__5.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) at System.Runtime.CompilerServices.ConfiguredTaskAwaitable1.ConfiguredTaskAwaiter.GetResult()
at ServiceModel.Grpc.Client.ClientChannelAdapter.d__21.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore1.GetResult(Int16 token)
at ServiceModel.Grpc.Client.ClientChannelAdapter.d__21.System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult(Int16 token) at System.Threading.Tasks.ValueTask1.get_Result()
at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
at Squid.InstantMessage.ClientApp.Program.d__2.MoveNext() in C:\Users\danny\RiderProjects\TheSquid_Master\Squid.InstantMessage.ClientApp\Program.cs:line 58
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Squid.InstantMessage.ClientApp.Program.d__2.MoveNext() in C:\Users\danny\RiderProjects\TheSquid_Master\Squid.InstantMessage.ClientApp\Program.cs:line 58
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Squid.InstantMessage.ClientApp.Program.

d__1.MoveNext() in C:\Users\danny\RiderProjects\TheSquid_Master\Squid.InstantMessage.ClientApp\Program.cs:line 25
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Squid.InstantMessage.ClientApp.Program.()

Download file with IAsyncEnumerable and Blazor WASM

Hi, i have implemented a Grpc method in ASP.NET Core to download a file:

public async IAsyncEnumerable<Utils.RequestResult<byte[]>> PdfAsync()
{
   //Get file from db...

   //Set response headers
   var result = new Utils.RequestResult<string>
    {
        Result = true,
        ErrorCode = 0,
        ErrorMessage = "",
        Data = "Test.pdf"
    };
   var headers = new Grpc.Core.Metadata { { "result-bin", BinarySerializeObject(result) } };
   await CallContext.WriteResponseHeadersAsync(headers);

   //Dowload file
   var bufferSize = 1024 * 1024 * 2;
   var buffer = new byte[bufferSize];
   var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
   var bytesRead = await fs.ReadAsync(buffer);
   while (bytesRead > 0)
   {
      var fileChunk = new Utils.RequestResult<byte[]>
      {
         Result = true,
         Data = bytesRead < bufferSize ? buffer.Take(bytesRead).ToArray() : buffer
      };

      yield return fileChunk;

      bytesRead = await fs.ReadAsync(buffer);
   }
   fs.Close();
}

note who inside method i get the CallContext using injected HttpContextAccessor as i don't want to use a CallContext method param:

protected ServerCallContext CallContext
{
   get
   {
      return m_httpContextAccessor?.HttpContext?.Features?.Get<IServerCallContextFeature>()?.ServerCallContext;
   }
}

i have some questions:
What's the best way to serialize an object to binary and set to metadata?
Attually i use this method:

public byte[] BinarySerializeObject(object obj)
{
    var ms = new MemoryStream();
    var serializer = new System.Runtime.Serialization.DataContractSerializer(obj.GetType());
    serializer.WriteObject(ms, obj);
    return ms.ToArray();
}

is this the default serialization used by this Grpc library?

On client side i call the method as:

var pdfContentStream = GrpcService.PdfAsync();

//Read full file
var pdfContent = new List<byte>();
await foreach (var fileChunk in pdfContentStream)
{
      ShowDownloadProgress(...);
      pdfContent.AddRange(fileChunk.Data);
}

//download using jsInterop
await JSInteropService.DownloadBinaryDataToFileAsync("test.zip", "", pdfContent.ToArray());

how can i get the response headers?

last question, most related to blazor and not this Grpc library but i try..

As i read the file as stream of data, i would like to write to file also as a stream (now i get the full byte array of data and then call JSInterop to fire the download by javascript) so there is a way to do this in Blazor? I would like to do this way so i can show a realistic download progress in the UI.

Thanks

Option to register / identify services and operations without using the attributes

We have a pretty large code base of services and related interfaces which are used in a WPF client right now directly accessing a globally available SQL server.

We'd want to get away from this situation and hoped, we could just use the services as-is as a gRPC service and put an authentication layer over it and than migrate the client to talk to the gRPC endpoints instead of locally accessing the DB.

So the question is, is there a way to do this without adding ServiceContract and OperationContract on all interfaces. We have an common interface on all services which we implement - which would allow us to identify the correct services and we'd like to simply make all methods available. Is there a way to alter the default discovery process?

Thanks,
Philipp

Generator 'ServiceModelGrpcSourceGenerator' failed

Describe the bug
Source code generator failed.

To Reproduce

  • update visual studio to 16.9.2
  • try to build Examples\Basic\Basic.sln

Warning CS8785 Generator 'ServiceModelGrpcSourceGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'FileNotFoundException' with message 'Could not load file or assembly 'ServiceModel.Grpc, Version=1.1.7.0, Culture=neutral, PublicKeyToken=ba2245ad9cab75c7' or one of its dependencies. The system cannot find the file specified.'

CallContext model is generated on Swagger

Describe the bug
I've got a Grpc code fist metho:
Task<CreateReply> Create(CreateServiceRequest serviceRequest, CallContext? context = default)
That method is represented in swagger, however, the Call-Context is also generated, when I trigger that endpoint it returns a 500 error. I would like to know if it's possible to exclude the "CallContext" parameter from being generated in the Swagger page.

Expected behavior
The "CallContext" parameter should not be generated in the Swagger page, even though it's included in the gRPC code for the "Create" method. This is because the "CallContext" parameter is intended to be an optional parameter used internally by the gRPC server, and not exposed to external clients.

Environment

  • .net 6

Generic contract naming conflicts

The following contract definition leads to a naming conflicts at runtime:

// contract
[ServiceContract]
public interface ICalculator<TValue>
{
    [OperationContract]
    Task<TValue> Sum(TValue x, TValue y);
}

internal sealed class CalculatorDouble : ICalculator<double>
{
    // POST: /ICalculator/Sum
    public Task<double> Sum(double x, double y) => x + y;
}

internal sealed class CalculatorInt32 : ICalculator<int>
{
    // POST: /ICalculator/Sum
    public Task<int> Sum(int x, int y) => x + y;
}

in both cases the operation name is "ICalculator/Sum"

Bind service via interface

Add possibility to bind a service via interface:

// contract
[ServiceContract]
public interface ICalculator { }

// service implementation with specific asp.net attributes
[Authorize]
internal sealed class Calculator1 : ICalculator {}

[AllowAnonymous]
internal sealed class Calculator2 : ICalculator {}

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddServiceModelGrpc();
		
        // register the implementation
        if (....)
        {
            services.AddTransient<ICalculator, Calculator1>();
        }
        else
        {
            services.AddTransient<ICalculator, Calculator2>();
        }
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseEndpoints(endpoints =>
        {
            // bind the service via interface
            endpoints.MapGrpcService<ICalculator>();
        });
    }
}

Question: Can gRPC service model hosted as a windows service

Due to some restrictions we can't host the gRPC as a .net core web api etc. Can you suggest alternative? I saw that there is a NativeServiceHost project. Can it be packaged as a windows service which host the gRPC server? What do you recommend if we can only do .net framework?

Null Headers in CallOptions when using DefaultCallOptionsFactory

I have tried using the following clientOptions both by passing them to ClientFactory and by passing them in to the generated client extensions (using the DesignTime capabilities)

var clientOptions = new ServiceModelGrpcClientOptions
{
    DefaultCallOptionsFactory = () =>
        new CallOptions(new Metadata(), DateTime.UtcNow.Add(delay), cts.Token)
};

The generated code looks like this:

Task<bool> Arpc.Tests.IMembershipManager.SubmitApplication(Arpc.Tests.ApplyForMembership value, Guid correlationId, CancellationToken cancellationToken)
{
    var __callOptions = new CallOptionsBuilder(DefaultCallOptionsFactory)
        .WithCancellationToken(cancellationToken)
        .Build();
    var __request = new Message<Arpc.Tests.ApplyForMembership, Guid>(value, correlationId);
    var __call = CallInvoker.AsyncUnaryCall(Contract.MethodSubmitApplication, null, __callOptions, __request);
    var __response = ClientChannelAdapter.GetAsyncUnaryCallResult(__call, null, __callOptions.CancellationToken);
    return __response;
}

The CallOptionsBuilder calls DefaultCallOptionsFactory and receives a new CallOptions built by the DefaultCallOptionsFactory configured on clientOptions above.

However, __callOptions has a null value for Headers!

Screen Shot 2021-01-20 at 9 35 00 AM

I am running net5 on MacOS.

Authentication with AzureAD error

There is an error: Grpc.Core.RpcException: 'Status(StatusCode="Cancelled", Detail="Bad gRPC response. Invalid content-type value: text/html;
when using a Blazor ServerSide App with AzureAd authentication. When I change the authentication in this App to exactly the same JwtBearer security from the AspNetCoreAuthenticationTestBase.cs test everything works perfectly. However switching to AzureAD with the following settings:

          services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
               .AddMicrosoftIdentityWebApp(options =>
               {
                   Configuration.Bind("AzureAd", options);
                   options.ResponseType = "code";
                   options.SaveTokens = true;
               });

gives the error, although the specified Token is correct. If I use the exact same authentication settings in a WASM Blazor app everything works fine. Also if I remove the Authorize attribute from the service everything works fine. In order to exclude possibilities I also tested the same application with Protobuf-Grpc and then the exact same exception is thrown.
I am aware that it is a bit 'strange' to use gRPC in this scenario (serverside blazor), but basically I am trying to debug my application serverside because of the better "debug experience".

The browser logging looks like:

Grpc.Core.RpcException: Status(StatusCode="Cancelled", Detail="Bad gRPC response. Invalid content-type value: text/html; charset=utf-8")
   at ServiceModel.Grpc.Client.ClientChannelAdapter.GetAsyncUnaryCallResult[T](AsyncUnaryCall`1 call, CallContext context, CancellationToken token)
   at QatramApp.Pages.FetchData.Reload() in C:\Application\DevQatram\QatramApp\Pages\FetchData.razor:line 54
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)

The client is created in this way:

  var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler()));
  var credentials = CallCredentials.FromInterceptor(async (context, metadata) =>
      metadata.Add("Authorization", $"Bearer {Token}"));
  Channel = GrpcChannel.ForAddress(navigationManager.BaseUri, Options = new GrpcChannelOptions
  {
      HttpClient = httpClient,
      Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
  });

  clientFactory = new ClientFactory(new ServiceModelGrpcClientOptions { MarshallerFactory = ProtobufMarshallerFactory.Default });
  var client = clientFactory.CreateClient<T>(Channel);

Any thoughts??
Thanks!
Jan

Question, what about performance?

Hi, I was looking for code-first gRPC and found ServiceModel.Grpc. Did you do any performance comparison to other code-first implementations, like protobuf-grpc and MagicOnion? Also since you also implemented a ProtoBufMarshaller... What were the reasons (if any) to not just use protobuf-grpc?

Very impressed with all the clear examples and documentation!

Basically I am trying to find out all the reasons to start using ServiceModel.Grpc, do you have some pointers to this?
Thanks!

services.AddServiceModelGrpc() - Configure services options

I need to pass options like MaxReceiveMessageSize and MaxSendMessageSize but that call is inside AddServiceModelGrpc
How can i pass this parameters?

services.AddGrpc(options =>
{
options.EnableDetailedErrors = true;
options.MaxReceiveMessageSize = 2 * 1024 * 1024; // 2 MB
options.MaxSendMessageSize = 5 * 1024 * 1024; // 5 MB
});

DesignTime issue with File Scoped Namespace

Describe the bug
C# 10 file scoped namespaces cause build error with source code generation.

error CS1106: Extension method must be defined in a non-generic static class

To Reproduce

using MyNamespace.Services;
using ServiceModel.Grpc.DesignTime;

namespace MyNamespace.Grpc;

[ExportGrpcService(typeof(MyService), GenerateAspNetExtensions = true)]
internal static partial class MyGrpcServices
{

}

Expected behavior
File scoped namespaces should work just like traditional namespaces during source code generation.

Environment

  • OS: Windows 11
  • .NET 6

sync over async

Problem

For some reasons my client has a limitation and can call only sync methods.
On other hand a server implementation requires async method, example:

// server implementation has to be async
async Task<TResult> DoSomething(T1 arg1, T2 arg2, CancellationToken token)
{
    await DoThis(arg1, token);
    await DoThat(arg2, token);
    return TResult;
}

// client call has to by sync
// TResult DoSomething(T1 arg1, T2 arg2)
var result = proxy.DoSomething(arg1, arg2);

as a result, the contract interface cannot be defined properly

// requires by client (gRPC blocking unary call)
[OperationContract]
TResult DoSomething(T1 arg1, T2 arg2);

// requires by server (gRPC async unary call)
[OperationContract]
Task<TResult> DoSomething(T1 arg1, T2 arg2, CancellationToken token);

Solution

Define the contract in the following manner:

  • operation contract has to be defined as async
  • the sync method is a decoration and is not a contract
// [OperationContract]
TResult DoSomething(T1 arg1, T2 arg2, CancellationToken token = default);

[OperationContract]
Task<TResult> DoSomethingAsync(T1 arg1, T2 arg2, CancellationToken token = default);

ServiceModel.Grpc should recognize such pattern and in client proxy for DoSomething generate blocking unary call to the contract DoSomethingAsync.

Criterias:

  • naming convention [DoSomething] + [Async]
  • data input should be exactly the same: T1 arg1, T2 arg2
  • return type should be exactly the same: TResult vs Task<TResult> or ValueTask<TResult>; void with Task or ValueTask
  • context parameters matching is optional: DoSomething may or may not have CancellationToken

the following interface definition is valid

// contract variant 1
[OperationContract]
Task<TResult> DoSomethingAsync(T1 arg1, T2 arg2, CancellationToken token = default);

// contract variant 2
[OperationContract]
ValueTask<TResult> DoSomethingAsync(T1 arg1, T2 arg2, CancellationToken token = default);

// decoration methods
TResult DoSomething(T1 arg1, T2 arg2);
TResult DoSomething(T1 arg1, T2 arg2, CancellationToken token = default);
TResult DoSomething(T1 arg1, T2 arg2, CancellationToken token);
TResult DoSomething(T1 arg1, CancellationToken token, T2 arg2);
TResult DoSomething(CancellationToken token, T1 arg1, T2 arg2);

Service source code generation

Service contract and implementation

[ServiceContract]
public interface ICalculator
{
    [OperationContract]
    Task<long> Sum(long x, int y, int z, CancellationToken token = default);
}

public sealed class Calculator : ICalculator
{
    Task<long> Sum(long x, int y, int z, CancellationToken token = default) => Task.FromResult(x + y + z);
}

Option 1: generate code based on ICalculator contract

[ExportGrpcService(typeof(ICalculator), GenerateAspNetExtensions = true)]
internal static partial class MyGrpcServices
{
    // generated code ...
    public static IServiceCollection AddCalculatorOptions(this IServiceCollection services, Action<ServiceModelGrpcServiceOptions<ICalculator>> configure)
    {
        services.AddServiceModelGrpcServiceOptions<ICalculator>(configure);
    }

    // generated code ...
    public static GrpcServiceEndpointConventionBuilder MapCalculator(this IEndpointRouteBuilder builder)
    {
        builder.MapGrpcService<ICalculator, CalculatorEndpointBinder>();
    }

    // generated code ...
    internal sealed class CalculatorEndpoint
    {
        public Task<Message<long>> Sum(ICalculator service, Message<long, int, int> request, ServerCallContext context) {}
    }

    // generated code ...
    internal sealed class CalculatorEndpointBinder
    {
        public void Bind(binder)
        {
            var channel = new CalculatorEndpoint();
            var metadata = new List<object>();

            // get metadata from ICalculator
            GetServiceMetadata(metadata);
            // get metadata from ICalculator.Sum
            GetSumMetadata(metadata);
            binder.AddUnaryMethod("Sum", metadata, channel.Sum);
        }

        // optional user code
        partial void GetServiceMetadata(IList<object> metadata);
        partial void GetSumMetadata(IList<object> metadata);
    }
}

Option 2: generate code based on Calculator implementation

[ExportGrpcService(typeof(Calculator), GenerateAspNetExtensions = true)]
internal static partial class MyGrpcServices
{
    // generated code ...
    public static IServiceCollection AddCalculatorOptions(this IServiceCollection services, Action<ServiceModelGrpcServiceOptions<Calculator>> configure)
    {
        services.AddServiceModelGrpcServiceOptions<Calculator>(configure);
    }

    // generated code ...
    public static GrpcServiceEndpointConventionBuilder MapCalculator(this IEndpointRouteBuilder builder)
    {
        builder.MapGrpcService<Calculator, CalculatorEndpointBinder>();
    }

    // generated code ...
    internal sealed class CalculatorEndpoint
    {
        public Task<Message<long>> Sum(Calculator service, Message<long, int, int> request, ServerCallContext context) {}
    }

    // generated code ...
    internal sealed partial class CalculatorEndpointBinder
    {
        public void Bind(binder)
        {
            var channel = new CalculatorEndpoint();
            var metadata = new List<object>();

            // get metadata from Calculator
            GetServiceMetadata(metadata);
            // get metadata from Calculator.Sum
            GetSumMetadata(metadata);
            binder.AddUnaryMethod("Sum", metadata, channel.Sum);
        }

        // optional user code
        partial void GetServiceMetadata(IList<object> metadata);
        partial void GetSumMetadata(IList<object> metadata);    }
}

Startup

    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddCalculatorOptions(options =>
            {
                options.DefaultMarshallerFactory = ...
                options.DefaultErrorHandlerFactory = ...
            });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapCalculator();
        });
    }

Question, Json web http APIs

Hi Max, love this package!

I'm currently trying to generate some UI documentation for each microservice.
I found some progression by microsoft to support grpc ui with some pre-release nuget packages.

Is there a way the service model approach can support swagger with this article's instructions?
https://docs.microsoft.com/en-us/aspnet/core/grpc/httpapi?view=aspnetcore-5.0

I guess there is a problem this the fact there is no protobuf file to place the option (google.api.http) method.

Anyway the swagger library responses me with:
warn: Microsoft.AspNetCore.Grpc.HttpApi.HttpApiServiceMethodProvider[1] Could not find bind method for ServiceModelName1Service.

warn: Microsoft.AspNetCore.Grpc.HttpApi.HttpApiServiceMethodProvider[1] Could not find bind method for ServiceModelName2Service.

So it's clear a sign that the library recognizes the gRPC mappings which are native to dotnet-grpc .
Is there a way to support it with code first approach built by your library with this information?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.