Accessing App Service Resources from Web Job (Console App)

David Warwick 121 Reputation points
2022-04-02T18:53:51.883+00:00

I have an app service which is running a web app that is hosted on Azure. From the app code, I use the Azure Management API to create custom domains and certificates for those domains.

It takes time for the certificate to be created. It can take about 5 minutes.

I am trying to create a Web Job that can use the management API to check to see if the certificate exists, and if it does exist, bind the certificate to the domain. My code to check for certificate existence and bind the certificate works fine when running it from the app in visual studio, but it does not work well when deployed to Azure and running it from Azure. I should say that spawning a thread and running the check periodically does not work well. It seems like running a web job is the perfect solution.

So I created a console app and I want to be able to access the SQL Server instance that is currently used by the app service, and I want to run the Azure Management API against my app service. But it seems like I do not have permissions set correctly to be able to execute the Azure Management API from my console app against my app service.

public  static async Task<bool> DoesCertificateExistAsync(string sHostName, IConfiguration configuration)
{
    string _ClientId = configuration.GetValue<string>("Azure:ClientId");
    string _ClientKey = configuration.GetValue<string>("Azure:ClientSecret");
    string _TenantId = configuration.GetValue<string>("Azure:TenantId");
    string _SubscriptionId = configuration.GetValue<string>("Azure:SubscriptionId");
    string _ResourceGroupName = configuration.GetValue<string>("Azure:ResourceGroupName");
    string _AlternateResourceGroupName = configuration.GetValue<string>("Azure:AlternateResourceGroupName");
    string _AppName = configuration.GetValue<string>("Azure:AppName");
    string _AppServicePlanName = configuration.GetValue<string>("Azure:AppServicePlanName");
    Uri _baseURI = new Uri($"https://management.azure.com/");

    HttpResponseMessage response = null;
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Add("Authorization", "Bearer " + GetAccessToken(configuration));
        string requestURl = _baseURI + $"subscriptions/{_SubscriptionId}/providers/Microsoft.Web/certificates?api-version=2021-02-01";

        response = await client.GetAsync(requestURl);
    }
    var result = response.Content.ReadAsStringAsync().Result;
    CertificateExistResponse obj = JsonConvert.DeserializeObject<CertificateExistResponse>(result);

    return obj.value.Exists(x => x.name == sHostName);
}

When I execute the line of code, response = await client.GetAsync(requestURl); my console app immediately crashes and I can't even check the error message. Like I said, this runs fine if I run it from my ASP.Net project which is associated with my app service.

What do I need to do to associate my console app so that I can execute the Azure Management API from the console app to my app service?

Azure App Service
Azure App Service
Azure App Service is a service used to create and deploy scalable, mission-critical web apps.
4,953 questions
{count} votes

1 answer

