ZuriaMidi 1.1.1

dotnet add package ZuriaMidi --version 1.1.1
                    
NuGet\Install-Package ZuriaMidi -Version 1.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ZuriaMidi" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ZuriaMidi" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="ZuriaMidi" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ZuriaMidi --version 1.1.1
                    
#r "nuget: ZuriaMidi, 1.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ZuriaMidi@1.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ZuriaMidi&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=ZuriaMidi&version=1.1.1
                    
Install as a Cake Tool

AuthIntegrationService & IGenericApi

A clean, testable abstraction layer for integrating with external SSO (Single Sign-On) services in .NET applications. This module decouples authentication services from direct HTTP client dependencies through a generic API interface.

Table of Contents

Overview

The AuthIntegrationService provides a robust foundation for SSO integration by abstracting HTTP operations through the IGenericApi interface. This design promotes loose coupling, testability, and maintainability while providing consistent logging and error handling.

Architecture Benefits

  • Decoupled Design: Easy to swap HTTP implementations (HttpClient, Refit, etc.)
  • Testability: Mock-friendly interface for unit testing
  • Consistency: Centralized logging and error handling
  • Extensibility: Simple to add new SSO endpoints
  • Type Safety: Strongly-typed request/response models

Features

  • ✅ Generic HTTP operations (GET, POST, PUT, PATCH, DELETE)
  • ✅ Async/await support with cancellation tokens
  • ✅ JSON serialization/deserialization
  • ✅ Comprehensive error handling
  • ✅ Built-in logging integration
  • ✅ Dependency injection ready
  • ✅ Unit test friendly

Project Structure

ZuriaMidi/
├── Features/
│   └── Authentication/
│       └── AuthIntegrationService.cs
├── Models/
│   └── Sso/                           # SSO DTOs
│       ├── SsoBaseResponse.cs
│       ├── LoginRequest.cs
│       └── RegisterRequest.cs
├── Services/
│   └── AuthServices/
│       ├── IGenericApi.cs            # Generic HTTP interface
│       └── HttpClientGenericApi.cs   # HttpClient implementation
└── Common/
    └── Config.cs                     # Configuration settings

Installation

Prerequisites

  • .NET 5.0 or later
  • System.Net.Http.Json package (included in .NET 5+)

NuGet Packages

<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />

Configuration

1. Application Settings

Add SSO configuration to your appsettings.json:

{
  "SSO": {
    "BaseUrl": "https://your-sso-provider.com/api",
    "EmailUrl": "https://your-sso-provider.com/api/email",
    "RegisterUserUrl": "https://your-sso-provider.com/api/register",
    "Timeout": "00:00:30"
  }
}

2. Dependency Injection

Configure services in Program.cs:

