بنقرة واحدة
interceptors
Use when adding gRPC interceptors for exception mapping, culture switching, or claims extraction.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Use when adding gRPC interceptors for exception mapping, culture switching, or claims extraction.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Use when designing gRPC services, proto files, or adding gRPC-Web or JSON transcoding.
Use when writing async code, propagating CancellationTokens, or fixing async/await pitfalls.
Use when applying or enforcing C# coding style — namespaces, sealed classes, var usage, XML docs.
Use when applying modern C# idioms — records, pattern matching, primary constructors, collection expressions.
Use when registering services, choosing lifetimes, or implementing DI patterns like decorator or keyed services.
Use when selecting or implementing design patterns in C# — factory, builder, strategy, decorator, or mediator.
| name | interceptors |
| description | Use when adding gRPC interceptors for exception mapping, culture switching, or claims extraction. |
| metadata | {"category":"microservice/grpc","agent":"command-architect"} |
| when_to_use | When creating or modifying gRPC interceptors for exception mapping, culture, or claims |
ApplicationExceptionInterceptor maps domain exceptions to RpcException with ProblemDetailsThreadCultureInterceptor reads language header and sets thread cultureaccess-claims-bin metadata headernamespace {Company}.{Domain}.Grpc.Interceptors;
public sealed class ApplicationExceptionInterceptor(
ILogger<ApplicationExceptionInterceptor> logger) : Interceptor
{
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
try
{
return await continuation(request, context);
}
catch (DomainException ex) when (ex is IProblemDetailsProvider provider)
{
logger.LogWarning(ex, "Domain exception: {Message}", ex.Message);
var problemDetails = provider.ToProblemDetails();
var metadata = new Metadata
{
{ "problem-details-bin",
Encoding.UTF8.GetBytes(
JsonConvert.SerializeObject(problemDetails)) }
};
throw new RpcException(
new Status(MapStatusCode(ex), ex.Message), metadata);
}
catch (Exception ex)
{
logger.LogError(ex, "Unhandled exception in gRPC handler");
throw new RpcException(
new Status(StatusCode.Internal, "Internal server error"));
}
}
private static StatusCode MapStatusCode(DomainException ex) => ex switch
{
NotFoundException => StatusCode.NotFound,
ConflictException => StatusCode.AlreadyExists,
ValidationException => StatusCode.InvalidArgument,
UnauthorizedException => StatusCode.PermissionDenied,
_ => StatusCode.Internal
};
}
namespace {Company}.{Domain}.Grpc.Interceptors;
public sealed class ThreadCultureInterceptor : Interceptor
{
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
var languageHeader = context.RequestHeaders
.FirstOrDefault(h => h.Key == "language")?.Value;
if (!string.IsNullOrEmpty(languageHeader))
{
var culture = new CultureInfo(languageHeader);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
return await continuation(request, context);
}
}
namespace {Company}.{Domain}.Grpc.Interceptors;
public sealed class AccessClaimsInterceptor : Interceptor
{
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
var claimsEntry = context.RequestHeaders
.FirstOrDefault(h => h.Key == "access-claims-bin");
if (claimsEntry is not null)
{
var claimsJson = Encoding.UTF8.GetString(claimsEntry.ValueBytes);
var claims = JsonConvert.DeserializeObject<AccessClaims>(claimsJson);
context.UserState["AccessClaims"] = claims;
}
return await continuation(request, context);
}
}
namespace {Company}.{Domain}.Grpc.Interceptors;
public sealed class LanguageClientInterceptor : Interceptor
{
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var headers = context.Options.Headers ?? new Metadata();
headers.Add("language", CultureInfo.CurrentCulture.Name);
var newContext = new ClientInterceptorContext<TRequest, TResponse>(
context.Method, context.Host,
context.Options.WithHeaders(headers));
return continuation(request, newContext);
}
}
builder.Services.AddGrpc(options =>
{
// Order matters: first registered = first executed
options.Interceptors.Add<ThreadCultureInterceptor>();
options.Interceptors.Add<AccessClaimsInterceptor>();
options.Interceptors.Add<ApplicationExceptionInterceptor>();
});
| Anti-Pattern | Correct Approach |
|---|---|
| Exception handling in every service method | Use exception interceptor |
| Culture setting in every handler | Use culture interceptor |
| Wrong interceptor order | Culture before exception interceptor |
| Exposing stack traces to clients | Log internally, return clean error to client |
# Find interceptors
grep -r ": Interceptor" --include="*.cs" src/
# Find interceptor registration
grep -r "Interceptors.Add" --include="*.cs" src/
# Find ProblemDetails in gRPC context
grep -r "problem-details-bin" --include="*.cs" src/