DEV Community

Cover image for What does the .csproj file tell you about your .NET project?
Bea Tavernaro
Bea Tavernaro

Posted on

What does the .csproj file tell you about your .NET project?

πŸ‡§πŸ‡· Prefer reading in Portuguese? Read the Brazilian Portuguese version.

On the first day of 30 Days of .NET, I explored what happens behind a simple dotnet run.

But before dotnet run, there's the project itself.

When we create a .NET application, even the simplest one possible, some files and folders automatically appear. Program.cs, .csproj, obj, then bin, and we just keep coding without worrying about understanding them.

I've opened a .csproj file several times myself to install a package, change a configuration, or check the .NET version, and then closed it without really understanding what I had done.

So today I decided to open up this little box and understand what each of these things is for.

Creating the simplest project possible

Let's start by creating a Console application:

Terminal command output

The dotnet new command creates projects and other files from templates. In this case, we're using the console template and naming the project Dia02.

Inside the folder, we have a structure similar to this:

Terminal command output

Not much to get started, but we already have quite a few things to understand here.

Program.cs β€” where it all begins

Opening the Program.cs file of a modern Console application, we find something like this:

Console.WriteLine("Hello, World!");
Enter fullscreen mode Exit fullscreen mode

The Program.cs file is where we find the application's entry point. It's where our program starts executing and, in larger applications, it's usually where we configure and initialize the necessary components before the application actually starts doing its work.

If you've seen older C# projects, you might be missing a few things in Program.cs.

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
    }
}
Enter fullscreen mode Exit fullscreen mode

Since C# 9, we've had top-level statements, which allow us to write the application's entry point code without explicitly declaring a Program class and a Main method, as in the second example. The compiler generates the necessary entry point for us, making Program.cs much less verbose and cleaner.

.csproj β€” the file that describes our project

Now we've reached the most important file in this article.

If we open Dia02.csproj, we'll find something like this:

csproj file

The .csproj file contains various pieces of information that tell the build system how the project should be handled: which SDK to use, which framework it targets, package references, references to other projects, and several compilation settings. Oh, and it's an XML file.

Looking at our example:

<Project Sdk="Microsoft.NET.Sdk"> : defines the Project SDK used by the project. In this case, we're using the base .NET SDK. In an ASP.NET Core application, for example, it's common to find Microsoft.NET.Sdk.Web.

<PropertyGroup> : groups project properties and settings. It doesn't define a specific configuration on its own, but works as a "group" for the properties inside it.

<OutputType>Exe</OutputType> : defines the type of output generated by the project. Exe indicates that we're building an executable application.

<TargetFramework>net10.0</TargetFramework> : defines which Target Framework the project will be built for. In this example, our target is .NET 10. This doesn't mean "which SDK I have installed", but rather which framework the application targets.

<ImplicitUsings>enable</ImplicitUsings> : makes the SDK automatically add some common using directives to the project. That's why we can use things like Console.WriteLine() without necessarily writing using System; at the beginning of the file.

<Nullable>enable</Nullable> : enables the Nullable Reference Types context, allowing the compiler to warn us about situations where a reference might be null. For example, it starts distinguishing between string name and string? name.

And what happens when we install a NuGet package?

Now let's make a few changes to our project.

We can add a package using the CLI:

dotnet package add Dapper
Enter fullscreen mode Exit fullscreen mode

After that, if we open our .csproj again, something new has appeared:

Dapper added to the project

Let's understand what's new:

<ItemGroup> : while PropertyGroup groups properties and settings, ItemGroup groups items that are part of or used by the project, such as packages, references to other projects, and files.

<PackageReference Include="Dapper" Version="..." /> : declares a dependency on a NuGet package. Include="Dapper" specifies which package we want to use, and Version="..." specifies which version of that package the project uses.

When we install a NuGet package, the dependency is registered in the .csproj file itself, along with the version used by the project. That's also why we don't need to commit the packages themselves to Git. When someone else clones the project, .NET can read the PackageReference entries and restore the necessary dependencies.

And where did bin and obj come from?

When we execute dotnet build

dotnet build output

.NET starts the build process for our project, and during this process, two folders that probably every .NET developer has encountered are created or updated: bin and obj.

bin and obj

Although both are related to the build process, they serve different purposes.

The bin folder contains the main files resulting from our application's compilation. Inside it, .NET organizes these files according to the build configuration and the project's Target Framework.

The obj folder, on the other hand, works as a workspace for the build process. While the output we're interested in goes to bin, during compilation, .NET and MSBuild need to generate and store several intermediate files. That's where obj comes in.

Normally, neither folder is versioned in Git. If we delete bin and obj and run dotnet build again, .NET regenerates whatever is needed.

Wrapping up

With tight deadlines, it's very easy to simply create a project, install packages, run dotnet build, and move on without thinking too much about what's happening behind the scenes. And most of the time, it really will work. But understanding how the project is structured and where .NET gets the information it needs to build our application makes a big difference when something stops working as expected which is almost all the time. The more we understand the tools we use every day, the easier it becomes to investigate errors and, consequently, the easier and faster it becomes to complete a task.

See you tomorrow!

References

Top comments (0)