C#中使用Grpc的示例

14

title: Grpc
description:
published: true
date: 2024-11-09T07:42:01.041Z
tags:
editor: markdown

dateCreated: 2024-11-09T07:42:01.041Z

https://learn.microsoft.com/zh-cn/aspnet/core/grpc/?view=aspnetcore-8.0

在 C# 中,gRPC 允许在客户端请求中添加元数据(Metadata)请求头,同时服务端也可以接收和处理这些元数据。以下是如何在 C# 中添加和使用 gRPC 元数据请求头的详细步骤。

1. 添加元数据请求头

您可以在 gRPC 客户端调用时使用 CallOptions 来添加元数据请求头。

客户端代码示例

using System;
using Grpc.Core;
using Grpc.Net.Client;

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new ExampleService.ExampleServiceClient(channel);

// 创建元数据
var headers = new Metadata
{
    { "custom-header-1", "value1" },
    { "custom-header-2", "value2" }
};

// 使用元数据进行RPC调用
var callOptions = new CallOptions(headers);
var response = await client.GetExampleAsync(new ExampleRequest(), callOptions);

2. 在服务端使用元数据请求头

在 gRPC 服务端,您可以通过上下文(ServerCallContext)来访问请求中的元数据。

服务端代码示例

using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Logging;

public class ExampleServiceImpl : ExampleService.ExampleServiceBase
{
    private readonly ILogger<ExampleServiceImpl> _logger;

    public ExampleServiceImpl(ILogger<ExampleServiceImpl> logger)
    {
        _logger = logger;
    }

    public override Task<ExampleResponse> GetExample(ExampleRequest request, ServerCallContext context)
    {
        // 获取元数据
        var headers = context.RequestHeaders;

        // 访问特定的元数据
        var customHeader1 = headers.GetValue("custom-header-1");
        var customHeader2 = headers.GetValue("custom-header-2");

        _logger.LogInformation($"Received custom-header-1: {customHeader1}");
        _logger.LogInformation($"Received custom-header-2: {customHeader2}");

        // 处理请求并返回响应
        var response = new ExampleResponse
        {
            Result = "Processed"
        };
        return Task.FromResult(response);
    }
}

3. 添加响应元数据

除了在请求中添加元数据外,服务端也可以在响应中返回元数据。

服务端代码示例(添加响应元数据)

public override async Task<ExampleResponse> GetExample(ExampleRequest request, ServerCallContext context)
{
    // 获取元数据
    var headers = context.RequestHeaders;

    // 访问特定的元数据
    var customHeader1 = headers.GetValue("custom-header-1");
    var customHeader2 = headers.GetValue("custom-header-2");

    // 添加响应元数据
    var responseHeaders = new Metadata
    {
        { "response-header-1", "response-value1" },
        { "response-header-2", "response-value2" }
    };
    await context.WriteResponseHeadersAsync(responseHeaders);

    // 处理请求并返回响应
    var response = new ExampleResponse
    {
        Result = "Processed"
    };
    return response;
}

4. 客户端接收响应元数据

客户端可以在接收到响应后访问响应元数据。

客户端代码示例(接收响应元数据)

using System;
using Grpc.Core;
using Grpc.Net.Client;

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new ExampleService.ExampleServiceClient(channel);

// 创建元数据
var headers = new Metadata
{
    { "custom-header-1", "value1" },
    { "custom-header-2", "value2" }
};

// 使用元数据进行RPC调用
var callOptions = new CallOptions(headers);
var call = client.GetExampleAsync(new ExampleRequest(), callOptions);

var response = await call.ResponseAsync;
var responseHeaders = await call.ResponseHeadersAsync;

// 访问响应元数据
var responseHeader1 = responseHeaders.GetValue("response-header-1");
var responseHeader2 = responseHeaders.GetValue("response-header-2");

Console.WriteLine($"Received response-header-1: {responseHeader1}");
Console.WriteLine($"Received response-header-2: {responseHeader2}");

