Protobuf-net.Grpc 服务契约继承
Protobuf-net.Grpc Service Contract Inheritance
我正在将应用程序从 .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 中使用基接口的方法?
在 github 上报告这将是一个很好的错误。问题是契约活页夹不会展开泛型,所以你最终会得到多个同名服务,使用 .NET 泛型占位符(但内部 API 不同):
IFoo`1/Method
IFoo`1/Method
从长远来看,我们应该修复图书馆。使用属性来解决这个问题并不容易,因为属性不是每个T
,但是:我们可以编写自己的活页夹:
// 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 并不关心它们是什么。
我正在将应用程序从 .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 中使用基接口的方法?
在 github 上报告这将是一个很好的错误。问题是契约活页夹不会展开泛型,所以你最终会得到多个同名服务,使用 .NET 泛型占位符(但内部 API 不同):
IFoo`1/Method
IFoo`1/Method
从长远来看,我们应该修复图书馆。使用属性来解决这个问题并不容易,因为属性不是每个T
,但是:我们可以编写自己的活页夹:
// 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 并不关心它们是什么。