.NET Core 接入 Nacos + gRPC 实战:从零搭建跨语言微服务

技术栈:.NET 8 + nacos-sdk-csharp + Grpc.AspNetCore + NacosNetCore.Extensions

本文从一个真实项目出发,手把手带你完成 .NET Core 接入 Nacos 的服务注册/发现,并基于 gRPC 实现 .NET 与 Java(Dubbo)之间的跨语言互调。所有代码均来自实际项目,可直接复用。


一、背景与痛点

在微服务架构中,Java 生态有 Nacos + Dubbo 的成熟方案,但 .NET 侧接入 Nacos 的资料相对较少。当你的团队需要:

  • .NET 服务注册到 Nacos,让 Java 侧能发现并调用

  • .NET 作为消费者,从 Nacos 发现并调用 Java Dubbo-gRPC 服务

  • REST 和 gRPC 双协议共存,对外提供 HTTP API 的同时支持 gRPC 高性能调用

网上能搜到的文章多是"跑通 Hello World",缺少真实项目的分层设计、自动注册、Proto 生成等工程化实践。本文补上这块空白。


二、最终效果

项目跑起来后,你将获得:

能力 说明
服务自动注册到 Nacos 启动即注册,无需手动操作
提供 gRPC 服务给 Java 调用 Java 通过 Dubbo-gRPC 协议直接调用
提供 REST API 给前端调用 标准 HTTP 接口,Swagger 文档自动生成
调用远程 Java gRPC 服务 从 Nacos 发现实例,发起 gRPC 调用
业务层与协议层分离 加新接口只需写业务逻辑,协议层自动适配
Proto 文件自动生成 C# 代码定义接口,一键生成 .proto 交给 Java 团队

三、环境准备

依赖 版本 说明
.NET SDK 8.0+ dotnet --version 验证
Nacos Server 2.2.0+ Docker 部署或独立部署均可
IDE VS 2022 / Rider 支持 .NET 8 项目

四、项目搭建

4.1 创建项目

dotnet new webapi -n NacosDemo-order --no-https
cd NacosDemo-order

4.2 安装 NuGet 包

这是本文用到的核心包,版本亲测可用:

# Nacos SDK
dotnet add package nacos-sdk-csharp --version 1.3.10
dotnet add package nacos-sdk-csharp.AspNetCore --version 1.3.10
dotnet add package nacos-sdk-csharp.Extensions.Configuration --version 1.3.10
dotnet add package nacos-sdk-csharp.IniParser --version 1.3.10
dotnet add package nacos-sdk-csharp.YamlParser --version 1.3.10
​
# Nacos 扩展(简化注册)
dotnet add package NacosNetCore.Extensions --version 1.0.4.4
​
# gRPC
dotnet add package Grpc.AspNetCore --version 2.80.0
dotnet add package Grpc.Tools --version 2.80.0
dotnet add package Google.Protobuf --version 3.34.1
dotnet add package Google.Api.CommonProtos --version 2.17.0
​
# 其他
dotnet add package Swashbuckle.AspNetCore --version 10.1.7
dotnet add package Newtonsoft.Json --version 13.0.4

踩坑提示nacos-sdk-csharp 的 ASP.NET Core 集成需要配合 NacosNetCore.Extensions 使用,后者封装了 AddNacosAspNet 方法,一行代码搞定注册。如果只用 nacos-sdk-csharp.AspNetCore,需要手动配置较多内容。


五、核心配置

5.1 appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.Hosting.Diagnostics": "Error",
      "Grpc": "Information"
    }
  },
  "AllowedHosts": "*",
  "nacos": {
    "ServerAddresses": [ "http://xx.xxx.xxx.xxx:8848" ],
    "Namespace": "dubbo",
    "ServiceName": "order-service-new",
    "GroupName": "dubbo_core_group",
    "UserName": "nacos",
    "Password": "nacos",
    "PreferredNetworks": "xx.xxx.",
    "Port": 5073
  },
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://0.0.0.0:5073",
        "Protocols": "Http1AndHttp2"
      },
      "Grpc": {
        "Url": "http://0.0.0.0:8084",
        "Protocols": "Http2"
      }
    }
  }
}

几个关键配置说明