Sort by: Most helpful
  1. David Warwick 121 Reputation points
    2022-04-03T20:47:41.707+00:00

    I figured out the issue. It had nothing to do with permissions. The code was not waiting for the async task to complete from the Main method. I'm posting the full code here. Notice the Wait method in the Main method.

        using BindCertificateJobConsoleApp.Models;
        using Microsoft.Extensions.Configuration;
        using Microsoft.IdentityModel.Clients.ActiveDirectory;
        using Newtonsoft.Json;
        using System;
        using System.Data.SqlClient;
        using System.IO;
        using System.Net.Http;
        using System.Text;
        using System.Threading.Tasks;
    
        namespace BindCertificateJobConsoleApp
        {
            class Program
            {
                public static void Main(string[] args)
                {
                    BindCertificate certificate = new BindCertificate();
                    certificate.Execute().Wait();
                }
            }
    
            public class BindCertificate
            {
                public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
                                .SetBasePath(Directory.GetCurrentDirectory())
                                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                                .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
                                .Build();
    
                Uri _baseURI = new Uri($"https://management.azure.com/");
    
                string _connectionString = Configuration.GetConnectionString("DefaultConnectionString");    
                string _ClientId = Configuration.GetValue<string>("Azure:ClientId");
                string _ClientKey = Configuration.GetValue<string>("Azure:ClientSecret");
                string _TenantId = Configuration.GetValue<string>("Azure:TenantId");    
                string _SubscriptionId = Configuration.GetValue<string>("Azure:SubscriptionId");
                string _ResourceGroupName = Configuration.GetValue<string>("Azure:ResourceGroupName");
                string _AlternateResourceGroupName = Configuration.GetValue<string>("Azure:AlternateResourceGroupName");
                string _AppName = Configuration.GetValue<string>("Azure:AppName");
                string _AppServicePlanName = Configuration.GetValue<string>("Azure:AppServicePlanName");
    
                public async Task Execute()
                {
                    string sSQL = "SELECT * " +
                                    "FROM AspNetUsers " +
                                    "WHERE DomainBound = 1 AND CertificateBound = 0";
    
                    Console.WriteLine("Starting Bind Certificate Job in Console App.");
    
                    try
                    {
                        using (SqlConnection conn = new SqlConnection(_connectionString))
                        {
                            conn.Open();
    
                            Console.WriteLine("Connected to database from job.");
    
                            using (SqlCommand cmd = new SqlCommand(sSQL, conn))
                            {
                                using (SqlDataReader reader = cmd.ExecuteReader())
                                {
                                    Console.WriteLine("Ran query from job.");
    
                                    while (reader.Read())
                                    {
                                        string sDomain = reader["Domain"].ToString();
    
                                        Console.WriteLine($"Processing domain {sDomain} from job.");
    
                                        if (await DoesCertificateExistAsync(sDomain, Configuration))
                                        {
                                            Console.WriteLine($"Certificate exists for domain {sDomain} from job.");
    
                                            int iNumRows = await UpdateDatabaseAsync($"UPDATE AspNetUsers SET CertificateExists = 1 WHERE Domain = '{sDomain}'");
    
    
                                            HttpResponseMessage responseMessage = await BindCertificateToCustomDomain(sDomain, Configuration);
    
                                            if (responseMessage.IsSuccessStatusCode)
                                            {
                                                iNumRows = await UpdateDatabaseAsync($"UPDATE AspNetUsers SET CertificateBound = 1 WHERE Domain = '{sDomain}'");
    
                                                Console.WriteLine($"Certificate for {sDomain} successfully bound.");
                                            }
                                            else
                                            {
                                                Console.WriteLine($"Certificate for {sDomain} not successfully bound.");
                                            }
                                        }
                                        else
                                        {
                                            Console.WriteLine($"Certificate does not exist for domain {sDomain} from job.");
                                        }
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Error while trying to bind certificate to hostname: {ex.Message}");
    
                    }
                    Console.WriteLine("Finished Bind Certificate Job in Console App.");
                }
    
                private async Task<int> UpdateDatabaseAsync(string sSQL)
                {
                    int iRows = -1;
                    try
                    {
                        using (SqlConnection conn = new SqlConnection(_connectionString))
                        {
                            conn.Open();
    
                            using (SqlCommand cmd = new SqlCommand(sSQL, conn))
                            {
                                iRows = await cmd.ExecuteNonQueryAsync();
                            }
                        }
                    }
                    catch (Exception ex)
                    {
    
                        Console.WriteLine($"Error while trying to update database with query {sSQL}: {ex.Message}");
                    }
                    return iRows;
                }
    
                private string GetAccessToken()
                {
                    var context = new AuthenticationContext("https://login.windows.net/" + _TenantId);
                    ClientCredential clientCredential = new ClientCredential(_ClientId, _ClientKey);
                    var tokenResponse = context.AcquireTokenAsync(_baseURI.ToString(), clientCredential).Result;
                    return tokenResponse.AccessToken;
                }
    
    
    
                private async Task<HttpResponseMessage> BindCertificateToCustomDomain(string sHostName, IConfiguration configuration)
                {
                    using (var client = new HttpClient())
                    {
                        client.DefaultRequestHeaders.Add("Authorization", "Bearer " + GetAccessToken());
                        string requestURl = _baseURI + $"subscriptions/{_SubscriptionId}/resourceGroups/{_ResourceGroupName}/providers/Microsoft.Web/sites/{_AppName}?api-version=2016-08-01";
                        string serverFarm = $"/subscriptions/{_SubscriptionId}/resourceGroups/{_AlternateResourceGroupName}/providers/Microsoft.Web/serverfarms/{_AppServicePlanName}";
                        string body = $"{{\"location\": \"West US\", \"properties\": {{\"HostNameSslStates\": [ {{ \"SslState\" : \"1\", \"ToUpdate\" : \"True\", \"Name\": \"{sHostName}\"}}]}}, \"kind\": \"app\", \"location\": \"West US\", \"tags\" : {{\"hidden-related:{serverFarm}\": \"empty\"}}}}";
    
                        var stringContent = new StringContent(body, Encoding.UTF8, "application/json");
                        return await client.PutAsync(requestURl, stringContent);
                    }
                }
    
                private async Task<bool> DoesCertificateExistAsync(string sHostName, IConfiguration configuration)
                {
                    HttpResponseMessage response = null;
                    using (var client = new HttpClient())
                    {
                        client.DefaultRequestHeaders.Add("Authorization", "Bearer " + GetAccessToken());
                        string requestURl = _baseURI + $"subscriptions/{_SubscriptionId}/providers/Microsoft.Web/certificates?api-version=2021-02-01";
    
                        try
                        {
                            response = await client.GetAsync(requestURl);
    
                            Console.WriteLine($"Received response checking for existence of certificate: {response.Content.ReadAsStringAsync().Result}");
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine($"Error checking for existence of certificate: {ex.Message}");
                        }
                    }
                    var result = response.Content.ReadAsStringAsync().Result;
                    CertificateExistResponse obj = JsonConvert.DeserializeObject<CertificateExistResponse>(result);
    
                    return obj.value.Exists(x => x.name == sHostName);
                }
            }
        }
    
    0 comments No comments