Blog Post

Apps on Azure Blog
5 MIN READ

Dependency Injection on Azure Functions: 5 Ways Selecting Instance from Instances of Same Interface

justinyoo's avatar
justinyoo
Icon for Microsoft rankMicrosoft
Jul 02, 2020

While building an Azure Functions application, setting an IoC container for dependency injection has many benefits by comparing to just using the static classes and methods. Azure Functions leverages the built-in IoC container featured by ASP.NET Core that is easy to use, without having to rely on any third-party libraries. Throughout this post, I'm going to discuss five different ways to pick up a dependency injected from multiple instances sharing with the same interface.

 

Implementing Interface and Classes

 

Let's assume that we're writing one IFeedReader interface and three different classes, BlogFeedReader, PodcastFeedReader and YouTubeFeedReader implementing the interface. There's nothing fancy.

 

 

public interface IFeedReader
{
    string Name { get; }
    string GetSingleFeedTitle();
}

public class BlogFeedReader : IFeedReader
{
    public BlogFeedReader()
    {
        this.Name = "Blog";
    }

    public string Name { get; }

    public string GetSingleFeedTitle()
    {
        return "This is blog item 1";
    }
}

public class PodcastFeedReader : IFeedReader
{
    public PodcastFeedReader()
    {
        this.Name = "Podcast";
    }

    public string Name { get; }

    public string GetSingleFeedTitle()
    {
        return "This is audio item 1";
    }
}

public class YouTubeFeedReader : IFeedReader
{
    public YouTubeFeedReader()
    {
        this.Name = "YouTube";
    }

    public string Name { get; }

    public string GetSingleFeedTitle()
    {
        return "This is video item 1";
    }
}

 

 

IoC Container Registration #1 – Collection

 

Let's register all the classes declared above into the Configure() method in Startup.cs.

 

 

public override void Configure(IFunctionsHostBuilder builder)
{
    builder.Services.AddTransient<IFeedReader, BlogFeedReader>();
    builder.Services.AddTransient<IFeedReader, PodcastFeedReader>();
    builder.Services.AddTransient<IFeedReader, YouTubeFeedReader>();
}

 

 

By doing so, all three classes have been registered as IFeedReader instances. However, from the consumer perspective, it doesn't know which instance is appropriate to use. In this case, using a collection as IEnumerable<IFeedReader> is useful. In other words, inject a dependency of IEnumerable<IFeedReader> to the FeedReaderHttpTrigger class and filter one instance from the collection.

 

 

public class FeedReaderHttpTrigger
{
    private readonly IFeedReader _reader;

    public FeedReaderHttpTrigger(IEnumerable<IFeedReader> readers)
    {
        this._reader = readers.SingleOrDefault(p => p.Name == "Blog");
    }

    [FunctionName(nameof(FeedReaderHttpTrigger.GetFeedItemAsync))]
    public async Task<IActionResult> GetFeedItemAsync(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "feeds/item")] HttpRequest req,
        ILogger log)
    {
        var title = this._reader.GetSingleFeedTitle();

        return new OkObjectResult(title);
    }
}

 

 

It's one way to use the collection as a dependency. The other way to use the collection is to use a loop. It's useful when we implement either a Visitor Pattern or Iterator Pattern.

 

 

public class FeedReaderHttpTrigger
{
    private readonly IEnumerable<IFeedReader> _readers;

    public FeedReaderHttpTrigger(IEnumerable<IFeedReader> readers)
    {
        this._readers = readers;
    }

    [FunctionName(nameof(FeedReaderHttpTrigger.GetFeedItemAsync))]
    public async Task<IActionResult> GetFeedItemAsync(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "feeds/item")] HttpRequest req,
        ILogger log)
    {
        var title = default(string);
        foreach (reader in this._readers)
        {
            if (reader.Name != "Blog")
            {
                continue;
            }

            title = reader.GetSingleFeedTitle();
        }

        return new OkObjectResult(title);
    }
}

 

 

IoC Container Registration #2 – Resolver

 

It's similar to the first approach. This time, let's use a resolver instance to get the dependency we want. First of all, declare both IFeedReaderResolver and FeedReaderResolver. Keep an eye on the instance of IServiceProvider as a dependency. It's used for the built-in IoC container of ASP.NET Core, which can access to all registered dependencies.

 

In addition to that, this time, we don't need the Name property any longer as we use conventions to get the required instance.

 

 

public interface IFeedReaderResolver
{
    IFeedReader Resolve(string name);
}

public class FeedReaderResolver : IFeedReaderResolver
{
    private readonly IServiceProvider _provider;

    public FeedReaderResolver(IServiceProvider provider)
    {
        this._provider = provider;
    }

    public IFeedReader Resolve(string name)
    {
        var type = Assembly.GetAssembly(typeof(FeedReaderResolver))
                           .GetType($"{name}FeedReader");
        var instance = this._provider.GetService(type);

        return instance as IFeedReader;
    }
}

 

 

After that, update Configure() on Startup.cs again. Unlike the previous approach, we registered xxxFeedReader instances, not IFeedReader. It's fine, though. The resolver sorts this out for FeedReaderHttpTrigger.

 

 

public override void Configure(IFunctionsHostBuilder builder)
{
    builder.Services.AddTransient<BlogFeedReader>();
    builder.Services.AddTransient<PodcastFeedReader>();
    builder.Services.AddTransient<YouTubeFeedReader>();

    builder.Services.AddTransient<IFeedReaderResolver, FeedReaderResolver>();
}

 

 

Update the FeedReaderHttpTrigger class like below.

 

 

public class FeedReaderHttpTrigger
{
    private readonly IFeedReader _reader;