总结

通过以上示例,您可以在 C# 中的 gRPC 客户端和服务端之间传递和使用元数据请求头。这样可以增强 gRPC 通信的灵活性,适应不同的应用场景,如身份验证、追踪等。

在 C# 中使用 gRPC 实现 JWT(JSON Web Token)身份认证和授权,通常包括以下步骤:

  1. 生成和验证 JWT
  2. 在客户端添加 JWT 到请求头
  3. 在服务端解析和验证 JWT
  4. 在服务端实现授权逻辑

下面将详细介绍如何在 C# 中实现这些步骤。

1. 生成和验证 JWT

在实际应用中,您通常会使用一个专门的认证服务来生成和验证 JWT。为了简化示例,我们假设 JWT 是由某个服务生成的,客户端会将其添加到请求头中。

您可以使用像 System.IdentityModel.Tokens.Jwt 这样的库来生成和验证 JWT。

安装 JWT 库

dotnet add package System.IdentityModel.Tokens.Jwt

2. 在客户端添加 JWT 到请求头

客户端需要在每个 gRPC 请求中添加 JWT 到请求头中。

客户端代码示例

using System;
using System.Threading.Tasks;
using Grpc.Net.Client;
using Grpc.Core;

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new ExampleService.ExampleServiceClient(channel);

// 假设已获取 JWT
var jwtToken = "your-jwt-token";

// 创建元数据并添加 JWT
var headers = new Metadata
{
    { "Authorization", $"Bearer {jwtToken}" }
};

// 使用元数据进行RPC调用
var callOptions = new CallOptions(headers);
var response = await client.GetExampleAsync(new ExampleRequest(), callOptions);

Console.WriteLine($"Response: {response.Result}");

3. 在服务端解析和验证 JWT

在服务端,您需要解析和验证从客户端传递过来的 JWT。这通常在 gRPC 服务方法调用之前进行,最好的方式是通过拦截器。

服务端代码示例

首先,定义一个 JWT 验证方法。

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;

public class JwtHelper
{
    public static ClaimsPrincipal ValidateToken(string token, string secret)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = System.Text.Encoding.ASCII.GetBytes(secret);

        tokenHandler.ValidateToken(token, new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false,
            ClockSkew = TimeSpan.Zero
        }, out SecurityToken validatedToken);

        return (JwtSecurityToken)validatedToken;
    }
}

然后,定义一个拦截器来处理身份验证。

using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Interceptors;

public class AuthInterceptor : Interceptor
{
    private readonly string _secret;

    public AuthInterceptor(string secret)
    {
        _secret = secret;
    }

    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request, 
        ServerCallContext context, 
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        var authorizationHeader = context.RequestHeaders.GetValue("Authorization");

        if (authorizationHeader != null && authorizationHeader.StartsWith("Bearer "))
        {
            var token = authorizationHeader.Substring("Bearer ".Length).Trim();
            try
            {
                var claimsPrincipal = JwtHelper.ValidateToken(token, _secret);
                // 将claimsPrincipal添加到context.User
                context.User = claimsPrincipal;
            }
            catch (Exception ex)
            {
                throw new RpcException(new Status(StatusCode.Unauthenticated, "Invalid token"));
            }
        }
        else
        {
            throw new RpcException(new Status(StatusCode.Unauthenticated, "Authorization header missing"));
        }

        return await continuation(request, context);
    }
}

4. 在服务端实现授权逻辑

在具体的 gRPC 服务方法中,您可以通过 ServerCallContext 来访问用户信息并进行授权检查。

服务端代码示例(服务方法)

using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Logging;

public class ExampleServiceImpl : ExampleService.ExampleServiceBase
{
    private readonly ILogger<ExampleServiceImpl> _logger;

    public ExampleServiceImpl(ILogger<ExampleServiceImpl> logger)
    {
        _logger = logger;
    }

