In previous two articles, we have seen how to set the environment variables and how the environment variables play role in config transformations. In this article, let’s have a look at how environment specific startup classes can be used in .NET web application.
Why ?
Every software application goes through various stages before reaching the production. The software that you are developing is on development environment during development, it might be on staging / test environments (one or more) during its testing and then it is deployed to production environment, where the real intended users can access the application.
The software might be interacting with external applications or services. The word external does not necessarily mean applications or services from outside the organization which owns the software. It may sometimes means other software components owned by same organization.
For example, a B2C application might have a phone app too, both of the UI apps interacting with Web APIs. And every component being developed by separate teams.
So, some of these services might not be available during development or testing. Or sometimes they are not accessible, or have limited access because of various reasons.
Also, some technical requirements might not be required on certain environments. e.g. generally, you may not want to enable DDos protection on development environment.
OR, some services like payment gateways, or government identity services might not be available for dev and test environments (or if they are available, they might have limited availability due to cost associated with it.).
So, in other words, the request processing pipeline setup might be different on different environments. Also, some environments might use stubs or mocks in place of calling actual services.
The startup.cs
is the file where you can configure request processing pipeline and other services required by your .NET web application.
Simplest Way
If the application startup for various environments does not differ largely and there are some minor things here and there, then the simplest method is to add conditions in the startup as shown in below code.
For this approach, you need to inject IWebHostEnvironment into the Startup
constructor. This class will expose methods to IsEnvironment(IHostEnvironment, String), along with other 3 methods to check the current environment name
Also, have a look at UseStartup overload, which in this case, uses generic type, as parameter.
For ex. Let’s have a look at the below code. It configures the exception handler for the MVC application, ensuring that server exception details are shown only on development environment. On all other environments, a custom error page would be shown. This is achieved by simple IF condition.
Similarly, you can use conditions to register dependency injection for interfaces to ensure that stubs are loaded for external services if the application is running on development environment.
Probable Issue
The issue with above mentioned approach is very obvious.
If the variations in the startups on every environments are very huge, then soon your startup logic will contain a lot of conditions, hard to read/perceive and hard to maintain too in many cases.
Solution – Environment Specific Startups
In order to reduce cyclomatic complexity (which increases due to multiple and/or nested IF statements), you can consider having environment specific startup file.
Before deciding, you must check two things
- How many IF…ELSE flows would be avoided.
- How to organize common startup code which is required for all environments
Some code duplication might happen if you choose to have environment specific startups classes. For ex, every file will have middleware to serve static files or handle exceptions lets say. Therefore, you should carefully design how this code should be organized to avoid redundancy and to keep the solution maintainable.
How does it work ?
There is a clue in the title, did you guess it ? Obviously, using ASPNETCORE_ENVIRONMENT
variable.
The .NET application uses Startup
class for bootstrapping the application. The application can define one class for all environments or multiple Startup classes for different environments.
The runtime uses below logic to select appropriate Startup class:
- If the class with name
Startup{EnvironmentName}
is present, then it is used for startup. - If there is no startup class with current environment name as Suffix, then the regular
Startup
class is used by runtime to bootstrap the solution.
Also, here, in below sample, we are using UseStartup overload which accepts assemblyName
parameter. This is the FullName of assembly, which contains all the Startup classes.
As said earlier, this is preferable only if the bootstrapping logic is completely different for some environments from other environments. Typically you may not need this but just in case you are worrying about a lot of conditional logic on startup, this may be a way to go.
Frankly speaking, I have not seen (YET) a real world application which needs this feature. Have you used this feature in any application ? OR are you planning to use it in near future ? Let me know your thoughts.
Pingback: How To Create Environment Specific Startup Methods In .NET Applications – The Code Blogger