Saturday, 19 April 2025

Building a KeyVaultService in .NET for Secure Secret Management

////Declare Interface
public interface IKeyVaultService
{
    public string GetSecret(string secretName, bool appendPrefix = true);

}
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
public class KeyVaultService : IKeyVaultService
{
    private const string _PREFIX = "XXX";
    private readonly string _keyVaultBaseUrl;
    protected  SecretClient _keyVaultClient;
    private static readonly ConcurrentDictionary<string, string> _keyVaultSecrets =
                         new ConcurrentDictionary<string, string>();
    private readonly KeyVaultConfigAppSetting _keyVaultLocalConfigAppSetting;
    private readonly ILogger _logger;
   
    /// <summary>
    /// initialize
    /// </summary>
    /// <param name="keyVaultLocalOptions"></param>
    /// <exception cref="Exception"></exception>
    public KeyVaultService(
        IOptions<KeyVaultConfigAppSetting> keyVaultLocalOptions
        ,ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<KeyVaultService>();
        _keyVaultLocalConfigAppSetting = keyVaultLocalOptions.Value;
        _keyVaultBaseUrl = _keyVaultLocalConfigAppSetting.BaseUrl;
        _keyVaultBaseUrl = _keyVaultLocalConfigAppSetting.BaseUrl
            ?? throw new InvalidOperationException
            ("KeyVaultBaseUrl is null or not configured");

        EnsureConfigured();

    }

    /// <summary>
    /// get secret value from keyvault
    /// </summary>
    /// <param name="secretName"></param>
    /// <param name="appendPrefix"></param>
    /// <returns></returns>
    public string GetSecret(string secretName, bool appendPrefix = true)
    {
        try
        {
            EnsureConfigured();
       
            if (appendPrefix && !secretName.StartsWith(_PREFIX))
                secretName = $"{_PREFIX}{secretName}";
            _keyVaultSecrets.TryGetValue(secretName, out var secretValue);

            if (string.IsNullOrEmpty(secretValue))
            {
                var secretBundle = _keyVaultClient.GetSecret(secretName);
                secretValue = secretBundle.Value?.Value;

                if (!string.IsNullOrEmpty(secretValue))
                    _keyVaultSecrets.TryAdd(secretName, secretValue);
            }

            return secretValue;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Keyvalut Error:{0} {1}", GetType()?.Name, ex.Message);
            throw new InvalidOperationException
            ("An error occurred while accessing the Keyvault.", ex);
        }

    }

    private void EnsureConfigured()
    {
        if (_keyVaultClient == null)
#if DEBUG
//Create azure key-vault instance using Application client id and client secret.
            ConfigureWithClientCredentials();
#else
            ConfigureAzureCredentials();
#endif
    }

    private void ConfigureWithClientCredentials()
    {


        var tenantId = _keyVaultLocalConfigAppSetting.TenantId;
        var sharedSecretKey = _keyVaultLocalConfigAppSetting.SharedSecretKey;
        var sharedSecretAppId = _keyVaultLocalConfigAppSetting.AppId;

        // If Shared Secret is present and not "Disabled",
        // use the shared secret (typically for local development).
        if (!string.IsNullOrWhiteSpace(sharedSecretKey)
        && sharedSecretKey != "Disabled")
        {
           
                ClientSecretCredential clientCredential =
                            new ClientSecretCredential
                            (tenantId, sharedSecretAppId, sharedSecretKey);
                _keyVaultClient = new SecretClient
                    (vaultUri: new Uri(_keyVaultBaseUrl), credential: clientCredential);
           
        }
        else
        {
            //fallback to the default credential authentication
            _keyVaultClient = new SecretClient(
                vaultUri: new Uri(_keyVaultBaseUrl)
                , credential: new DefaultAzureCredential());
        }

    }

