.NET Core 2.2 Azure Function v2 Dependency Injection - c#

I am having an issue registering a singleton using Microsoft.Extensions.DependencyInjection. I have created a Startup class (which is definitely working) and created an ITestService with implementation.
The singleton is injected into the function's constructor. Initially I had no issues with this implementation, however, a couple days later the function fails because it can't resolve ITestService. I have no clue why.
here is the Startup class
using Microsoft.Azure.WebJobs.Hosting;
[assembly: WebJobsStartup(typeof(Startup))]
namespace Test.Functions
{
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("local.settings.json", true, true)
.AddEnvironmentVariables()
.Build();
builder.Services.AddSingleton<ITestService>(new TestService("test string"));
}
}
}
And here is the function
public class TestFunction
{
private readonly ITestService testService
public TestFunction(ITestService testService)
{
this.testService = testService ?? throw new ArgumentNullException(nameof(testService));
}
[FunctionName(nameof(TestFunction))]
public void Run([ServiceBusTrigger("test", Connection = "ServiceBusConnection")]Message message, ILogger log)
{
////use test service here
}
}
When I debug startup and look at Services I see that the implementation type is null for ITestService, which I assume is why it won't resolve. Like I mentioned this totally worked for a couple days. The version of functions etc have not changed. Any ideas how to get this working again would be greatly appreciated.
update
I tried to simplify this even further and created another dummy interface with an implementation that has a parameter-less constructor. I added it using:
builder.AddSingleton<ITestService2, TestService2>()
It obviously assigned the type to the implementation type, but when it came time to inject it into the constructor it failed with the same can't activate exception.

There's a regression in the latest version of the function host that has broken Dependency Injection.
In order to work around this in an Azure environment, you can lock down the specific version of the functions host by setting the FUNCTIONS_EXTENSION_VERSION application setting to 2.0.12342.0.
If you're running the function host locally using the azure-functions-core-tools NPM package, be sure to use 2.4.419 as the latest version (2.4.498) results in the same issue. You can install that explicitly with the following:
npm i -g azure-functions-core-tools#2.4.419
See this GitHub issue for more background.

Try this in your code:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITestService, TestService>();
}

Have a go with this
builder.Services.AddSingleton<ITestService>(s => new TestService("test string"));
This uses the IServiceProvider in order to provide the string parameter to the constructor.
EDIT :
Try Changing your code to the below and installing Willezone.Azure.WebJobs.Extensions.DependencyInjection
This adds the extension method AddDependencyInjection and allows you to do the traditional ConfigureServices method call in a net core app startup.
using Microsoft.Azure.WebJobs.Hosting;
[assembly: WebJobsStartup(typeof(Startup))]
namespace Test.Functions
{
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("local.settings.json", true, true)
.AddEnvironmentVariables()
.AddDependencyInjection(ConfigureServices)
.Build();
}
private void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITestService>(s => new TestService("test string"));
}
}
}

Related

EF Migrations with multi-project AWS Lambda solution in C#

I'm pretty sure this is a DI configuration question specific to the AWS Lambda project template in VS.
I have a .NET 6 solution with multiple projects (Clean Architecture). For the purposes of this question, there are two projects:
ProjectName.Lambdas.Aggregator - based on the AWS Lambda template in Visual Studio. References ProjectProjectName.Infrastructure.
ProjectProjectName.Infrastructure - Holds all of the EF references, context class, entities, etc.
The function entrypoint triggers the DI configuration.
I'll paste relevant code below (any code not related to this question has been removed).
My question is: When I run dotnet ef migrations add InitialMigration (I'm setting the project of the migration to the Infrastructure project and the startup project to my Lambda function) I get the following error:
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions' while attempting to activate 'SolutionName.ProjectName.Infrastructure.Persistence.Relational.Postgres.ApplicationContext'
What I think is happening is that because this is an AWS Lambda project, the FunctionHandler entry point (whose constructor initializes the DI container) is not called during a migration, therefore it has no idea how to inject DbContextOptions.
How do I get migrations to work in this setup?
Startup.cs
using SolutionName.ProjectName.Infrastructure.Persistence.Relational.Postgres;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace SolutionName.ProjectName.SessionAggregator;
public class Startup
{
private readonly IConfigurationRoot _configuration;
public Startup()
{
_configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
}
public IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddDbContext<ApplicationContext>(options => options.UseNpgsql(_configuration.GetConnectionString("ApplicationContext")));
IServiceProvider provider = services.BuildServiceProvider();
return provider;
}
}
Function.cs
using Amazon.Lambda.Core;
using Amazon.Lambda.SQSEvents;
using SolutionName.ProjectName.Infrastructure.Persistence.Relational.Postgres;
using Microsoft.Extensions.DependencyInjection;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace SolutionName.ProjectName.SessionAggregator;
public class Function
{
private readonly ApplicationContext _context;
public Function()
{
var startup = new Startup();
IServiceProvider provider = startup.ConfigureServices();
_context = provider.GetRequiredService<ApplicationContext>();
}
public async Task FunctionHandler(SQSEvent evnt, ILambdaContext context)
{
foreach (var message in evnt.Records)
{
await ProcessMessageAsync(message, context);
}
}
private async Task ProcessMessageAsync(SQSEvent.SQSMessage message, ILambdaContext context)
{
context.Logger.LogInformation($"Processed message {message.Body}");
// TODO: Do interesting work based on the new message
await Task.CompletedTask;
}
}
ApplicationContext.cs
using SolutionName.ProjectName.Infrastructure.Persistence.Relational.Postgres.Entities;
using Microsoft.EntityFrameworkCore;
namespace SolutionName.ProjectName.Infrastructure.Persistence.Relational.Postgres;
public class ApplicationContext : DbContext
{
public ApplicationContext(DbContextOptions options) : base(options) { }
public DbSet<SessionEntity> Sessions { get; set; }
}
So I found a series of blog posts that answered my question. It's a bit too involved to summarize here. I'm going to post links to the three blog posts, I guess a moderator can delete this question if/when the blog posts go down:
Part 1: https://blog.tonysneed.com/2018/12/16/add-net-core-di-and-config-goodness-to-aws-lambda-functions/
Part 2: https://blog.tonysneed.com/2018/12/20/idesigntimedbcontextfactory-and-dependency-injection-a-love-story/
Part 3: https://blog.tonysneed.com/2018/12/21/use-ef-core-with-aws-lambda-functions/