配置项 说明
nacos.Namespace Nacos 命名空间 ID,不是名称!在 Nacos 控制台创建命名空间后获取
nacos.PreferredNetworks 多网卡环境下指定注册 IP 的前缀,避免注册了 127.0.0.1 或内网不可达的 IP
nacos.Port 注册到 Nacos 的端口号,Java 侧通过这个端口发现你的 HTTP 服务
Kestrel.Endpoints 双端口配置:5073 承载 REST + Swagger,8084 专供 gRPC(HTTP/2)

踩坑提示:gRPC 要求 HTTP/2,而浏览器和部分客户端走 HTTP/1.1。Kestrel 的双端口方案让两种协议各走各的,互不干扰。5073 端口设置 Http1AndHttp2 是为了支持 Swagger UI 和 gRPC-Web。

5.2 csproj 配置 Proto 文件

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <RootNamespace>NacosDemo_order</RootNamespace>
  </PropertyGroup>
​
  <ItemGroup>
    <!-- 消费端 proto:生成 gRPC Client -->
    <Protobuf Include="Protos\Consumer\dictservice.proto" GrpcServices="Client" />
    <!-- 生产端 proto:生成 gRPC Server -->
    <Protobuf Include="Protos\Producer\classification_service_generated.proto" GrpcServices="Server" />
  </ItemGroup>
​
  <!-- ... NuGet 引用省略 ... -->
</Project>

GrpcServices="Client""Server" 决定了生成的是客户端桩代码还是服务端基类,别搞反了。


六、Nacos 服务注册

6.1 一行代码注册 Nacos

Program.cs 中:

var builder = WebApplication.CreateBuilder(args);
​
// 注册 Nacos —— 核心就这一行
builder.Services.AddNacosAspNet(builder.Configuration, "nacos");
​
var app = builder.Build();
app.Run();

AddNacosAspNetNacosNetCore.Extensions 提供的扩展方法,它会:

  1. 读取 appsettings.json"nacos" 节点的配置

  2. 向 Nacos Server 注册当前服务实例

  3. 启动心跳保活(Nacos 2.x 使用 gRPC 长连接,不再依赖 HTTP 心跳)

  4. 应用关闭时自动注销

6.2 验证注册成功

启动项目后,打开 Nacos 控制台 → 服务列表,应该能看到:

  • 服务名:order-service-new

  • 分组:dubbo_core_group

  • 命名空间:dubbo

  • 实例 IP 和端口正确

也可以通过 HTTP 接口验证:

curl http://localhost:8848/nacos/v1/ns/instance/list?serviceName=order-service-new&groupName=dubbo_core_group&namespaceId=dubbo

七、gRPC 服务提供(Provider 端)

这部分是 .NET 作为 gRPC 服务提供者,让 Java 侧能通过 Dubbo-gRPC 调用我们。

7.1 定义 Proto 文件

syntax = "proto3";
​
package com.cn.order.api.dubbo;
option csharp_namespace = "NacosDemo_order.Protos";
​
message ClassificationRequest {
  string key = 1;
}
​
message ClassificationListResponse {
  repeated ClassificationInfo classifications = 1;
}
​
message ClassificationInfo {
  string id = 1;
  string name = 2;
  int32 sort = 3;
}
​
message HealthCheckRequest {
}
​
message HealthCheckResponse {
  string status = 1;
  string message = 2;
  string version = 3;
}
​
service ClassificationDubboService {
  rpc GetClassificationList(ClassificationRequest) returns (ClassificationListResponse);
  rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
}

注意package 名称必须和 Java Dubbo 侧的包名完全一致(如 com.cn.order.api.dubbo),否则 Java 侧通过 Nacos 找到服务后无法正确路由到 gRPC 方法。

7.2 分层架构实现 gRPC 服务

这里我采用了 Business 层 + Grpc 层 的分层设计:

Services/
├── Business/                    # 业务逻辑层(核心)
│   └── ClassificationService   # 纯 C# 业务逻辑,无协议依赖
└── Grpc/                       # gRPC 协议层(适配器)
    └── ClassificationGrpcService # 协议转换,调用 Business 层

为什么要分层? 因为业务逻辑不应被 gRPC 协议绑架。将来如果要加 REST、GraphQL 或消息队列,只需新增对应的协议适配层,Business 层不动。

接口定义

// Interface/IClassificationService.cs
[ProtoService(
    Package = "com.cn.order.api.dubbo",
    ServiceName = "ClassificationDubboService",
    CSharpNamespace = "NacosDemo_order.Protos"
)]
public interface IClassificationService
{
    Task<ClassificationListResponse> GetClassificationList(ClassificationRequest request);
    Task<HealthCheckResponse> HealthCheck(HealthCheckRequest request);
}