    public FeedReaderHttpTrigger(IFeedReaderResolver resolver)
    {
        this._reader = resolver.Resolve("Blog");
    }

    [FunctionName(nameof(FeedReaderHttpTrigger.GetFeedItemAsync))]
    public async Task<IActionResult> GetFeedItemAsync(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "feeds/item")] HttpRequest req,
        ILogger log)
    {
        var title = this._reader.GetSingleFeedTitle();

        return new OkObjectResult(title);
    }
}

 

 

IoC Container Registration #3 – Resolver + Factory Method Pattern

 

Let's slightly modify the resolver that uses the factory method pattern. After this modification, it removes the dependency on the IServiceProvider instance. Instead, it creates the required instance by using the Activator.CreateInstance() method.

 

 

public class FeedReaderResolver : IFeedReaderResolver
{
    public IFeedReader Resolve(string name)
    {
        var type = Assembly.GetAssembly(typeof(FeedReaderResolver))
                           .GetType($"{name}FeedReader");
        var instance = Activator.CreateInstance(type);

        return instance as IFeedReader;
    }
}

 

 

If we implement the resolver class in this way, we don't need to register all xxxFeedReader classes to the IoC container, but IFeedReaderResolver would be sufficient. By the way, make sure that all xxxFeedReader instances cannot be used as a singleton if we take this approach.

 

 

public override void Configure(IFunctionsHostBuilder builder)
{
    builder.Services.AddTransient<IFeedReaderResolver, FeedReaderResolver>();
}

 

 

IoC Container Registration #4 – Explicit Delegates

 

We can replace the resolver with an explicit delegates. Let's have a look at the code below. Within Startup.cs, declare a delegate just outside the Startup class.

 

 

public delegate IFeedReader FeedReaderDelegate(string name);

 

 

Then, update Configure() like below. As we only declared the delegate, its actual implementation goes here. The implementation logic is not that different from the previous approach.

 

 

public override void Configure(IFunctionsHostBuilder builder)
{
    builder.Services.AddTransient<BlogFeedReader>();
    builder.Services.AddTransient<PodcastFeedReader>();
    builder.Services.AddTransient<YouTubeFeedReader>();

    builder.Services.AddTransient<FeedReaderDelegate>(provider => name =>
    {
        var type = Assembly.GetAssembly(typeof(FeedReaderResolver))
                           .GetType($"FeedReaders.{name}FeedReader");
        var instance = provider.GetService(type);

        return instance as IFeedReader;
    });
}

 

 

Update the FeedReaderHttpTrigger class. As FeedReaderDelegate returns the IFeedReader instance, another method call should be performed through the method chaining.

 

 

public class FeedReaderHttpTrigger
{
    private readonly FeedReaderDelegate _delegate;

    public FeedReaderHttpTrigger(FeedReaderDelegate @delegate)
    {
        this._delegate = @delegate;
    }

    [FunctionName(nameof(FeedReaderHttpTrigger.GetFeedItemAsync))]
    public async Task<IActionResult> GetFeedItemAsync(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "feeds/item")] HttpRequest req,
        ILogger log)
    {
        var title = this._delegate("Blog").GetSingleFeedTitle();

        return new OkObjectResult(title);
    }
}

 

 

IoC Container Registration #5 – Implicit Delegates with Lambda Function

 

Instead of using the explicit delegate, the Lambda function can also be used as the implicit delegate. Let's modify the Configure() method like below. As there is no declaration of the delegate, define the Lambda function.

 

 

public override void Configure(IFunctionsHostBuilder builder)
{
    builder.Services.AddTransient<BlogFeedReader>();
    builder.Services.AddTransient<PodcastFeedReader>();
    builder.Services.AddTransient<YouTubeFeedReader>();

    builder.Services.AddTransient<Func<string, IFeedReader>>(provider => name =>
    {
        var type = Assembly.GetAssembly(typeof(FeedReaderResolver))
                           .GetType($"FeedReaders.{name}FeedReader");
        var instance = provider.GetService(type);

        return instance as IFeedReader;
    });
}

 

 

As the injected object is the Lambda function, FeedReaderHttpTrigger should accept the Lambda function as a dependency. While it's closely similar to the previous example, it uses the Lambda function this time for dependency injection.

 

 

public class FeedReaderHttpTrigger
{
    private readonly Func<string, IFeedReader> _func;

    public FeedReaderHttpTrigger(Func<string, IFeedReader> func)
    {
        this._func = func;
    }

    [FunctionName(nameof(FeedReaderHttpTrigger.GetFeedItemAsync))]
    public async Task<IActionResult> GetFeedItemAsync(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "feeds/item")] HttpRequest req,
        ILogger log)
    {
        var title = this._func("Blog").GetSingleFeedTitle();

        return new OkObjectResult(title);
    }
}

 

 

---

 

So far, we have discussed five different ways to resolve injected dependencies using the same interface, while building an Azure Functions application. Those five approaches are very similar to each other. Then, which one to choose? Well, there's no one approach better than the other four, but I guess it would depend purely on the developer's preference.

 

This post has been cross-posted to DevKimchi.

 

Updated Jul 02, 2020
Version 6.0
  • RemcoSchrijver_'s avatar
    RemcoSchrijver_
    Copper Contributor

    For the collection pattern #1 also an option for choosing which implementation to use you could use object.GetType() == typeof([class]) ? Or would that not work.

  • Vivek_51db24's avatar
    Vivek_51db24
    Copper Contributor

    This post came real handy for me. I had to device solution which reacted differently to events it was receiving and all the events hand similar construct. Thank You justinyoo.