How to use dependency injection in Azure Durable Functions? - azure

I want to create an Azure Durable Function that will download a CSV from the Internet and based on the data in this file, it will update my database using EntityFramework.
I set up the simple startup function that is triggered with TimeTrigger. This function is responsible for starting the orchestrator. The orchestrator executes multiple activities in parallel. There are around 40000 work items to be processed, so that's the number of activities that are triggered by orchestrator. Some of these activities will need to update the database (insert/update/delete rows). For this I need a database connection. I can configure DI in the StartUp in the following way:
public override void Configure(IFunctionsHostBuilder builder)
{
var connectionString = Environment.GetEnvironmentVariable("DefaultConnection");
builder.Services.AddDbContext<SqlContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddScoped<IDbContext, SqlContext>();
}
}
However all my functions (orchestrator, activity function, etc.) are static and reside in a static class. I haven't seen any example where durable functions were defined in a non-static class and I had all kinds of problems when I tried that myself, so I assumed they must be static without diving too much into it.
I do not know how to pass my DbContext object to the Activity function, so it can update the data in the database when needed.
How should I resolve it?

I want to create an Azure Durable Function that will download a CSV from the Internet and based on the data in this file, it will update my database using EntityFramework.
Configure DI in the StartUp in the following way:
public override void Configure(IFunctionsHostBuilder builder) {
var connectionString = Environment.GetEnvironmentVariable("DefaultConnection");
builder.Services.AddDbContext<IDbContext, SqlContext>(options =>
options.UseSqlServer(connectionString)); //To inject DbContext
builder.Services.AddHttpClient(); //To inject HttpClient
}
Ensure you host your function app on Azure Functions Runtime V3+ so the class and methods don’t have to be static.
This will allow regular classes that have non-static constructors with injectable arguments
public class MyFunction {
private readonly HttpClient httpClient;
private readonly IDbContext dbContext;
//ctor
public MyFunction(IHttpClientFactory factory, IDbContext dbContext) {
httpClient = factory.CreateClient();
this.dbContext = dbContext;
}
[FunctionName("Function_Name_Here")]
public async Task Run(
[OrchestrationTrigger] IDurableOrchestrationContext context) {
// ... access dependencies here
}
// ... other functions, which can include static, but they wont
// have access to the instance fields.
}
This series of articles might be of some assistance to you
A Practical Guide to Azure Durable Functions — Part 2: Dependency Injection

Related

Adding ApplicationInsights to Azure Function with Autofac

I've got an Azure function which I'm adding Application Insights logging to.
It uses autofac to manage the dependencies in a config file, and I have added as follows:
private static void RegisterDependencies(ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<ApplicationInsightsLoggerFactory>()
.As<ILoggerFactory>()
.SingleInstance();
containerBuilder.Register(client => new TelemetryClient(new TelemetryConfiguration("[instrumentation key]"))).As<TelemetryClient>().SingleInstance();
...
In Program.cs 'Main', this class is called like this IocConfig.Configure()
The function runs with no errors, but the logging is not appearing in Application Insights. Have I missed something in this configuration?
You don't to write the DI yourself to get a TelemetryClient. The built-in DI is already available for you to just get one:
public class MyFunctionClass
{
private readonly TelemetryClient telemetryClient;
/// Using dependency injection will guarantee that you use the same configuration for telemetry collected automatically and manually.
public MyFunctionClass(TelemetryConfiguration telemetryConfiguration)
{
this.telemetryClient = new TelemetryClient(telemetryConfiguration);
}

Running some startup tasks in Configure of Azure FunctionsStartup

I have a function that is bound to a QueueTrigger. In this function I generate a file and write this to a Blob Storage.
But before writing (uploading) the file I want to make sure that the container exists. Is the Configure method in the startup class that inherits FunctionsStartup the right place? It feels wrong to do it every time the trigger runs, isn't it?
I'm using DI to supply my function class some services.
[FunctionName("MyFunction")]
public async Task Run([QueueTrigger(MyQueueName, Connection = "AzureWebJobsStorage")]
MyObject queueMessage, ILogger log)
{
var bytes = Encoding.UTF8.GetBytes("MyFileContent");
// Check if container exists - but not everytime?
var blobClient = new BlobClient(_settings.ConnectionString, _settings.ContainerName, _settings.FileName);
await using var memoryStream = new MemoryStream(bytes);
await blobClient.UploadAsync(memoryStream, true);
}
using MyApp.FunctionApp;
using MyApp.FunctionApp.Options;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(Startup))]
namespace MyApp.FunctionApp
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
// Some startup tasks here like ensuring existence of a Blob Container?
builder.Services.AddOptions<Storage>().Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("Storage").Bind(settings);
});
}
}
}
Depending on the frequency of how often you want to check, you could even do something as simple as this:
//shared variable for all instances that run on the same VM
private static bool HaveCheckedBlobContainer = false;
Then, on each invocation:
if (!HaveCheckedBlobContainer)
{
//perform check ...
HaveCheckedBlobContainer = true;
}
I'll generally have an Initialize() method to set up some expensive instances that need to be stored in static member variables. I'll call Initialize() on each invocation, and use a check such as
_someMemberVariable ??= getItFromMyDiContainerOrInstantiateId();
So that it's only executed once, regardless of invocation count.