Business 层(纯业务逻辑)

// Services/Business/ClassificationService.cs
public class ClassificationService : IClassificationService
{
    public async Task<ClassificationListResponse> GetClassificationList(ClassificationRequest request)
    {
        // 模拟数据源
        string json = @"
        [
            {""id"":""1"",""name"":""AA"",""sort"":1},
            {""id"":""2"",""name"":""BB"",""sort"":2},
            {""id"":""3"",""name"":""CC"",""sort"":3},
            {""id"":""4"",""name"":""DD"",""sort"":4}
        ]";
​
        var classificationList = JsonSerializer.Deserialize<List<ClassificationInfo>>(json);
        if (!string.IsNullOrEmpty(request.key))
        {
            classificationList = classificationList?
                .Where(x => x.name.Contains(request.key)).ToList();
        }
​
        return new ClassificationListResponse
        {
            classifications = classificationList ?? new List<ClassificationInfo>()
        };
    }
​
    public async Task<HealthCheckResponse> HealthCheck(HealthCheckRequest request)
    {
        return new HealthCheckResponse
        {
            status = "UP",
            message = "服务运行正常",
            version = "1.0.0"
        };
    }
}

Grpc 层(协议适配)

// Services/Grpc/ClassificationGrpcService.cs
public class ClassificationGrpcService : Protos.ClassificationDubboService.ClassificationDubboServiceBase
{
    private readonly IClassificationService _classificationService;
    private readonly ILogger<ClassificationGrpcService> _logger;
​
    public ClassificationGrpcService(
        IClassificationService classificationService,
        ILogger<ClassificationGrpcService> logger)
    {
        _classificationService = classificationService;
        _logger = logger;
    }
​
    public override async Task<Protos.ClassificationListResponse> GetClassificationList(
        Protos.ClassificationRequest request,
        ServerCallContext context)
    {
        try
        {
            _logger.LogInformation($"收到 gRPC 调用 GetClassificationList,key={request.Key}");
​
            // DTO 转换
            var dtoRequest = new DTO.ClassificationRequest { key = request.Key };
            var dtoResponse = await _classificationService.GetClassificationList(dtoRequest);
​
            // Proto 转换
            var response = new Protos.ClassificationListResponse();
            foreach (var item in dtoResponse.classifications)
            {
                response.Classifications.Add(new Protos.ClassificationInfo
                {
                    Id = item.id,
                    Name = item.name,
                    Sort = item.sort
                });
            }
​
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "gRPC GetClassificationList 调用失败");
            throw;
        }
    }
}

Grpc 层只做三件事:接收请求 → 转换 DTO → 调用 Business 层 → 转换响应。不写任何业务逻辑。


八、gRPC 服务消费(Consumer 端)

.NET 同时也是消费者,需要调用 Java 侧的 Dubbo-gRPC 服务(如字典服务)。

8.1 获取 Java 侧的 Proto 文件

从 Java 团队拿到 .proto 文件后放到 Protos/Consumer/ 目录:

// Protos/Consumer/dictservice.proto
syntax = "proto3";
​
package com.cn.common.api.dubbo;
option csharp_namespace = "GrpcServiceDemo";
​
message DictRequest {
  int32 tenant_id = 1;
  string key = 2;
}
​
message DictListResponse {
  repeated DictResponse dicts = 1;
}
​
// ... 其他消息定义省略 ...
​
service DictDubboService {
  rpc getDictsByKey(DictRequest) returns (DictListResponse);
  // ... 其他方法 ...
}

8.2 从 Nacos 发现服务并发起 gRPC 调用

