Asked  7 Months ago    Answers:  5   Viewed   186 times

Apologies, I'm new to Azure. I created a service bus and queue via the Azure portal using this tutorial.

I can write and read from the queue ok. The problem is, to deploy to the next environment, I have to either update the ARM template to add the new queue or create the queue in code. I can't create the queue through the portal in the next environment.

I've chosen the latter: check to see if the queue exists and create as required via code. I already have an implementation for this for a CloudQueueClient (in the Microsoft.WindowsAzure.Storage.Queue namespace). This uses a CloudStorageAccount entity to create the CloudQueueClient, if it doesnt exists.

I was hoping it would be this simple but it appears not. I'm struggling to find a way to create a QueueClint (in the Microsoft.Azure.ServiceBus namespace). All I have is the Service Bus connection string and the queue name but having scoured Microsoft docs, there's talk of a NamespaceManager and MessagingFactory (in a different namespace) involved in the process.

Can anyone point me in the direction of how to achieve this and more importantly, is this the right approach? I'll be using DI to instantiate the queue so the check/creation will only be done once.

The solution is required for a service bus queue and not a storage account queue. Differences outlined here

Thanks

 Answers

4

Sean Feldman's answer pointed me in the right direction. The main nuget packages/namespaces required (.net core ) are

  • Microsoft.Azure.ServiceBus
  • Microsoft.Azure.ServiceBus.Management

    Here's my solution:

    private readonly Lazy<Task<QueueClient>> asyncClient; private readonly QueueClient client;

    public MessageBusService(string connectionString, string queueName)
    {
        asyncClient = new Lazy<Task<QueueClient>>(async () =>
        {
            var managementClient = new ManagementClient(connectionString);
    
            var allQueues = await managementClient.GetQueuesAsync();
    
            var foundQueue = allQueues.Where(q => q.Path == queueName.ToLower()).SingleOrDefault();
    
            if (foundQueue == null)
            {
                await managementClient.CreateQueueAsync(queueName);//add queue desciption properties
            }
    
    
            return new QueueClient(connectionString, queueName);
        });
    
        client = asyncClient.Value.Result; 
    }
    

Not the easiest thing to find but hope it helps someone out.

Wednesday, November 3, 2021
 
5

OK so I have it working now :) Because we use octopus deploy we don't want multiple config files so we just have the one appsettings.Release.json file which gets the values substituted base on the environment being deployed too.

Below is the main function code.

public static class Function
    {
        // Format in a CRON Expression e.g. {second} {minute} {hour} {day} {month} {day-of-week}
        // https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer
        // [TimerTrigger("0 59 23 * * *") = 11:59pm
        [FunctionName("Function")]
        public static void Run([TimerTrigger("0 59 23 * * *")]TimerInfo myTimer, ILogger log)
        {

            // If running in debug then we dont want to load the appsettings.json file, this has its variables substituted in octopus
            // Running locally will use the local.settings.json file instead
#if DEBUG
            IConfiguration config = new ConfigurationBuilder()
                .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables()
                .Build();
#else
            IConfiguration config = Utils.GetSettingsFromReleaseFile();
#endif

            // Initialise dependency injections
            var serviceProvider = Bootstrap.ConfigureServices(log4Net, config);

            var retryCount = Convert.ToInt32(config["RetryCount"]);

            int count = 0;
            while (count < retryCount)
            {
                count++;
                try
                {
                    var business = serviceProvider.GetService<IBusiness>();
                    business.UpdateStatusAndLiability();
                    return;
                }
                catch (Exception e)
                {
                    // Log your error
                }
            }

        }

    }

The Utils.cs file looks as follows

public static class Utils
    {

        public static string LoadSettingsFromFile(string environmentName)
        {
            var executableLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            // We need to go back up one level as the appseetings.Release.json file is not put in the bin directory
            var actualPathToConfig = Path.Combine(executableLocation, $"..\appsettings.{environmentName}.json");
            using (StreamReader reader = new StreamReader(actualPathToConfig))
            {
                return reader.ReadToEnd();
            }
        }

        public static IConfiguration GetSettingsFromReleaseFile()
        {
            var json = Utils.LoadSettingsFromFile("Release");
            var memoryFileProvider = new InMemoryFileProvider(json);

            var config = new ConfigurationBuilder()
                .AddJsonFile(memoryFileProvider, "appsettings.json", false, false)
                .Build();
            return config;
        }

    }

The appsettings.Release.json is set as Content and Copy Always in visual studio. It looks like this

{
  "RetryCount": "#{WagonStatusAndLiabilityRetryCount}",
  "RetryWaitInSeconds": "#{WagonStatusAndLiabilityRetryWaitInSeconds}",
  "DefaultConnection": "#{YourConnectionString}"
}

Actually I believe you could have an appsettings.config file there already and skip the appsettings.Release.json file, but this is working and you can do what you want with it now.

Thursday, July 29, 2021
 
saad
 
5

Does this approach work for you ?

MessagingFactory factory = MessagingFactory.CreateFromConnectionString(cnxString);
var deadLetterPath = SubscriptionClient.FormatDeadLetterPath(topicPath,subName);
var dlqReceiver = factory.CreateMessageReceiver(deadLetterPath, ReceiveMode.ReceiveAndDelete);

I haven't test it out here (in a meeting), but give it a try

cheers

Friday, July 30, 2021
 
1

If you want to put a message away for a while and you have a place to write down the SequenceNumber (which might be in Session state in a sessionful queue), you can Defer() it. Deferred messages can be retrieved using a special Receive overload giving the SequenceNumber; that's also the only way to get at them again other than them expiring, so careful. This feature was built for workflows and state machines so that they can deal with out of order message arrival.

Tuesday, August 3, 2021
 
Zizoo
 
2

This usually means that there is some bad configuration in your function app which is causing the host to fail to start up. Things like having an invalid host.json or an invalid proxies.json file are a possible cause.

To diagnose, it's best to look at the function host logs. If you open your function app in the Azure portal and turn on log streaming, you should be able to get more information about what's going on.

If you're not able to find anything, please let us know the name of your function app either publicly or privately and we can help take a look at the logs for you.

Thursday, August 5, 2021
 
Daveel
 
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share