    public override Task<ExampleResponse> GetExample(ExampleRequest request, ServerCallContext context)
    {
        // 从context.User获取用户信息
        var user = context.User;

        if (user == null || !user.Identity.IsAuthenticated)
        {
            throw new RpcException(new Status(StatusCode.PermissionDenied, "Access denied"));
        }

        // 进行授权检查,例如检查用户角色
        if (!user.IsInRole("AllowedRole"))
        {
            throw new RpcException(new Status(StatusCode.PermissionDenied, "Access denied"));
        }

        // 处理请求并返回响应
        var response = new ExampleResponse
        {
            Result = "Processed"
        };
        return Task.FromResult(response);
    }
}

5. 配置 gRPC 服务

最后,您需要在 gRPC 服务中注册拦截器。

配置代码示例

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

// 添加gRPC服务
builder.Services.AddGrpc();

var app = builder.Build();

app.MapGrpcService<ExampleServiceImpl>();

app.Run();

Program.csStartup.cs 中添加拦截器:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

// 添加gRPC服务
builder.Services.AddGrpc(options =>
{
    options.Interceptors.Add<AuthInterceptor>(provider => new AuthInterceptor("your-secret-key"));
});

var app = builder.Build();

app.MapGrpcService<ExampleServiceImpl>();

app.Run();

通过这些步骤,您可以在 C# 中使用 gRPC 实现基于 JWT 的身份认证和授权。这将确保只有合法的请求才能访问您的 gRPC 服务,并且可以根据用户的角色或权限进行授权检查。

在 C# 中使用 gRPC 实现客户端流、服务端流和双向流的通信模式,涉及到定义 gRPC 服务、实现服务方法以及编写客户端代码。这些模式适用于不同的场景,如客户端批量上传数据、服务器持续发送数据和双向实时通信。

以下是如何在 C# 中实现这三种流模式的详细步骤:

1. 客户端流(Client Streaming)

客户端流模式允许客户端发送一系列请求消息到服务器,服务器在接收所有请求消息后返回一个响应。

定义 proto 文件

syntax = "proto3";

package example;

message RequestMessage {
  int32 id = 1;
  string content = 2;
}

message ResponseMessage {
  string result = 1;
}

service ExampleService {
  rpc ClientStreaming (stream RequestMessage) returns (ResponseMessage);
}

服务端实现

using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Logging;

public class ExampleServiceImpl : ExampleService.ExampleServiceBase
{
    private readonly ILogger<ExampleServiceImpl> _logger;

    public ExampleServiceImpl(ILogger<ExampleServiceImpl> logger)
    {
        _logger = logger;
    }

    public override async Task<ResponseMessage> ClientStreaming(IAsyncStreamReader<RequestMessage> requestStream, ServerCallContext context)
    {
        int count = 0;
        while (await requestStream.MoveNext())
        {
            var request = requestStream.Current;
            _logger.LogInformation($"Received message {request.Id}: {request.Content}");
            count++;
        }

        return new ResponseMessage
        {
            Result = $"Received {count} messages"
        };
    }
}

客户端实现

using System;
using System.Threading.Tasks;
using Grpc.Net.Client;
using Grpc.Core;

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new ExampleService.ExampleServiceClient(channel);

using var call = client.ClientStreaming();

for (int i = 1; i <= 5; i++)
{
    await call.RequestStream.WriteAsync(new RequestMessage { Id = i, Content = $"Message {i}" });
}

await call.RequestStream.CompleteAsync();

var response = await call;
Console.WriteLine(response.Result);

2. 服务端流(Server Streaming)

服务端流模式允许服务器发送一系列响应消息到客户端,客户端接收这些消息后处理。

定义 proto 文件

syntax = "proto3";

package example;

message RequestMessage {
  int32 id = 1;
  string content = 2;
}

message ResponseMessage {
  string result = 1;
}

service ExampleService {
  rpc ServerStreaming (RequestMessage) returns (stream ResponseMessage);
}

服务端实现

using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Logging;