    private void ConfigureAzureCredentials()
    {
        try
        {
            _keyVaultClient = new SecretClient(
                vaultUri: new Uri(_keyVaultBaseUrl)
                , credential: new DefaultAzureCredential());
        }
        catch (Exception ex)
        {
            _logger.LogError
            (ex,"Keyvalut Error: {0}  - {1}", GetType()?.Name, ex.Message);
            throw;
        }
    }
}

Middleware in .Net Core

Middleware is an software used to handle web request pipeline. 

Describe in simple term means it's organize the request and response using delegates and pipeline.

It is an decision maker to pass the next request to particular component.

Example:

3-tier Architecture (API, Business Layer, Data Layer)

we will receive n-number request at the time middleware anonymously  handle the request in pipeline to execute API --> Business Layer --> Data Layer and response vise versa.

Middleware Roles and Responsibility

    1.Chooses whether to pass the request to the next component in the pipeline.

    2.Can perform work before and after the next component in the pipeline.

Request delegates are used to build the request pipeline. The request delegates handle each HTTP request.


Request delegates are configured using RunMap, and Use extension methods. An individual request delegate can be specified in-line as an anonymous method (called in-line middleware), or it can be defined in a reusable class. These reusable classes and in-line anonymous methods are middleware, also called middleware components. Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline or short-circuiting the pipeline. When a middleware short-circuits, it's called a terminal middleware because it prevents further middleware from processing the request.

Built-in middleware
MiddlewareDescription
HTTP Method OverrideAllows an incoming POST request to override the method.
HTTPS RedirectionRedirect all HTTP requests to HTTPS.
HTTP Strict Transport Security (HSTS)Security enhancement middleware that adds a special response header.

middleware workflow






Friday, 4 April 2025

Elegant HTTP Response Parsing in .NET

/// <summary>
/// Extension methods for parsing API responses into OperationalResult<T>.
/// </summary>
public static class ApiResponseParser
{
    /// <summary>
    /// Parses the HTTP response and returns a standardized OperationalResult<T>.
    /// Handles success, primitive types, and structured error responses.
    /// </summary>
    /// <typeparam name="T">The expected type of the result.</typeparam>
    /// <param name="apiResponse">The HTTP response message.</param>
    /// <returns>An OperationalResult containing the parsed result or an error.
    /// </returns>
    public static async Task<OperationalResult<T>> GetParsedApiResponse<T>
                        (this HttpResponseMessage apiResponse)
    {
        var responseBody = await apiResponse.Content.ReadAsStringAsync();

        if (ValidHttpStatusCodes.Contains(apiResponse.StatusCode)
                && apiResponse.StatusCode != HttpStatusCode.BadRequest)
        {
            var type = typeof(T);

            if (string.IsNullOrEmpty(responseBody))
            {
                return OperationalResult<T>.Successed();
            }

            if (type.IsPrimitive)
            {
                var primitiveValue = (T)Convert.ChangeType(apiResponse.Content,
                                     typeof(T));
                return OperationalResult<T>.Successed(primitiveValue);
            }

            var data = responseBody.JsonDeserializeObject<T>();
            return OperationalResult<T>.Successed(data);
        }
        else
        {
            var apiError = responseBody.JsonDeserializeObjectTryParse<ApiError>();

            if (apiError != null)
            {
                return OperationalResult<T>.Failed(
                    (ResponseStatusType)apiError.Code, apiError.Message);
            }

            return OperationalResult<T>.Failed(
                (ResponseStatusType)apiResponse.StatusCode,
                new ErrorDto($"Error: {apiResponse.StatusCode}"
                , apiResponse.ReasonPhrase)
            );
        }
    }

    /// <summary>
    /// A list of valid HTTP status codes considered as successful responses.
    /// </summary>
    public static readonly IEnumerable<HttpStatusCode> ValidHttpStatusCodes =
                            new List<HttpStatusCode>
                            {
                                HttpStatusCode.OK,
                                HttpStatusCode.Created,
                                HttpStatusCode.NoContent
                            };
}