4

I am working on a C# project that sits on top of a 3rd party CMS. The team is leveraging Dependency Injection to promote loose coupling between classes.

I have the need to "extend" the apis of the CMS with common functions that are used in several pages.
What makes it interesting is these common functions have multiple dependencies. In this case, is it more appropriate to extend this functionality using static extension methods or by creating new interfaces?

Context

Let's say the 3rd Party has two interfaces IContentLoader and IPageSecurity that work with Page objects:

namespace 3rdParty.Api
{
    public class Page{}

    public interface IContentLoader{
        T LoadItem<T>(Guid id) where T : Page;
    }

    public interface IPageSecurity
    {
        bool CurrentUserCanReadPage(Page p);   
    }
}

And I want to write a common method like:

public IEnumerable<T> LoadAllChildPagesTheUserCanRead(Guid id) where T:Page
{
    //load page, recursively collect children, then
    //filter on permissions
}

(I admit this example is a bit trite)

Extension Methods

I could create a static extension method using Property Injection:

public static class IContentLoaderExtensions
{
   public static Injected<IPageSecurity> PageSecurity {get;set;}

   public static IEnumerable<T> LoadAllChildItems(
      this IContentLoader contentLoader, Guid id){}
}

This method is then very discoverable, we use IContentLoader often so it's easier for a team member to find it. However, I have read that Property Injection is generally less beneficial than Constructor Injection and should be avoided if possible.

Wrapper

On the other hand, I could create a Wrapper:

public class AdvancedContentLoader
{        
     public AdvancedContentLoader(IContentLoader c, IPageSecurity p){
       //save to backing fields
     }

     IEnumerable<T> LoadAllChildItems(Guid id){}  
}

This approach allows for Constructor Injection, which avoids the potential hazards of Property Injection, but makes the method less discoverable. The consumer would need to know to depend on AdvancedContentLoader instead of using the IContentLoader they are use to.

Summary

In this case where a method has multiple dependencies, is it better to promote discoverability by using an extension method and take whatever brittleness may come from using Property Injection? Or is Construction Injection so favorable that I should create a wrapper class at the cost of making the method harder to find?

Community
  • 1
  • 1
Philip Pittle
  • 11,821
  • 8
  • 59
  • 123
  • You say that you've read that property injection is less beneficial than constructor injection, but the link is related to service location being an anti-pattern. Property injection is a perfectly valid technique in the right circumstances. – David Osborne Oct 27 '14 at 09:41
  • It seems to me this is the very reason why extension methods were created. Why would you not want to use them in this case? – theMayer Oct 28 '14 at 17:51
  • @rmayer06 - one reason not to use static/extension method is if DI container in question does not support injection of static fields to static classes. I.e. Unity can't directly do so (one can write some code to [Inject static properties with Unity](http://stackoverflow.com/questions/15134625/unity-static-property-injection), but code is not exactly best example of DI). – Alexei Levenkov Oct 29 '14 at 06:22
  • I guess the question is too abstract for me- after reading your example, there just really isn't enough to go on in terms of crafting a solution, or suggesting a different design approach altogether. – theMayer Oct 29 '14 at 13:57

3 Answers3

3

I would lean more towards the wrapper class but I would create another interface for it. I would name it similar so developers can find it.

public interface IContentLoaderWithPageSecurity : IContentLoader
{
    IEnumerable<T> LoadAllChildItems<T>(IContentLoader contentLoader, Guid id) { }
}

New interface but same starting name so intellisense can help developers. Also this interface has to implement the 3rd party interface.

I would change your AdvancedContentLoader class to implement this interface and chain all calls to IContextLoader to 3rd party implementation and handle just the specific methods it needs to handle.

public class AdvancedContentLoader : IContentLoaderWithPageSecurity 
{
    private readonly IContentLoader _internalContentLoader;
    private IPageSecurity _pageSecurity;

    public AdvancedContentLoader(IContentLoader contentLoader, IPageSecurity pageSecurity)
    {
        _internalContentLoader = contentLoader;
        _pageSecurity = pageSecurity;
    }

    // Chain calls on the interface to internal content loader
    public T LoadItem<T>(Guid id) where T : Page
    {
        return _internalContentLoader.LoadItem<T>(id);
    }

    public IEnumerable<T> LoadAllChildItems<T>(IContentLoader contentLoader, Guid id)
    {
        // do whatever you need to do here
        yield break;
    }
}