EFCore migration issue with Azure Function App built using Clean Architecture

I have created an Azure Function App using .Net Core with Clean Architecture as defined here:
This is how my Project Structure looks like:
The Entity Framework is implemented in the Infrastructure Layer and it looks like this:
ApplicationDbContext Code & DI inside Infrastructure
namespace AppFunctions.Infrastructure.Persistence
{
public class ApplicationDbContext : DbContext, IApplicationDbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<Product> Products { get; set; }
public Task<int> SaveChangesAsync()
{
return base.SaveChangesAsync();
}
}
}
namespace AppFunctions.Infrastructure
{
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)),ServiceLifetime.Transient);
services.AddScoped<IApplicationDbContext>(provider => provider.GetRequiredService<ApplicationDbContext>());
return services;
}
}
}
And this DI is registered in Azure Function App's Startup class like this:
[assembly: FunctionsStartup(typeof(StartUp))]
namespace JSStockValuationFrameworkAppFunctions
{
internal class StartUp : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
ConfigureServices(builder.Services);
}
private void ConfigureServices(IServiceCollection services)
{
// Configurations
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile($"local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
services.AddApplication();
services.AddInfrastructure(configuration);
}
}
}
Here, I'm facing an issue with Migration. I tried the following command:
dotnet ef migrations add "SampleMigration" --project Infrastructure --startup-project FunctionApp --output-dir Persistence\Migrations
But getting this error:
MSBUILD : error MSB1009: Project file does not exist.
Switch: C:\FrameworkAppFunctions\AppFunctions
Unable to retrieve project metadata. Ensure it's an SDK-style project. If you're using a custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, Use the --msbuildprojectextensionspath option.
SDK-style project. If you're using a custom BaseIntermediateOutputPath
or MSBuildProjectExtensionsPath values, Use the
--msbuildprojectextensionspath option. ```
It could be resolved by running dotnet ef dbcontext scaffold <list_of_options> command from the parent folder which consists of Solution file in it. Also, use cd .. and rerun the command which will give you the result.
Also, I can see you are using back slashes \ in you command (Persistence\Migrations) change all with forward slash /
For more information you can go through this link.
The problem has been resolved with the following code IDesignTimeDbContextFactory
namespace Infrastructure.Persistence.Configuration
{
public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseSqlServer("Connection string goes here...");
return new ApplicationDbContext(optionsBuilder.Options);
}
}
}

Dependency Injection in .NET Core inside a class library

How can I inject one class into another inside a .NET Core library project?
Where should I configure DI as it is done in StartUp Class ConfigureServices in API project?
After googling a lot I could not find a comprehensive answer with an example to this question. Here is what should be done to use DI in Class library.
In your library:
public class TestService : ITestService
{
private readonly ITestManager _testManager;
public TestService(ITestManager testManager)
{
_testManager = testManager;
}
}
public class TestManager : ITestManager
{
private readonly ITestManager _testManager;
public TestManager()
{
}
}
Then extend IServiceCollection in the library:
public static class ServiceCollectionExtensions
{
public static void AddTest(this IServiceCollection services)
{
services.AddScoped<ITestManager, TestManager>();
services.AddScoped<ITestService, TestService>();
}
}
Lastly in the main app StartUp (API, Console, etc):
public void ConfigureServices(IServiceCollection services)
{
services.AddTest();
}
There are many thought processes for how you manage this, as eventually, the caller will need to register your DI processes for you.
If you look at the methods used by Microsoft and others, you will typically have an extension method defined with a method such as "AddMyCustomLibrary" as an extension method off of the IServiceCollection. There is some discussion on this here.
Dependency Injection is configured at the Composition Root, basically the application entry point. If you do not have control over the application entry point you can not force anyone to use dependency injection with your class library. However you can use interface based programming and create helper classes to register every type in your library for a variety of Composition Root scenarios which will allow people to use IOC to instantiate your services regardless of whatever type of program they are creating.
What you can do is make services in your class library depend on interfaces of other services in your library so that the natural way to use them would be to register your services with the container that is in use and also allow for more efficient unit testing.
I'm not sure I fully understood your intent... But maybe you can make your implementation spin its own private ServiceProvider, something like this:
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public class MyBlackBox {
private readonly IServiceProvider _services = BuildServices();
protected MyBlackBox() {}
public static MyBlackBox Create() {
return _services.GetRequiredService<MyBlackBox>();
}
private static void ConfigureServices(IServiceCollection services) {
services.AddTransient<MyBlackBox>();
// insert your dependencies here
}
private static IServiceProvider BuildServices() {
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging();
serviceCollection.AddOptions();
serviceCollection.AddSingleton(config);
serviceCollection.AddSingleton<IConfiguration>(config);
ConfigureServices(serviceCollection);
return serviceCollection.BuildServiceProvider();
}
private static IConfigurationRoot BuildConfig() {
var path = Directory.GetCurrentDirectory();
var builder = new ConfigurationBuilder().SetBasePath(path).AddJsonFile("appsettings.json");
return builder.Build();
}
}
You can then register your implementation on the "Parent" ServiceProvider, and your dependencies would not be registered on it.
The downside is that you'll have to reconfigure everything, mainly logging and configuration.
If you need access to some services from the parent ServiceProvider, you can create something to bind them together:
public static void BindParentProvider(IServiceProvider parent) {
_services.AddSingleton<SomeService>(() => parent.GetRequiredService<SomeService>());
}
I'm pretty sure there's better ways to create nested ServiceProviders, though.
You can use Hosting Startup assemblies class library as an alternative to explicitly register them from the calling assembly.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/platform-specific-configuration?view=aspnetcore-3.1#class-library
[assembly: HostingStartup(typeof(HostingStartupLibrary.ServiceKeyInjection))]
namespace HostingStartupLibrary
{
public class Startup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => {
services.AddSingleton<ServiceA>();
});
}
}
}
You can look at ServiceCollection Extension Pattern.
https://dotnetcoretutorials.com/2017/01/24/servicecollection-extension-pattern/
If you write this extension in class library, you can inject classes/services in this.
But I don't know is it a good pattern ?
so I can call the library with its services already attached, just use them.
this works for me:
public class LibraryBase
{
ctor... (mĂșltiple services)
public static IHostBuilder CreateHostBuilder(IHostBuilder host)
{
return host.ConfigureServices(... services)
}
}
Main:
public class Program
{
Main{... ConfigureServicesAsync()}
private static async Task ConfigureServicesAsync(string[] args)
{
IHostBuilder? host = new HostBuilder();
host = Host.CreateDefaultBuilder(args);
LibraryBase.CreateHostBuilder(host);
host.ConfigureHostConfiguration()
// ... start app
await host.StartAsync();
}
}

Get root directory of Azure Function App v2

I build an Azure Function App (v2). Configuration tasks necessary for all functions are done in a Setup class that is structured like the following:
[assembly: WebJobsStartup(typeof(Startup))]
internal class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
Configuration = new ConfigurationBuilder()
.SetBasePath(<functionAppDirectory>)
.AddJsonFile("local.settings.json")
.Build();
builder.AddDependencyInjection(ConfigureServices);
}
public IConfiguration Configuration { get; set; }
private void ConfigureServices(IServiceCollection services)
{
var connection = Configuration.GetConnectionString("<myconnection-string>");
...
}
}
In ConfigureServices I want to read a connection string from a configuration file. For that the function app base folder has be specified with SetBasePath. But I found no way to get access to this path. According to https://github.com/Azure/azure-functions-host/wiki/Retrieving-information-about-the-currently-running-function an ExecutionContext can be injected in a function, which contains the path need. But how do I access ExecutionContext in my Startup class?
You can use this piece of code in your startup file.
I have just tested it today for my project and it works on both cloud and local.
var executioncontextoptions = builder.Services.BuildServiceProvider()
.GetService<IOptions<ExecutionContextOptions>>().Value;
var currentDirectory = executioncontextoptions.AppDirectory;
TL;DR: just use Environment.GetEnvironmentVariable.
The ConfigurationBuilder approach shows up in a lot of blog posts, and worked up until we started doing DI. But there is no context parameter, so ConfigurationBuilder immediately starts to cause some strain.
I think people went this direction because in Azure Functions 2, we switched to ASP.NET Core configuration which caused ConfigurationManager to stop working. ConfigurationBuilder was a reasonable place to land. It felt congruent with MVC, and worked fine up until the introduction of DI.
But now that we are doing DI, it's becoming clear that Environment.GetEnvironmentVariable might have been the better choice all along for this platform... There's less code overhead, and it maps cleanly to the configuration model of Azure Functions: in dev, it picks up items in the local.settings.json > Values array, and in production it picks up your environment variables, and it just works.
It is different than what we do in MVC. Until these platforms come into closer parity, however, we should do what makes sense in Functions, rather than trying to force solutions from MVC.
So:
[assembly: WebJobsStartup(typeof(StartUp))]
namespace Keystone.AzureFunctions
{
public class StartUp : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
var connectionString = Environment.GetEnvironmentVariable("KeystoneDB");
// Configure EF
builder.Services.AddDbContext<KeystoneDB>(options => options.UseSqlServer(connectionString));
}
}
}
And your local.settings.json might look like this:
{
"IsEncrypted": false,
"Values": {
"KeystoneDB": "[CONNECTION STRING HERE]"
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
You can also use Key Vault with Environment. It works great.
Greeting,
I found a solution that works in the Startup :
var fileInfo = new FileInfo(Assembly.GetExecutingAssembly().Location);
string path = fileInfo.Directory.Parent.FullName;
var configuration = new ConfigurationBuilder()
.SetBasePath(Environment.CurrentDirectory)
.SetBasePath(path)
.AddJsonFile("appsettings.json", false)
.Build();
You might want to use FunctionsStartupAttribute and IFunctionsHostBuilder from Microsoft.Azure.Functions.Extensions, for example:
[assembly:FunctionsStartup(typeof(SampleFunction.FunctionsAppStartup))]
namespace SampleFunction
{
public class FunctionsAppStartup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
string appRootPath = builder.GetContext().ApplicationRootPath;
// ...
}
}
}
The only workaround I found for configuration builder in Startup() method is to use hardcoded path "/home/site/wwwroot/"
var config = new ConfigurationBuilder()
.SetBasePath("/home/site/wwwroot/")
.AddJsonFile("config.json", optional: false)
.Build();
System.Environment.CurrentDirectory does not work in Azure. Though it works locally. But in Azure it gives an error: The configuration file 'config.json' was not found and is not optional. The physical path is '/config.json'. And function does not start.
Try using Environment.CurrentDirectory

Azure WebJob and wiring up IServiceCollecton from Microsoft.Extensions.DependencyInjection

I'm trying to figure out how to do dependency injection in an Azure WebJob using a ServiceCollection from Microsoft.Extensions.DependencyInjection
E.g.:
services.AddTransient<IAdminUserLogsService, AdminUserLogsService>();
I can't quite figure out how to wire up this service collection into something that the WebJobs JobHostConfiguration.JobActivator can understand
My intention is to re-use the default service wiring I've setup with this method as per the default AspNet core Startup.cs way.
Still wasn't able to find much after searching around last night.
But after a bit of fiddling, I managed to get something working with the following:
EDIT: I've added a more complete solution with Entity Framework.
I should note that my ASP.Net Core webapp is built upon 4.6.2 instead of pure core.
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.WebJobs.ServiceBus;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
namespace Settlements.WebJob
{
public class ServiceJobActivator : IJobActivator
{
IServiceProvider _serviceProvider;
public ServiceJobActivator(IServiceCollection serviceCollection) : base()
{
_serviceProvider = serviceCollection.BuildServiceProvider();
}
public T CreateInstance<T>()
{
return _serviceProvider.GetRequiredService<T>();
}
}
class Program
{
static void Main()
{
var config = new JobHostConfiguration();
var dbConnectionString = Properties.Settings.Default.DefaultConnection;
var serviceCollection = new ServiceCollection();
// wire up your services
serviceCollection.AddTransient<IThing, Thing>();
// important! wire up your actual jobs, too
serviceCollection.AddTransient<ServiceBusJobListener>();
// added example to connect EF
serviceCollection.AddDbContext<DbContext>(options =>
options.UseSqlServer(dbConnectionString ));
// add it to a JobHostConfiguration
config.JobActivator = new ServiceJobActivator(serviceCollection);
var host = new JobHost(config);
host.RunAndBlock();
}
}
}

Categories

Resources