AOP - PostSharp vs Windsor

by DotNetNerd 27. February 2009 20:56

På mit seneste projekt har jeg kigget nærmere på at lave AOP ved hjælp af henholdsvis PostSharp og Castle Windsors IInterceptor. De to fremgangsmåder har hver sine fordele og ulemper, men begge har også nogle shortcomings.

Castle Windsors bud på AOP virker ved at man implementerer en IInterceptor, hvilket kræver at man skriver en Intercept metode der tager et IInvocation objekt som argument, hvorpå man kan kalde Proceed der hvor metoden skal indkapsles. Interceptoren registreres så for en klasse, hvilket bevirker at alle metoder på klassen indkapsles.
En typisk implementation ser sådan her ud:

public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        // Entry logic
        invocation.Proceed();
        // Exit logic
    }
}

Postsharp, som et et lille framework til at lave AOP i .NET, virker ved at man implementerer en attribut der extender en af frameworkets aspects - eksempelvis OnExceptionAspect, OnMethodInvocationAspect eller OnMethodBoundaryAspect. I og med det er en attribut man implementerer påføres denne blot de metoder man ønsker skal indkapsles.
To helt typiske eksempler kunne være:

public class LoggingAttribute : OnMethodBoundaryAspect   
{       
    public override void OnEntry(MethodExecutionEventArgs eventArgs) { //Entry logic }        
    public override void OnExit(MethodExecutionEventArgs eventArgs) { //Exit logic }   
}

public class LoggingAttribute : OnMethodInvocationAspect   
{
    public override void OnInvocation(MethodInvocationEventArgs eventArgs)       
    {           
        //Entry logic
        eventArgs.Delegate.DynamicInvoke(eventArgs.GetArguments());           
        //Exit logic
    }
}

PostSharp er dermed mere finkornet i forhold til at man kan vælge hvilke metoder der indkapsles, og ved at man implementerer et passende aspect - omvendt er det i scenarier hvor alle metoder skal påvirkes og det virker dejligt ligetil med IInterceptor når amn alligevel bruger castle windsor.

Et punkt hvor castle windsor udmærker sig er at argumenter kan tilgås via IInvocation objektet ud fra navn frem for at de udelukkende kan tilgås via index - som desværre det eneste PostSharp tilbyder. Det er efter min mening en rimelig stor mangel i PostSharp, da det betyder at man ikke kan gøre koden robust i forhold til scenarier hvor et parameters navn er mere pålidelige end index - noget der er typisk i f.eks MVC løsninger.

I et scenarie jeg sad med fornylig var det actions på en controller, der bliver kaldt via reflection af MVC frameworket, som jeg ønskede at indkapsle, således at jeg via AOP kunne styre authorization. Her viste sig nogle uhensigtsmæssighed i begge måder at lave AOP. Det var for det første mere besværligt at finde ud af hvilken action der var kaldt, på grund af begge frameworks egentlig indkapsler de metoder der kalder min action via reflection. Derudover var det svært at lave koden solid i forhold til ændringer i navngivning af controllers og actions - da mine regler for authorization nødvendigvis må bindes op  på hvilken controller/action de gælder for.

Løsningen blev at lave unittests der via reflection verificerer at de authorization regler der er sat op rent faktisk matcher en controller og action. For at sikre at man ikke glemmer at sætte regler op for en given action indførte jeg desuden "secure by default" ved at nægte adgang til actions der ikke er konfigureret authorization for.

 

Tags:

Authorization i MVC

by DotNetNerd 9. February 2009 15:46

Ud af boksen er MVC frameworket udstyret med en AuthorizeAttribute, således at man kan angive på sine actions at det kun er brugere i bestemte roller der har lov at tilgå den.

[Authorize(Roles="Employee")]

Det i sig selv virker smart nok, men hvor tit har du lavet en løsning hvor authentization ikke er mere komplekst end det?

På mit nuværende projekt løb jeg hurtigt på det problem at en bruger godt må tilgå en action, hvis det id metoden tager som parameter er id’et på hans egen bruger. Altså med andre ord, han/hun må godt se egne data.

Jeg ville stadig helst undgå at skulle skrive kode i starten af mine actions til at undersøge den slags, da det er en af de ting der er oplagt at lave ved hjælp af AOP. Da attributter kun kan tage statiske værdier, var det udelukket at løse det ved at lave min egen FilterAttribute. Efter at have været lidt i tænkeboks fandt jeg frem til at bruge Castle Windsors IInterceptor til at lave validering af actions.

public class AuthorizationInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        if (invocation.Arguments != null && invocation.Arguments.Length > 0 && invocation.Arguments[0] != null)
        {
            if (invocation.Arguments[0].GetType() == typeof(RequestContext))
            {
                var context = (RequestContext)invocation.Arguments[0];
                var values = context.RouteData.Values;
                var auth = new AuthorizationManager();

                if (!auth.Authorize((string)values["controller"] + "/" + (string) values["action"], (string) values["id"]))
                {
                    throw new UnauthorizedAccessException("You are not authorized for this action");
                }
            }
        }

        invocation.Proceed();
    }
}

Selve det at lave authorization kan nu implementeres i en separat AuthorizationManager klasse, der også kan bruges i forbindelse med f.eks at generere en menu med de punkter, en bruger har ret til at se.

Efter blandt andet at have ladet mig inspirere af authorization via en config-fil fandt jeg frem til at jeg gerne ville samle konfigurationen, men at jeg dog ville undgå en xml-fil, og istedet have mulighed for at lave mere advanceret authentization. Det førte til nedenstående løsning, som jeg er blevet rimeligt godt tilfreds med, da det giver mig en stor grad af fleksibilitet.

public bool Authorize(string controllerAction, string id)
{
    IPrincipal user = HttpContext.Current.User;
    Employee empl = _repo.GetByUserName(user.Identity.Name);
    var employeeId = 0;
    int.TryParse(id, out employeeId);
    var dic = new Dictionary<string, Predicate<Employee>>
                  {
                      {
                          "Employee/Edit", employee => user.IsInRole(Config.Admin) ||
                                                       (user.IsInRole(Config.Employee) &&
                                                       employee.Id == employeeId)
                          },
                  };

    Predicate<Employee> pred;
    return !dic.TryGetValue(controllerAction, out pred) || pred.Invoke(empl);
}

Tags:

Who am I?

My name is Christian Holm Diget, and I work as an independent consultant, in Denmark, where I write code, give advice on architecture and help with training. On the side I get to do a bit of speaking and help with miscellaneous community events.

Some of my primary focus areas are code quality, programming languages and using new technologies to provide value.

Microsoft Certified Professional Developer

Microsoft Most Valuable Professional

Month List