We use dependency injection in our Azure Function (v2 on netstandard20) using parameter binding with IExtensionConfigProvider. After upgrading Microsoft.NET.Sdk.Functions from 1.0.13 to 1.0.19 (which forced an upgrade of Microsoft.Azure.Webjobs.Host to v3) this doesn't work anymore. I can't hit a breakpoint in my IExtensionConfigProvider.Initialize function any more. The same version of the Functions SDK works fine for a sample project with target framework net462, for which it uses Microsoft.Azure.WebJobs v2.
Here's the error it gives at runtime:
Error indexing method 'Function1.Run'. Microsoft.Azure.WebJobs.Host:
Cannot bind parameter 'customThing' to type CustomType. Make sure the
parameter Type is supported by the binding.
And here's the code for the sample app:
public static class Function1
{
[FunctionName("ThisFunction")]
public static async Task Run(
[TimerTrigger("0 */1 * * * *")]TimerInfo timer,
[Inject(typeof(CustomType))] CustomType customThing,
ExecutionContext context)
{
Console.WriteLine(customThing.GetMessage());
}
}
public class CustomType
{
public string GetMessage() => "Hi";
}
[Binding]
[AttributeUsage(AttributeTargets.Parameter)]
public class InjectAttribute : Attribute
{
public Type Type { get; }
public InjectAttribute(Type type) => Type = type;
}
public class InjectConfiguration : IExtensionConfigProvider
{
private IServiceProvider _serviceProvider;
public void Initialize(ExtensionConfigContext context)
{
var services = new ServiceCollection();
services.AddSingleton<CustomType>();
_serviceProvider = services.BuildServiceProvider(true);
context
.AddBindingRule<InjectAttribute>()
.BindToInput<dynamic>(i => _serviceProvider.GetRequiredService(i.Type));
}
}
With the changes made in v3 to DI and the extension model to create a extension (an IExtensionConfigProvider implementation is an extension) now you first need to create a startup class, using [assembly:WebJobsStartup] assembly attribute and implementing IWebJobsStartup interface. In there you can add your own services to the builder via builder.Services and register your extension's config provider class:
[assembly: WebJobsStartup(typeof(WebJobsExtensionStartup ), "A Web Jobs Extension Sample")]
namespace ExtensionSample
{
public class WebJobsExtensionStartup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
//Don't need to create a new service collection just use the built-in one
builder.Services.AddSingleton<CustomType>();
//Registering an extension
builder.AddExtension<InjectConfiguration>();
}
}
}
Then in your IExtensionConfigProvider you can inject any dependencies via constructor injections, for example, binding, bindingproviders, or any other custom dependency. In your case you can just get a reference to the built-in IServiceProvider:
public class InjectConfiguration : IExtensionConfigProvider
{
private IServiceProvider _serviceProvider;
public InjectConfiguration(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Initialize(ExtensionConfigContext context)
{
context
.AddBindingRule<InjectAttribute>()
.BindToInput<dynamic>(i => _serviceProvider.GetRequiredService(i.Type));
}
}
To get the host to load the extension, it must be registered inside bin/extensions.json file, in JavaScript or Java functions via the func extensions install command. In C# the SDK 1.0.19 looks at build time for classes attributed with WebJobsStartup assembly attribute in the current function project or any dependency (ProjectReference or PackageReference) of the current project, and generates the corresponding extensions.json file.
Related
Here is what I have so far. I am trying to create a new ThemeManagementViewModel and inject into that a resource service using:
Microsoft.Extensions.DependencyInjection version 5.0.1 nuget package
public static class Startup
{
public static IServiceProvider ServiceProvider { get; set; }
public static IServiceProvider Init()
{
var serviceProvider = new ServiceCollection().ConfigureServices()
.BuildServiceProvider();
ServiceProvider = serviceProvider;
return serviceProvider;
}
}
public static class DependencyInjectionContainer
{
public static IServiceCollection ConfigureServices(this IServiceCollection services)
{
services.AddSingleton<IDatabaseService, DatabaseService>();
services.AddSingleton<IResourceService, ResourceService>();
services.AddTransient<ThemeManagementViewModel>();
return services;
}
}
public partial class ThemeManagementViewModel : BaseViewModel
{
private readonly IResourceService _resourceService;
public ThemeManagementViewModel(IResourceService resourceService)
{
_resourceService = resourceService;
}
}
public partial class ResourceService : IResourceService
{
private IDatabaseService _databaseService;
public ResourceService(IDatabaseService databaseService)
{
_databaseService = databaseService;
}
}
public interface IResourceService
{
void SetResourceColors();
}
public class ThemeManagementPage : HeadingView
{
private readonly ThemeManagementViewModel _vm;
public ThemeManagementPage()
{
BindingContext = _vm = new ThemeManagementViewModel();
}
}
When I build my application it gives me a message for this line:
BindingContext = _vm = new ThemeManagementViewModel();
and this is the message that I am getting.
There is no argument given that corresponds to the required
formal parameter 'resourceService' of
'ThemeManagementViewModel.ThemeManagementViewModel(IResourceService)'
I thought that the DI was supposed to insert the service into the constructor of ThemeManagementViewModel but it seems not to be working.
Dependency injection will not simply take place anywhere where you construct an object. You need to go explicitly through your DI framework.
In this case, you need to call GetRequiredService() of your IServiceProvider object.
var _vm = Startup.ServiceProvider.GetRequiredService<ThemeManagementViewModel>();
Also, from your code, we don't see that you use your DependencyInjectionContainer class at all. You must make sure that your ConfigureServices method is called explicitly.
DI cannot do magic. The compiler doesn't know anything about it. You have to use it explicitly. It looks like it could do magic in the context of ASP.net website projects. But that is only because there it is the ASP.net framework that handles the things that you need to do explicitly in other types of projects.
Tutorial on how to use DI in .net applications
I have a .NET core WebAPI project that uses Hangfire for background jobs. I am trying to setup Simple Injector for DIs. My porject has an IFoo and a Foo class that looks as follows
public interface IFoo
{
void DoSomething();
}
public class Foo : IFoo
{
public Foo() { }
public void DoSomething()
{
Console.WriteLine($"Foo::DoSomething");
}
}
Below is how I setup the Simple Injector container. I am using Hangfire.SimpleInjector nuget package
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var container = new SimpleInjector.Container();
container.Register<IFoo, Foo>();
GlobalConfiguration.Configuration.UseActivator(
new Hangfire.SimpleInjector.SimpleInjectorJobActivator(container));
services.AddHangfire(x => x.UseSqlServerStorage(<My Connection string>));
services.AddHangfireServer();
services.AddControllers();
}
}
The background job is setup as following in controller
public IActionResult DoSomething()
{
var jobID = BackgroundJob.Enqueue<IFoo>( x => x.DoSomething());
return Ok();
}
But this job fails with following stack trace.
An exception occurred during processing of a background job.
System.InvalidOperationException A suitable constructor for type
'MyWebAPI.Controllers.IFoo' could not be located. Ensure the type is
concrete and services are registered for all parameters of a public
constructor.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider, Type, Object[])
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetServiceOrCreateInstance(IServiceProvider, Type)
at Hangfire.AspNetCore.AspNetCoreJobActivatorScope.Resolve(Type type)
at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_0.<PerformJobWithFilters>b__0()
at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter, PerformingContext, Func`1)
at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_1.<PerformJobWithFilters>b__2()
at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext, IEnumerable`1)
at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext, IStorageConnection, String)
What am I doing wrong in setting all this up?
I am not sure the DI in Hangfire is for this purpose.
You need dependency injection to resolve inner dependencies,
not to resolve the main type you want to use.
You can check the documentation here.
Check this answer with same problem.
I'm trying to do DI in Azure Functions V2 with my service but even after reading the documentation I'm not understanding how to register a service with parameters.
In the example below I want to use the built-in Logger and the DB context within CustomService and the CustomService within SpecialService. These are in the constructors of the respective services, i.e. CustomService(IMyContext context, ILogger logger) and SpecialService(ICustomService customService).
Startup.cs:
[assembly: FunctionsStartup(typeof(Startup))]
namespace AzFunction.IoC
{
class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddLogging();
var connectionString = "test-conn-string";
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
builder.Services.AddDbContext<MyDbContext>(
options => options.UseSqlServer(connectionString));
builder.Services.AddScoped<ICustomService, CustomService>();
builder.Services.AddScoped<ISpecialSerivce, SpecialService>();
}
}
}
It compiles and and hits the breakpoints with in startup.cs but does not seem to be able to find the services that are registered.
The error:
Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'customService' to type ICustomService. Make sure the parameter Type is supported by the binding.
I was trying to inject into the Azure functions parameters instead of the constructor, here is the correct way:
public class GreatClass
{
private ICustomService _customService;
public GreatClass(ICustomService customService)
{
_customService = customService;
}
[FunctionName("MyFunc")]
public async Task Run([TimerTrigger("%RunFrequency%", RunOnStartup = true)]TimerInfo myTimer,
// ICustomService customService, // Incorrect!
ILogger log)
{
//Logic
}
}
I am designing a NuGet package that will be consumed by my application. Due to the project's already implemented architecture, I need to provide a way to instantiate objects using dependency injection both for MVC and Web API outside my controller scope.
Currently I have a class that works in MVC projects, by instantiating objects using the DependencyResolver
public abstract class MyBaseClass<T> where T : class
{
public static T Instance
{
get
{
return DependencyResolver.Current.GetService<T>();
}
}
}
However, when consuming this same class from a WebAPI project, the DependencyResolver is not present, so Im not able to retrieve any object.
I have tried to access the dependency resolver via the HttpContext but have been unsuccessfull. Do you have any way I can access it through a NuGet package?
Thanks!
If it's possible, I'd suggest avoid the service locator pattern and inject the dependency through the constructor instead:
public abstract class MyBaseClass<T> where T : class
{
public MyBaseClass(T instance)
{
Instance = instance;
}
public T Instance { get; }
}
This will allow you to use your package through any "main" entry point (e.g. MVC or WebAPI) that you choose. At that point, it would be the responsibility of the consumer to provide the necessary dependency resolution strategy.
Here's an example of how a consumer (in this case a WebAPI service) of the package would implement the above code:
public class MyWebApiClass : MyBaseClass<MyDependency>
{
public MyWebApiClass(MyDependency resolvedDependency) : base(resolvedDependency) { }
}
public class MyDependency
{
public string Foo { get; set; }
public MyDependency()
{
Foo = "Bar";
}
}
Then the consuming service would also register those dependencies:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddTransient<MyDependency>();
services.AddTransient<MyWebApiClass>();
}
... and inject as needed, allowing the framework to resolve the dependencies (IoC at work):
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly MyWebApiClass _myWebApiClass;
public ValuesController(MyWebApiClass myWebApiClass)
{
_myWebApiClass = myWebApiClass;
}
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { _myWebApiClass.Instance.Foo };
}
}
Making a call to the GET endpoint above, we can see our instance being resolved in MyBaseClass:
I have implemented the IAsyncAuthorizationFilter interface to a class; in addition, that class is derived from Attribute, so that I can mark controller classes and action methods using it. This works so far; the Task OnAuthorizationAsync(AuthorizationFilterContext context) method is called before the action method, and if the request is not authenticated, an HTTP 401 is returned for the response.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class CustomAuthenticationAttribute : Attribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
...
}
}
This attribute is used as follows...
[CustomAuthenticationAttribute]
public class SomeDataController : Controller
{
[HttpGet]
public async Task GetData()
{
...
}
}
Now, I want to use an application service (which obtains secret private key information required for authentication from a database) and tried to use property injection for that. Injecting dependencies via the ctor is not a good choice here since it´s implemented as an attribute. So I tried...
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class CustomAuthenticationAttribute : Attribute, IAsyncAuthorizationFilter
{
public IPrivateKeyLookupService KeyService { get; set; }
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
string publicKey = ...
...
var privateKey = await this.KeyService.GetPrivateKeyFrom(publicKey);
...
}
}
...but property injection does not seem to work here. The service is registered with the IoC, but properties do not get wired. This is an ASP.NET Core 1.1 project in which I use Autofac. In the Startup class my ConfigureServices method has something like that...
public void ConfigureServices(IServicesCollection collection)
{
...
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<AuthKeyService>().As<IPrivateKeyLookupService>();
containerBuilder.Populate(services);
this.container = containerBuilder.Build();
}
Does Autofac support auto-wiring for IAsyncAuthorizationFilter types? And if not, how could I polyfill that functionality?
It turned out to be very simple...
In ASP.NET Core there is the TypeFilter attribute which is also a filter that creates another filter (specified by Type) and satisfies its constructor arguments by involving dependency injection.
I changed the implementation of IAsyncAuthorizationFilter; removed the properties and added a ctor instead, because with TypeFilter ctor injection would work now...
public sealed class CustomAuthenticationAttribute : IAsyncAuthorizationFilter
{
private readonly IPrivateKeyLookupService keyService;
public CustomAuthenticationAttribute(
IPrivateKeyLookupService keyService)
{
this.keyService = keyService;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
...
}
}
I also removed inheritance from Attribute because it´s not needed anymore as well as the declaration of the AttributeUsage attribute.
So, I can use my attribute as follows...
[TypeFilter(typeof(CustomAuthenticationAttribute))]
public class SomeDataController : Controller
{
[HttpGet]
public async Task GetData()
{
...
}
}
Passing additional arguments to the filter´s ctor is also possible via the object[] Arguments property of the TypeFilter class.
A normal thing one needs to do is get ISomething that you registered previously, especially when you implement custom attributes bet really anywhere in the HTTP pipeline will work.
Basically what you like to do is get your "hands" on a HttpContext and use the IServiceProvider located in HttpContext.RequestServices
From there it's all easy sailing if you included
using Microsoft.Extensions.DependencyInjection;
Let me demonstrate this in a small sample where I get to the registered IMemoryCache (you need to register it for this to work of course by adding services.AddMemoryCache(); in your startup.cs)
Here is the copy past stuff:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
public class SomeAttribute : Attribute,IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context )
{
IMemoryCache memory= context.HttpContext.RequestServices.GetService<IMemoryCache>();
}
}