public class ExampleServiceImpl : ExampleService.ExampleServiceBase
{
    private readonly ILogger<ExampleServiceImpl> _logger;

    public ExampleServiceImpl(ILogger<ExampleServiceImpl> logger)
    {
        _logger = logger;
    }

    public override async Task ServerStreaming(RequestMessage request, IServerStreamWriter<ResponseMessage> responseStream, ServerCallContext context)
    {
        for (int i = 1; i <= 5; i++)
        {
            await responseStream.WriteAsync(new ResponseMessage { Result = $"Response {i} for {request.Content}" });
            await Task.Delay(1000); // 模拟延迟
        }
    }
}

客户端实现

using System;
using System.Threading.Tasks;
using Grpc.Net.Client;
using Grpc.Core;

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new ExampleService.ExampleServiceClient(channel);

var request = new RequestMessage { Id = 1, Content = "Request content" };
using var call = client.ServerStreaming(request);

while (await call.ResponseStream.MoveNext())
{
    var response = call.ResponseStream.Current;
    Console.WriteLine(response.Result);
}

3. 双向流(Bidirectional Streaming)

双向流模式允许客户端和服务器之间进行双向的消息流,每一方都可以独立地发送和接收消息。

定义 proto 文件

syntax = "proto3";

package example;

message RequestMessage {
  int32 id = 1;
  string content = 2;
}

message ResponseMessage {
  string result = 1;
}

service ExampleService {
  rpc BidirectionalStreaming (stream RequestMessage) returns (stream ResponseMessage);
}

服务端实现

using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Logging;

public class ExampleServiceImpl : ExampleService.ExampleServiceBase
{
    private readonly ILogger<ExampleServiceImpl> _logger;

    public ExampleServiceImpl(ILogger<ExampleServiceImpl> logger)
    {
        _logger = logger;
    }

    public override async Task BidirectionalStreaming(IAsyncStreamReader<RequestMessage> requestStream, IServerStreamWriter<ResponseMessage> responseStream, ServerCallContext context)
    {
        while (await requestStream.MoveNext())
        {
            var request = requestStream.Current;
            _logger.LogInformation($"Received message {request.Id}: {request.Content}");

            await responseStream.WriteAsync(new ResponseMessage { Result = $"Echo {request.Content}" });
        }
    }
}

客户端实现

using System;
using System.Threading.Tasks;
using Grpc.Net.Client;
using Grpc.Core;

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new ExampleService.ExampleServiceClient(channel);

using var call = client.BidirectionalStreaming();

var responseTask = Task.Run(async () =>
{
    await foreach (var response in call.ResponseStream.ReadAllAsync())
    {
        Console.WriteLine($"Received: {response.Result}");
    }
});

for (int i = 1; i <= 5; i++)
{
    await call.RequestStream.WriteAsync(new RequestMessage { Id = i, Content = $"Message {i}" });
    await Task.Delay(1000); // 模拟延迟
}

await call.RequestStream.CompleteAsync();
await responseTask;

总结

通过以上示例,您可以在 C# 中实现 gRPC 的客户端流、服务端流和双向流模式。这些模式适用于不同的应用场景,如客户端批量上传数据、服务器持续推送数据和实时双向通信。

在 C# 中使用 gRPC 时,处理异常并将详细的异常信息传递给客户端是确保应用程序健壮性和调试便利性的重要步骤。以下是如何在 gRPC 中处理异常并返回详细异常信息的方法。

1. 服务端处理异常

在服务端处理异常时,可以使用 RpcException 来捕获和返回详细的错误信息。此外,可以通过拦截器统一处理未捕获的异常并转换为 gRPC 错误响应。

定义服务和方法

首先,定义 gRPC 服务和方法。

syntax = "proto3";

package example;

message ExampleRequest {
  string name = 1;
}

message ExampleResponse {
  string message = 1;
}

service ExampleService {
  rpc GetExample (ExampleRequest) returns (ExampleResponse);
}

服务端实现异常处理

