Skip to main content
ANVISoftware Solutions
Lesson 3 of 14Intermediate15 min

Project Structure

By the end of this lesson

Navigate a Blazor project and know what each file is for.

A Blazor project is an ASP.NET Core project with a folder of .razor files in it. Nothing is hidden and nothing is generated behind your back, but a handful of small files carry more weight than their size suggests. Knowing which ones saves a lot of searching later.

This lesson walks the project the template gives you and names the job of each part. Create the project as you read, so the file names mean something.

Creating and running the employee portal
Shell
# Create a Blazor Web App
dotnet new blazor -o EmployeePortal

# Or ask for interactivity up front: None, Server, WebAssembly or Auto
dotnet new blazor -o EmployeePortal --interactivity Server

cd EmployeePortal

# Build, run, and rebuild when a file is saved
dotnet watch run
  • dotnet new blazor creates the current Blazor Web App template: server-rendered by default, with interactivity added per component.
  • The interactivity switch decides which render modes the project is wired up for. It sets up registrations in Program.cs, so choosing wrongly now costs you a few lines later rather than a new project.
  • dotnet watch run rebuilds and reloads the browser when you save a .razor file. It shortens the edit-and-look loop enough to be worth the habit.
  • The console prints the address it is listening on. Use the https one: the template configures a local development certificate, and some browser features are unavailable over plain http.

What each part of the project is for:

.razor files
One component each. The file name becomes the component name, so EmployeeList.razor is used in markup as an EmployeeList element. A component with a @page directive is also a page.
Components/Pages
The routable components, the ones carrying @page. Roughly one per screen: Employees.razor, EmployeeDetail.razor.
Components/Layout
The surroundings. MainLayout.razor and the navigation menu live here. A layout is a component with a hole in it that the page renders into.
_Imports.razor
Shared @using directives. A using added here applies to every component in that folder and below, which saves repeating the same eight lines in forty files. It applies to components only, not to plain .cs files.
App.razor
The root document in a Blazor Web App: the html, head and body elements, the script that enables interactivity, and the component holding the router. In a standalone WebAssembly project this file holds the router itself.
Routes.razor
The router. It scans the assembly for components with @page, matches the requested address and renders the match inside the default layout.
Program.cs
Startup. Services are registered here and the request pipeline is configured. When a component asks for something with @inject, this is the file that decided what it gets.
wwwroot
Static files served as they are: CSS, images, the favicon, any JavaScript you still need. This folder is the web root, so wwwroot/css/site.css is requested as /css/site.css.
appsettings.json
Configuration read at startup: API addresses, logging levels, feature switches. Not secrets — see the note under the file below.
Program.cs
C#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

builder.Services.AddHttpClient<EmployeeApiClient>(client =>
{
    client.BaseAddress = new Uri(builder.Configuration["EmployeeApi:BaseAddress"]!);
});

var app = builder.Build();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.Run();
  • AddRazorComponents registers what the framework needs in order to render components. AddInteractiveServerComponents adds the server interactive render mode; a project that also runs components in the browser adds the WebAssembly equivalent alongside it.
  • The typed HttpClient registration means a component can ask for an EmployeeApiClient and receive one with its base address already set from configuration. Dependency injection has its own lesson later in this course; the point here is that this file is where the answer lives.
  • The exclamation mark after the configuration lookup tells the compiler you expect a value there. A missing setting then fails loudly at startup rather than quietly later.
  • UseAntiforgery is required for form posts. The template includes it, and removing it breaks EditForm when a form is rendered on the server.
  • MapRazorComponents names the root component, App. AddInteractiveServerRenderMode is what makes @rendermode InteractiveServer legal — without it, requesting a page that asks for that mode fails with a message about the render mode not being configured.
  • Recent templates call MapStaticAssets instead of UseStaticFiles, which adds fingerprinting and compression for static files. UseStaticFiles still works and is shown here because it is the form you will meet in most existing projects.
_Imports.razor, and the router in Routes.razor
C#
@* _Imports.razor — inherited by every component in this folder and below *@
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using EmployeePortal.Components
@using EmployeePortal.Models

@* Routes.razor *@
<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>
  • Everything in _Imports.razor is a using directive that every component below it inherits. Add a namespace here once instead of at the top of every file.
  • It does not reach plain .cs files. A service class still needs its own using directives, which surprises people who added a namespace here and cannot work out why the class will not compile.
  • Router scans the named assembly for components carrying @page and builds the route table at startup. You never register a route by hand.
  • RouteView renders the matched component inside DefaultLayout. That is why a page shows the navigation and footer without asking for them.
  • FocusOnNavigate moves keyboard focus to the first h1 after a navigation. Without it, someone using a keyboard or a screen reader stays parked where the old link was and the new page goes unannounced.
  • In a Blazor Web App, an address matching nothing is handled by the server, which returns a 404. A standalone WebAssembly application has no server in that loop, so its router carries a NotFound section instead.
appsettings.json
JSON
{
  "EmployeeApi": {
    "BaseAddress": "https://localhost:7180/"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
  • Nested keys are read with a colon between the levels, which is the EmployeeApi:BaseAddress lookup used in Program.cs.
  • appsettings.Development.json overrides these values on a developer machine, and environment variables override both. That ordering is how the same build runs in three environments.
  • A Blazor Server project reads this file on the server, so it is never downloaded. A standalone WebAssembly project's configuration is served to the browser, so treat every value in that one as public and keep credentials behind an API.

Summary

  • A Blazor project is an ASP.NET Core project plus components; nothing about the layout is magic
  • Each .razor file is one component, named by its file, and a @page directive turns it into a page
  • _Imports.razor supplies using directives to components in its folder and below, but not to .cs files
  • App.razor is the root document and Routes.razor holds the router, which builds routes by scanning for @page
  • Program.cs registers services and render modes, wwwroot serves static files, and appsettings.json holds configuration but never secrets a browser could download

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

Map the project yourself

Create a project with dotnet new blazor and answer these three questions by reading the files rather than searching the internet.

Which file decides that every page gets the navigation menu? Which file gives the home page its route, and what is that route? If you added a component called DepartmentPicker.razor, what would you write in markup to use it?

Show solution

Routes.razor names the default layout on the RouteView, and MainLayout.razor renders the navigation menu inside itself. The default is set once, centrally, which is why no page mentions it.

Home.razor carries @page "/" — the route is the single slash. Routes come from the components, not from a route table you maintain.

A DepartmentPicker element, with the component's parameters written as attributes. The file name is the element name, and no registration or import is needed as long as the namespace is in scope, usually through _Imports.razor.

The reason to work this out by reading is that it is the same skill you will need on an unfamiliar project, where nobody has written the tour for you.

Think about it

Why does _Imports.razor exist?

Components could each declare their own using directives, the way .cs files do. Why does the template add a shared file instead, and what do you give up by using it?

Show solution

Components pull in the same handful of namespaces almost every time — the framework's own, your models, your components. Repeating five lines in every file is noise that hides the one unusual import that matters.

What you give up is locality. A reader looking at one component cannot see where a type came from, because the using is in another file. On a large project with several _Imports.razor files at different levels, that can take a moment to untangle.

The trade is usually worth it for framework and model namespaces, and far less clearly worth it for a namespace only two components use. Put those in the components that need them.

Saved in this browser only.