Building once and running on multiple environments

After building your applications, you have to think about deploying them to different environments. As you have already seen in the previous section on configuration, you can use configuration files to change the configuration of your services and even your application.

In the case of multiple environments, you have to duplicate the appsettings.json file for each environment and name it accordingly: appsettings.{EnvironmentName}.json.

ASP.NET Core 3 will automatically retrieve configuration settings in hierarchical order, first from the common appsettings.json file and then from the corresponding appsettings.{EnvironmentName}.json file, while adding or replacing values if necessary.

However, developing conditional code that uses different components based on different deployment environments and configurations seems to be complicated at first. In traditional applications, you must create a lot of code to handle all of the different operations by yourself and then maintain it.

In ASP.NET Core 3, you have a vast number of internal functionalities at your disposal to achieve this goal. You can then simply use environment variables (development, staging, production, and more) to indicate a specific runtime environment, thus configuring your application for that environment. 

As you will see during this section, you can use specific method names and even class names to use existing injection and override mechanisms, provided by ASP.NET Core 3 out of the box, to configure your applications.

In the following example, we are adding an environment-specific component (SendGrid) to the application, which only has to be used if the application is deployed to a specific production environment (Azure):

  1. Add the Sendgrid NuGet package to the project. This will be used for future Azure production deployments of the Tic-Tac-Toe application:

  1. Add a new service called SendGridEmailService within the Services folder. This will be used to send emails via SendGrid. Have it inherit the IEmailService interface and implement the specific SendEmail method. Firstly, the constructor:
        public class SendGridEmailService : IEmailService 
        { 
          private EmailServiceOptions _emailServiceOptions; 
          private ILogger<EmailService> _logger; 
          public SendGridEmailService(IOptions<EmailServiceOptions>
emailServiceOptions, ILogger<EmailService> logger) { _emailServiceOptions = emailServiceOptions.Value; _logger = logger; } //....
}
  1. And then add a SendMail method to the same SendGridEmailService class:
public Task SendEmail(string emailTo, string subject, string 
message) { _logger.LogInformation($"##Start## Sending email via
SendGrid to :{emailTo} subject:{subject} message:
{message}"); var client = new SendGrid.SendGridClient(_emailServiceOptions.
RemoteServerAPI); var sendGridMessage = new SendGrid.Helpers.Mail.SendGridMessage { From = new SendGrid.Helpers.Mail.EmailAddress(
_emailServiceOptions.UserId) }; sendGridMessage.AddTo(emailTo); sendGridMessage.Subject = subject; sendGridMessage.HtmlContent = message; client.SendEmailAsync(sendGridMessage); return Task.CompletedTask; }
  1. Add a new extension method to more easily declare specific email services for specific environments. For that, go to the Extensions folder and add a new EmailServiceExtension class:
        public static class EmailServiceExtension 
        { 
          public static IServiceCollection AddEmailService(
this IServiceCollection services, IHostingEnvironment
hostingEnvironment, IConfiguration configuration) { services.Configure<EmailServiceOptions>
(configuration.GetSection("Email")); if (hostingEnvironment.IsDevelopment() ||
hostingEnvironment.IsStaging()) { services.AddSingleton<IEmailService, EmailService>(); } else { services.AddSingleton<IEmailService,
SendGridEmailService>(); } return services; } }
  1. Update the Startup class to use the created assets. For better readability and maintainability, we will go even further and create a dedicated ConfigureServices method for each environment we have to support, remove the existing ConfigureServices method, and add the following environment-specific ConfigureServices methods. First, we configure the definitions and constructor:
public IConfiguration _configuration { get; }
public IHostingEnvironment _hostingEnvironment { get; }
public Startup(IConfiguration configuration,
IHostingEnvironment hostingEnvironment)
{
_configuration = configuration;
_hostingEnvironment = hostingEnvironment;
}

Secondly, we configure common services: 

public void ConfigureCommonServices(IServiceCollection services) 
{ 
  services.AddLocalization(options => options.ResourcesPath = 
"Localization"); services.AddMvc().AddViewLocalization(
LanguageViewLocationExpanderFormat.Suffix, options =>
options.ResourcesPath = "Localization").AddDataAnnotationsLocalization(); services.AddSingleton<IUserService, UserService>(); services.AddSingleton<IGameInvitationService,
GameInvitationService>(); services.Configure<EmailServiceOptions>
(_configuration.GetSection("Email")); services.AddEmailService(_hostingEnvironment, _configuration); services.AddRouting(); services.AddSession(o => { o.IdleTimeout = TimeSpan.FromMinutes(30); }); }

And finally, we configure a few specific services:

 
        public void ConfigureDevelopmentServices(
IServiceCollection services) { ConfigureCommonServices(services); } public void ConfigureStagingServices(
IServiceCollection services) { ConfigureCommonServices(services); } public void ConfigureProductionServices(
IServiceCollection services) { ConfigureCommonServices(services); }
Note that you could also apply the same approach to the Configure method in the Startup class. For that, you just remove the existing Configure method and add new methods for the environments you would like to support, such as ConfigureDevelopment, ConfigureStaging, and ConfigureProduction. The best practice would be to combine all common code into a ConfigureCommon method and call it from the other methods, as shown here for specific ConfigureServices methods.
  1. Start the application by pressing F5 and verify that everything is still running correctly. You should see that the added methods will automatically be used and that the application is fully functional.

That was easy and straightforward! No specific conditional code for the environments, nothing complicated to evolve and to maintain; just very clear and easy-to-understand methods that contain the environment name they have been developed for. This constitutes a very clean solution to the problem of building once and running on multiple environments.

But that is not all! What if we told you that you do not need to have a single Startup class? What if you could have a dedicated Startup class for each environment with only the code applicable to its context? That would be great, right? Well, that is exactly what ASP.NET Core 3 provides.

To be able to use dedicated Startup classes for each environment, you just have to update the Program class, the main entry point for ASP.NET Core 3 applications. You change a single line in the BuildWebHost method to pass the assembly name .UseStartup("TicTacToe") instead of .UseStartup<Startup>(), and then you can use this fantastic feature:

    public static IWebHost BuildWebHost(string[] args) => 
      WebHost.CreateDefaultBuilder(args) 
        .CaptureStartupErrors(true) 
        .UseStartup("TicTacToe") 
        .PreferHostingUrls(true) 
        .UseUrls("http://localhost:5000") 
        .UseApplicationInsights() 
        .Build(); 
      } 
    }

Now, you can add dedicated Startup classes for different environments, such as StartupDevelopment, StartupStaging, and StartupProduction. As with the method approach before, they will be used automatically; nothing else needs to be done on your side. Just update the Program class, implement your environment-specific Startup classes, and it works. ASP.NET Core 3 really makes our lives much easier by providing these useful features.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.82.21