📄 ReceiptPrinterService.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ReceiptPrinter;
using static ReceiptPrinter.ReceiptPrinter;

namespace Receipt.Web.Services;

public sealed partial class ReceiptPrinterService(ILogger<ReceiptPrinterService> logger, ReceiptPrinterClient client)
{
    public async Task<string?> Print(string content)
    {
        LogPrintingContent(content);

        var request = new PrintRequest() { Typst = new() { Content = content } };

        try
        {
            await client.PrintAsync(request);
            return null;
        }
        catch (RpcException ex)
        {
            LogPrintingException(ex);
            return ex.Status.Detail;
        }
    }

    [LoggerMessage(Message = "Printing content: {Content}", Level = LogLevel.Debug)]
    private partial void LogPrintingContent(string content);

    [LoggerMessage(Message = "Printer failed", Level = LogLevel.Warning)]
    private partial void LogPrintingException(Exception ex);
}

public sealed class ReceiptPrinterOptions
{
    [Required]
    public required Uri Address { get; set; }
}

[OptionsValidator]
public sealed partial class ReceiptPrinterOptionsValidator : IValidateOptions<ReceiptPrinterOptions>;

public static class ReceiptPrinterServiceCollectionExtensions
{
    public static IServiceCollection AddReceiptPrinterService(this IServiceCollection services)
    {
        services.AddOptions<ReceiptPrinterOptions>().BindConfiguration("ReceiptPrinter").ValidateOnStart();
        services.AddTransient<IValidateOptions<ReceiptPrinterOptions>, ReceiptPrinterOptionsValidator>();

        services.AddGrpcClient<ReceiptPrinterClient>(
            "ReceiptPrinter",
            (sp, options) =>
            {
                options.Address = sp.GetRequiredService<IOptions<ReceiptPrinterOptions>>().Value.Address;
            }
        );

        services.AddTransient<ReceiptPrinterService>();

        return services;
    }
}