How to Bootstrap and Create .NET Projects
Create .NET project
To create a .NET project from scratch is as simple as using Visual Studio wizard. Go to File => New Project
, or Add New Project
to an existing solution. Once a new project has been created, you can start coding right away. However, the default project settings produced by wizards are hardly acceptable for professional teams, because they set a too low bar on quality. Moreover, no wizards can know about other setup steps you need to perform in your particular development environment.
In this article, I will walk you through several important settings you should enable as soon as you create a new project, which is important to minimize a future technical debt. Also, we will review some common practices many .NET developers apply when they are structuring solutions and new projects. Even if you are not applying some of these ideas, it is nice to learn and get an overview of what most teams do.
Structure
Having a well-defined structure is vital for complex projects. This improves the on-boarding experience when newcomers are joining a team, and makes your life easier when you are supporting old projects. There are two key indicators of a good structure:
- Using solution and project folders
- Consistent naming
Folders
Solution folders, sometimes referred to as virtual folders, are a very handy instrument to group your projects. In the Solution Explorer view simply right-click and select Add => New Solution Folder
, then drag-and-drop any of the existing projects to this new folder. These folders are not mirrored in the file system, letting you keep physical structure unchanged, so moving the projects from one Solution Folder to another does not move them physically.
Having numbered prefixes is not required, but it is making the folders appear ordered right in the Solution Explorer window.
Visual Studio can work with multiple solutions at the same time by leveraging Partitioned single solution or Multi-solution models. They are rarely used, so I won’t cover them in this article.
Unlike Solution folders, Project folders match the physical folders structure and therefore persist as real folders on the disk. Moreover, Project folders containing a C# code should match project’s namespace. This makes navigation pretty natural. You can even enable a ReSharper rule to warn on such mismatches.
Naming
There are few recommended rules to apply related to naming:
- Use CamelCase.
- A project name should match its output assembly name.
- A project containing automated tests should have the suffix
.Tests
. - All project names should have a common prefix, like
Company.Product
.
There are few reasonable rules as well. You should decide on your own when to apply them based on common sense (and English grammar, of course):
- Use subjects in plural form when a container (project or folder) contains multiple instances of the same kind (e.g.
Tests
orSystem.Collections
). - Use singular form when the whole container contains code all about a single entity (e.g. System.Collections.ObjectModel`).
- For short abbreviations, use upper case like
System.IO
does. - For long abbreviations, use CamelCase like
Modules.Forex.
.
A rule of thumb: a short abbreviation should not be longer than three characters.
Configuring Solution
Configuring a solution is as simple as providing all infrastructure files you need for your environment. Although some of them can be added later (such as CI integration files), few files you would better have in the very beginning.
ReSharper Settings
If you are professional .NET developer, then you very likely use ReSharper. ReSharper is very flexible in managing its settings. As a team leader, you can create and distribute Team Shared settings which will be used by other developers. Team Shared settings are stored in a file with extension .DotSettings
. ReSharper will pick these settings automatically if the file name is matching Visual Studio solution name:
MyCompany.MyProduct.sln
MyCompany.MyProduct.sln.DotSettings
Therefore, you should create this file in the very beginning if you ultimately want to apply some settings to the whole team. A good example would be the rule of using (or not using) var
keyword. Your Team Sharedsettings file can have just this one rule, while others are the developers’ preference. It is worth mentioning that the same way ReSharper settings can be set on a per-project level, because you may have some legacy code that you cannot modify (e.g. change to use var
keyword).
If you named this file correctly, as shown in the example, then any new instance of Visual Studio with a fresh ReSharper setup will be picking this file automatically and enforce the rules. Do not forget to commit this file to the source control.
StyleCop Rules
Same as ReSharper settings, you can share StyleCop settings. If you use ReSharper, then you are probably having integration plugin installed that will leverage StyleCop from ReSharper. However, StyleCop stores its settings independently in files named Settings.StyleCop
. Similarly, you can have this file along with solution file and project files.
If you are using StyleCop, do not forget to run StyleCop configuration tool and disable the checks you do not want to perform. By default, all checks are enabled. Save new settings to this file and commit to the source control.
Text Files
If you are building a public product and are going to publish the source code, do not forget to create and commit these files as well:
README.md
LICENSE
I recommend using markdown format for the README.md
file, because it became an industrial standard and supported by public source control services like GitHub, as well as in-house servers like BitBucket (former Stash).
NuGet Specifications
If you are building a library which is to be distributed on NuGet Gallery, then you very likely need to create package specification files, like MyProject.nuspec
. I prefer creating these files manually and commit them to the source control. Packages are usually released by one of your Continuous Integration (CI for short) job, but also at any time your can build and publish a package manually from the console as follow:
nuget.exe pack MyLibrary.nuspec
Just don’t forget to increment package version before executing this command.
CI specific files
We all use different CI servers, and they all have different configuration scripts and settings. I would just mention some of the common additions you may consider adding:
- NUnit settings, which specify what assemblies contain tests to be executed on CI server for particular jobs. All tests are practically split into a few categories. There are unit tests that should be run on every build, performance tests that are executed nightly, and integration tests are executed on the per-release basis.
- NCover settings, which specify what test assemblies should be analyzed for test coverage.
- SonarQube settings, which determine software metrics will be collected.
- Job scripts, such as NAnt, PowerShell or simply Windows batch files.
Configuring Projects
Project files, namely .csproj
or .vbpro
, contain all settings used by Visual Studio and MSBuild. However, not all of them are available from Project Properties window. To edit these files in Visual Studio manually, you should do the following:
- Right-click on a project in Solution Explorer view.
- Select Unload Project .
- Right-click again to choose action Edit xyz.csproj.
- Complete editing.
- Right-click on the project again, and choose Reload Project.
Alternatively, you can open a project file in your favorite text editor, edit it and save. When you are switching back to Visual Studio window, you will be prompted to reload the changed project.
Warnings Control
Building a high-quality software requires you to never ignore build warnings. Therefore, you should enable the maximum warnings level and treat any warnings as errors. Note that you should do this for all build configurations you have, such as Debug and Release. The best way to do this is to write the following settings to the common property group:
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
And make sure you don’t have the same settings in other property groups. Otherwise, they will override the corresponding properties from the common group.
FxCop
Running FxCop is merely practical to do on every build. Most teams prefer running FxCop from time to time (usually before a release) to make sure there were no severe errors introduced. However, if you want to perform ultimate checking on every build, add this option:
<RunCodeAnalysis>true</RunCodeAnalysis>
Note that FxCop, like StyleCop, has its own settings which can be placed in the root folder and added to the source control. These settings are likely used when running FxCop on CI servers.
Documentation
This part is about XmlDoc. If you are building a public API, then you should create and maintain API documentation. Most developers start with API development (actual coding), and just before a release they enable project setting Build / XML documentation file
. Naturally, after another rebuild a bunch of errors appear, because every missing XmlDoc is resulting in a build error. To avoid this, you should enable mentioned option in the very beginning.
If you are too lazy to write a proper documentation, or you do not like typing too much text, try instruments that automate this process such as GhostDoc.
Code Contracts
Code Contracts is an excellent framework from Microsoft Research, which allows you to express preconditions, postconditions and object invariants in your code for runtime
checking, static analysis, and documentation. I used this in many critical projects, and it helped a lot so I encourage you to give it a try.
If you decide to use Code Contracts, then it is important to enable Contracts at the very beginning, when you have just created a new project. Adding Contracts in the middle of development is possible, but will require changes through many classes, to make Contacts match to each other. So, do not forget to enable all required settings (at least CodeContractsEnableRuntimeChecking
) and make sure these settings appear in the common property group.
StyleCop Enforcement
Previously we talked about StyleCop configuration for development time. However, when your project is built on a CI server, ReSharper has no effect there and therefore we should enable StyleCop validation to run with MSBuild.
This is usually done by manual modification of the project file. You need to unload the project in Visual Studio, edit the project file and then load the project back:
<PropertyGroup>
<!— add this to the common property group (common to Debug/Release/etc) —>
<StyleCopTreatErrorsAsWarnings>false</StyleCopTreatErrorsAsWarnings>
</PropertyGroup>
<!— add this Import in the very bottom —>
<Import Project="$(ProgramFiles)\MSBuild\Microsoft\StyleCop\v4.3\Microsoft.StyleCop.targets">
The setting StyleCopTreatErrorsAsWarnings
will do what it says: it will break your build on any StyleCop rule violation. The import element is required for MSBuild to add StyleCop task to the build chain.
You may have noticed the path to Program Files
. Because developers may have different StyleCop versions installed, some teams prefer keeping a private copy of the same StyleCop installation under source control. In this case, the path will be relative. This also makes CI machines setup easier, as you don’t need to install StyleCop locally.
AssemblyInfo
Every .NET project created by Visual Studio wizard will have AssemblyInfo.cs
file populated automatically (see Properties subfolder) which contains some of the Assembly
attributes, but no wizard can fill all the Assembly
attributes for you. Make sure you have at least these attributes populated:
AssemblyTitle
AssemblyDescription
AssemblyCompany
AssemblyProduct
AssemblyCopyright
AssemblyVersion
This bare minimum is required for any assemblies you are going to distribute. A practical reason behind this is NuGet: If you are using automatic NuGet specification creation from the selected assembly file, this tool will derive needed information from these properties.
You can also populate one more property in the very beginning:
InternalsVisibleTo
This property makes internal classes and interfaces visible to the specified assembly. This is generally used for automated tests you are going to create for your project.
Connection Strings
How to manage connection strings is a very popular question on the Stack Overflow. The problem is how to make connection strings unique for every developer or a CI job, and not to expose connection details while publishing the source code.
In App.config
(for desktop applications) or Web.config
(for web applications), make the following setting which will be loading user.config
file in runtime. Keep this under your source control:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings configSource="user.config"></connectionStrings>
</configuration>
Apparently, user.config
file should be excluded from source control, and every developer should have a local copy of this file, preserving connection string privacy:
<connectionStrings>
<add name="test" connectionString="Server=.;Database=...;"/>
</connectionStrings>
.gitignore
For those who use Git as the source control, it is important to add some file patterns to the .gitignore
file. However, our smart community has already built a generalized file, which can be found here: github.com/github/gitignore/blob/master/VisualStudio.gitignore.
You should take it as a reference .gitignore
file and simply add your custom exclusions you may additionally need.
GitHub Badges
You may have seen nice looking badges appearing on the README
project’s page. If you are publishing your project on the GitHub, consider connecting your project to public services for:
- Building: to show a build is failing or passing.
- Testing: to show test coverage and test execution status.
- Publishing: to show the latest NuGet package version.
A complete list of badges and related services can be found on shields.io. You may find many interesting badges that are good for Open Source projects.
Once you have registered your project with a selected service, you will be given a link to the image and a complete markdown-syntax link, which you can add to your README.md
file. By the way, this is one of the reasons why you should prefer markdown for Readme files.
Sample markdown badges, from the Roslyn project:
[![Build Status]([https://dotnet-ci.cloudapp.net/job/roslyn_master_win_dbg_unit32/badge/icon)](https://dotnet-ci.cloudapp.net/job/roslyn_master_win_dbg_unit32/)](https://dotnet-ci.cloudapp.net/job/roslyn_master_win_dbg_unit32/badge/icon)](https://dotnet-ci.cloudapp.net/job/roslyn_master_win_dbg_unit32/)) [![Join the chat at [https://gitter.im/dotnet/roslyn](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dotnet/roslyn?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)](https://gitter.im/dotnet/roslyn](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dotnet/roslyn?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge))
Automatic Solution Structure Validation
Even though you have set all the settings we discussed in this article, sooner or later one of your developers may change them and commit the changes to the source control. Sometimes this happens by mistake, and often these changes are not caught during the code review. Other than these accidents, we should be watching for the following common errors:
- Bad references: when someone is referencing a local assembly which others maybe do not have, or when someone has deleted a file from the disk, while link to that file remains in
.csproj
file. This will break the build for sure, but it may happen too late once the change is pushed, and others pulled it. This is especially crucial for static web files, which you cannot verify during the build. - Naming consistency: the tools like StyleCop can control C# source code, but no tools can enforce rules for Project files or Assembly properties. A good example is this: We want to name projects to match output assembly name, and we want project names to have a common prefix like
MyCompany.MyProduct
.
I found that watching for these errors in Code Reviews is error prone and should be automated. So I wrote a simple tool that performs these and many other checks to verify the solution consistency. Meet SolutionInspector. This is Open Source and distributed under MIT license. You can build it from source code or install from NuGet:
Install-Package SolutionInspector
The tool walks through the entire solution structure and applies many validation rules. The rules are configured by XML files, placed along with others solution files. To control settings on the per-project basis, you simply add the same file with different settings to the corresponding project folder.
No configuration file is required by default. In this case, the tool will apply all available rules and yield all issues to the console.
Here is the configuration file example:
<?xml version="1.0" encoding="utf-8"?>
<Settings xmlns:xsi="[https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema">](https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema">)
<SolutionSettings>
<MinSolutionFormatVersion>12.00</MinSolutionFormatVersion>
<MaxSolutionFormatVersion>12.00</MaxSolutionFormatVersion>
<DetectMissingFiles>true</DetectMissingFiles>
<ProjectNamePrefix>MyCompany.MyProduct.</ProjectNamePrefix>
<ProjectNameIsFileName>true</ProjectNameIsFileName>
<IgnoredProjects>
AVerySpecialProject1;
AVerySpecialProject2;
</IgnoredProjects>
</SolutionSettings>
<ProjectSettings>
<DetectMissingFiles>true</DetectMissingFiles>
<AllowBuildEvents>true</AllowBuildEvents>
<AssemblyNameIsProjectName>true</AssemblyNameIsProjectName>
<RootNamespaceIsAssemblyName>true</RootNamespaceIsAssemblyName>
<RequiredImports>StyleCop.MSBuild.Targets</RequiredImports>
<Properties>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<StyleCopTreatErrorsAsWarnings>false</StyleCopTreatErrorsAsWarnings>
</Properties>
</ProjectSettings>
</Settings>
Although the settings are rather descriptive, I’m going to explain some of them:
MinSolutionFormatVersion
/MaxSolutionFormatVersion
will prevent your developers from switching Visual Studio version.DetectMissingFiles
is very useful for static web content or other non-code files added to the solution or a project.AllowBuildEvents
can prevent from adding custom build events, which may do unnecessary things.Properties
is the most flexible element: you can check any properties against desired values, whether those are known properties or custom.
Conclusion
We have reviewed several standard practices, configuration files and project settings you can apply when starting a new project. Doing this in the very beginning would decrease future technical debt and will make your product source code looking nice and professional. For Open Source projects this is of particular importance, because any contributor would know your expectations by examining solution configurations and project files.
This post originally appeared in Toptal