// Controllers/ValuesController.cs
[ApiController]
[Route("api/[controller]")]
public class ValuesController : ControllerBase
{
    private readonly Nacos.V2.INacosNamingService _svc;
    private readonly ILogger<ValuesController> _logger;
​
    public ValuesController(
        Nacos.V2.INacosNamingService svc,
        ILogger<ValuesController> logger)
    {
        _svc = svc;
        _logger = logger;
    }
​
    [HttpGet("TestCallBygRPC")]
    public async Task<string> TestCallBygRPC()
    {
        try
        {
            // 1. 从 Nacos 获取健康实例
            var instance = await _svc.SelectOneHealthyInstance(
                "providers:com.cn.common.api.dubbo.DictDubboService::",
                "dubbo_core_group"
            );
            if (instance == null) return "服务实例不可用";
​
            // 2. 拼接 gRPC 地址(注意:用 gRPC 端口,不是 Dubbo 注册的端口)
            var address = $"http://{instance.Ip}:8084";
            var channel = GrpcChannel.ForAddress(address);
​
            // 3. 构造请求
            var request = new DictRequest
            {
                TenantId = 0,
                Key = "busReqTypeDict"
            };
​
            // 4. 调用远程服务
            var client = new DictDubboService.DictDubboServiceClient(channel);
            var response = await client.getDictsByKeyAsync(request);
​
            return response.ToString();
        }
        catch (RpcException ex)
        {
            _logger.LogError(ex, $"gRPC 调用失败:{ex.Status}");
            return $"错误:{ex.Status.Detail}";
        }
    }
}

几个关键点

  1. 服务名格式:Dubbo 注册到 Nacos 的服务名是 providers:{接口全限定名}:: 这种格式,不是简单的服务名

  2. 端口问题:Nacos 返回的 instance.Port 是 Dubbo 协议端口,gRPC 通常在不同端口(如 8084),需要硬编码或通过元数据获取

  3. SelectOneHealthyInstance:Nacos SDK 自带负载均衡,自动选择一个健康实例


九、自动注册机制(告别手动配置)

每新增一个 Service 或 gRPC 服务都要手动注册?太累了。通过反射扫描实现自动注册。

9.1 自动注册 Business 层

// Common/ServiceCollectionExtensions.cs
public static IServiceCollection AddServicesByAssembly(this IServiceCollection services)
{
    var assembly = Assembly.GetExecutingAssembly();
​
    var serviceTypes = assembly.GetTypes()
        .Where(t =>
            t.IsClass &&
            !t.IsAbstract &&
            t.Name.EndsWith("Service") &&
            t.GetInterfaces().Any()
        ).ToList();
​
    foreach (var type in serviceTypes)
    {
        var interfaceType = type.GetInterfaces()
            .FirstOrDefault(i => i.Name == $"I{type.Name}");
        if (interfaceType != null)
        {
            services.AddScoped(interfaceType, type);
        }
    }
​
    return services;
}

约定规则

  • 类名以 Service 结尾

  • 实现了对应的 I{Name}Service 接口

  • 例如 ClassificationServiceIClassificationService

9.2 自动映射 gRPC 服务

public static IEndpointRouteBuilder MapGrpcServicesByAssembly(this IEndpointRouteBuilder endpoints)
{
    var assembly = Assembly.GetExecutingAssembly();
​
    var grpcServiceTypes = assembly.GetTypes()
        .Where(t =>
            t.IsClass &&
            !t.IsAbstract &&
            t.Name.EndsWith("GrpcService") &&
            t.BaseType != null &&
            t.BaseType.Name.EndsWith("ServiceBase")
        ).ToList();
​
    var mapGrpcServiceMethod = typeof(GrpcEndpointRouteBuilderExtensions)
        .GetMethods(BindingFlags.Public | BindingFlags.Static)
        .FirstOrDefault(m =>
            m.Name == "MapGrpcService" &&
            m.IsGenericMethod &&
            m.GetParameters().Length == 1
        );
​
    foreach (var serviceType in grpcServiceTypes)
    {
        if (mapGrpcServiceMethod != null)
        {
            var genericMethod = mapGrpcServiceMethod.MakeGenericMethod(serviceType);
            genericMethod.Invoke(null, new object[] { endpoints });
        }
    }
​
    return endpoints;
}

约定规则

  • 类名以 GrpcService 结尾

  • 继承自 proto 生成的 ServiceBase

9.3 Program.cs 一行搞定

// 自动注册所有 Service
builder.Services.AddServicesByAssembly();
​
// ... 其他配置 ...
​
// 自动映射所有 gRPC 服务
app.MapGrpcServicesByAssembly();

之后新增业务,只需要:

  1. I{Name}Service 接口

  2. {Name}Service 实现类

  3. {Name}GrpcService 适配类

零配置,自动生效。


十、Proto 自动生成工具(亮点功能)

这是本项目最值得分享的功能:从 C# 代码反向生成 .proto 文件

