{"id":85,"date":"2020-03-31T13:19:09","date_gmt":"2020-03-31T20:19:09","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sdk\/?p=85"},"modified":"2021-05-11T13:11:54","modified_gmt":"2021-05-11T20:11:54","slug":"best-practices-for-using-azure-sdk-with-asp-net-core","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sdk\/best-practices-for-using-azure-sdk-with-asp-net-core\/","title":{"rendered":"Best practices for using Azure SDK with ASP.NET Core"},"content":{"rendered":"<p>If you are developing an ASP.NET Core application, you know that there is a common way of structuring your application. There is a central bootstrap class (<code>Startup<\/code>) and a number of classes that fulfill roles in the application, like controllers, view models, and so on. The tooling within Visual Studio makes this very easy to accomplish.<\/p>\n<p>As with the integration of any SDK, when you want to integrate with the Azure SDK, there are good ways and bad ways to structure your code. In this article, I will cover the best practices that you should follow to maximize the scalability, performance, and security of your applications when using the Azure SDK in an ASP.NET Core application.<\/p>\n<p>The advice comes down to three best practices:<\/p>\n<ol>\n<li>Centrally configure services during app startup.<\/li>\n<li>Store your configuration separately from code.<\/li>\n<li>Use the <a href=\"https:\/\/devblogs.microsoft.com\/azure-sdk\/authentication-and-the-azure-sdk\/\"><code>DefaultAzureCredential<\/code><\/a>.<\/li>\n<\/ol>\n<p>Let&#8217;s take each of these in turn.<\/p>\n<h2>Centrally configure services during app startup<\/h2>\n<p>Every ASP.NET Core application starts by booting up the application using the instructions provided in the <code>Startup<\/code> class. This includes a <code>ConfigureServices()<\/code> method that is an ideal place to configure the Azure service clients. You can then consume these Azure service clients wherever you need to by using <em>Dependency Injection<\/em>.<\/p>\n<p>To configure the services, first add the following NuGet packages to your project:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-net\/blob\/master\/sdk\/extensions\/Microsoft.Extensions.Azure\/README.md\">Microsoft.Extensions.Azure<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-net\/blob\/master\/sdk\/identity\/Azure.Identity\/README.md\">Azure.Identity<\/a><\/li>\n<li>The <code>Azure.*<\/code> package for the Azure service client you wish to add.<\/li>\n<\/ul>\n<p>For example, let&#8217;s say I want to use Key Vault secrets and Blob Storage, I could do the following:<\/p>\n<pre><code>$&gt; dotnet add package Microsoft.Extensions.Azure\n$&gt; dotnet add package Azure.Identity\n$&gt; dotnet add package Azure.Security.KeyVault.Secrets\n$&gt; dotnet add package Azure.Storage.Blobs\n<\/code><\/pre>\n<p>Now I can update the <code>ConfigureServices()<\/code> method to register a service client for each service.<\/p>\n<pre><code>public void ConfigureServices(IServiceCollection services)\n{\n  services.AddAzureClients(builder =&gt;\n  {\n    \/\/ Add a KeyVault client\n    builder.AddSecretClient(keyVaultUrl);\n\n    \/\/ Add a storage account client\n    builder.AddBlobServiceClient(storageUrl);\n\n    \/\/ Use the environment credential by default\n    builder.UseCredential(new EnvironmentCredential());\n  });\n\n  services.AddControllers();\n}\n<\/code><\/pre>\n<p>In this example, you would need to explicitly specify the <code>keyVaultUrl<\/code> and <code>storageUrl<\/code> Both variables are <code>Uri<\/code> types. In addition, you would need to set up a service principal, then configure environment variables to let the application know what service principal to use. This is done by specifying the <code>AZURE_TENANT_ID<\/code>, <code>AZURE_CLIENT_ID<\/code>, and <code>AZURE_CLIENT_SECRET<\/code> environment variables.<\/p>\n<p>With the services configured in <code>Startup<\/code>, I can now use dependency injection to use the clients. For example, I&#8217;ve got a Web API controller class that uses the blob storage client:<\/p>\n<pre><code>[ApiController]\n[Route(\"[controller]\")]\npublic class MyApiController : ControllerBase\n{\n  private readonly BlobServiceClient blobServiceClient;\n\n  public MyApiController(BlobServiceClient blobServiceClient)\n  {\n    this.blobServiceClient = blobServiceClient;\n  }\n\n  \/\/\/ Get a list of all the blobs in the demo container\n  [HttpGet]\n  public async Task&lt;IEnumerable&lt;string&gt;&gt; Get()\n  {\n    var containerClient = this.blobServiceClient.GetBlobContainerClient(\"demo\");\n    var results = new List&lt;string&gt;();\n    await foreach (BlobItem blob in containerClient.GetBlobsAsync()) \n    {\n      results.Add(blob.Name);\n    }\n    return results.ToArray();\n  }\n}\n<\/code><\/pre>\n<h2>Store your configuration separately from code<\/h2>\n<p>My first attempt at <code>ConfigureServices()<\/code> has embedded <code>Uri<\/code> objects. This is a problem because I may want to run against different environments in development vs. production. The ASP.NET Core team suggests <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/configuration\/?view=aspnetcore-3.1#default-configuration\">storing such configurations in environment dependent JSON files<\/a>. Thus, I might have an <code>appSettings.Development.json<\/code> file with one set of settings, and an <code>appSettings.Production.json<\/code> with another set of configurations. The format of the file is:<\/p>\n<pre><code>{\n  \"AzureDefaults\": {\n    \"Diagnostics\": {\n      \"IsTelemetryDisabled\": false,\n      \"IsLoggingContentEnabled\": true\n    },\n    \"Retry\": {\n      \"MaxRetries\": 3,\n      \"Mode\": \"Exponential\"\n    }\n  },\n  \"KeyVault\": {\n    \"VaultUri\": \"https:\/\/mykeyvault.vault.azure.net\"\n  },\n  \"Storage\": {\n    \"ServiceUri\": \"https:\/\/mydemoaccount.storage.windows.net\"\n  }\n}\n<\/code><\/pre>\n<p>You can add any options from the <a href=\"https:\/\/github.com\/Azure\/azure-sdk-for-net\/blob\/master\/sdk\/core\/Azure.Core\/src\/ClientOptions.cs\">ClientOptions<\/a> into the <code>AzureDefaults<\/code> section.<\/p>\n<p>The <code>Configuration<\/code> object is injected from the host, and stored inside the <code>Startup<\/code> constructor. I can now modify the <code>ConfigureServices()<\/code> method to use this configuration:<\/p>\n<pre><code>public void ConfigureServices(IServiceCollection services)\n{\n  services.AddAzureClients(builder =&gt;\n  {\n    \/\/ Add a KeyVault client\n    builder.AddSecretClient(Configuration.GetSection(\"KeyVault\"));\n\n    \/\/ Add a storage account client\n    builder.AddBlobServiceClient(Configuration.GetSection(\"Storage\"));\n\n    \/\/ Use the environment credential by default\n    builder.UseCredential(new EnvironmentCredential());\n\n    \/\/ Set up any default settings\n    builder.ConfigureDefaults(Configuration.GetSection(\"AzureDefaults\"));\n  });\n\n  services.AddControllers();\n}\n<\/code><\/pre>\n<p>This is a step towards the correct usage. However, I still have to specify the credential explicitly.<\/p>\n<h2>Use DefaultAzureCredential<\/h2>\n<p>Fixing the credentials is probably the easiest part of this process. Use the <code>DefaultAzureCredential<\/code> object for the credential handling. The <code>DefaultAzureCredential<\/code> chooses the best authentication mechanism based on your environment, allowing you to move your app seamlessly from development to production with no code changes.<\/p>\n<p>To enable it, just swap out the <code>EnvironmentCredential<\/code> with <code>DefaultAzureCredential<\/code>.<\/p>\n<p>Here is the final <code>ConfigureServices()<\/code> method:<\/p>\n<pre><code>public void ConfigureServices(IServiceCollection services)\n{\n  services.AddAzureClients(builder =&gt;\n  {\n    \/\/ Add a KeyVault client\n    builder.AddSecretClient(Configuration.GetSection(\"KeyVault\"));\n\n    \/\/ Add a storage account client\n    builder.AddBlobServiceClient(Configuration.GetSection(\"Storage\"));\n\n    \/\/ Use the environment credential by default\n    builder.UseCredential(new DefaultAzureCredential());\n\n    \/\/ Set up any default settings\n    builder.ConfigureDefaults(Configuration.GetSection(\"AzureDefaults\"));\n  });\n\n  services.AddControllers();\n}\n<\/code><\/pre>\n<p>The <code>DefaultAzureCredential<\/code> checks several methods of authenticating your service. First, it checks to see if you have the environment variables set. If you have explicitly provided credentials in this manner, they are used. Next, it checks to see if you have set up a managed identity. The mechanism for doing this varies by hosting platform. For virtual machines and Azure App Services, for example, there is a managed identity section in the portal. You can also configure the managed identity using your favorite command line tool (Azure CLI, PowerShell, Azure Resource Manager, Terraform, etc.). You must ensure you have provided the managed service principal with permissions to access the resources you are trying to use. For more information on using managed identities, check <a href=\"https:\/\/docs.microsoft.com\/azure\/active-directory\/managed-identities-azure-resources\/overview\">the documentation<\/a>.<\/p>\n<h2>More settings<\/h2>\n<p>With this basic setup, you can do much more:<\/p>\n<ul>\n<li>Provide multiple service clients with different names.<\/li>\n<li>Configure global settings, like the retry settings.<\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/azure\/azure-monitor\/learn\/dotnetcore-quick-start\">Send your logs to Azure Monitor<\/a>.<\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/azure\/azure-app-configuration\/quickstart-aspnet-core-app?tabs=core2x\">Store your configuration within App Configuration<\/a>.<\/li>\n<\/ul>\n<p>Let&#8217;s take a look at a couple of these.<\/p>\n<h3>Configure multiple service clients with different names<\/h3>\n<p>Let&#8217;s say you have two storage accounts &#8211; one for private information and one for public information. Your application transfers data from the public to private storage account after some operation. You need to have two storage service clients. To set this up, in <code>ConfigureServices()<\/code>:<\/p>\n<pre><code>public void ConfigureServices(IServiceCollection services)\n{\n  services.AddAzureClients(builder =&gt;\n  {\n    builder.AddBlobServiceClient(Configuration.GetSection(\"PublicStorage\"));\n    builder.AddBlobServiceClient(Configuration.GetSection(\"PrivateStorage\"))\n      .WithName(\"PrivateStorage\");\n  });\n}\n<\/code><\/pre>\n<p>In your controllers, you can access the named service clients using the <code>IAzureClientFactory<\/code>:<\/p>\n<pre><code>public class HomeControllers : Controller\n{\n  private BlobServiceClient publicStorage, privateStorage;\n\n  public HomeController(BlobServiceClient defaultClient, IAzureClientFactory&lt;BlobServiceClient&gt; clientFactory)\n  {\n    this.publicStorage = defaultClient;\n    this.privateStorage = clientFactory.GetClient(\"PrivateStorage\");\n  }\n}\n<\/code><\/pre>\n<p>The un-named service client is still available in the same way as before. Named clients are additive to this.<\/p>\n<h3>Configure a new retry policy<\/h3>\n<p>At some point, you will want to change the default settings for a service client. You may want different retry settings, or to use a different service API version, for example. You can set the retry settings globally or on a per service basis. Let&#8217;s say you have added the following to your <code>appSettings.json<\/code> file:<\/p>\n<pre><code>{\n  \"AzureDefaults\": {\n    \"Retry\": {\n      \"maxTries\": 3\n    }\n  },\n  \"KeyVault\": {\n    \"VaultUri\": \"https:\/\/mykeyvault.vault.azure.net\"\n  },\n  \"Storage\": {\n    \"ServiceUri\": \"https:\/\/store1.storage.windows.net\"\n  },\n  \"CustomStorage\": {\n    \"ServiceUri\": \"https:\/\/store2.storage.windows.net\"\n  }\n}\n<\/code><\/pre>\n<p>You could then write something like the following:<\/p>\n<pre><code>public void ConfigureServices(IServiceCollection services)\n{\n  services.AddAzureClients(builder =&gt;\n  {\n    \/\/ Establish the global defaults\n    builder.ConfigureDefaults(Configuration.GetSection(\"AzureDefaults\"));\n    builder.UseCredential(new DefaultAzureCredential());\n\n    \/\/ A Key Vault Secrets client using the global defaults\n    builder.AddSecretClient(Configuration.GetSection(\"KeyVault\"));\n\n    \/\/ A Storage client with a custom retry policy\n    builder.AddBlobServiceClient(Configuration.GetSection(\"Storage\"))\n      .ConfigureOptions(options =&gt; options.Retry.MaxRetries = 10);\n\n    \/\/ A named storage client with a different custom retry policy\n    builder.AddBlobServiceClient(Configuration.GetSection(\"CustomStorage\"))\n      .WithName(\"CustomStorage\")\n      .ConfigureOptions(options =&gt; {\n        options.Retry.Mode = Azure.Core.RetryMode.Exponential;\n        options.Retry.MaxRetries = 5;\n        options.Retry.MaxDelay = TimeSpan.FromSections(120);\n      });\n  });\n}\n<\/code><\/pre>\n<p>You can also place policy overrides in the <code>appSettings.json<\/code> file:<\/p>\n<pre><code>{\n  \"KeyVault\": {\n    \"VaultUri\": \"https:\/\/mykeyvault.vault.azure.net\",\n    \"Retry\": {\n      \"maxRetries\": 10\n    }\n  }\n}\n<\/code><\/pre>\n<h2>Want to hear more?<\/h2>\n<p>Follow us on <a href=\"https:\/\/twitter.com\/AzureSDK\">Twitter at @AzureSDK<\/a>. We&#8217;ll be covering more best practices in cloud-native development as well as providing updates on our progress in developing the next generation of Azure SDKs.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you are developing an ASP.NET Core application, you know that there is a common way of structuring your application.   The tooling within Visual Studio makes this very easy to accomplish.  Similarly, when integrating the AZURE SDK, there are good and bad ways to structure your code.  This article covers the best practices.<\/p>\n","protected":false},"author":31981,"featured_media":99,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[236,161],"class_list":["post-85","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-sdk","tag-asp-net-core","tag-dotnet"],"acf":[],"blog_post_summary":"<p>If you are developing an ASP.NET Core application, you know that there is a common way of structuring your application.   The tooling within Visual Studio makes this very easy to accomplish.  Similarly, when integrating the AZURE SDK, there are good and bad ways to structure your code.  This article covers the best practices.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/85","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/users\/31981"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/comments?post=85"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/85\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media\/99"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media?parent=85"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/categories?post=85"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/tags?post=85"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}