원클릭으로
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/