Skip to content

Instantly share code, notes, and snippets.

@carlosble
Created June 22, 2020 14:01
Show Gist options
  • Save carlosble/7ec255f680eec2d0a21e6f6c80e0da3b to your computer and use it in GitHub Desktop.
Save carlosble/7ec255f680eec2d0a21e6f6c80e0da3b to your computer and use it in GitHub Desktop.
Decorator pattern and metaprogramming with C#
using System;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
namespace Metaprogramming
{
enum Role
{
admin,
user,
anonymous
}
class User
{
public Role Role { get; set; }
}
class Service
{
public virtual void Execute(string someArgument, User user)
{
Console.WriteLine("------- EXECUTE 1");
// some logic
}
public virtual void Execute(string someArgument)
{
Console.WriteLine("------- EXECUTE 2");
// some logic
}
}
class AuthDecorator : Service
{
private Service decorated;
public AuthDecorator(Service decorated)
{
this.decorated = decorated;
}
public override void Execute(string someArgument)
{
Console.WriteLine("AUTH");
decorated.Execute(someArgument);
Console.WriteLine("END");
}
public override void Execute(string someArgument, User user)
{
if (user.Role == Role.admin) // cross-cutting concern - SRP
{
Console.WriteLine("Checking Auth for role:" + user.Role);
decorated.Execute(someArgument, user);
Console.WriteLine("Final auth");
}
else
{
throw new UnauthorizedAccessException();
}
}
}
class LoggingDecorator : Service
{
private Service decorated;
public LoggingDecorator(Service decorated)
{
this.decorated = decorated;
}
public override void Execute(string someArgument)
{
Console.WriteLine("Logging call to Execute with:" + someArgument);
decorated.Execute(someArgument);
Console.WriteLine("Call finished");
}
public override void Execute(string someArgument, User user)
{
Console.WriteLine("Logging call to Execute with:" + someArgument);
decorated.Execute(someArgument, user);
Console.WriteLine("Call finished");
}
}
class AnotherService
{
[Logging] // AOP Aspect Oriented Programming
[WriteAction]
public virtual void Execute(String arg1)
{
Console.WriteLine("--EXECUTE AnotherService");
}
[Logging]
[Authorized]
[WriteAction]
public virtual void Execute(String arg1, User user)
{
Console.WriteLine("--EXECUTE AnotherService with user");
}
}
internal class WriteActionAttribute : Attribute
{
}
class AnotherServiceProxy<T>
{
private T target;
public AnotherServiceProxy(T target)
{
this.target = target;
}
public void Execute(params Object[] args)
{
var myType = target.GetType();
var methods = myType.GetMethods();
foreach (var method in methods)
{
var parameters = method.GetParameters();
if (args.Length == parameters.Length /* TODO check types */ &&
method.IsPublic)
{
var attributes = method.GetCustomAttributes();
foreach (var attribute in attributes)
{
if (attribute.GetType() == typeof(LoggingAttribute))
{
Console.WriteLine("LOGGING:" + args[0]);
}
if (attribute.GetType() == typeof(AuthorizedAttribute))
{
Console.WriteLine("CHECKING AUTH");
if (args.Length == 2 && args[1].GetType() == typeof(User))
{
if (((User) args[1]).Role != Role.admin)
{
throw new UnauthorizedAccessException();
}
}
}
}
method.Invoke(target, args);
}
}
}
}
class AuthorizedAttribute : Attribute
{
}
class LoggingAttribute : Attribute
{
}
class Factory
{
public static Service CreateService()
{
return new AuthDecorator(new LoggingDecorator(new Service()));
}
public static AnotherServiceProxy<T> CreateAnotherServiceProxy<T>() where T : new()
{
return new AnotherServiceProxy<T>(new T());
}
}
public class Tests
{
[Test]
public void TestLogging()
{
var service = Factory.CreateService();
service.Execute("hello");
}
[Test]
public void TestAuth()
{
var service = Factory.CreateService();
service.Execute("Hello user", new User{Role = Role.admin});
}
[Test]
public void TestAuthWithAttributes()
{
var service = new AnotherService();
service.Execute("Hello user");
}
[Test]
public void TestAuthWithAttributesAndUser()
{
var service = new AnotherService();
service.Execute("Hello user", new User{Role = Role.anonymous});
}
[Test]
public void TestAuthWithAttributesDecorator()
{
var service = Factory.CreateAnotherServiceProxy<AnotherService>();
service.Execute("Hello user", new User{Role = Role.admin});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment