Global Visual Studio Files
Introduction
You may not know, but Visual Studio (or more accurately, MSBuild and other tools used implicitly) can make use of local files to configure some aspects of its working. In this post I will introduce and explain why we should use (at least) some of them. These files apply regardless of the solution format in use, are not related to a specific format.
Here is the list with a brief summary of what each file is for. Of course, other files may be used for other purposes and different technologies somewhat outside the control of Visual Studio/MSBuild (e.g., NPM, Less, etc).
| File | Purpose |
| Directory.Build.props | Defines shared MSBuild properties for projects under that directory tree. Imported early in the build. Commonly used for target frameworks, nullable settings, analysers, company metadata, warning levels, etc. |
| Directory.Build.targets | Defines shared MSBuild targets/items/tasks for projects under that directory tree. Imported late in the build. Commonly used for custom build steps, validation, packaging hooks, generated files, etc. |
| msbuild.rsp | Default command-line switches to MSBuild.exe for a specific project.Visual Studio does not apply any .rsp response file. Better to use Directory.Build.rsp as it applies to all projects. Not commonly used. |
| Directory.Build.rsp | MSBuild response file for passing command-line switches automatically to builds under a directory. Visual Studio does not apply any .rsp response file. Not commonly used. |
| Directory.Packages.props | Used for Central Package Management. Defines NuGet package versions in one place with <PackageVersion />, so individual projects can reference packages without specifying versions. |
| Directory.Solution.props | Less common. Solution-level MSBuild props imported when building a .slnx solution in newer MSBuild scenarios. Used for solution-wide properties. |
| Directory.Solution.targets | Less common. Solution-level targets imported when building a solution. Used for solution-wide custom build logic. |
| global.json | Pins the .NET SDK version used by the solution/repo. Helps ensure developers and CI use the same SDK. |
| NuGet.config | Defines NuGet package sources, credentials behaviour, package restore settings, and source mapping. Can apply repo-wide depending on location. |
| dotnet-tools.json | File created when you install a tool for your project with dotnet tool install command. This file contains details like version, installation command, etc, about all the tools installed for your project. |
| libman.json | Manifest file for the Library Manager (LibMan) tool used in ASP.NET Core and web projects. It specifies which client-side libraries (like jQuery, Bootstrap, or Lodash, amongst others) to fetch, from which sources, and where to save them in your project. |
| .editorconfig | Defines code style, formatting, analyser severity, suppression, naming rules, etc. Applies to files by directory scope and is honored by Visual Studio, Roslyn analysers, and many editors. |
| .globalconfig | Repo-wide analyser configuration file for Roslyn. Similar to .editorconfig, but intended for global analyser settings rather than per-directory file style. |
| packages.lock.json | NuGet lock file. Records exact resolved package dependency graph for repeatable restores. Usually generated per project, but can be managed repo-wide depending on restore setup. |
| launchSettings.json | A configuration file within ASP.NET Core applications (Properties folder) to define various profiles for how the application can be launched during development, in the local machine. |
Directory.Build.props
This file contains properties that should apply to all projects in the solution, which is generally good for coherence. This example applies .NET 10 with the latest C# language specs, whether to use C# nullable reference types and implicit usings, and if the output should be deterministic.
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<Deterministic>true</Deterministic>
</PropertyGroup>
</Project>
Directory.Build.targets
This one defines targets (which can have conditions) for the projects in the directory. This example ensures that project MyProject.DomainModel does not reference any other project or NuGet package.
<Project>
<Target Name="ValidateDomainDependencies"
BeforeTargets="ResolveReferences"
Condition="'$(MSBuildProjectName)' == 'MyProject.DomainModel'">
<Error Condition="'@(PackageReference)' != ''"
Text="MyProject.DomainModel must not reference NuGet packages." />
<Error Condition="'@(ProjectReference)' != ''"
Text="MyProject.DomainModel must not reference other projects." />
</Target>
</Project>
msbuild.rsp and Directory.Build.rsp
These files contain parameter options to be passed to MSBuild. The difference between the two is that the first should be placed in a project directory, and the second, in the root of a repository, so that it applies to all projects underneath it. Both are only applied by MSBuild, the Visual Studio IDE ignores them. Use %MSBuildThisFileDirectory% to reference the directory where the file is located. Here's a sample that enables some settings.
# Enable parallel builds across all available CPU cores -maxCpuCount # Restore packages and build concurrently -restore -m # Enforce clean terminal output (displays warnings/errors at the end) -clp:Summary;ErrorsOnly;WarningsOnly # Automatically configure binlog generation using the file's current directory -bl:%MSBuildThisFileDirectory%build.binlog # Define continuous integration parameters if executing a hosted build -p:ContinuousIntegrationBuild=true
Directory.Packages.props
This file defines NuGet package versions for the entire solution (all projects in a sub directory). This ensures that the same versions are used everywhere, then it's just a matter of individual projects referencing a NuGet package, without specifying its version. This is called Central Package Management and here is a sample that defines some NuGet package versions:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="10.8.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
</Project>
Directory.Solution.props
This is a specialised MSBuild file used to customize configuration settings specifically for solution builds. Not so commonly used, but here is an example:
<Project>
<PropertyGroup>
<!-- Speeds up solution restores via static graph evaluation -->
<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>
<!-- Some solution-wide property -->
<SomeSolutionWideProperty>True</SomeSolutionWideProperty>
</PropertyGroup>
</Project>
Directory.Solution.targets
Directory.Solution.targets is an MSBuild file placed in the root path above a solution file to customize or extend the build process at the solution level. The following example displays a message after the build finishes:
<Project>
<Target Name="CustomSolutionPostBuild" AfterTargets="Build">
<Message Importance="high" Text="Finished building solution in $(SolutionDir)" />
</Target>
</Project>
global.json
This file allows specifying a fixed .NET version for an entire directory. You should use a global.json file in .NET development when you need to lock, pin, or strictly manage the .NET SDK version used to execute .NET CLI commands. By default, the .NET CLI always uses the latest version of the SDK installed on your machine; you need a global.json when that default behavior causes issues. Example:
{
"sdk": {
"version": "10.0.302",
"rollForward": "latestPatch",
"allowPrerelease": false
}
}
NuGet.config
In this file we specify NuGet package sources, which can possibly include credentials, for private repos. This is used when restoring NuGet packages. Here's a small example:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
<add key="myprivaterepo" value="https://my.private.repo/nuget/index.json" />
</packageSources>
</configuration>
dotnet-tools.json
The dotnet-tools.json file is a configuration manifest used by the .NET CLI to manage local tools specific to a project or repository, like, for example, EF. An example:
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "10.0.8",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}
libman.json
This is the configuration manifest file used by LibMan (Library Manager) to download and manage client-side web libraries - like jQuery, Bootstrap, or Lodash - directly into web projects without needing heavy package managers like npm. Here's an example:
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "jquery@3.7.1",
"destination": "wwwroot/lib/jquery",
"files": [
"jquery.min.js",
"jquery.min.map"
]
},
{
"library": "bootstrap@5.3.3",
"destination": "wwwroot/lib/bootstrap"
}
]
}
.editorconfig
Configures IDE style preferences and analysis rules, for all files (*), or specific files (*.cs, for example). Here is a big example from https://github.com/dotnet/samples/blob/main/.editorconfig:
# editorconfig.org
# top-most EditorConfig file
root = true
# Default settings:
# A newline ending every file
# Use 4 spaces as indentation
[*]
insert_final_newline = true
indent_style = space
indent_size = 4
[project.json]
indent_size = 2
# C# and Visual Basic files
[*.{cs,vb}]
charset = utf-8-bom
# Code quality analyzers
dotnet_analyzer_diagnostic.category-Security.severity = error
dotnet_code_quality.ca1802.api_surface = private, internal
# Miscellaneous style rules
dotnet_sort_system_directives_first = true
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# avoid this. unless absolutely necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
dotnet_naming_style.static_prefix_style.required_prefix = s_
dotnet_naming_style.static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
# Code quality
dotnet_style_readonly_field = true:suggestion
dotnet_code_quality_unused_parameters = non_public:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:refactoring
dotnet_style_prefer_conditional_expression_over_return = true:refactoring
# C# files
[*.cs]
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
# Code style defaults
csharp_using_directive_placement = outside_namespace:suggestion
csharp_prefer_braces = true:refactoring
csharp_preserve_single_line_blocks = true:none
csharp_preserve_single_line_statements = false:none
csharp_prefer_static_local_function = true:suggestion
csharp_prefer_simple_using_statement = false:none
csharp_style_prefer_switch_expression = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_methods = true:refactoring
csharp_style_expression_bodied_constructors = true:refactoring
csharp_style_expression_bodied_operators = true:refactoring
csharp_style_expression_bodied_properties = true:refactoring
csharp_style_expression_bodied_indexers = true:refactoring
csharp_style_expression_bodied_accessors = true:refactoring
csharp_style_expression_bodied_lambdas = true:refactoring
csharp_style_expression_bodied_local_functions = true:refactoring
# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
# Null checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Other features
csharp_style_prefer_index_operator = false:none
csharp_style_prefer_range_operator = false:none
csharp_style_pattern_local_over_anonymous_function = false:none
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Types: use keywords instead of BCL types, and permit var only when the type is clear
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_elsewhere = false:suggestion
# Visual Basic files
[*.vb]
# Modifier preferences
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
# C++ Files
[*.{cpp,h,in}]
curly_bracket_next_line = true
indent_brace_style = Allman
# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
indent_size = 2
# Xml build files
[*.builds]
indent_size = 2
# Xml files
[*.{xml,stylecop,resx,ruleset}]
indent_size = 2
# Xml config files
[*.{props,targets,config,nuspec}]
indent_size = 2
# Shell scripts
[*.sh]
end_of_line = lf
[*.{cmd, bat}]
end_of_line = crlf
.globalconfig
The core difference between .editorconfig and .globalconfig is that .editorconfig is path-based and configures both IDE text editor styles and code diagnostics, whereas .globalconfig is compilation-based, ignores folder structures, and is designed purely for code analysis rules. It should have is_global = true in order to apply to all files. If rules conflict between an .editorconfig file and a .globalconfig file, the settings in .editorconfig take precedence. See also this and this.
is_global = true # Set the severity of a specific analyzer rule project-wide dotnet_diagnostic.CA1822.severity = warning dotnet_diagnostic.CA1056.severity = none
It is possible to specify one config file per project:
<Project>
<ItemGroup>
<GlobalAnalyzerConfigFiles Include="Relative/Path/To/PackageName.globalconfig" />
</ItemGroup>
</Project>packages.lock.json
NuGet uses the packages.lock.json file at the project root directory to restore packages for PackageReference based projects. Unlike npm, NuGet does not generate this file by default, it must first be enabled:
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>This file serves the exact same high-level purpose as its Node.js counterpart, but it operates explicitly within the .NET ecosystem to achieve repeatable and deterministic package restores. When using PackageReference in your .csproj files, NuGet naturally resolves dependencies using a "lowest applicable version" rule. However, it can still be affected by floating versions or upstream changes to deep, indirect sub-dependencies. The lock file fixes this completely and it should be checked-in to source control.
For CI/CD builds, you can use the following command line options to enable locked mode:
dotnet restore --locked-mode
launchSettings.json
In .NET and ASP.NET Core, launchSettings.json is a configuration file used exclusively in local development machines to define how your application starts up. An example with HTTP and HTTPS and also Docker Compose launch scenarios:
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5066",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7107;http://localhost:5066",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker Compose": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"serviceActions": {
"my-web-api": "StartDebugging",
"sql-database": "StartWithoutDebugging",
"redis-cache": "DoNotStart"
}
}
}
}
Conclusion
And this is it. As a rule of thumb, this is what I'd recommend:
- Use Directory.Build.props for shared project settings
- Use Directory.Build.targets for shared build behaviour
- Use Directory.Packages.props for shared NuGet package versions
- Use global.json for SDK version consistency
- Use a local NuGet.config file for custom repositories
- Use .editorconfig for code style and analyser rules
As always, feel free to comment or correct-me if there is need!
Comments
Post a Comment