In the previous article, we provided a basic introduction to MAUI. In theory, this article should guide you through creating your first MAUI application for a detailed evaluation, gradually diving deeper.
If you need to create your first MAUI app, please visit .NET MAUI Creating Mobile Apps — Get Started and MAUI and Blazor Sharing a Single UI, Comparable to Flutter, Implementing Windows, macOS, Android, iOS, and Web Universal UI. The focus of this article is not on creating a MAUI application but on how to better configure a MAUI project.
Say Goodbye to Annoying "obj"
For a long time, we have been troubled by an issue: whenever a C# program is compiled, an "obj" directory is generated under the project folder, which is very unsightly. Once committed to Git, we have to exclude them one by one (which is a pain if there are many projects). This is very frustrating. Can we move it away?
Before completing the following settings, please do as I say:
- Create a C++ project (we are not going to develop in C++, but it will play a very positive role in our subsequent learning).

Create an XML file and rename it to Directory.build.props (the name is arbitrary, but I like this one).



Double-click the Directory.build.props file to open and edit it, delete all content in the XML, and add the following settings in the file:
<Project>
<PropertyGroup>
<BaseIntermediateOutputPath>$(MSBuildThisFileDirectory).vs\$(SolutionName)\Intermediate\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
</PropertyGroup>
</Project>
I will not explain the purpose of the BaseIntermediateOutputPath setting field here. If you need to know, please visit: Common MSBuild Project Properties - MSBuild | Microsoft Docs (If you are using VS2022 for Mac, I'm sorry to tell you it does not support this property. I have checked the official issue; this bug has existed since VS2019 for Mac, but Microsoft turns a blind eye to it). To avoid awkward situations, we need to add a conditional judgment that this setting only takes effect when the operating system is Windows.
<!--This property allows you to say goodbye to obj-->
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('windows'))">
<BaseIntermediateOutputPath>$(MSBuildThisFileDirectory).vs\$(SolutionName)\Intermediate\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
</PropertyGroup>
Here I have to say that the Visual Studio team at Microsoft is truly powerful. You only need to install VS to open the same code on different platforms without any additional compilation settings.
We Are a Family
When a solution contains multiple projects (csproj), we definitely want all the DLLs, EXEs, and configuration files generated by all projects to be compiled into a fixed location. Usually, we manually set the compilation output path. This method is highly discouraged because the path you choose is often unreliable (especially in a team project). We only need to make a small change (this setting is still added in Directory.build.props):
<PropertyGroup>
<!--This property allows you to plan a unified output path-->
<OutputPath>$(MSBuildThisFileDirectory)Binary\</OutputPath>
</PropertyGroup>
Let It Apply to All
We often think: can we configure something in one place that affects all projects, such as enabling Nullable, setting the C# language version, etc.? Just add the following configuration to Directory.build.props:
<PropertyGroup>
<!--This property allows you to plan a unified output path-->
<OutputPath>$(MSBuildThisFileDirectory)Binary\</OutputPath>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Can't Do Without You
Have you ever been troubled by this: when all projects (csproj) under my solution need the same package, can I reference it only once? Please follow me:
- Create an XML file (you are already familiar with this) and rename it to
Directory.build.targets(I like this name).


- Modify the content of
Directory.build.targetsto:
<Project>
<!--This design allows all projects under your current solution to access the package-->
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>
Make It Proactive
After completing the above settings, you have become a solution management expert. But that's not enough. As we all know, .NET 6 or C# 10 introduced file-scoped namespaces. You only need to write namespace xxx;, fewer braces make your code look cleaner. Unfortunately, if you don't make any changes, it will never be so proactive. The default class you create will always look like this:
namespace MauiLib1
{
internal class Class2
{
}
}
Please follow me to complete the following operations, and it will become more obedient:
- Create an editorconfig file.


- Double-click
.editorconfigand enable the file-scoped namespace configuration (if you open it as text instead of the settings interface, don't worry; you can complete the subsequent settings after the next restart).

Now you have completed some efficient settings. Close VS and reopen the solution for all the above settings to take effect. Don't forget to delete the generated compilation garbage, such as these:

Now the new class you create will look like this:
namespace MauiLib1;
internal class Class3
{
}
Recompile the solution, and all your DLLs and EXEs will be generated under the Binary directory. Each csproj directory will be very clean (note that the above settings are invalid for C++ projects; C++ projects need separate configuration, which is not covered here).
Don't Forget It
After completing the above settings, the C++ project we just created seems to have no role. That's right; the C++ project is just for you to see (I'm just playing with you). Next, we need to discuss how you know about these (as shown in the figure):

Actually, these are some built-in macro definitions in VS. In C#, we cannot know why these macros exist. At this point, we need the C++ project:




Here you can see these various setting fields and the corresponding macro values. These macros belong to VS, so they also work for C# projects.
It Gives Too Much
Next, we will discuss some deeper MAUI settings. On Windows, it always compiles Android, iOS, and MacCatalyst by default, which is too much; I don't need them. You can modify the TargetFrameworks (like this):

Note: Once you modify this setting, you can no longer select other platforms to verify code correctness (code reliability will not be guaranteed). The most obvious benefit of removing other platform compilation settings is that compilation becomes extremely fast.
Make It Loyal
When I generate a Windows application, I need to reference a Windows-platform-specific package, such as the well-known PInvoke.User32. Obviously, this library is only suitable for the Windows platform. Referencing it on other platforms may not cause compilation errors, but there will inevitably be an unrelated DLL in the package file (maybe not; I haven't tested it, but I guess it does). This is something we don't want to see. So we should do this:
<!--This is a setting exclusive to Windows, making it a loyal companion to Windows-->
<ItemGroup Condition="$(TargetFramework.Contains('-windows'))">
<!-- Required - WinUI does not yet have buildTransitive for everything -->
<PackageReference Include="PInvoke.User32" Version="0.7.104" />
</ItemGroup>
They Need Isolation
When writing code, we often encounter that some code is applicable to Windows but not to other platforms. At this point, you can use compilation macro commands such as #if, #elif, #else, #endif, etc.
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if WINDOWS
string? name = "Windows";
#elif MACCATALYST
string? name = "Mac";
#else
string? name = "Mobile";
#endif
return builder.Build();
}
Let It Be Itself
We are very much looking forward to being able to run a Windows application written in MAUI by simply double-clicking the EXE (which was previously a luxury). Now you only need to modify two settings to achieve this (in the main project's csproj file). Add the following two configurations (after using this configuration, AnyCPU compilation is no longer supported, so we make a conditional compilation):
<!--This solution allows your MAUI-generated EXE on Windows to be itself-->
<PropertyGroup Condition="'$(Platform)' != 'AnyCPU' And $(TargetFramework.Contains('-windows'))">
<!-- Unpack : SelfContainedDeployment for winui3 -->
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
</PropertyGroup>
Multi-Platform Compilation Configuration
In the previous change, because the added settings for running EXE directly are not supported under AnyCPU compilation environment, you need to configure multi-platform compilation schemes (such as x64, x86, ARM64, etc.). The configuration method is as follows:
- Open Configuration Manager.

- Add platforms like X64.


In summary, we have completed most of the settings in the solution and project (csproj). Making these settings will make some of your work more handy.
All the above settings have been uploaded to GitHub.