Initial version

This commit is contained in:
2026-06-09 22:27:38 +02:00
parent 5afbf3b01c
commit 5ff2ac8941
57 changed files with 2343 additions and 98 deletions
+19
View File
@@ -0,0 +1,19 @@
using EonaCat.gRPC.Proto;
namespace EonaCat.gRPC.Client;
public interface ITokenProvider
{
Task<AuthenticationResponse> GetTokenAsync();
}
public class AppTokenProvider : ITokenProvider
{
private AuthenticationResponse _token;
public async Task<AuthenticationResponse> GetTokenAsync()
{
if (_token == null)
_token = new AuthenticationResponse { AccessToken = "test", ExpiresIn = 300 };
return _token;
}
}
+19
View File
@@ -0,0 +1,19 @@
using Grpc.Core;
using ProtoBuf.Grpc.Client;
using EonaCat.gRPC.Proto;
namespace EonaCat.gRPC.Client;
public class AuthService
{
public static async Task Authenticate(CallInvoker invoker)
{
var authenticationClient = invoker.CreateGrpcService<IAuthenticationService>();
var authenticationResponse = await authenticationClient.Authenticate(new AuthenticationRequest
{
UserName = "admin",
Password = "admin"
});
Console.WriteLine($"Received Authentication Response - \nToken: {authenticationResponse.AccessToken}\nExpires In: {authenticationResponse.ExpiresIn}");
}
}
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Interfaces\**" />
<EmbeddedResource Remove="Interfaces\**" />
<None Remove="Interfaces\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Protos\base.proto" />
<None Remove="Protos\user.proto" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.5" />
<PackageReference Include="EonaCat.LogStack" Version="0.0.8" />
<PackageReference Include="Grpc.Net.Client" Version="2.80.0" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.80.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EonaCat.gRPC.Proto\EonaCat.gRPC.Proto.csproj" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\base.proto" GrpcServices="Client" />
<Protobuf Include="Protos\user.proto" GrpcServices="Client" />
</ItemGroup>
</Project>
+101
View File
@@ -0,0 +1,101 @@
using Bogus;
using Grpc.Core;
using Microsoft.Extensions.Logging;
using ProtoBuf.Grpc.Client;
using EonaCat.gRPC.Client.Helpers;
using EonaCat.gRPC.Proto;
using EonaCat.LogStack.Logging;
namespace EonaCat.gRPC.Client;
public static class Extension
{
public static async Task ExecutePrograms(CallInvoker callInvoker)
{
while (true)
{
Console.WriteLine("\nEnter 1 to execute Authenticate.\n" +
"Enter 2 to execute Create User With Demo Data\n" +
"Enter 3 to execute User List Async\n" +
"Enter 4 to execute User GetById Async\n" +
"Enter 0 to exit.\n");
var input = Console.ReadLine();
input = input?.Trim(' ', '"', '\'');
if (!int.TryParse(input, out var value) || value is not (0 or 1 or 2 or 3 or 4))
{
ConsoleExtensions.Error("Invalid input. Please enter a valid option (0, 1, 2, 3, or 4).");
continue;
}
if (value == 0)
break;
try
{
switch (value)
{
case 1:
await AuthService.Authenticate(callInvoker);
break;
case 2:
await CreateUser(callInvoker);
break;
case 3:
await UserListAsync(callInvoker);
break;
case 4:
await UserGetByIdAsync(callInvoker);
break;
}
}
catch (Exception e)
{
ConsoleExtensions.Error($"Error: {e.Message}");
}
}
}
public static async Task Authenticate(CallInvoker invoker)
{
var authenticationClient = invoker.CreateGrpcService<IAuthenticationService>();
var authenticationResponse = await authenticationClient.Authenticate(new AuthenticationRequest
{
UserName = "admin",
Password = "admin"
});
ConsoleExtensions.Success($"Received Authentication Response - \nToken: {authenticationResponse.AccessToken}\nExpires In: {authenticationResponse.ExpiresIn}");
}
private static async Task CreateUser(CallInvoker callInvoker)
{
var userClient = callInvoker.CreateGrpcService<IProtoUserService>();
var faker = new Faker();
var userCreateRequest = new UserCreateRequest
{
FirstName = faker.Person.FirstName,
LastName = faker.Person.LastName,
Email = faker.Person.Email
};
var userResponse = await userClient.Create(userCreateRequest);
ConsoleExtensions.PrintResponse(userResponse);
}
private static async Task UserGetByIdAsync(CallInvoker callInvoker)
{
Console.Write("Please Enter an UserId : ");
var userId = Console.ReadLine() ?? string.Empty;
userId = userId.Trim(' ', '"', '\'');
var userClient = callInvoker.CreateGrpcService<IProtoUserService>();
var userResponse = await userClient.GetByIdAsync(userId);
ConsoleExtensions.PrintResponse(userResponse);
}
private static async Task UserListAsync(CallInvoker callInvoker)
{
var userClient = callInvoker.CreateGrpcService<IProtoUserService>();
var userResponse = await userClient.GetAsync();
ConsoleExtensions.PrintResponse(userResponse);
}
}
@@ -0,0 +1,35 @@
using Grpc.Core;
using Grpc.Core.Interceptors;
using EonaCat.gRPC.Proto;
using Microsoft.AspNetCore.Http;
namespace EonaCat.gRPC.Client.Helpers;
public class AuthHeaderInterceptor : Interceptor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public AuthHeaderInterceptor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var metadata = new Metadata();
//var authResponse = AuthService.Authenticate();
var authResponse = new AuthenticationResponse();
metadata.Add("authorization", $"Bearer {authResponse.AccessToken}");
metadata.Add("expiry", $"{authResponse.ExpiresIn}");
var userIdentity = _httpContextAccessor.HttpContext?.User.Identity;
if (userIdentity is { IsAuthenticated: true, Name: { } })
metadata.Add("User", userIdentity.Name);
var callOptions = context.Options.WithHeaders(metadata);
context = new ClientInterceptorContext<TRequest, TResponse>(context.Method, context.Host, callOptions);
return base.AsyncUnaryCall(request, context, continuation);
}
}
@@ -0,0 +1,70 @@
using EonaCat.gRPC.Proto;
using EonaCat.Json;
namespace EonaCat.gRPC.Client.Helpers;
public static class ConsoleExtensions
{
public static void Success(string data, bool disableNewLine = false)
{
Console.ForegroundColor = ConsoleColor.Green;
if (disableNewLine)
Console.Write(data);
else
Console.WriteLine(data);
Console.ResetColor();
}
public static void Error(string message, bool disableNewLine = false)
{
Console.ForegroundColor = ConsoleColor.Red;
if (disableNewLine)
Console.Write(message);
else
Console.WriteLine(message);
Console.ResetColor();
}
public static void PrintResponse<T>(BaseResponse<T> response, Formatting formatting = Formatting.Indented) where T : class?
{
try
{
if (response.IsSuccess)
{
Success("------ Success Response ------");
if (response.Data != null)
{
Success("Response:");
Success(JsonHelper.ToJson(response, formatting));
foreach (var property in response.Data.GetType().GetProperties())
{
var value = property.GetValue(response.Data);
if (value != null)
{
Success($"{property.Name}: {value}");
}
else
{
Success($"{property.Name}: null");
}
}
}
else
{
Success("No data available in the response.");
}
}
else
{
// Print error response
Error("------ Error Response ------");
Error($"Message: {response.Message}");
}
}
catch (Exception e)
{
// do nothing.
}
}
}
@@ -0,0 +1,70 @@
// enhance-your-grpc-client-logs-with-a-generic-logging-interceptor
// https://anthonygiretti.com/2022/08/08/net-6-enhance-your-grpc-client-logs-with-a-generic-logging-interceptor/
using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Logging;
namespace EonaCat.gRPC.Client.Helpers;
public class TracerInterceptor : Interceptor
{
private readonly ILogger<TracerInterceptor> _logger;
public TracerInterceptor(ILoggerFactory logger)
{
_logger = logger.CreateLogger<TracerInterceptor>();
}
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(
ClientInterceptorContext<TRequest, TResponse> context,
AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
where TRequest : class
where TResponse : class
{
_logger.LogDebug($"Calling {context.Method.Name} {context.Method.Type} method at {DateTime.UtcNow} UTC from machine {Environment.MachineName}");
var continued = continuation(context);
return continued;
}
public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(
ClientInterceptorContext<TRequest, TResponse> context,
AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation)
where TRequest : class
where TResponse : class
{
_logger.LogDebug($"Calling {context.Method.Name} {context.Method.Type} method at {DateTime.UtcNow} UTC from machine {Environment.MachineName}");
var continued = continuation(context);
return continued;
}
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation)
where TRequest : class
where TResponse : class
{
_logger.LogDebug($"Calling {context.Method.Name} {context.Method.Type} method. Payload received: {request.GetType()} : {request}");
_logger.LogDebug($"Calling {context.Method.Name} {context.Method.Type} method at {DateTime.UtcNow} UTC from machine {Environment.MachineName}");
var continued = continuation(request, context);
return continued;
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
where TRequest : class
where TResponse : class
{
_logger.LogDebug($"Calling {context.Method.Name} {context.Method.Type} method. Payload received: {request.GetType()} : {request}");
_logger.LogDebug($"Calling {context.Method.Name} {context.Method.Type} method at {DateTime.UtcNow} UTC from machine {Environment.MachineName}");
var continued = continuation(request, context);
return continued;
}
}
+33
View File
@@ -0,0 +1,33 @@
using Grpc.Core.Interceptors;
using Grpc.Net.Client;
using EonaCat.gRPC.Client.Helpers;
using EonaCat.gRPC.Client;
using EonaCat.LogStack.Logging;
using EonaCat.LogStack.Extensions;
using Microsoft.Extensions.Logging;
Console.WriteLine("Welcome to EonaCat.gRPC Client Application.");
const string serverAddress = "http://localhost:5227";
//var (invoker, channel) = Extension.ConfigureChannel(serverAddress, loggerFactory);
using var channel = GrpcChannel.ForAddress(serverAddress, new GrpcChannelOptions
{
LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
{
builder.AddEonaCatLogging();
builder.SetMinimumLevel(LogLevel.Debug);
})
});
var invoker = channel.Intercept(new TracerInterceptor(Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
{
builder.AddEonaCatLogging();
builder.SetMinimumLevel(LogLevel.Debug);
})));
await Extension.ExecutePrograms(invoker);
Console.ReadKey();
await channel.ShutdownAsync();
+10
View File
@@ -0,0 +1,10 @@
syntax = "proto3";
option csharp_namespace = "GRPC.NET7.Api.Protos";
package base;
message BaseResponse {
bool isSuccess = 1;
string message = 2;
string data = 3;
}
+20
View File
@@ -0,0 +1,20 @@
syntax = "proto3";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
import "Protos/base.proto";
option csharp_namespace = "GRPC.NET7.Api.Protos";
package user;
service User {
rpc Create(UserCreateRequest) returns (base.BaseResponse) { }
rpc Get(google.protobuf.Empty) returns (base.BaseResponse) { }
}
message UserCreateRequest {
string firstName = 1;
string lastName = 2;
string email = 3;
//google.protobuf.Timestamp dateOfBirth = 4;
string gender = 5;
}