传统流程是先写 .proto → 生成 C# 代码 → 写业务逻辑。但在 .NET 先行的项目中,我们往往先设计 C# 接口,再把契约给 Java 团队。手动写 .proto 容易出错且重复劳动。

10.1 设计思路

C# 接口定义 + DTO  →  [ProtoService] / [ProtoMessage] 标记  →  运行生成工具  →  输出 .proto 文件

10.2 自定义属性

// Common/ProtoGen/ProtoAttributes.cs
​
/// <summary>
/// 标记需要生成 .proto 的服务接口
/// </summary>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
public class ProtoServiceAttribute : Attribute
{
    public string Package { get; set; }           // proto 包名
    public string ServiceName { get; set; }        // 服务名
    public string CSharpNamespace { get; set; }    // C# 命名空间
}
​
/// <summary>
/// 标记需要生成 proto 消息的类
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum)]
public class ProtoMessageAttribute : Attribute { }
​
/// <summary>
/// 标记字段编号
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class ProtoFieldAttribute : Attribute
{
    public int Number { get; set; }
    public ProtoFieldAttribute(int number) => Number = number;
}

10.3 标记 DTO

// DTO/ProtoMessageClasses.cs
[ProtoMessage]
public class ClassificationRequest
{
    [ProtoField(1)]
    public string key { get; set; }
}
​
[ProtoMessage]
public class ClassificationInfo
{
    [ProtoField(1)]
    public string id { get; set; }
​
    [ProtoField(2)]
    public string name { get; set; }
​
    [ProtoField(3)]
    public int sort { get; set; }
}
​
[ProtoMessage]
public class ClassificationListResponse
{
    [ProtoField(1)]
    public List<ClassificationInfo> classifications { get; set; }
}

10.4 核心生成器

生成器通过反射扫描程序集,自动完成:

  1. 找到所有 [ProtoService] 标记的接口

  2. 收集接口方法中用到的请求/响应类型

  3. 额外收集 [ProtoMessage] 标记的类型

  4. 生成 messageservice 定义

  5. C# 类型自动映射为 proto 类型(stringstringintint32List<T>repeated T

  6. 属性名自动转 snake_case(ClassificationListclassification_list

// 核心类型映射
private string GetProtoType(Type type)
{
    if (type == typeof(string)) return "string";
    if (type == typeof(int) || type == typeof(int?)) return "int32";
    if (type == typeof(long) || type == typeof(long?)) return "int64";
    if (type == typeof(bool) || type == typeof(bool?)) return "bool";
    if (type == typeof(double) || type == typeof(double?)) return "double";
    if (type == typeof(float) || type == typeof(float?)) return "float";
    if (type == typeof(byte[])) return "bytes";
    if (type.IsEnum) return type.Name;
    return type.Name; // 自定义消息类型
}

10.5 一键生成

dotnet run -- --generate-proto

输出到 Protos/Producer/classification_service_generated.proto,直接交给 Java 团队即可。

10.6 生成结果

syntax = "proto3";
​
package com.cn.order.api.dubbo;
option csharp_namespace = "NacosDemo_order.Protos";
​
message ClassificationRequest {
  string key = 1;
}
​
message ClassificationListResponse {
  repeated ClassificationInfo classifications = 1;
}
​
message HealthCheckRequest {
}
​
message HealthCheckResponse {
  string status = 1;
  string message = 2;
  string version = 3;
}
​
message ClassificationInfo {
  string id = 1;
  string name = 2;
  int32 sort = 3;
}
​
service ClassificationDubboService {
  rpc GetClassificationList(ClassificationRequest) returns (ClassificationListResponse);
  rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
}

十一、Program.cs 完整配置

using Nacos.AspNetCore.V2;
using NacosDemo_order.Common;
using NacosDemo_order.Tools;
​
// Proto 生成工具入口(独立运行)
if (args.Length > 0 && args[0] == "--generate-proto")
{
    ProtoGenerationTool.Run(args);
    return;
}
​
var builder = WebApplication.CreateBuilder(args);
​
// 1. 自动注册 Business 层服务
builder.Services.AddServicesByAssembly();
​
// 2. 注册控制器和 gRPC
builder.Services.AddControllers();
builder.Services.AddGrpc();
builder.Services.AddHealthChecks();
​
// 3. 注册 Nacos(一行搞定)
builder.Services.AddNacosAspNet(builder.Configuration, "nacos");
​
// 4. 注册 HttpClientFactory
builder.Services.AddHttpClient("nacosService");
​
// 5. Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "Order Service API",
        Version = "v1",
        Description = "基于 .NET Core + Nacos + gRPC 的订单服务接口文档"
    });
});
​
var app = builder.Build();
​
// 中间件
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "Order Service v1");
        options.RoutePrefix = string.Empty;
    });
}
​
app.UseRouting();
app.UseAuthorization();
​
// 映射控制器
app.MapControllers();
​
// 自动映射所有 gRPC 服务(无需手动添加)
app.MapGrpcServicesByAssembly();
​
// 健康检查端点
app.MapHealthChecks("/health");
​
app.Run();