// Register HttpClient with configuration
builder.Services.AddHttpClient<IGenericApi, HttpClientGenericApi>(client =>
{
    var ssoConfig = builder.Configuration.GetSection("SSO");
    client.BaseAddress = new Uri(ssoConfig["BaseUrl"]);
    client.Timeout = TimeSpan.Parse(ssoConfig["Timeout"] ?? "00:00:30");
    
    // Add default headers if needed
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

// Register application services
builder.Services.AddScoped<AuthIntegrationService>();

Usage

Basic Implementation

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
    private readonly AuthIntegrationService _authService;
    private readonly ILogger<AuthController> _logger;

    public AuthController(
        AuthIntegrationService authService, 
        ILogger<AuthController> logger)
    {
        _authService = authService;
        _logger = logger;
    }

    [HttpGet("check-email/{email}")]
    public async Task<IActionResult> CheckEmail(string email, CancellationToken cancellationToken)
    {
        try
        {
            var result = await _authService.EmailExistOnSso(email);
            return Ok(result);
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Failed to check email existence for {Email}", email);
            return StatusCode(500, "External service unavailable");
        }
    }

    [HttpPost("register")]
    public async Task<IActionResult> RegisterUser([FromBody] RegisterRequest request, CancellationToken cancellationToken)
    {
        try
        {
            var result = await _authService.RegisterUser(request);
            return Ok(result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to register user");
            return BadRequest("Registration failed");
        }
    }
}

Advanced Usage with Custom Headers

public class AuthIntegrationService
{
    private readonly IGenericApi _ssoService;
    private readonly ILogger<AuthIntegrationService> _logger;

    public async Task<SsoBaseResponse> AuthenticatedRequest(string endpoint, string token)
    {
        // For requests requiring authentication, you might need to extend IGenericApi
        // or configure the HttpClient with authentication headers
        var url = $"{Config.BaseUrl}/{endpoint}";
        var response = await _ssoService.GetAsync<SsoBaseResponse>(url);
        return response;
    }
}

API Reference

IGenericApi Interface

The core interface providing HTTP operations:

public interface IGenericApi
{
    /// <summary>
    /// Performs a GET request and deserializes the response
    /// </summary>
    Task<TResponse> GetAsync<TResponse>(string url, CancellationToken cancellationToken = default);
    
    /// <summary>
    /// Performs a POST request with JSON body
    /// </summary>
    Task<TResponse> CreateAsync<TResponse, TRequest>(string url, TRequest body, CancellationToken cancellationToken = default);
    
    /// <summary>
    /// Performs a PUT request with JSON body
    /// </summary>
    Task<TResponse> UpdateAsync<TResponse, TRequest>(string url, TRequest body, CancellationToken cancellationToken = default);
    
    /// <summary>
    /// Performs a PATCH request with JSON body
    /// </summary>
    Task<TResponse> PatchAsync<TResponse, TRequest>(string url, TRequest body, CancellationToken cancellationToken = default);
    
    /// <summary>
    /// Performs a DELETE request
    /// </summary>
    Task<TResponse> DeleteAsync<TResponse>(string url, CancellationToken cancellationToken = default);
    
    /// <summary>
    /// Performs a DELETE request with JSON body
    /// </summary>
    Task<TResponse> DeleteAsync<TResponse, TRequest>(string url, TRequest body, CancellationToken cancellationToken = default);
}

AuthIntegrationService Methods

public class AuthIntegrationService
{
    /// <summary>
    /// Checks if an email exists in the SSO system
    /// </summary>
    public async Task<SsoBaseResponse> EmailExistOnSso(string emailAddress);
    
    /// <summary>
    /// Registers a new user in the SSO system
    /// </summary>
    public async Task<SsoBaseResponse> RegisterUser(RegisterRequest request);
    
    /// <summary>
    /// Authenticates a user against the SSO system
    /// </summary>
    public async Task<LoginResponse> AuthenticateUser(LoginRequest request);
}

Testing

Unit Testing with Moq

public class AuthIntegrationServiceTests
{
    private readonly Mock<IGenericApi> _mockApi;
    private readonly Mock<ILogger<AuthIntegrationService>> _mockLogger;
    private readonly AuthIntegrationService _service;

    public AuthIntegrationServiceTests()
    {
        _mockApi = new Mock<IGenericApi>();
        _mockLogger = new Mock<ILogger<AuthIntegrationService>>();
        _service = new AuthIntegrationService(Mock.Of<ICreateLogin>(), _mockApi.Object, _mockLogger.Object);
    }

    [Fact]
    public async Task EmailExistOnSso_Should_Return_Success_Response()
    {
        // Arrange
        var email = "test@example.com";
        var expectedResponse = new SsoBaseResponse { Success = true, Message = "Email found" };
        
        _mockApi.Setup(x => x.GetAsync<SsoBaseResponse>(
                It.Is<string>(url => url.Contains(email)), 
                It.IsAny<CancellationToken>()))
               .ReturnsAsync(expectedResponse);

        // Act
        var result = await _service.EmailExistOnSso(email);

        // Assert
        Assert.NotNull(result);
        Assert.True(result.Success);
        Assert.Equal("Email found", result.Message);
    }

    [Fact]
    public async Task EmailExistOnSso_Should_Handle_HttpRequestException()
    {
        // Arrange
        var email = "test@example.com";
        
        _mockApi.Setup(x => x.GetAsync<SsoBaseResponse>(
                It.IsAny<string>(), 
                It.IsAny<CancellationToken>()))
               .ThrowsAsync(new HttpRequestException("Service unavailable"));

        // Act & Assert
        await Assert.ThrowsAsync<HttpRequestException>(() => 
            _service.EmailExistOnSso(email));
    }
}

Integration Testing

public class AuthIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    private readonly HttpClient _client;

    public AuthIntegrationTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _client = _factory.CreateClient();
    }

    [Fact]
    public async Task CheckEmail_Should_Return_Valid_Response()
    {
        // Arrange
        var email = "test@example.com";

        // Act
        var response = await _client.GetAsync($"/api/auth/check-email/{email}");

        // Assert
        response.EnsureSuccessStatusCode();
        var content = await response.Content.ReadAsStringAsync();
        Assert.Contains("success", content.ToLower());
    }
}

Error Handling

The service includes comprehensive error handling:

public async Task<SsoBaseResponse> SafeEmailCheck(string email)
{
    try
    {
        return await EmailExistOnSso(email);
    }
    catch (HttpRequestException ex) when (ex.Message.Contains("timeout"))
    {
        _logger.LogWarning("SSO service timeout for email check: {Email}", email);
        return new SsoBaseResponse { Success = false, Message = "Service temporarily unavailable" };
    }
    catch (TaskCanceledException ex)
    {
        _logger.LogWarning("SSO request cancelled: {Email}", email);
        return new SsoBaseResponse { Success = false, Message = "Request cancelled" };
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Unexpected error during email check: {Email}", email);
        throw;
    }
}

Best Practices

  1. Always use CancellationToken: Pass cancellation tokens through the call chain
  2. Configure Timeouts: Set appropriate timeout values for external services
  3. Implement Retry Logic: Consider adding Polly for retry policies
  4. Log Appropriately: Log requests/responses but avoid sensitive data
  5. Validate Input: Validate parameters before making external calls
  6. Handle Exceptions: Implement proper exception handling strategies

Troubleshooting

Common Issues

Issue: HttpRequestException: Name resolution failed

  • Solution: Verify the SSO BaseUrl in configuration

Issue: TaskCanceledException: The operation was canceled

  • Solution: Increase timeout values or check network connectivity

Issue: JsonException: The JSON value could not be converted

  • Solution: Verify response DTOs match the actual API response structure

Debugging Tips

  1. Enable detailed HTTP logging:

    builder.Services.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug));
    
  2. Add request/response logging to HttpClientGenericApi

  3. Use tools like Fiddler or Postman to test SSO endpoints directly

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.1 218 10/5/2025
1.1.0 209 9/29/2025
1.0.9 165 9/26/2025
1.0.8 147 9/26/2025
1.0.7 166 9/26/2025
1.0.6 168 9/26/2025
1.0.5 332 9/17/2025
1.0.4 323 9/17/2025
1.0.3 327 9/17/2025
1.0.2 328 9/17/2025
1.0.1 332 9/17/2025
1.0.0 345 9/16/2025