I am creating a generic Startup.cs in a helper library for Azure Functions v4. I am using the DependencyResolver class to resolve dependency injection. Here is the code:
using Infrastructure.DependecyInjection.LifecycleEnum;
using Infrastructure.DependecyInjection.ServiceModel;
using Infrastructure.QueryBuilder;
using Infrastructure.QueryBuilder.KataQueryBuilder;
using Infrastructure.QueryExecutor;
using Infrastructure.QueryExecutor.DapperQueryExecutor;
using Infrastructure.ReadModel;
using Microsoft.Extensions.DependencyInjection;
using Polly.Registry;
using SqlKata.Compilers;
using System.ComponentModel;
namespace Infrastructure.DependecyInjection;
public static class DependencyResolver
{
[Description("Resolve the dependency by considering using Sql Kata as a foundation. God Bless Chat GPT")]
public static IServiceCollection ResolveIQueryBuilder(this IServiceCollection services, //string schema,
object kataQueryBuilderService, Lifecycle lifecycle = Lifecycle.Scoped) //where TCompiler : Compiler, new()
{
var lifetime = lifecycle.GetServiceLifetime();
services.Add(
new ServiceDescriptor(
typeof(IQueryBuilder),
provider => kataQueryBuilderService,
lifetime)
);
return services;
}
public static IServiceCollection ResolveIKataQueryBuilder<TCompiler>(this IServiceCollection services, string schema
, Lifecycle lifecycle=Lifecycle.Scoped) where TCompiler : Compiler, new()
{
services.Add(
new ServiceDescriptor(typeof(IKataQueryBuilder<TCompiler>), provider => new KataQueryBuilder<TCompiler>(schema,
Activator.CreateInstance<TCompiler>()), lifecycle.GetServiceLifetime()));
return services;
}
[Description("Resolve the dependency by using Dapper as a foundation.")]
public static IServiceCollection ResolveIQueryCommand(this IServiceCollection services
, Lifecycle lifecycle = Lifecycle.Singleton)
{
services.Add(new ServiceDescriptor(typeof(IQueryCommand), typeof(DapperQueryCommand), lifecycle.GetServiceLifetime()));
return services;
}
public static IServiceCollection ResolveIReadModel<TPolicy> (this IServiceCollection services, string connectionString,
IQueryCommand queryCommandService, IQueryBuilder queryBuilderService
,TPolicy policy, Lifecycle lifecycle = Lifecycle.Scoped)
where TPolicy : IReadOnlyPolicyRegistry<string>
{
ServiceLifetime serviceLifetime = lifecycle.GetServiceLifetime();
services.Add(
new ServiceDescriptor(typeof(IReadModel)
, provider => new ReadModel.ReadModel(
connectionString,
queryCommandService,
policy,
queryBuilderService
)
, lifecycle.GetServiceLifetime()
)
);
return services;
}
[Description("Debugging purpose only")]
internal static void CheckServices(this IServiceCollection services)
{
var querycommand = services.BuildServiceProvider().GetRequiredService(typeof(IQueryCommand));
var querybuilder = services.BuildServiceProvider().GetRequiredService(typeof(IQueryBuilder));
var readmodel = services.BuildServiceProvider().GetRequiredService(typeof(IReadModel));
}
public static IServiceCollection ResolveExternalServices<TServices>(this IServiceCollection services,TServices externalServices)
where TServices : IEnumerable<Service>
{
foreach(var externalService in externalServices)
{
services.Add(
new ServiceDescriptor(externalService._serviceType, externalService._implementationType
, externalService._lifecycle.GetServiceLifetime())
);
}
return services;
}
private static ServiceLifetime GetServiceLifetime(this Lifecycle lifecycle)
{
return lifecycle switch
{
Lifecycle.Scoped => ServiceLifetime.Scoped,
Lifecycle.Transient => ServiceLifetime.Transient,
_ => ServiceLifetime.Singleton
};
}
}
I use the following class as the base for the generic startup:
using Infrastructure.DependecyInjection;
using Infrastructure.DependecyInjection.LifecycleEnum;
using Infrastructure.DependecyInjection.ServiceModel;
using Infrastructure.QueryBuilder;
using Infrastructure.QueryBuilder.KataQueryBuilder;
using Infrastructure.QueryExecutor;
using Infrastructure.ReadModel;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Polly.Registry;
using SqlKata.Compilers;
namespace Infrastructure.GenericStartup;
public static class Startup
{
public static IFunctionsHostBuilder UseGenericStartup<TServices>(this IFunctionsHostBuilder builder,
TServices services, Lifecycle lifecycle=Lifecycle.Scoped) where TServices : IEnumerable<Service>
{
//TODO KeyVault and AppConfig integration
string connectionString = "MyConnectionString";
string schema = "MySchema";
PolicyRegistry policyRegistry = new PolicyRegistry()
{
{ RetryPolicyNames.SQL_COMMAND_RESILIENCE, PolicyDefinitions.SqlQueryRetryPolicy }
};
_= builder.Services.ResolveIKataQueryBuilder<SqlServerCompiler>(schema, lifecycle);
var kataQueryBuilderService=builder.Services.BuildServiceProvider().GetRequiredService(typeof(IKataQueryBuilder<SqlServerCompiler>));
_= builder.Services.ResolveIQueryBuilder(kataQueryBuilderService,lifecycle);
var queryBuilderService=builder.Services.BuildServiceProvider().GetRequiredService(typeof(IQueryBuilder));
_= builder.Services.ResolveIQueryCommand(lifecycle);
var queryCommandService=builder.Services.BuildServiceProvider().GetRequiredService(typeof(IQueryCommand));
_= builder.Services.ResolveIReadModel(connectionString,(IQueryCommand)queryCommandService,
(IQueryBuilder)queryBuilderService,policyRegistry,lifecycle);
var readModelService=builder.Services.BuildServiceProvider().GetRequiredService(typeof(IReadModel));
_= builder.Services.ResolveExternalServices(services);
return builder;
}
}
The startup of my Function is:
using AzFuncTest.ServiceProvider;
using Infrastructure.DependecyInjection.LifecycleEnum;
using Infrastructure.GenericStartup;
using Infrastructure.QueryBuilder;
using Infrastructure.QueryExecutor;
using Infrastructure.ReadModel;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
[assembly: FunctionsStartup(typeof(AzFuncTest.Startup))]
namespace AzFuncTest;
public class Startup:FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.UseGenericStartup(Services.GetServices(), Lifecycle.Scoped);
}
}
When I run the function, it starts without any issues. However, when I make a call to the function, I get the following error:
"Microsoft.Azure.WebJobs.Script.WebHost: Registered factory delegate returns service Infrastructure.ReadModel.ReadModel is not assignable to scoped container with {no name}"
This is the function code:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Common.Global;
using Infrastructure.ReadModel;
namespace AzFuncTest;
public class TestFunction
{
private readonly IReadModel _readModel;
public TestFunction(IReadModel readModel)
{
_readModel = readModel ?? throw new ArgumentNullException(nameof(readModel));
}
[FunctionName(GlobalCostants.TEST)]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = GlobalCostants.TEST+ "/MaterialCode/{materialCode}")]
HttpRequest req, string materialCode)
{
return new OkObjectResult("OK");
}
}
Can someone who has experienced the same issue help me? I've tried everything and I'm still unable to resolve it.