Access SignalR Hub without Constructor Injection

With AspNetCore.SignalR (1.0.0 preview1-final) and AspNetCore.All (2.0.6), how can I invoke a method on a hub in server code that is not directly in a Controller and is in a class that cannot be made via Dependency Injection?
Most examples assume the server code is in a Controller and should 'ask' for the hub via an injectable parameter in a class that will created by DI.
I want to be able to call the hub's method from server code at any time, in code that is not injected. The old SignalR had a GlobalHost that enabled this approach. Basically, I need the hub to be a global singleton.
Now, everything seems to be dependent on using Dependency Injection, which is introducing a dependency that I don't want!
I've seen this request voiced in a number of places, but haven't found a working solution.
Edit
To be more clear, all I need is to be able to later access the hubs that I've registered in the Configure routine of the Startup class:
app.UseSignalR(routes =>
{
routes.MapHub<PublicHubCore>("/public");
routes.MapHub<AnalyzeHubCore>("/analyze");
routes.MapHub<ImportHubCore>("/import");
routes.MapHub<MainHubCore>("/main");
routes.MapHub<FrontDeskHubCore>("/frontdesk");
routes.MapHub<RollCallHubCore>("/rollcall");
// etc.
// etc.
});
If I register them like this:
services.AddSingleton<IPublicHub, PublicHubCore>();
it doesn't work, since I get back an uninitiated Hub.
No It's not possible. See "official" answer from david fowler https://github.com/aspnet/SignalR/issues/1831#issuecomment-378285819
How to inject your hubContext:
Best solution is to inject your hubcontext like IHubContext<TheHubWhichYouNeedThere> hubcontext
into the constructor.
See for more details:
Call SignalR Core Hub method from Controller
Thanks to those who helped with this. Here's what I've ended up on for now...
In my project, I can call something like this from anywhere:
Startup.GetService<IMyHubHelper>().SendOutAlert(2);
To make this work, I have these extra lines in Startup.cs to give me easy access to the dependency injection service provider (unrelated to SignalR):
public static IServiceProvider ServiceProvider { get; private set; }
public static T GetService<T>() { return ServiceProvider.GetRequiredService<T>(); }
public void Configure(IServiceProvider serviceProvider){
ServiceProvider = serviceProvider;
}
The normal SignalR setup calls for:
public void Configure(IApplicationBuilder app){
// merge with existing Configure routine
app.UseSignalR(routes =>
{
routes.MapHub<MyHub>("/myHub");
});
}
I don't want all my code to have to invoke the raw SignalR methods directly so I make a helper class for each. I register that helper in the DI container:
public void ConfigureServices(IServiceCollection services){
services.AddSingleton<IMyHubHelper, MyHubHelper>();
}
Here's how I made the MyHub set of classes:
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
public class MyHub : Hub { }
public interface IMyHubHelper
{
void SendOutAlert(int alertNumber);
}
public class MyHubHelper : IMyHubHelper
{
public IHubContext<MyHub> HubContext { get; }
public MyHubHelper(IHubContext<MyHub> hubContext)
{
HubContext = hubContext;
}
public void SendOutAlert(int alertNumber)
{
// do anything you want to do here, this is just an example
var msg = Startup.GetService<IAlertGenerator>(alertNumber)
HubContext.Clients.All.SendAsync("serverAlert", alertNumber, msg);
}
}
This is a nice solution. In .NET Core 2.1 the service provider is disposed and you get cannot access disposed object. The fix is to create a scope:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider.CreateScope().ServiceProvider;

ASP.NET Core How to read the content of an AzureTable

