Protobuf-net.Grpc 服务契约继承

Posted

技术标签:

【中文标题】Protobuf-net.Grpc 服务契约继承【英文标题】:Protobuf-net.Grpc Service Contract Inheritance 【发布时间】:2020-05-11 10:47:30 【问题描述】:

我正在将应用程序从 .Net FW 升级到 .Net Core。还将 WCF 服务升级为 gRPC 服务。我们决定使用 protobuf-net.Grpc。

我们对服务合同使用多级继承。

[ServiceContract]
public interface IBaseServiceContract<TDataModel> where TDataModel : DataModelObject



[ServiceContract]
public interface ICRUDLServiceContract<TDataModel> : IBaseServiceContract<TDataModel> where TDataModel : DataModelObject

    Task<Response<TDataModel>> Create(Request<TDataModel> request);
    Task<Response<TDataModel>> Read(Request<TDataModel> request);
    Task<VoidResponse> Update(Request<TDataModel> request);
    Task<VoidResponse> Delete(Request<TDataModel> request);
    Task<Response<DataResult<TDataModel>>> List(Request<DataSourceQuery> request);
    Task<Response<IEnumerable<TDataModel>>> ListAll();

一个简单的服务合约如下所示:

[ServiceContract]
public interface IProductService : ICRUDLServiceContract<Product>


在使用服务层的控制器层中,我们有通用控制器,它们通过基本通用接口来使用这些服务契约:

[ApiController]
public class CRUDLController<TDataModel, TServiceContract, TViewModel> : CRUDController<TDataModel, TServiceContract, TViewModel>
    where TDataModel : DataModelObject, new()
    where TServiceContract : ICRUDLServiceContract<TDataModel>
    where TViewModel : ViewModelObject, new()


    public CRUDLController(ILogger<BaseController<TDataModel, TServiceContract, TViewModel>> logger, TServiceContract dataService, IMapper mapper)
        : base(logger, dataService, mapper)
    
    

    [HttpGet]
    [Route("ListAll")]
    public virtual async Task<ActionResult<IEnumerable<TViewModel>>> ListAll()
    
        try
        
            var response = await _dataService.ListAll();
            return _mapper.Map<IEnumerable<TViewModel>>(response.Result).ToList();
        
        catch (Exception ex)
        
            throw CreateUserException(ex, MethodBase.GetCurrentMethod().Name);
        
    

    [HttpGet]
    [ActionName("List")]
    public virtual async Task<ActionResult<DataSourceResult>> List([ModelBinder(typeof(DataSourceQueryModelBinder))] DataSourceQuery query)
    
        try
        
            var response = await _dataService.List(query.AsRequest());
            return response.MapToDataSourceResult<TDataModel, TViewModel>(_mapper);
        
        catch (Exception ex)
        
            throw CreateUserException(ex, MethodBase.GetCurrentMethod().Name);
        
    

        ...

在服务应用程序的 Startup.cs 中,我们将所有服务映射为:

...
endpoints.MapGrpcService<ProductService>();
endpoints.MapGrpcService<TitleService>();
...

当我启动服务应用程序时,我在控制台日志中看到错误:

info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/2 POST https://localhost:7001/OverBase.Core.Contract.CRUDLServiceContract`1/List application/grpc -
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:

gRPC - /OverBase.Core.Contract.CRUDLServiceContract`1/List
gRPC - /OverBase.Core.Contract.CRUDLServiceContract`1/List

我已经尝试从基本接口中删除 [ServiceContract] 然后

warn: Grpc.AspNetCore.Server.Model.Internal.ServiceRouteBuilder[3]
No gRPC methods discovered for OverBase.Services.Program.ProductService.

基础接口中的方法已从服务中消失。

有没有办法在 protobuf-net.Grpc 中使用基接口中的方法?

【问题讨论】:

您可以启用调试级别的日志记录吗?绑定时,您应该看到所有映射 - 例如Added gRPC method 'Subscribe' to service 'MegaCorp.TimeService'. Method type: 'ServerStreaming', route pattern: '/MegaCorp.TimeService/Subscribe'. - 我可以假设它们在这里发生碰撞吗?大概我们在这里想要的是某种方式来在每个 T 级别定义子类型的路由?含义:这里的问题是`1。我们可能可以很容易地修复那个 - 到CRUDLServiceContract`FooCRUDLServiceContract`Bar 或其他什么?它需要一个相当小的代码更改。 具体来说,这个方法大概应该考虑泛型:github.com/protobuf-net/protobuf-net.Grpc/blob/master/src/… 仅供参考:长期修复现已在 github 上:github.com/protobuf-net/protobuf-net.Grpc/commit/… 【参考方案1】:

在 github 上报告这将是一个很棒的错误。问题是合约绑定器不会展开泛型,因此您最终会使用 .NET 泛型占位符(但内部有不同的 API)使用相同名称的多个服务:

IFoo`1/Method
IFoo`1/Method

从长远来看,我们应该修复库。使用属性来解决这个问题并不容易,因为属性不是 per-T,但是:我们可以编写自己的 binder:

    // note: this could also simply recognize a few known interfaces
    class MyServiceBinder : ServiceBinder
    
        protected override string GetDefaultName(Type contractType)
        
            var val = base.GetDefaultName(contractType);
            if (val.EndsWith("`1") && contractType.IsGenericType)
               // replace IFoo`1 with IFoo`TheThing
                var args = contractType.GetGenericArguments();
                if (args.Length == 1)
                
                    val = val.Substring(0, val.Length - 1) + args[0].Name;
                
            
            return val;
        
    

对于 ASP.NET,我们使用 DI 进行注册:

services.AddSingleton(BinderConfiguration.Create(binder: new MyServiceBinder()));

对于客户端,您可以将其传递给客户端创建者:

static readonly ClientFactory s_ClientFactory = ClientFactory.Create(
    BinderConfiguration.Create(binder: new MyServiceBinder()));
// ...
var calculator = http.CreateGrpcService<IWhatever>(s_ClientFactory);

这样做的结果是我们绑定到:

IFoo`X/Method
IFoo`Y/Method

显然,您可以随意提出不同的模式! gRPC 并不关心它们是什么。

【讨论】:

以上是关于Protobuf-net.Grpc 服务契约继承的主要内容,如果未能解决你的问题,请参考以下文章

使用 protobuf-net.Grpc 生成通用服务的 .proto 文件

使用 protobuf-net.Grpc 的客户端回调

首次使用 protobuf-net c# 访问 gRPC 端点时性能缓慢

尝试使用 protobuf-net 序列化 System.Numerics.Quaternion

wcf服务契约继承

wcf服务契约代理链