Service Bus - krzysiek861/AvanadeWorkshop Wiki

Checkout branch

If you wish to start from this step, you can checkout the following branch: git checkout feature/service-bus

Azure Service Bus send message and add a web job

It is a time to add a web job to the project. Right click on WebApp project and add New Azure WebJob Project.

Important! Make sure that newly created project is in the same .Net Framework version. They should be for instance 4.8 like in our example:

It would be better if you add NuGet packages by managing them in the whole solution context, so you are sure, that all packages are at the same version across all projects.

  • Autofac 6.4.0
  • Azure.Messaging.ServiceBus 7.8.1
  • Azure.Security.KeyVault.Secrets 4.3.0
  • Microsoft.Azure.KeyVault.Core 3.0.5
  • Microsoft.Azure.WebJobs.Extensions 4.0.1
  • Microsoft.Azure.WebJobs.Extensions.ServiceBus 5.5.1
  • Microsoft.Extensions.Configuration 6.0.1
  • Microsoft.Extensions.Configuration.EnvironmentVariables 6.0.1
  • Microsoft.Extensions.Configuration.UserSecrets 6.0.1
  • Microsoft.Extensions.Hosting 6.0.1
  • Microsoft.Extensions.Logging 6.0.0
  • Microsoft.Extensions.Logging.Configuration 6.0.0
  • Microsoft.Identity.Client.Extensions.Msal 2.21.0
  • Newtonsoft.Json 13.0.1

Add reference to Avanade.AzureWorkshop.WebApp.

Create a new class IocConfig in the main project directory and add this content:

public class IocConfig
{
    private static IContainer _container;

    public IocConfig()
    {
        var builder = new ContainerBuilder();

        builder.RegisterType<GamesService>();
        builder.RegisterType<PlayersService>();
        builder.RegisterType<TeamsRepository>();
        builder.RegisterType<ImagesService>();
        builder.RegisterType<BinaryFilesRepository>();
        builder.RegisterType<SendgridService>();
        builder.RegisterType<TelemetryService>();

        _container = builder.Build();
    }

    public IContainer GetConfiguredContainer()
    {
        return _container;
    }
}

Create a new class DelayedConfigurationSource in the main project directory and add this content:

public class DelayedConfigurationSource : IConfigurationSource
{
    private IConfigurationProvider Provider { get; } = new DelayedConfigurationProvider();
    public IConfigurationProvider Build(IConfigurationBuilder builder) => Provider;
    public void Set(string key, string value) => Provider.Set(key, value);

    private class DelayedConfigurationProvider : ConfigurationProvider
    {
        public override void Set(string key, string value)
        {
            base.Set(key, value);
            OnReload();
        }
    }
}

Paste the following code to Program.cs file:

internal class Program
{
    public static IContainer Container { get; private set; }

    static async Task Main()
    { 
        var container = new IocConfig();
        Container = container.GetConfiguredContainer();

        var delayedConfigurationSource = new DelayedConfigurationSource();

        var builder = new HostBuilder()
            .ConfigureHostConfiguration(configurationBuilder =>
            {
                configurationBuilder.Add(delayedConfigurationSource);
            });
        builder.UseEnvironment(Environments.Development);
        builder.ConfigureLogging((context, b) =>
        {
            b.AddConsole();
        });
        builder.ConfigureWebJobs(b =>
        {
            b.AddAzureStorageCoreServices();
            b.AddServiceBus();
        });
        var host = builder.Build();
        using (host)
        {
            var manager = new SecretsManager();
            manager.Initialize();

            delayedConfigurationSource.Set("ConnectionStrings:AzureWebJobsServiceBus", GlobalSecrets.ServiceBusConnectionString);
            delayedConfigurationSource.Set("ConnectionStrings:AzureWebJobsDashboard", GlobalSecrets.StorageAccountConnectionString);
            delayedConfigurationSource.Set("ConnectionStrings:AzureWebJobsStorage", GlobalSecrets.StorageAccountConnectionString);

            await host.RunAsync().ConfigureAwait(false);
        }
    }
}

And the following code to Functions.cs file:

public class Functions
{
    private const string SubscriptionName = "webjobssubscription";

    public async Task ProcessGameMessage([ServiceBusTrigger(nameof(GameMessageModel), SubscriptionName, Connection = "AzureWebJobsServiceBus")] GameMessageModel message, TextWriter textWriter)
    {
        await ProcessMessage(textWriter, message, async (scope, model) =>
        {
            var gamesService = scope.Resolve<GamesService>();
            var telemetryService = scope.Resolve<TelemetryService>();
            await gamesService.SaveGameResult(message);
            await WriteMessage(message.CorrelationId, textWriter);
            telemetryService.Log("Succesfully saved game result", message.CorrelationId);
        });
    }

    private static async Task ProcessMessage<TMessage>(TextWriter textWriter, TMessage message, Func<ILifetimeScope, TMessage, Task> action)
        where TMessage : BaseMessageModel
    {
        using (var scope = Program.Container.BeginLifetimeScope())
        {
            await WriteMessage($"Processing topic message {typeof(TMessage).Name}. Body: {JsonConvert.SerializeObject(message)}", textWriter);

            try
            {
                await action(scope, message);
            }
            catch (Exception ex)
            {
                textWriter.WriteLine($"Unexpected error {ex.Message} {ex.StackTrace} {ex.InnerException}");
                throw;
            }

        }
    }

    private static async Task WriteMessage(string message, TextWriter writer)
    {
        await writer.WriteLineAsync(message);
    }
}

Configure application settings

App.config (Topics project)

Copy your KeyVault URI and paste it to the KeyVaultUri app setting.

You should add whole appSettings section.

Check if all compiles and publish the app. At this point you should be able to consume messages.

Now, you can play a game, simply click "play a game" link under group stadings. Open Web Jobs logs to check logs.

At this commit you get a logic that saves played game when the message is processed. Play some games, analyze the code and see which teams advance to the next stage of championships.

Obstacles

Please check that option Always On is set to On in application settings.

⚠️ **GitHub.com Fallback** ⚠️