Implementing and Refining the Proxy Pattern in C# .NET 8

Anderson Godoy
4 min read6 days ago

The Proxy design pattern is a structural pattern used extensively in software development to provide a substitute that controls access to another object, ensuring encapsulation, security, and efficiency. In this article, we’ll explore how to implement, refine, and apply advanced practices for the Proxy pattern while leveraging modern features of C# .NET 8.

What is the Proxy Pattern?

The Proxy is a structural pattern that acts as an intermediary between the client and the real object. It is beneficial in scenarios such as:

  • Access Control: Protecting critical operations from unauthorized users.
  • Caching: Storing results to reduce load for repetitive operations.
  • Resilience: Managing failures in distributed systems with retries.
  • Lazy Initialization: Creating objects only when necessary.

Basic Proxy Implementation

Let’s start with a basic implementation to control access to a critical service.

public interface IService
{
void PerformOperation();
}

public class CriticalService : IService
{
public void PerformOperation()
{
Console.WriteLine("Critical operation executed.");
}
}

public class ProxyService : IService
{
private readonly CriticalService _realService;
private readonly string _userRole;

public ProxyService(string userRole)
{
_realService = new CriticalService();
_userRole = userRole;
}

public void PerformOperation()
{
if (_userRole == "Admin")
{
_realService.PerformOperation();
}
else
{
Console.WriteLine("Access denied! You do not have permission to perform this operation.");
}
}
}

Refactoring for Advanced Scenarios

1. Modular Configuration

Add flexibility by enabling or disabling behaviors like caching and logging dynamically.

public class ConfigurableProxy : IService
{
private readonly IService _realService;
private readonly bool _enableLogging;
private readonly bool _enableCaching;
private readonly Dictionary<string, string> _cache = new();

public ConfigurableProxy(IService realService, bool enableLogging, bool enableCaching)
{
_realService = realService;
_enableLogging = enableLogging;
_enableCaching = enableCaching;
}

public void PerformOperation()
{
if (_enableCaching && _cache.ContainsKey("operation_result"))
{
if (_enableLogging)
Console.WriteLine("Cache Hit: Result found in cache.");
Console.WriteLine(_cache["operation_result"]);
return;
}

if (_enableLogging)
Console.WriteLine("Executing operation...");

_realService.PerformOperation();

if (_enableCaching)
{
_cache["operation_result"] = "Critical Operation Result";
if (_enableLogging)
Console.WriteLine("Result stored in cache.");
}
}
}

2. Dynamic Proxies with AOP

Use Castle.DynamicProxy to add cross-cutting concerns like logging and validation dynamically.

using Castle.DynamicProxy;

public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine($"Starting method: {invocation.Method.Name}");
invocation.Proceed();
Console.WriteLine($"Method completed: {invocation.Method.Name}");
}
}

public static class ProxyGeneratorFactory
{
private static readonly ProxyGenerator Generator = new();

public static T CreateProxy<T>(T target) where T : class
{
return Generator.CreateInterfaceProxyWithTarget(target, new LoggingInterceptor());
}
}

Usage:

var criticalService = new CriticalService();
var proxy = ProxyGeneratorFactory.CreateProxy<IService>(criticalService);
proxy.PerformOperation();

3. Resilience with Polly

Automatically handle transient failures with retry policies using Polly.

using Polly;

public class ResilientProxy : IService
{
private readonly IService _realService;
private readonly RetryPolicy _retryPolicy;

public ResilientProxy(IService realService)
{
_realService = realService;
_retryPolicy = Policy
.Handle<Exception>()
.Retry(3, (exception, retryCount) =>
{
Console.WriteLine($"Retry {retryCount}: {exception.Message}");
});
}

public void PerformOperation()
{
_retryPolicy.Execute(() => _realService.PerformOperation());
}
}

4. Integration with JWT

Modernize authentication by using JSON Web Tokens (JWT).

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

public class JwtAuthorizationService
{
public bool IsAuthorized(string token, string requiredRole)
{
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);

var roleClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value;
return roleClaim == requiredRole;
}
}

Integrated Proxy:

public class JwtProxyService : IService
{
private readonly IService _realService;
private readonly JwtAuthorizationService _authService;
private readonly string _token;

public JwtProxyService(IService realService, JwtAuthorizationService authService, string token)
{
_realService = realService;
_authService = authService;
_token = token;
}

public void PerformOperation()
{
if (_authService.IsAuthorized(_token, "Admin"))
{
_realService.PerformOperation();
}
else
{
Console.WriteLine("Access denied! Invalid token or insufficient permissions.");
}
}
}

5. Observability with OpenTelemetry

Add detailed monitoring and tracing for distributed systems.

using OpenTelemetry;
using OpenTelemetry.Trace;

public class ObservabilityProxy : IService
{
private readonly IService _realService;
private readonly Tracer _tracer;

public ObservabilityProxy(IService realService, Tracer tracer)
{
_realService = realService;
_tracer = tracer;
}

public void PerformOperation()
{
using var span = _tracer.StartActiveSpan("PerformOperation");
try
{
_realService.PerformOperation();
span.SetStatus(Status.Ok);
}
catch (Exception ex)
{
span.SetStatus(Status.Error.WithDescription(ex.Message));
throw;
}
}
}

Testing the Proxy

Use xUnit and Moq to validate Proxy behavior.

public class ProxyTests
{
[Fact]
public void LoggingProxy_ShouldLogMessages()
{
var mockService = new Mock<IService>();
var proxy = new LoggingProxy(mockService.Object);

proxy.PerformOperation();

mockService.Verify(service => service.PerformOperation(), Times.Once);
}
}

Conclusion

The Proxy pattern, when combined with modern tools like Polly, OpenTelemetry, and JWT, can transform your architecture into something secure, resilient, and highly observable.

The practices demonstrated in this article show how to refine the Proxy pattern to meet the demands of complex, distributed systems while maintaining simplicity and modularity.

If you found this article helpful, share it with your network to help other developers explore the power of the Proxy pattern in C# .NET 8! 🚀

--

--

Anderson Godoy
Anderson Godoy

Written by Anderson Godoy

Experienced .NET developer (20+ years). Passionate about optimizing algorithms, best practices, and helping others excel in secure, quality software solutions.

Responses (1)