We as software developers have created several disciplines for coding: test-driven developments, clean code principles, and screaming architecture, to name a few. However, we often find our web.config files can sprawl out of control. We just don’t pay as much attention to our configuration as we do to our classes and methods. The good news is that many mistakes are easily avoided, and with a little discipline we can put our configs on par with the rest of our codebase.
In this article, I’ll show you how. We’ll look at five of the most common configuration mistakes and how to avoid them.
1. Pretending Constants Are Configuration
Often, we put constants we could hardcode in our application into our configs “just in case.” We have such an obsession with reusability and future-proofing that we’re inclined to make life harder on ourselves on the off chance something changes. I call YAGNI on this.
We deal in code. That’s our language strength. We get a compiler (usually). We get Typeahead. In a web.config, we get some with weaker IDE help that ultimately breaks at runtime. Additionally, many times the more extensible we attempt to make something, the harder it becomes to maintain. I’ve so often seen configs like this:
<add key="work_days" value="Mon,Tues,Wed,Thurs,Fri" />
Do you really need to configure this in a config, instead of an enum? Is this really going to change? Probably not, and if it does you can change the code in one place and move on with your work. It can get even worse when it evolves:
<add key="work.days.names" value="Mon,Tues,Wed,Thurs,Fri" /> <add key="work.days.descriptions" value="Monday,Tuesday, Wednesday, Thursday, Friday" />
At this point, this is more easily defined as a data structure inside your application. It’s clearly asking to be a Work struct with a Name and Description. (I’ve seen even worse examples, but I hope you get the point.) The problem is, the configuration was designed for environment-specific values, like database connection info and URLs to external services.
We can avoid this by just hardcoding these property values as constants. If you see a config, ask yourself: Does this differ across environments? If not, consider moving it to an application-wide constant. I still advise using dependency injection in your components where appropriate as opposed to directly referencing them. Let your composition root reference the constant.
It may also be a configuration that a third-party library needs. I’d still check if this configuration varies across environments. Often, these libraries have a code version of the configuration you can use instead.
2. Organizing by Technical Facets Instead of Features
As developers, we have the tendency to keep our minds in the technical. We want to think in terms of components, layers, and tools. However, we’re often paid to solve business problems. When we align our code around its business needs, we have a screaming architecture that saves us a lot of pain. It also makes it easy to onboard onto new parts of the system.
The same guidance applies to our configurations. All too often I see this code smell:
<add key="services.endpoints.admin" value="http://admin.site.com" /> <add key="services.endpoints.twitter" value="http://twitter.com" /> <add key="services.tags.twitter value="software" /> <add key="services.impersonation.admin" value="true" />
Note that the top-level information is about the technical nature of the endpoints: They’re service endpoints.
Instead, I prefer a setup like this:
<add key="admin.endpoint" value="http://admin.site.com" /> <add key="admin.can_impersonate" value="true" /> <add key="twitter.endpoint" value="http://twitter.com" /> <add key="twitter.tags" value="software" />
Everything is grouped by its purpose in the application, letting you easily see what’s needed in an environment to achieve a feature. This will also let you split more easily if you need to modularize.
3. Leaving Compilation Debug to “True”
For many years, it’s been commonplace for a .NET project to start with a web.config that includes this:
This little config is useful for being able to see what’s going on while you’re testing an app and building new features. However, it’s easy to forget to turn this off in higher environments. This can lead to a myriad of problems in your application:
- Compiling web pages takes longer.
- You can run out of memory.
- It overrides your request timeouts, which can cause your application to hang.
- It can hog your memory.
There are more issues you can read about here. You can avoid these problems by setting up your prod and certain preproduction config files ahead of time with debug=”false.”
4. Plainly Storing Sensitive Information
It’s very common, especially in the early stages of a project, to store our credentials and other sensitive information in our configs in plaintext. This is a large security risk. Forget someone getting access to your config file while it’s running. Often, we have our code flying around left and right, which lets people look through it. We may open it up to other members of the company. We may even show off some bits at a demo. In all these scenarios, we risk exposing our credentials and other sensitive keys.
Securing your credentials and secrets may be difficult at times, but it will prevent all this pain. Here are a couple of methods.
First, we can encrypt sensitive data. It’s a bit of an effort, but we can use the ASP.NET IIS Registration tool (aspnet_regiis.exe) to encrypt sections of our web.config file. Here’s a detailed guide on how to do that.
Another method is to store your sensitive information in an external config file that’s persisted outside your source control system and loaded in at deployment time. Configuring it looks like this:
<appSettings file="secrets.config" > <add key="safe_config" value="plaintext" /> </appSettings>
This way, you can keep your credentials in a more secure place and your harmless configs in the main file. The app won’t know the difference at runtime.
5. Trying to Use One in .NET Core
Alas, the advent of .NET Core no longer uses web.config files. So don’t bother adding one to your project without some extra elbow grease. However, it’s been replaced with a slew of other, more robust options:
- Azure Key Vault
- Command-line arguments
- Custom providers (installed or created)
- Directory files
- Environment variables
- In-memory .NET objects
- Settings files
This article offers more details. If for some reason none of these satisfy your needs, you can also make your own custom configuration provider with the prevalence of containerization. My preference is environment variables, but your needs may be different.
Configure It Right
It may take some discipline to follow these guidelines and ensure your configuration is healthy. We often neglect our configuration files, focusing only on our code. But they’re important, and neglecting them will only cause you pain. With just a little discipline, you can avoid this pain and focus on the fun parts of your job. You can even automate some of these rules to make them easy to follow.