十二、踩坑记录

坑 1:Nacos 注册 IP 不对

现象:Nacos 控制台显示的 IP 是 127.0.0.1 或某个内网不可达的 IP,Java 侧调不通。

解决:配置 PreferredNetworks,指定注册 IP 的前缀匹配:

"PreferredNetworks": "XX.XXX."

SDK 会优先选择 IP 前缀匹配的网卡地址注册。

坑 2:gRPC 端口与 Dubbo 端口混淆

现象:从 Nacos 拿到实例后直接用 instance.Port 连 gRPC,报连接失败。

解决:Dubbo 注册到 Nacos 的端口是 Dubbo 协议端口(如 20880),gRPC 通常在另一个端口(如 8084)。需要单独约定或通过 Nacos 元数据传递:

// 方式 1:硬编码 gRPC 端口(简单场景)
var address = $"http://{instance.Ip}:8084";
​
// 方式 2:通过元数据获取(推荐)
var grpcPort = instance.Metadata.TryGetValue("grpcPort", out var port) ? port : "8084";
var address = $"http://{instance.Ip}:{grpcPort}";

坑 3:Proto 包名不匹配

现象:Java 侧通过 Nacos 找到服务,但 gRPC 调用报 UNIMPLEMENTED

解决.proto 文件中的 package 必须与 Java Dubbo 接口的包名完全一致。Java 侧是 com.cn.order.api.dubbo,proto 也必须是同样的包名,否则 gRPC 的 service 路径对不上。

坑 4:HTTP/2 和浏览器不兼容

现象:Swagger 页面正常,但 gRPC 调用失败。

解决:Kestrel 双端口方案,HTTP 端口(5073)走 Http1AndHttp2,gRPC 专用端口(8084)走纯 Http2。这样 Swagger 和 gRPC 各走各的。


十三、Java 侧调用指南

把生成的 .proto 文件给 Java 团队后,他们需要:

  1. 将 proto 文件放入项目中,使用 protoc 生成 Java Stub

  2. 通过 Nacos 发现 .NET 服务实例

  3. 使用 Dubbo-gRPC 协议调用

服务发现信息

Nacos 服务名 providers:com.cn.order.api.dubbo.ClassificationDubboService::
分组 dubbo_core_group
命名空间 dubbo
gRPC 端口 8084

十四、扩展指南

新增业务服务

只需 3 步:

# 1. 写接口
Interface/IOrderService.cs
​
# 2. 写业务实现
Services/Business/OrderService.cs
​
# 3. 写 gRPC 适配
Services/Grpc/OrderGrpcService.cs

编译启动,自动注册,零配置。

新增 Proto 消费

  1. 把 Java 团队的 .proto 文件放到 Protos/Consumer/

  2. .csproj 中添加 <Protobuf Include="Protos/Consumer/xxx.proto" GrpcServices="Client" />

  3. 注入 INacosNamingService 发现服务,用生成的 Client 类发起调用


十五、总结

本文从实战角度,完整覆盖了 .NET Core 接入 Nacos + gRPC 的关键路径:

模块 核心要点
Nacos 注册 AddNacosAspNet 一行搞定,注意 PreferredNetworks
gRPC Provider 分层架构(Business + Grpc),协议与逻辑解耦
gRPC Consumer Nacos 发现 → 拼地址 → 创建 Client → 调用
自动注册 反射扫描,约定优于配置,新增服务零配置
Proto 生成 C# 代码 → .proto 文件,跨团队协作利器
双端口方案 Kestrel 多 Endpoint,HTTP/1.1 和 HTTP/2 各得其所
posted @ 2026-07-30 17:57  HuWen233  阅读(1)  评论(0)    收藏  举报