I have a problem with dependecy injection in Azure Functions. I read every guide and any issue here on stackoverflow but I can't found a solution.
I'm working with Visual Studio 2019 and Azurite for testing in my local machine. I tried to make a project without and It works fine.
The project is an Azure Functions with HttpTrigger.
Here the link to my github repository
I paste here my Startup's code:
[assembly: FunctionsStartup(typeof(Startup))]
namespace ZanettiClod.SampleAzureFunctions
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddSingleton<IProductService, ProductService>();
builder.Services.AddSingleton<IProductRepository<Product>, ProductRepository>();
}
}
}
And my Program's code:
namespace ZanettiClod.SampleAzureFunctions
{
public class Program
{
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.Build();
host.Run();
}
}
}
My GetProducts's Code:
namespace ZanettiClod.SampleAzureFunctions
{
public class GetProducts
{
private readonly IProductService _productService;
public GetProducts(IProductService productService)
{
_productService = productService;
}
[Function("GetProducts")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get")]
HttpRequestData req,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("GetProducts");
logger.LogInformation("C# HTTP trigger function processed a request.");
var products = await _productService.GetAllProducts();
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(products);
return response;
}
}
}
And here is the error I get back:
screeshot
Thanks in advance for the help
You could make below changes to make it work. I tested for Dependency injection and it worked.
Move dependency injection from startup.cs to program.cs. That's how it works for target framework .Net 5.0.
Documentation - https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide
namespace ZanettiClod.SampleAzureFunctions
{
public class Program
{
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(s =>
{
s.AddHttpClient();
s.AddSingleton<IProductService, ProductService>();
s.AddSingleton<IProductRepository<Product>, ProductRepository>();
})
.Build();
host.Run();
}
}
}
Change the qualifier for _productService from static to readonly in CreateProduct class. Dependency injection doesn't work on static member variables. Also remove static qualifier from your function.
public class CreateProduct
{
private readonly IProductService _productService;
public CreateProduct(IProductService productService)
{
_productService = productService;
}
[Function("CreateProduct")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("CreateProduct");
logger.LogInformation("C# HTTP trigger function processed a request.");
var product = await req.ReadFromJsonAsync<Product>();
_productService.CreateProduct(product);
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(
new
{
Result = true,
Message = $"Name: {product.Name}, Price: {product.Price}"
});
return response;
}
}
}
Related
I have a console .net core app where I am creating a client using Singleton scope using ConfigureServices.
My thought it will create once and all the future call server by this single client.
But my GetClient calls every time when ever I am making a call like await _hubClient.GetClient();
Can I return the client only once during service startup and use through out until the app restarted?
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((_, services) =>
{
services.AddSingleton<IHubClient, HubClient>(x =>
{
var requiredService = x.GetRequiredService<ILogger<HubClient>>();
return new HubClient(requiredService);
});
});
public interface IHubClient
{
public Task GetClient();
}
public class HubClient : IHubClient
{
private readonly ILogger<HubClient> _logger;
public HubClient(ILogger<HubClient> logger)
{
_logger = logger;
}
public async Task GetClient()
{
await Task.Delay(1);
_logger.LogInformation("gets called every time");
// code to return client
return client;
}
}
Call:
public class Service : IInvocable
{
private readonly IHubClient _hubClient;
public Service(IHubClient hubClient)
{
_hubClient = hubClient;
}
public async Task Invoke()
{
await _hubClient.GetClient();
}
}
I'm currently writing an integration test (https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-5.0) for my ASP .Net Core 5 REST API.
The API is using Serilog for logging (with the static Serilog Logger). I am running tests with NUnit, Visual Studio 2019, Resharper.
I want all the messages, that are logged during the runtime of the API code, to be visible in the test console output.
For example, if this controller method is called:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Serilog;
namespace My.Crazy.Api.Controllers
{
public sealed class WheelsController : Controller
{
[HttpGet("getwheels")]
public async Task<IActionResult> Get()
{
Log.Error("An extremely urgent error");
return Ok();
}
}
}
I expect the "An extremely urgent error" message to be shown in the test console.
However, this is not happening.
Here is my TestServer setup:
[OneTimeSetUp]
public async Task Setup()
{
var hostBuilder = new HostBuilder()
.ConfigureWebHost(webHost =>
{
webHost.UseTestServer();
webHost.UseStartup<Startup>(); // Startup is the API project's Startup class
Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
});
var host = await hostBuilder.StartAsync();
_client = host.GetTestClient();
}
[Test]
public async Task FirstTest()
{
var response = await _client.GetAsync("getwheels");
}
I have also tried logging with a custom Sink:
...
// in the test setup
Log.Logger = new LoggerConfiguration().WriteTo.Sink(new CustomSink()).CreateLogger();
...
public class CustomSink : ILogEventSink
{
public void Emit(LogEvent logEvent)
{
var message = logEvent.RenderMessage();
Console.WriteLine(message);
}
}
This does not work as well. However, I have confirmed that the Emit method is being invoked when API code logs any message.
Finally, I have tried using a File output:
Log.Logger = new LoggerConfiguration().WriteTo.File("C:\\temp\\test_output.txt").CreateLogger();
which worked as expected. However, I still want to log in the console.
Is this possible?
Using anything else for Serilog or NUnit is unfortunately not an option.
So I would try with a custom logger provider with logger:
LoggerProvider:
public class NUnitLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
return new NUnitLogger();
}
public void Dispose()
{
}
}
Logger:
public class NUnitLogger : ILogger, IDisposable
{
public void Dispose()
{
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func<TState, Exception, string> formatter) {
var message = formatter(state, exception);
Debug.WriteLine(message);
}
public bool IsEnabled(LogLevel logLevel) => true;
public IDisposable BeginScope<TState>(TState state) => this;
}
Then in the test file:
var hostBuilder = new HostBuilder()
.ConfigureWebHost(webHost =>
{
webHost.UseTestServer()
.UseStartup<TestStartup>()
.ConfigureLogging((hostBuilderContext, logging) =>
{
logging.Services.AddSingleton<ILoggerProvider, NUnitLoggerProvider>();
});
});
And instead of Debug.WriteLine(message) you can use something else to log to.
I had the same problem. After days of digging, I found a workaround with the initialization of the test server. The key is in setting to true the PreserveExecutionContext which is by default false. Setting it to true brings the logs to the test output. False - no server logs are visible, only client ones.
var path = Assembly.GetAssembly(typeof(MyTestServer))?.Location;
var directoryName = Path.GetDirectoryName(path);
if (directoryName == null)
throw new InvalidOperationException("Cannot obtain startup directory name");
var hostBuilder = new WebHostBuilder()
.UseContentRoot(directoryName)
.ConfigureAppConfiguration(
configurationBuilder => configurationBuilder.AddJsonFile("appsettings.json", false))
.UseStartup<Startup>()
.ConfigureTestServices(services =>
{
//adding mock services here
});
server = new TestServer(hostBuilder)
{
//set this to true!!!
PreserveExecutionContext = true
};
Note: we're running these tests (and the system under test) on .NET7. I am not sure whether this makes any difference.
I have set up DI for an Azure function but it will not resolve when I run the function. The code I have is:
StartUp:
[assembly: FunctionsStartup(typeof(OmegaConnector.StartUp))]
namespace OmegaConnector
{
public class StartUp : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
builder.Services.AddLogging();
builder.Services.AddVehicleSearchCosmosDataProvider();
builder.Services.AddScoped<IProcessSearchData, SearchProcessor>(); <- This one
}
}
IProcessSearchData:
public interface IProcessSearchData
{
Task<bool> ProcessData(string campaign);
}
SearchProcessor:
public class SearchProcessor : IProcessSearchData
{
public async Task<bool> ProcessData(string campaign)
{
return true;
}
}
Function:
public OmegaConnectorFunction(ILogger<OmegaConnectorFunction> logger, IProcessSearchData searchProcessor)
{
I get the error:
Executed 'CatchCampaign' (Failed, Id=daef3371-fa4d-4d1f-abad-7ad343537872)
[27/05/2020 12:17:27] Microsoft.Extensions.DependencyInjection.Abstractions: Unable to resolve service for type 'OmegaConnector.Interfaces.IProcessSearchData' while attempting to activate 'OmegaConnector.OmegaConnectorFunction'.
Sorry if this is too simple but I just can't see what I have wrong here. I think I have this set up correctly but I obviously don't. Can anyone see what I need to do?
See here: https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-class-library
The Functions 3.x packages are built with .NET Core 3.1 in mind.
Try keeping these versions in sync so there are no dependency compatibility problems.
From what I understood of the documentation provided by Microsoft the issue may be that the service needs to be injected into the class that contains the function.
I'm unsure if this is what you've done from the code examples you've provided. An example of this is:
public class OmegaConnectorFunction
{
private readonly ILogger _logger;
private readonly IProcessSearchData _searchProcessor;
public OmegaConnectorFunction(ILogger<OmegaConnectorFunction> logger, IProcessSearchData searchProcessor)
{
_logger = logger;
_searchProcessor = searchProcessor;
}
[FunctionName("OmegaConnectorFunction")]
public async Task<IActionResult> Run([HttpTrigger] HttpRequest request)
{
var campaign = await request.Content.ReadAsAsync<string>();
_searchProcessor.ProcessData(campaign);
return new OkResult();
}
}
public class OmegaConnectorFunction {
private readonly IProcessSearchData _searchProcessor;
public OmegaConnectorFunction(IProcessSearchData searchProcessor)
{
_searchProcessor = searchProcessor;
}
[FunctionName("OmegaConnectorFunction")]
public async Task<IActionResult> Run([HttpTrigger] HttpRequest request, ILogger log) // ILogger is automatically imported
{
var campaign = await request.Content.ReadAsAsync<string>();
_searchProcessor.ProcessData(campaign);
return new OkResult();
}
}
I would like to use DI in my WebJobs same way I use in WebApps but honestly I don't know how Functions.cs is called. If I insert a Console.WriteLine inside Functions.cs's constructor it is not printed.
How could I make this work?
Program.cs
class Program
{
static void Main(string[] args)
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
});
builder.ConfigureLogging((context, b) => {
b.AddConsole();
});
builder.ConfigureServices((context, services) => {
// Inject config
services.Configure<Secrets.ConnectionStrings>(context.Configuration.GetSection("ConnectionStrings"));
services.AddSingleton<Functions>();
services.AddSingleton<MyEmail>();
services.AddSingleton<IMyFunc, MyFunc>();
services.BuildServiceProvider();
});
var host = builder.Build();
using (host)
{
host.Run();
}
}
}
Functions.cs
public class Functions
{
private static IOptions<Secrets.ConnectionStrings> _myConnStr;
private static MyEmail _myEmail;
private static IMyFunc _myFunc;
public Functions(IOptions<Secrets.ConnectionStrings> ConnectionString, MyEmail MyEmail, MyFunc MyFunc)
{
_myConnStr = ConnectionString;
_myEmail = MyEmail;
_myFunc = MyFunc;
Console.WriteLine("Functions constructor");
}
public static void ProcessQueueMessage
(
[QueueTrigger("teste")]
string message,
ILogger logger
)
{
// use my injected stuff
}
}
Thank you very much.
Azure fuctions work a bit different than you are used to. You can do something like the following below:
using System;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;
[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]
namespace MyNamespace
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddSingleton((s) => {
return new MyService();
});
builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
}
}
}
You can read a lot more on Microsoft homepage here: https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection
If using dependency injection via constructor with your function then the function itself needs to be an instance member and not static member.
Your constructor is not being called in your example because the function is static and is thus there is no need to call the instance constructor
public class Functions {
private readonly IOptions<Secrets.ConnectionStrings> connectionStrings;
private readonly MyEmail myEmail;
private readonly IMyFunc myFunc;
public Functions(IOptions<Secrets.ConnectionStrings> connectionStrings, MyEmail myEmail, MyFunc MyFunc) {
this.connectionString = connectionStrings;
this.myEmail = myEmail;
this.myFunc = myFunc;
Console.WriteLine("Functions constructor");
}
public void ProcessQueueMessage(
[QueueTrigger("teste")]
string message,
ILogger logger
) {
// use my injected stuff
}
}
From there it is just a matter of following the details from documentation
Reference Use dependency injection in .NET Azure Functions
I'm looking into upgrading this library to SignalR 2.0
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy
I want it to support the 2.0 Owin pipeline with the IAppBuilder interface, instead of using RouteCollection like SignalR 1.x did.
Problem is, how can I get the routecollection from the IAppBuilder? I need it to regiser a custom IHttpHandler that handles my custom js script (Like SignalR registers their hub script)
1.x setup of my lib looks like this
public static class SignalRConfig
{
public static void Register(RouteCollection routes)
{
routes.MapHubs();
routes.MapEventProxy<Contracts.Events.Event>();
}
}
My goal for 2.0 config it something like this
public static class SignalRConfig
{
public static void Configuration(IAppBuilder app)
{
app.MapSignalR();
app.MapEventProxy<Contracts.Events.Event>();
}
}
My code that is dependent on RouteCollection looks like this
public static class RouteCollectionExtensions
{
public static void MapEventProxy<TEvent>(this RouteCollection routes)
{
Bootstrapper.Init<TEvent>();
routes.Add(new Route(
"eventAggregation/events",
new RouteValueDictionary(),
new RouteValueDictionary() {{"controller", string.Empty}},
new EventScriptRouteHandler<TEvent>()));
}
}
edit: Looks like its very complex to get Owin to serve a request, can I use helper methods in SignalR 2.0 to register a route and a handler to that route?
update:
Looks like im on the right track with this code
using Owin;
using SignalR.EventAggregatorProxy.Boostrap;
namespace SignalR.EventAggregatorProxy.Owin
{
public static class AppBuilderExtensions
{
public static void MapEventProxy<TEvent>(this IAppBuilder app)
{
Bootstrapper.Init<TEvent>();
app.Map("/eventAggregation/events", subApp => subApp.Use<EventScriptMiddleware<TEvent>>());
}
}
}
Now I just need to implement the EventScriptMiddleware
update: Last piece of the puzzle, now I just need my middleware to actually spit out the javacript, should be easy
namespace SignalR.EventAggregatorProxy.Owin
{
public class EventScriptMiddleware<TEvent> : OwinMiddleware
{
public EventScriptMiddleware(OwinMiddleware next) : base(next)
{
}
public override Task Invoke(IOwinContext context)
{
return context.Response.WriteAsync("Hello world!!");
}
}
}
Final version looks like this, app builder extension
public static class AppBuilderExtensions
{
public static void MapEventProxy<TEvent>(this IAppBuilder app)
{
Bootstrapper.Init<TEvent>();
app.Map("/eventAggregation/events", subApp => subApp.Use<EventScriptMiddleware<TEvent>>());
}
}
Invoke method in Middleware
public override Task Invoke(IOwinContext context)
{
var response = context.Response;
response.ContentType = "application/javascript";
response.StatusCode = 200;
if (ClientCached(context.Request, scriptBuildDate))
{
response.StatusCode = 304;
response.Headers["Content-Length"] = "0";
response.Body.Close();
response.Body = Stream.Null;
return Task.FromResult<Object>(null);
}
response.Headers["Last-Modified"] = scriptBuildDate.ToUniversalTime().ToString("r");
return response.WriteAsync(js);
}
Full source code here
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/tree/master/SignalR.EventAggregatorProxy/Owin