copy the files app.sln and app.csproj from the templates folder to your main app folder (or a specific edition-subfolder)
restart VS Code
if VS Code / C# Dev Kit asks which solution to load, choose the correct app.sln for the folder you want to work on
Note
2sxc app setups contains multiple .sln files, so VS Code / C# Dev Kit should usually prompt you to choose one and remember that choice for the workspace.
If it does not load the correct solution reliably, use the .NET: Open Solution command and manually open the root app.sln.
That's it - you should now have IntelliSense on all your C# and Razor files.
To upgrade the extension:
Install the latest version from the marketplace, replacing the previous version.
Then restart VS Code.
Tip
Anything that follows is just a deep-dive, you usually do not need to understand it.
For most users, you can stop reading here.
How it works
The extension uses the OmniSharp extension to provide IntelliSense and other C# features in VS Code.
By including a .sln and .csproj file in the app folder, OmniSharp can recognize the project structure and provide the necessary tooling support.
Basically this is what happens:
The app.sln file defines a solution that includes the app.csproj project.
The app.csproj imports the /extensions/dotnet-project/all-in-one.import.csproj which contains everything needed for the project
The all-in-one.import.csproj file imports the necessary SDKs and packages to enable C# development in the app folder.
The app.sln file is a standard Visual Studio solution file that defines the structure of the project.
It includes the app.csproj project and any other projects that may be needed in the future.
This is what's probably in the file:
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
#
# Visual Studio .sln File for 2sxc App
# This is necessary so that VS Code can perform intellisense in Razor
# It also requires a csproj file to exist as well
#
# Read more and get help for issues on https://go.2sxc.org/vscode
#
VisualStudioVersion = 18.3.11520.95
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "app", "app.csproj", "{9F7A078F-99D5-4EF4-8EC0-C6B920FE679C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9F7A078F-99D5-4EF4-8EC0-C6B920FE679C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F7A078F-99D5-4EF4-8EC0-C6B920FE679C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F7A078F-99D5-4EF4-8EC0-C6B920FE679C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F7A078F-99D5-4EF4-8EC0-C6B920FE679C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
There's not much to add here, just leave it as is and it will work.
The app.csproj file is a standard C# project file that defines the project structure and dependencies.
It imports the all-in-one.import.csproj file which contains all the necessary SDKs and packages to enable C# development in the app folder.
Here's what's probably in the file:
<Project Sdk="Microsoft.NET.Sdk.Web">
<!-- This file helps VS Code provide IntelliSense - see https://go.2sxc.org/vscode -->
<!-- Keep this file intentionally small and use the imports from extensions/dotnet-project to add the necessary logic. -->
<!-- Import everything so it just magically works. For fine-grained control, import the individual files instead of the all-in-one. -->
<Import Project="extensions\dotnet-project\all-in-one.import.csproj" />
</Project>
There is not much to add here either, just make sure to keep the import path correct and it will work.
Internally it's a bit more complex, because it has to work with all kinds of different scenarios such as:
It could be running in DNN or Oqtane.
It needs to work with both .NET Framework (net48) and .NET Core (net10) projects.
It's always in an App, but some Apps have it in their root folder, while others have it in an edition-subfolder.
In Oqtane, the path will vary if you're running a production or development version of Oqtane.
When using editions and opening VS Code on the root folder, it may have identical files in each edition, confusing the analyzers, so we have to remove editions such as live or bs4 from the project when running in design-time in DNN.
In DNN we had to do some trickery to make older Razor code work with the newer .net Core Razor Code Analyzers.
This is the inner logic which is implemented in the all-in-one.import.csproj file, which imports the necessary SDKs and packages based on the environment and project type.
The all-in-one.import.csproj file is a C# project file that imports the necessary SDKs and packages to enable C# development in the app folder.
It contains logic to determine the environment and project type, and imports the appropriate SDKs and packages based on that.
Here's what actually happens in the file:
flowchart TB
subgraph allInOne["<code>extensions/dotnet-project/all-in-one.import.csproj</code>"]
direction TB
prep["prep"]
cmsDetect["cmsDetect"]
dnn["dnn"]
oqtane["oqtane"]
common["common"]
dnnDevKit["dnnDevKit"]
end
subgraph prep["Step 1: Preparation <code>/1-prep/</code>"]
direction LR
prepNs["namespace-and-output-type.props"] -->
prepBuild["constants-and-warnings.props"]
end
subgraph cmsDetect["Step 2: Prepare Paths and Detect CMS <code>/2-detect/</code>"]
direction LR
prepCMS["detect-cms.props"]
end
subgraph dnn["Step 3: Setup DNN <code>/3-setup/dnn/</code>"]
direction LR
dnnDlls["import-dlls.props"]
dnnProps["properties.props"]
end
subgraph oqtane["Step 3: Setup Oqtane <code>/3-setup/oqtane/</code>"]
direction LR
oqtDlls["import-dlls.props"]
oqtProps["properties.props"]
end
subgraph common["Step 8: Final Steps <code>/8-final/</code>"]
direction LR
commonEditions["ignore-editions.props"]
commonDlls["import-dlls.props"]
end
subgraph dnnDevKit["Step 9: DNN C# DevKit Helpers <code>/9-devkit/dnn/all.props</code>"]
direction LR
DTPATHS["razor-tool-paths.props"]
DTAN["razor-analyzers.props"]
DTTOOLS["razor-tooling.props"]
DTCSHTML["include-cshtml.props"]
end
dnnProps --> dnnDlls
oqtProps --> oqtDlls
commonDlls --> commonEditions
APP["app.csproj<br>(your file)"] --> allInOne
prep --> cmsDetect
cmsDetect -- "CmsType == oqtane" --> oqtane
cmsDetect -- "CmsType == dnn" --> dnn
dnn --> common
oqtane --> common
common -- "if DesignTimeBuild and CmsType == dnn" --> dnnDevKit
DTPATHS --> DTAN
DTAN --> DTTOOLS
DTTOOLS --> DTCSHTML
prepCMS@{ shape: subproc}
dnnDevKit:::noWrap
allInOne:::noWrap
prep:::noWrap
cmsDetect:::noWrap
dnn:::noWrap
oqtane:::noWrap
classDef noWrap white-space:nowrap
This is the flow of code:
Set CmsType=auto if not previously set.
Normalize to lowercase - so it should only be auto, dnn, or oqtane at this point.
Run the platform-specific detection logic, which looks for the marker DLL in the expected paths for each platform, and sets DnnDetected or OqtaneDetected accordingly.
If CmsType is still auto, switch it to dnn or oqtane if only one of them was detected.
If CmsType is not dnn or oqtane, throw an error because the host cannot be resolved.
As an output, it will have a final:
CmsType value of either dnn or oqtane that can be used for conditional imports and properties later on.
PathBin value pointing to the correct bin folder of the host, which is needed for reference imports later on.
flowchart TD
A[Start] --> B{Is CmsType set?}
B -- No --> C[Set CmsType=auto]
B -- Yes --> D[Normalize CmsType to lowercase]
D --> E[Run DNN detection logic]
D --> F[Run Oqtane detection logic]
E --> G{Is DNN detected?}
F --> H{Is Oqtane detected?}
G -- Yes --> I[DnnDetected=true]
H -- Yes --> J[OqtaneDetected=true]
I --> K{Is CmsType still auto?}
J --> K
K -- Yes and DNN only --> L[Set CmsType=dnn]
K -- Yes and Oqtane only --> M[Set CmsType=oqtane]
K -- No or Both detected --> N[Error: Ambiguous or invalid CmsType]
L --> O[Set PathBin to DnnBinPath]
M --> P[Set PathBin to OqtaneProdBinPath, fallback to OqtaneDevBinPath]
O --> Q[End with CmsType=dnn and PathBin set]
P --> R[End with CmsType=oqtane and PathBin set]
When you need to do some customizations, you can always
Pre-set some of the variables such as CmsType in your app.csproj before the import.
Customize your own app.csproj file to import specific files instead of the all-in-one.import.csproj.
Here's an example where we needed another DLL.
<Project Sdk="Microsoft.NET.Sdk.Web">
<!-- This file helps VS Code provide IntelliSense - see https://go.2sxc.org/vscode -->
<!-- Best keep this file small and use imports from extensions/dotnet-project. -->
<!-- Import everything so it just magically works. -->
<Import Project="extensions\dotnet-project\all-in-one.import.csproj" />
<!-- Note: For fine-grained control, import the individual files instead of the all-in-one. -->
<!-- Load other important DLLs which are not in the main bundle -->
<ItemGroup>
<Reference Include="$(PathBin)\System.Collections.Immutable.dll" />
</ItemGroup>
</Project>
The PathBin variable is used to specify the path where the DLLs are.
This allows us to use the same rules for Dnn and Oqtane, just with a different path to start from.
Here's what you should know:
Dnn usually has the App files in /Portals/[portal]/2sxc/[app]/ so the DLLs relatively in ..\..\..\..\bin
Oqtane has the App files in /2sxc/[site-id]/app/ but the DLLs are in different locations depending on Dev vs. Production builds
in development built it places the DLLs in \bin\Debug\net8.0 so the relative path is usually ..\..\..\bin\Debug\net8.0
in production builds it places the DLLs in the root folder, so the relative path is usually ..\..\..
If you're working with Polymorphism then you have many of the same files, which confuses IntelliSense.
For example, /live and /staging have the same files, and /bs3, /bs4 and /bs5 have the same files.
So intellisense might find the same class in multiple places, and show warnings.
To handle this, you should decide which is your primary folder, and then exclude the others.
This is just an example to exclude /live as we're always working on /staging:
<!-- Example: exclude /live as we're always working on /staging -->
<ItemGroup>
<None Remove="live\**" />
<Content Remove="live\**" />
<Compile Remove="live\**" />
<EmbeddedResource Remove="live\**" />
</ItemGroup>
Understanding csproj files
In case you're not familiar with .csproj files, here's a quick overview:
PropertyGroup is used to define variables which are used later in the file
ItemGroup is used to define lists of items, like files, references, etc.
Both of these can have conditions, so you can define different settings for different situations.
The TargetFramework is the .net Framework you are targeting.
The value like net472 or net48 are called target framework moniker or TFM.
You can find a list of them on the Microsoft Docs.
Recommended values:
Dnn: net472 or net48 (officially, Dnn requires 4.7.2, but 4.8 is what is normally installed because of security)
Oqtane: net10 (Oqtane 10+)
The LangVersion is the C# version you are using.
You can find a list of them on the Microsoft Docs.
Recommended values:
Dnn: 8.0 (Dnn 9.11.02+ using 2sxc 17 and Roslyn Compiler)