在服务方法中捕获异常并返回 RpcException

using System;
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Logging;

public class ExampleServiceImpl : ExampleService.ExampleServiceBase
{
    private readonly ILogger<ExampleServiceImpl> _logger;

    public ExampleServiceImpl(ILogger<ExampleServiceImpl> logger)
    {
        _logger = logger;
    }

    public override async Task<ExampleResponse> GetExample(ExampleRequest request, ServerCallContext context)
    {
        try
        {
            // 模拟可能引发异常的逻辑
            if (string.IsNullOrEmpty(request.Name))
            {
                throw new ArgumentException("Name cannot be empty");
            }

            return new ExampleResponse
            {
                Message = $"Hello, {request.Name}"
            };
        }
        catch (ArgumentException ex)
        {
            _logger.LogError(ex, "Invalid argument");
            throw new RpcException(new Status(StatusCode.InvalidArgument, ex.Message));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Internal server error");
            throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
        }
    }
}

使用拦截器统一处理异常

拦截器可以统一处理未捕获的异常,并将其转换为 RpcException 返回给客户端。

using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Logging;

public class ExceptionInterceptor : Interceptor
{
    private readonly ILogger<ExceptionInterceptor> _logger;

    public ExceptionInterceptor(ILogger<ExceptionInterceptor> logger)
    {
        _logger = logger;
    }

    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request, 
        ServerCallContext context, 
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        try
        {
            return await continuation(request, context);
        }
        catch (RpcException)
        {
            throw; // gRPC 异常直接抛出
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unhandled exception");
            throw new RpcException(new Status(StatusCode.Internal, "Internal server error"), ex.Message);
        }
    }
}

注册拦截器

Program.csStartup.cs 中注册拦截器。

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

// 添加gRPC服务
builder.Services.AddGrpc(options =>
{
    options.Interceptors.Add<ExceptionInterceptor>();
});

var app = builder.Build();

app.MapGrpcService<ExampleServiceImpl>();

app.Run();

2. 客户端处理异常

在客户端捕获 RpcException 并处理详细错误信息。

using System;
using System.Threading.Tasks;
using Grpc.Net.Client;
using Grpc.Core;

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new ExampleService.ExampleServiceClient(channel);

try
{
    var response = await client.GetExampleAsync(new ExampleRequest { Name = "" });
    Console.WriteLine(response.Message);
}
catch (RpcException ex)
{
    Console.WriteLine($"RPC Error: {ex.Status.StatusCode}");
    Console.WriteLine($"Detail: {ex.Status.Detail}");
    if (ex.StatusCode == StatusCode.InvalidArgument)
    {
        Console.WriteLine("Invalid argument provided.");
    }
    else if (ex.StatusCode == StatusCode.Internal)
    {
        Console.WriteLine("Internal server error.");
    }
}

总结

通过在服务端使用 RpcException 和拦截器,您可以捕获和处理异常,并将详细的异常信息返回给客户端。客户端可以捕获 RpcException 并根据异常的详细信息进行相应的处理。这样可以提高 gRPC 应用的健壮性,并方便调试和错误排查。

以下是详细的步骤和代码示例,确保正确配置反射服务。

1. 确认安装了反射 NuGet 包

确保已安装 Grpc.AspNetCore.Server.Reflection NuGet 包:

dotnet add package Grpc.AspNetCore.Server.Reflection

2. 配置 gRPC 服务以启用反射

以下是如何在 Program.cs 文件中正确配置和映射反射服务:

Program.cs 示例:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Grpc.AspNetCore.Server.Reflection;

var builder = WebApplication.CreateBuilder(args);

// 添加gRPC服务和反射服务
builder.Services.AddGrpc();
builder.Services.AddGrpcReflection();

var app = builder.Build();

// 在开发环境启用反射
if (app.Environment.IsDevelopment())
{
    app.MapGrpcReflectionService();
}

app.MapGrpcService<ExampleServiceImpl>();

app.Run();