The benefits of this is if you are using DI Container you can just register IContentLoaderWithPageSecurity interface to the class and you are still coding to an interface.

The naming convention helps the developers find it with intellisense, if the namespace of the class is in the using directive.

The new interface implements the old one so existing code base that needs IContentLoader you can still pass down IContentLoaderWithPageSecurity into those methods.

I would only lean towards extension methods if I didn't require a new dependency and could just just what is already there - otherwise you have to get "smart" and do property injection or something like the ConditionalWeakTable to hold extra state for the class.

I agree with Wiktor Zychla that this starts to become peoples subjective opinions.

CharlesNRice
  • 3,219
  • 1
  • 16
  • 25
2

I suggest a decorated content loader. This approach follows SRP principle, where you don't mix responsiblities - I still have a content loader and when I want to implemenet loading multiple elements, I delegate this to another class.

 public class DecoratedContentLoader : IContentLoader
 {        
     IContentLoader c;
     IPageSecurity p;

     public DecoratedContentLoader(IContentLoader c, IPageSecurity p)
     {
         this.c = c;
         this.p = p;           
     }

     public T LoadItem<T>(Guid id) where T : Page
     {
         var page = c.LoadItem<T>( id );
         if ( p.CanUserReadPage( p ) )
            return p;
         // throw or return null
     }
 }

As you can see, this uses the security provider but still implements a single item provider interface.

Thus, another class responsible for loading multiple items can just take IContentProvider as an argument and use either the bare one or the decorated one without distinguishing between the two.

 public class AdvancedContentLoader
 {    
     // no need for additionak parameters, works
     // with any loader, including the decorated one    
     public AdvancedContentLoader( IContentLoader c )
     {
        //save to backing fields
     }

     IEnumerable<T> LoadAllChildItems(Guid id){}  
  }     
Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
  • Does this approach scale? Let's say I have a 4 line "utility" method that depends on 5 api classes. I would need to create 5 decorators and *then* create an 'Advanced' class to host the method. I am not saying this approach is incorrect, it just seems like a lot of overhead when I'm not seeing much benefit? – Philip Pittle Oct 20 '14 at 19:40
  • Yes, decorators are designed to be scalable. With an extra decorator for feature, you combine them freely in any order. Comparing to your approach where you have an extra method for all possible combinations of api services. Plus the SRP which you seem to violate by having two responsibilities in one class (using single item loader to load a list + combining loader with page level security). – Wiktor Zychla Oct 20 '14 at 19:50
  • So the example I gave was intentionally brief. What I'm really interested in is a scenario where I can't decorate a 3rd party api and I have a method that *has* to use two services to do any useful work. In that case I can't separate out the pieces into decorators, and I arrive back at my original question, new wrapper class, or extension method? – Philip Pittle Oct 23 '14 at 10:21
  • 1
    My answer was intented to point out a more clean OO approach focusing on patterns and separation of concerns. Otherwise, if you insist on picking between the two, well, you have enumerated basic differences on your own - these are facts. What lies beyond is a land of subjective opinions on what is more important, discoverability or di conformance. There is no objective answer on that without broad context. – Wiktor Zychla Oct 23 '14 at 12:58
1

So, my initial reaction to this is that there might be a bit of over-thinking going on here. If I understand your question correctly, you are trying to figure out the easiest way to extend a third party API. In this case, the API has an interface that you like, IContentLoader, and your goal is to add another method to this interface which enables it:

  • in addition to loading a given page (defined by Guid),
  • to recursively load all child pages as well,
  • so long as the user has permission (which is in the responsibility of IPageSecurity).

According to Microsoft,

Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type.

Which, if I understand, is exactly what you are trying to do here. I will admit that the structure and function for IPageSecurity does not make much sense to me, and that could be the reason behind the confusion. Bottom line, is there any reason why you would choose not to go this route? Perhaps your purpose is complicated by your example.

theMayer
  • 15,456
  • 7
  • 58
  • 90
  • I was adding a method that only worked directly on a single type (ie `IPageSecurity`) then extension methods are the obvious choice. The question I'm trying to explore is how to deal with a case when I'm using DI and a proposed extension method would have several external dependencies. – Philip Pittle Oct 30 '14 at 11:03
  • It really sounds like the API you are working with is very poorly designed, at least from an OO standpoint. In this case, I might be inclined to try wrappers, but you are never going to fully eradicate bad design this way. Is the API open source? Can you edit the source code to fix it? – theMayer Oct 30 '14 at 17:13