0

I am trying to use Azure Appconfiguration in one of my functions but I am not fully understanding why it does not work, I get the object ref error when trying to declare the CosmosClient, it seems as it cannot read the appSettings object for some reason

the relevant code looks as follows

  public class BarCodeScanV4
    {
        //
        // Read basic settings from the local APP Service Configurations
        // This uses local.settings.json when ran locally
        //
        // NOTE: V4 and onwards of this function can NOT be run locally
        // because it uses managed identity to access the global configuration store
        //
        private static readonly IConfiguration config = new ConfigurationBuilder()
                .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables()
                .Build();

        private static readonly string envLabel = config["envLabel"];
        private static readonly string configurationStorePrimaryEndpoint = config["configurationStorePrimaryEndpoint"];
        //
        // End of read basic settings
        //

        //
        // Read global settings from Azure APP Configuration
        // This does NOT use local.settings.json so the settings must exist in Azure even when ran locally
        //
        private static IConfiguration Configuration { set; get; }
        private static IConfigurationRefresher ConfigurationRefresher { set; get; }
        private static Settings appSettings { get; set; }

        static BarCodeScanV4()
        {
            
            var builder = new ConfigurationBuilder();

            builder.AddAzureAppConfiguration(options =>
            {
                options
                    .Connect(new Uri(configurationStorePrimaryEndpoint), new ManagedIdentityCredential())
                    .Select(KeyFilter.Any, envLabel)
                    .ConfigureRefresh(refreshOptions =>
                         refreshOptions.Register("SLAPI:Settings:ConfigurationStoreVersion", refreshAll: true)
                                       .SetCacheExpiration(TimeSpan.FromSeconds(300))
                                          
                    );
                ConfigurationRefresher = options.GetRefresher();
            });
            Configuration = builder.Build();

            //
            // Use helper class to fetch the global settings
            // NOTE: This is not used at runtime, this is settings used by singletons etc
            // runtime settings are added below in the actual function
            // 
            appSettings = Utils.AppSettings.GetAppSettingsAsync(appSettings, Configuration);

            //
            // End of fetching global settings
            // 
        }
        //
        // End of read global settings
        //

        //
        // Create the clients outside of the function to ensure they are reused and not re created for every call to the function
        // this is CRITICAL for performance and avoid running out of ports on the connections.
        //
        private static CosmosClient cosmosClient = new CosmosClient(appSettings.CosmosDBEndpoint, appSettings.CosmosDBPrimaryKey, new CosmosClientOptions() { AllowBulkExecution = false, ConnectionMode = ConnectionMode.Direct });
        private static EventHubProducerClient eventHubClient = new EventHubProducerClient(appSettings.EventHubConnectionString, appSettings.EventHubName);

        private static Database database = null;
        private static Container usersContainer = null;
        private static Container slapiAuthContainer = null;
        private static Container slapiFormattedDataContainer = null;
        private static Container promosContainer = null;
Matt Douhan
  • 2,053
  • 1
  • 21
  • 40
  • 1
    I'd recommend making your question title reflect the question of not being able to read the settings, since otherwise your question sounds like a generic "why am I getting a null reference exception?" question, and the answer to that is always "because something is null." This doesn't seem to be like the core of your question, so you should edit your question title to reflect that. – ProgrammingLlama Aug 17 '20 at 03:18
  • This is the expected behavior. [Static field initializers are always executed before a static constructor runs](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors#remarks) – Aluan Haddad Aug 17 '20 at 03:23
  • @AluanHaddad ok so how can I work around this? – Matt Douhan Aug 17 '20 at 03:24
  • 2
    Initialize `cosmosClient` in the static constructor – Aluan Haddad Aug 17 '20 at 03:25
  • @AluanHaddad I moved it inside the static constructor but still have the same issue, I read the link you mentioned but I clearly is missing something regarding this – Matt Douhan Aug 17 '20 at 10:30
  • You would have to do it for `eventHubClient` as well. – Aluan Haddad Aug 17 '20 at 10:32
  • @AluanHaddad yes I did I moved it all in, and move the null field in its down to the actual function itself so that now everything is inside the static ctor – Matt Douhan Aug 17 '20 at 10:32
  • Sorry, I missed something about your code: you are calling `GetAppSettingsAsync` inside of a constructor. You are firing and forgetting an asynchronous operation, and you thus have a race condition. – Aluan Haddad Aug 17 '20 at 10:37
  • @AluanHaddad yes but I cant use await there so its a catch22 really not sure how to work around that – Matt Douhan Aug 17 '20 at 10:39
  • Why not? Azure Functions supports `public async static Task RunAsync() {...}` where you can define your logic and configuration. There's no need to use a `static` constructor. – Aluan Haddad Aug 17 '20 at 10:45
  • @AluanHaddad I have to declare cosmosClient outside of the Run part of the function in order to not starve the tcp ports as the load increases – Matt Douhan Aug 17 '20 at 10:46
  • There are plenty of ways to handle that. For example, writing a `static async Task InitializeAsync() {...}` function that initializes your static fields if they have not been initialized. If they have been, it does nothing. – Aluan Haddad Aug 17 '20 at 10:50
  • @AluanHaddad ok but I cannot make the entire function class async it is now supported so it wont let me write that type of method – Matt Douhan Aug 17 '20 at 11:00
  • In your `RunAsync` make sure `InitializeAsync` is done. Call it once, like in your static constructor, and save the result: `private static Task InitializeTask;` `InitializeTask = InitializeAsync();`. Then in `RunAsync`, `await InitializeTask` can be the very first line. Your fire and forget approach is just fundamentally incorrect and will not work as you have seen from the errors you encounter. – Aluan Haddad Aug 17 '20 at 11:02
  • @AluanHaddad but I cannot, since I need client to be initialized outside of the Run otherwise it creates a new close to for every invocation of the function and it crashes the function after only a free minutes under load so I need the settings done before I init the client and both needs to happen outside of the Run – Matt Douhan Aug 17 '20 at 11:42
  • Re-read what I wrote. I'm not suggesting you initialize inside run. I'm suggesting that you write an asynchronous initializer, call it in a synchronous context outside of run and save the resulting task in a field. The asynchronous run just needs to await that field to ensure that initialization is complete. – Aluan Haddad Aug 17 '20 at 11:44
  • @AluanHaddad sorry I am not following what you mean, I made the GetAppSettingsAsync async but now I cannot call it from outside the run so obvously there us something I am missing here that’s you are assuming I understand which I dont :) – Matt Douhan Aug 17 '20 at 11:57
  • Fair enough. Async functions return tasks. A task can be stored in a field. By storing the task that is returned by an asynchronous initialization function in a static field, you allow other code , such as run, to await this task, effectively queuing on it such that run does not proceed until it has completed. With such an approach, initialization only happens once , you don't create multiple clients or initialize multiple times, but you allow initialization to be asynchronous. You have to do something because your current initialization is fundamentally wrong – Aluan Haddad Aug 17 '20 at 12:03
  • @AluanHaddad this is a bit above my pay grade, could you make a simple example as an answer? – Matt Douhan Aug 17 '20 at 12:10
  • I've never written an Azure function, so I'm reluctant to answer this question. I just noticed some very obvious bugs that you had, things which apply to all C# code, so I pointed them out – Aluan Haddad Aug 17 '20 at 12:13

0 Answers0