Blazor Conventions
Objectives
This part of the document is mainly based…
- On our experience to write the simplest and best performing code possible.
- On how to create and use Razor components in Blazor apps documentation.
- On Microsoft Coding Conventions.
Component name
Blazor Components are implemented using a combination of C# and HTML markups. The HTML tags must be in [Component].razor
file and C# code must be written in [Component].razor.cs
associated file: don’t use @code { }
section in razor file.
Common Blazor naming conventions include:
-
Component file paths use Pascal case. Paths indicate typical folder locations. For example,
Pages/ProductDetailPage.razor
indicates that theProductDetailPage
component has a file name ofProductDetailPage.razor
and resides in thePages
folder of the app. -
Component file paths for routable components match their URLs with hyphens appearing for spaces between words in a component’s route template. For example, a
ProductDetailPage
component with a route template of@page "/product-detail"
is requested in a browser at the relative URL/product-detail
.If several apps are in the same project, consider each app as a “module”:
Pages/Studio/ProductDetailPage.razor
and@page "/studio/product-detail"
. If these apps are in different projects, the app name will be global and not included in folders:Pages/ProductDetailPage.razor
and@page "/studio/product-detail"
. -
A component’s name uses Pascal case. For example,
ProductDetailPage.razor
is valid.
Component structure
Organize files and components in a folder structure like this. This makes it easy to find the code related to a page, without having to browse the entire file explorer. Try, as much as possible, to respect the SOLID principles. Mainly by creating autonomous and extensible components: inject the smallest possible service or parameter, manage all the possibilities offered by the component. For example, a data modification page should display the data, check their values and save the data at the end of the process.
-
Group pages and dialog boxes of the same module in a sub-folder of Pages folder, with this module name (pluralized). For example:
Pages\Calendars\
. -
Suffix pages with Page.razor and dialog boxes with Dialog.razor. Name the page name in the singular. For example:
\Pages\Calendars\CalendarMainPage.razor
and\Pages\Calendars\CalendarEditDialog.razor
. -
(Optional) Put the parameters of a dialog box in a class
[Name]DialogParameters
. And save it into the file[Name]Dialog.razor.Parameters.cs
, to use the “Nested files” representation in Visual Studio. See the exampleCalendarCloneDialog.razor.Parameters.cs
below. -
The code behind a dialog box (for example, to save data) must be in this class. For example, in the OnCloseAsync method (or equivalent method) of this class. Don’t write logic dialog box features in the main call window (except to refresh the main screen).
-
Common and project shared components (between multiple modules) are stored in a folder Components, and are suffixed by
Component
.
For example:
|-- App.razor
|-- Components
| |-- TimeComponent.razor
| |-- TimeComponent.razor.cs
|-- Pages
| |-- Calendars
| | |-- CalendarMainPage.razor
| | |-- CalendarMainPage.razor.cs
| | |-- CalendarMainPage.razor.css
| | |-- CalendarMainPage.razor.js
| | |-- CalendarEditDialog.razor
| | |-- CalendarEditDialog.razor.cs
| | |-- CalendarEditDialog.razor.css
| | |-- CalendarEditDialog.razor.Parameters.cs
VSCode and Visual Studio have a File nesting feature to regroup visually each item of same page or dialog.
In VSCode, set this settings: "explorer.fileNesting.patterns": { "$(capture).razor, $(capture).razor.*"
.
Methods and properties
The variables and properties must be organized in the following order.
- Private variables (using readonly if possible) and constants
- The [Inject] properties
- [Parameter] properties
- Public methods and properties
- Internal and Protected methods/properties
- Private methods/properties
Place the attributes (Inject, Parameter, …) above the property.
[Inject]
public IJSRuntime JsRuntime { get; set; } = default!;
Since the project has been defined as accepting nullables, it is recommended to define non-null variables and properties with a default!
value. To avoid having a warning, at compile time, assign this value by default, only to properties that do not accept null values. For properties that accept null values, don’t forget to define them with the ?
(including simple types or objects like string?
, int?
or object?
.
public IConnectionManager Connection { get; set; } = default!;
JavaScript collocated with a component
⚠️ Avoid having too many calls between .NET and JavaScript, because that’s require additional overhead:
- By default, calls are asynchronous.
- By default, parameters and return values are JSON-serialized to provide an easy-to-understand conversion mechanism between .NET and JavaScript types.
- Additionally, on Blazor Server, these calls are passed across the network.
If you need to create JavaScript functions, use the Collocation of JS files.
Collocation of JavaScript files for pages and Razor components is a convenient way to organize scripts in an app: it’s easier to centralize and find the JS code associated to a component. It does not pollute the client with global functions(*). And the component will load this code only when needed.
(*) If a JavaScript function is used by at least 3 components, you can include it in a global file (in wwwroot/js).
Save the JavaScript code in a .js file below the component.
- Create a
[Component].razor.js
file with your JavaScript code.export function myModule_myJsMethod() { ... }
- Add the following constant and two properties.
private const string JAVASCRIPT_FILE = "./Pages/[Path]/[Component].razor.js"; [Inject] private IJSRuntime JsRuntime { get; set; } = default!; private IJSObjectReference JsModule { get; set; } = default!;
- Call the JavaScript function, from your C# code, as follows.
if (JsModule == null) { JsModule = await JsRuntime.InvokeAsync<IJSObjectReference>("import", JAVASCRIPT_FILE); } await JsModule.InvokeVoidAsync("myModule_myJsMethod");
CSS isolation
If you want to customize the style of your component, it is a good practice to write your styles in a sub-css file: [Component].razor.css
.
Within the generated bundled file, each component is associated with a scope identifier. For each styled component, an HTML attribute is appended with the format b-[string]
, where the [string] placeholder is generated by the framework (ex. b-3xxtam6d07).
Find more details in the Microsoft documentation.
To apply changes to a child component, use the ::deep
pseudo-element to any descendant elements in the parent component’s .razor.css
file. The ::deep
pseudo-element selects elements that are descendants of an element’s generated scope identifier.
Example:
::deep h1 {
color: red;
}
⚠️
::deep
is only for child components. So, add a global<div>
in your component to define the components used as child components.More details here: https://youtu.be/91zH36Q8Qro
To avoid conflicts with other projects or libraries, add a common prefix to all style names (ex. .studio-nsi-popup
).
Performances
More details on https://docs.microsoft.com/en-us/aspnet/core/blazor/performance
- Avoid unnecessary rendering of component subtrees
- Ensure that child component parameters are of primitive immutable types.
- Override ShouldRender.
-
Create lightweight, optimized components.
-
Avoid thousands of component instances (inline child components into their parents).
-
Don’t receive too many parameters.
To reduce parameter load, bundle multiple parameters in a custom class. For example:
[Parameter] public GridOptions? Options { get; set; }
-
Ensure cascading parameters are fixed.
<CascadingValue Value="this" IsFixed="true"> <SomeOtherComponents> </CascadingValue>
-
Avoid attribute splatting with CaptureUnmatchedValues.
-
Don’t trigger events too rapidly.
Some browser events fire extremely frequently. For example, onmousemove and onscroll can fire tens or hundreds of times per second. In most cases, you don’t need to perform UI updates this frequently. If events are triggered too rapidly, you may harm UI responsiveness or consume excessive CPU time.
-
Avoid rerendering after handling events without state changes
By default, components inherit from
ComponentBase
, which automatically invokesStateHasChanged
after the component’s event handlers are invoked. -
Avoid recreating delegates for many repeated elements or components.
- Optimize JavaScript interop speed.