I develop a ASP.NET Core application working with Azure Tables.
So, I created a tables storage account in Azure Portal, created a table, filled it with some test data, and now I would like to display the content of that table to test the reading.
my appsettings.json is
{
"ConnectionStrings": {
"MyTables":"DefaultEndpointsProtocol=https;AccountName=yyy;AccountKey=xxx;EndpointSuffix=core.windows.net"
},
"Logging": {
"IncludeScopes": false,
[etc etc...]
}
}
And my Startup.cs:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
// here in debug we can see the connection string, that is OK
Console.WriteLine($"conn string:{Configuration["ConnectionStrings:MyTables"]}");
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}
And here is my controller I try to Display the values:
using Microsoft.AspNetCore.Mvc;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using NextMove.Models;
using System.Text;
[...]
public class HelloWorldController : Controller
{
public string ReadTables() {
// ????? Code does not work, as Startup not a reference
string myConnString = Startup.Configuration["ConnectionStrings:MyTables"];
//////////////////////////////////
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(myConnString);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("themes");
TableQuery<ProjectThemeEntity> query = new TableQuery<ProjectThemeEntity>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "fr"));
StringBuilder response = new StringBuilder("Here is your test table:");
foreach (ProjectThemeEntity item in table.ExecuteQuery(query)) {
response.AppendLine($"Key: {item.RowKey}; Value: {item.Description}");
}
return response.ToString();
}
//
// GET: /HelloWorld/
public IActionResult Index() {
return View();
}
Questions:
a) How to fix this code in order to get the connection string?
b) There should be a "Table.ExecuteQuery(query)" as per this MSDN article in the controller's foreach, but it does not find such a method in CloudTable class, I however added the necessary references, as shown in the controller's code above, only two "Async" methods are available:
PS.
-For the (b) question several people has the same issue here, hope the situation changed now...
You can't access Startup.Configuration from the controller because it's not a static property. Even though you've made it public (generally not a good idea) it still requires you to have an instance of Startup to get access to it.
Generally to get access to settings in ASP.NET Core it's best to create a class with the properties you want and use the IOptions pattern to get them with Dependency Injection. In your startup where you configure your services (add services to the dependency injection container) you would use the helper methods to add your configuration object to the container and then in your controller you would specify you wanted an IOptions or IOptionsSnapshot to get access to it.
I'd suggest you don't put your data access in your controller though. It makes your controller harder to read and harder to maintain if you need to change your strategy later. Move your ReadTables method to its own class and add it to the DI container in Startup taking whatever settings you need to create the service. Use constructor injection in your controller to get the service and execute calls from your controller actions where you need them.

Global Static Dictionary Initialization from Database in Webapi

I want to Initialize a global Dictionary from Database in my web Api. Do i need to inject my DBContext in Global.Asax or Owin Startup. Any example would be much appreciated.
Any kind initialization purposes can be made in your custom defined OWIN Startup class class, like this:
using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
[assembly: OwinStartup(typeof(WebAPIRestWithNest.Startup))]
namespace YourNamespace
{
public class Startup
{
public Dictionary<string, string> Table {get; private set;}
public void Configuration(IAppBuilder app)
{
// token generation
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = false,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(8),
Provider = new SimpleAuthorizationServerProvider()
});
// token consumption
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
app.UseWebApi(WebApiConfig.Register());
Table = ... Connect from DB and fill your table logic ...
}
}
}
After that you can use your Startup.Table property from your application.
In general, it is bad practice to access objects using static field in the asp.net applications because this may lead to bugs that are hardly detected and reproduced: especially this is true for non-immutable/not-thread-safe objects like Dictionary.
I assume you want to cache some DB data in memory to avoid excessive SQL queries. It is good idea to use standard asp.net caching for this purpose:
public IDictionary GetDict() {
var dict = HttpRuntime.Cache.Get("uniqueCacheKey") as IDictionary;
if (pvtData==null) {
dict = doLoadDictionaryFromDB(); // your code that loads data from DB
HttpRuntime.Cache.Add(cacheKey, dict,
null, Cache.NoAbsoluteExpiration,
new TimeSpan(0,5,0), // cache at least for 5 minutes after last access
CacheItemPriority.Normal, null);
}
return dict;
}
This approach allows you to select appropriate expiration policy (without the need to reinventing the wheel with static dictionary).
If you still want to use static dictionary, you can populate it on the application start (global.asax):
void Application_Start(object sender, EventArgs e)
{
// your code that initializes dictionary with data from DB
}

Resources