Planning a Blazor
Application
EBOOK
Table of Contents
1. ASP.NET Core Architecture / 3
5. Managing CSS Scope with Razor
Components / 38
1.1 Ecosystem / 4
1.2 Version and Support / 5
5.1 Global Scope / 39
1.3 The Platform / 5
5.2 CSS Isolation / 39
1.4 Blazor Server / 6
5.3 The Scale of CSS Scope / 42
1.5 Blazor WebAssembly / 8
5.4 Customization Through Parameters / 43
1.6 Blazor Hybrid with .NET MAUI / 9
5.5 CSS Custom Properties (Variables) / 45
1.7 Blazor United .NET 8 preview / 10
5.6 A Completely Customizable UI Library / 50
1.8 Choosing the Right Approach / 10
6. Component Libraries / 51
2. Inroads / 11
6.1 Telerik UI for Blazor / 52
2.1 Migration Tools / 11
7. Is This Low-Code? / 52
2.2 Migration From .NET Framework / 12
2.3 Migrating From Web Forms / 13
8. Blazor Data Grid / 52
2.4 Migration FromMVC on .NET Framework / 15
2.5 Transitioning From MVC on .NET Core to Blazor / 16
9. Auto-Generating Data Grid Columns / 54
3. Tooling and Development / 18
10. Customization / 57
3.1 Dotnet CLI / 18
3.2 Progress Telerik REPL for Blazor / 18
10.1 Accessibility / 59
3.3 Visual Studio / 21
11. Section 508 / 59
3.4 Visual Studio Code / 21
3.5 Ryder / 21
12. W3C Web Content Accessibility Guidelines
(WCAG) 2.1 / 60
3.6 Progress Telerik Fiddler Everywhere / 22
4. Test Coverage / 23
13. WAI-ARIA / 60
4.1 Unit Testing / 24
14. Keyboard Navigation / 61
4.2 xUnit / 24
4.3 Blazor Component Testing / 27
4.4 bUnit / 27
15. Authorization and Identity / 61
4.5 Mocks / 29
15.1 Authorized View Component / 62
4.6 Progress Telerik JustMock / 30
16. [Authorize] Attribute / 63
4.7 Blazor Automated System Testing / 34
4.8 Progress Telerik Test Studio / 34
16.1 Procedural Logic / 63
4.9 Blazor Automated Test Recorder / 35
17. Conclusion / 65
2
© 2023 Progress. All Rights Reserved.
There’s a lot more to planning a Blazor application than meets the eye. This eBook
documents a high-level outline of what developers need to consider when choosing:
migration strategies, target platforms, tooling, testing and user interface. In each
section, we’ll look at the available options so you can assemble an informed plan for
what’s ahead.
ASP.NET Core Architecture
Blazor is just one framework that is part of the much larger .NET platform of tools,
programming languages and libraries for building many different types of applications.
With .NET Core, you can build nearly any type of app. Some are cross-platform and some
target a specific set of operating systems and devices. Ultimately, the choice is up to
developers and what audience they are trying to reach with the application. .NET enables
this wide array of options through a standard set of base class libraries and APIs that are
common to all .NET applications. In Table 1, the frameworks that support Blazor are .NET,
ASP.NET Core and .NET MAUI.
App Model or Framework
Objectives
Clients & Targets
.NET [Core]
Open source developer
platform
web, cloud, desktop mobile,
machine learning, IOT,
gaming
ASP.NET Core
Web framework for .NET
Core
Web apps, APIs, real-time
services, microservices
.NET MAUI
Desktop and mobile
framework for .NET Core
Windows, macOS, iOS,
Android
Table 1
Each app model can also expose additional APIs that are specific to the operating systems
it runs on, or the capabilities it provides. For example, ASP.NET is the cross-platform
web framework that provides additional APIs for building web apps that run on Linux or
Windows, while part of ASP.NET Blazor can run on the server or directly in the browser via
the .NET runtime for WebAssembly.
3
© 2023 Progress. All Rights Reserved.
Ecosystem
To extend functionality, Microsoft and others maintain a healthy .NET package ecosystem.
NuGet is a package manager built specifically for .NET that contains over 300,000 unique
packages. This includes packages that work across different .NET models and frameworks,
or dedicated libraries that enhance Blazor by providing UI component or extending its
capabilities within the browser.
Blazor in ASP.NET Core and Beyond
Blazor’s role is to provide an application architecture that uses C#, HTML and CSS
technologies, allowing web developers to write applications using web standards and
the power of the .NET ecosystem. While Blazor has been historically associated with web
applications that run in the browser, its capabilities extend to the server, desktop and
mobile clients as well via .NET MAUI. A block diagram of these technologies can be seen in
Figure 1.
.NET
ASP.NET
Blazor
WebAssembly
Blazor Server
+ .NET
MAUI
Blazor Hybrid
Figure 1: A block diagram of .NET technologies and their relationship to Blazor hosting models.
UI
4
Sli
Routing
© 2023 Progress. All Rights Reserved.
Version and Support
When planning a Blazor application, it is important to consider the currently available
versions of .NET and the roadmap ahead. Generally, each new version of .NET brings new
features to Blazor, while minimizing breaking changes. Since each organization handles
support policies differently, Microsoft enables two choices of support. Customers can
choose Long Term Support (LTS) releases or Standard Term Support (STS) releases.
The quality of all releases is the same. The only difference is the length of support. LTS
releases get free support and patches for three years, shown in Table 3. STS releases get
free support and patches for 18 months. The LTS and STS releases are alternated so that
LTS releases will occur on even-numbered releases while STS releases occur on oddnumbered releases. Each release is expected yearly on or around November 8.
Version
Type
Original Release
End of life
.NET 8
LTS
November 8, 2023*
*November 12, 2026
.NET 7
STS
November 8, 2022
May 14, 2024
.NET 6
LTS
November 8, 2021
November 12, 2024
* Estimated date
The Platform
Blazor initially started as a client-side Singe Page Application (SPA) framework. The goal
of Blazor was to run .NET code in the browser using WebAssembly while using the Razor
syntax for markup templates. The name Blazor is a clever play on words that mixes the
terms Browser and Razor together.
Later in development, the ASP.NET team added a server-side hosting model to Blazor,
named Blazor Server. This methodology was created to help transition developers who
were reluctant to adopt WebAssembly and introduce benefits of server-side rendering
with ASP.NET Core as a webserver.
After several major releases, a third hosting model was added to .NET 7. With this update,
Blazor was combined with .NET MAUI, forming Blazor Hybrid. Blazor Hybrid is a pattern
for building native applications on devices using Windows, macOS, iOS and Android
operating systems.
5
© 2023 Progress. All Rights Reserved.
While each hosting model offers fundamentally different strengths, they all rely on the
same underlying architecture. This approach enables developers to write most of their
code independent of the hosting model. Ideally, the only time code can be identified as
server or client centric is when data is being fetched. Let’s take a closer look at the three
hosting models to further understand their viability.
Blazor Server
With a Blazor Server application, the browser is treated as a thin client. All the application
code is running on the server and using the server’s .NET runtime. The initial page
rendering is handled by the server and delivered to the browser. To enable the thinclient operation, a lightweight SignalR library is booted on the client and establishes
a connection back to the server. The SignalR client then maintains asynchronous
communication with the server using web sockets. Messages sent between the server
and client only contain events and updates, keeping traffic to a minimum. As we can see
in Figure 2, Blazor runs on the server and communicates with the browser over SignalR,
while the thin-client handles updates to the page.
ASP.NET Core
DOM
Blazor
SignalR
Blazor.server.js
.NET Runtime
Figure 2: This diagram shows Blazor running server-side and interacting with the Browser using a SignalR connection.
Client-side
Unlike traditional SPA applications, Blazor Server does not need to fetch data using HTTP
requests. Because the application code runs completely server-side, the data layer may
be tightly coupled without the need for a web API layer to deliver data over HTTP/JSON.
.NET Application
This approach is ideal for line-of-business applications where a persistent connection is
Blazor
available. Because Blazor Server relies on SignalR, it requires a WebSocket connection,
App.dll
.NET Runtime
DOM
therefore
offline mode is not supported.
WA
Instead of using HttpClient as with Blazor WebAssembly, we can instead inject services
into the application for direct use. Ultimately, Blazor Server applications require less
abstractions, don’t require data serialization and deserialization and can be composed
6
© 2023 Progress. All Rights Reserved.
.NET
within a single project. Project size and scope can also affect the abstraction level, but it’s
nice to have the choice of staying uncomplicated.
The Blazor framework doesn’t prescribe how you design your application layers. As you
build Blazor applications, you’ll see that the application stack is reduced, especially with
the Blazor Server app model. This challenges you to re-think the design patterns you may
be used to. Modern applications use familiar patterns like Model-View-Controller (MVC),
Mode-View-ViewModel (MVVM) or Mode-View-Presenter (MVP). These patterns work
well and Blazor can adopt these strategies, but the Blazor Server model really shines with
Vertical Slices Architecture.
ASP.NET
.NET
+ MAUI
Blazor
Blazor
Server
Hybrid
Typical
application
models couple together parts of the applicationBlazor
by role: view,
data and
WebAssembly
unit of work. A Vertical Slice Architecture couples the application by common features (ex:
Blog Posts), as shown in Figure 3. The reason this approach works so well in Blazor is the
component model, routing and .NET. The entire application stack can be completed with
only components and .NET classes.
UI
Slice
Routing
Logic
Database
Figure 3: Vertical Slices group code by feature instead of grouping code by function.
Blazor Server is an excellent choice for an application that needs to start quickly and
operate at scale. Because Blazor Server is part of ASP.NET Core, it works natively with
Azure SignalR Service. Azure SignalR Service allows multiple instances to work together to
scale millions of client connections. The service also supports multiple global regions for
sharding, high availability or disaster recovery purposes.
7
© 2023 Progress. All Rights Reserved.
Blazor WebAssembly
Blazor running directly in the browser decoupled from a server is referred to as a Blazor
ASP.NET Core
DOM
WebAssembly app. WebAssembly (wasm) is a binary instruction format for web browsers
Blazoras a compilation target
SignalR
Blazor.server.js
that is designed
for high-level
languages like C++. Blazor leverages
this technology
the .NET runtime, which is compiled to a WebAssembly module (the
.NET via
Runtime
.NET runtime for WebAssembly). As we can see in Figure 4, introducing the .NET runtime
to the browser enables .NET libraries, DLLs, to run directly on the client.
Client-side
.NET Application
Blazor
App.dll
.NET Runtime
DOM
WA
Figure 4: This diagram shows the .NET runtime inside the browser using WebAssembly. Blazor uses this runtime to work
directly with standard .NET libraries.
There are tradeoffs with this approach in the way of performance and package size,
but portability is the real strength here. Blazor WebAssembly applications operate
independently of a server and only rely on connectivity for HTTP/JSON requests. This
makes Blazor WebAssembly ideal for applications that benefit from offline capabilities or
Progressive Web Application (PWA) features.
Since Blazor applications use .NET libraries, there is no requirement to recompile existing
code or use special complier targeting. Application code can be shared across .NET
projects, resulting in writing less code. In a typical JavaScript frontend application, code
for validation and data transfer is often duplicated because it is required in both .NET
and JavaScript layers. This is not required in a Blazor application because both client and
server code can reference the same libraries.
Optional Ahead-of-Time (AOT) Compilation
[WebAssembly] AOT compilation results in runtime performance improvements at
the expense of a larger app size.
8
© 2023 Progress. All Rights Reserved.
Blazor WebAssembly supports ahead-of-time (AOT) compilation, where you can
compile your .NET code directly into WebAssembly. AOT compilation results in runtime
performance improvements at the expense of a larger app size.
Without enabling AOT compilation, Blazor WebAssembly apps run on the browser using
a .NET Intermediate Language (IL) interpreter implemented in WebAssembly. Because
the .NET code is interpreted, apps typically run slower than they would on a server-side
.NET just-in-time (JIT) runtime. The AOT performance improvement can yield dramatic
improvements for apps that execute CPU-intensive tasks. The drawback to using AOT
compilation is that AOT-compiled apps are generally larger than their IL-interpreted
counterparts, so they usually take longer to download to the client when first requested.
Blazor Hybrid with .NET MAUI
Blazor can be used for cross platform application development and be deployed to
Windows, macOS, iOS and Android. Cross platform capabilities are derived from .NET
Muliplatform Application User Interface (.NET MAUI). .NET MAUI is an evolution of
Xamarin.Forms, which initially targeted iOS and Android. .NET MAUI uses a single API to
unify Android, iOS, macOS and Windows APIs into a write-once run-anywhere developer
experience. .NET MAUI apps provide deep access into each native platform.
.NET MAUI is an evolution of Xamarin.Forms.
In a Blazor Hybrid app, .NET code and Razor components run natively on the device.
Components render to an embedded Web View control through a local interop channel.
Blazor Hybrid does not run in the browser, and WebAssembly isn’t involved, as shown in
Figure 5. In Blazor Hybrid, components load and execute code quickly and have full access
to the native capabilities of the device through the .NET platform.
Web View
Web View
DOM
DOM
Blazor
Blazor
.NET
.NET
Native mobile app
Native desktop app
Figure 5: This diagram shows a Blazor Hybrid with .NET MAUI application and its architecture.
9
© 2023 Progress. All Rights Reserved.
Blazor Hybrid support is built into the .NET MAUI framework. .NET MAUI includes the
BlazorWebView control that permits rendering Razor components into an embedded
Web View. By using .NET MAUI and Blazor together, you can reuse one set of web UI
components across mobile, desktop and web.
Blazor United .NET 8 Preview
At the time of writing, .NET 8 Preview 1 has shipped and a roadmap for Blazor in .NET
8 is under development. In .NET 8, the team at Microsoft is working on combining the
benefits of server-side and client-side rendering, using Blazor as the programming model.
The tentative name for this technique is Blazor United. Blazor united will allow developers
to use the best of both Blazor Server and Blazor WebAssembly in a single project, while
minimizing tradeoffs. In addition, new techniques using progressive enhancement and
streaming rendering will be implemented to increase flexibility of the platform.
Choosing the Right Approach
Blazor is a modern web development framework that offers multiple application models: Blazor
Server, Blazor WebAssembly and Blazor Hybrid with .NET MAUI. Choosing an approach for
your next application will depend on the needs of your application and capabilities of the clients
you serve. Because Blazor offers choice, we can tailor the application to our needs.
Since Blazor uses web standard technology, it can target any browser on any platform.
However, Blazor can cater to different needs by leveraging server-side rendering or
client-side rendering. For applications that require offline capabilities, client-side rendering
with Blazor WebAssembly is ideal. This option allows Blazor to run as a progressive web
application (PWA). A comparison between hosting models can be seen in Table 3.
Hosting Model
Operation
Strength
Trade-off
Blazor Server
Server, thin client via SignalR
Speed
Relies on quality internet
connection
Cost of server operation
Direct data access
Blazor
WebAssembly
Browser via WebAssembly
Low/No server cost
Offline support
Large initial payload
Webservices for data access
Blazor Hybrid
Desktop and mobile via .NET MAUI
Desktop and mobile
Desktop and mobile expertise
Blazor United?*
Dynamic mode mixing Blazor
WebAssembly and Blazor Server
Strengths of Server and
WebAssembly
Unknown
Only exists as a roadmap idea
?* Speculation based on .NET 8 roadmap planning.
Table 3: Hosting Models
10
© 2023 Progress. All Rights Reserved.
No matter which Blazor hosting model fits your application, Blazor is a solid choice for
commercial and line-of-business applications. Blazor is the next generation web framework
for .NET developers with great productivity value and low learning curve, especially when
coming from previous .NET technologies.
Inroads
ASP.NET Framework and ASP.NET Core are both web application frameworks using
.NET technologies like C#, but there are some key differences in their performance
characteristics. ASP.NET Core is lightweight when compared to ASP.NET Framework;
it’s faster and more efficient due to memory and performance optimizations in the new
generation of .NET (Core). In addition to raw performance, ASP.NET Core includes crossplatform support, enabling Blazor WebAssembly.
If you’re making your way to Blazor from other ASP.NET technologies, you will likely find
as many similarities as differences. While .NET business logic is easily portable between
.NET frameworks, ASP.NET has evolved over the years, changing the way applications are
configured, initialized and handle middleware. In addition, Blazor has changed the frontend
component model in comparison to MVC (Razor Views) and Web Forms.
Migration Tools
While familiar concepts exist between .NET versions, several differences between technology
stacks need to be addressed during the migration process. To help simplify the process,
Microsoft has released migration tools: the .NET Upgrade Assistant and the Compatibility
library. A summary of these tools can be seen in Table 5. The .NET Upgrade Assistant is
intended to analyze and update .NET Framework projects from the command line (CLI) or
Visual Studio. When the assistant runs on a solution, it will analyze dependencies prior to
upgrade, then it will provide an analysis report for each of the projects in the solution. Once
the analysis is complete, the assistant can determine which projects need to be upgraded and
recommend the order in which the projects should be upgraded. In addition, the assistant can
re-target projects, update or remove dependencies and update recognized configuration files.
Additional manual steps will likely be required, but the assistant simplifies the process and
provides guidance where automation is not available.
Some of the most common issues found when porting existing code from .NET Framework to
.NET are dependencies on APIs and technologies that are only found in .NET Framework because
of its reliance on Windows. The Windows Compatibility Pack provides many of these technologies
as a NuGet package, so it’s much easier to build .NET applications and .NET Standard libraries.
11
© 2023 Progress. All Rights Reserved.
The compatibility pack is a logical extension of .NET Standard 2.0 that significantly
increases the API set. When using the compatibility pack, existing code compiles
with almost no modifications. To keep its promise of “the set of APIs that all .NET
implementations provide”, .NET Standard doesn’t include technologies that can’t work
across all platforms, such as registry, Windows Management Instrumentation (WMI) or
reflection emit APIs. The Windows Compatibility Pack sits on top of .NET Standard and
provides access to these Windows-only technologies. It’s especially useful for developers
who want to move to .NET but plan to stay on Windows, at least as a first step. In that
scenario, you can use Windows-only technologies to remove the migration hurdle.
Tool
Purpose
.NET Upgrade Assistant
to assist in upgrading your .NET applications
to the latest versions of .NET
.NET Upgrade Assistant (Visual Studio)
to assist in upgrading your .NET applications
to the latest versions of .NET from Visual
Studio
Microsoft.Windows.Compatibility
A large portion of .NET Framework APIs that
may be missing from .NET [Core]
Table 5: Migration tools
Performance:
.NET vs. .NET
Framework
Optimizations began with .NET
Core, with the “Core” branding
Migration From .NET Framework
The Migration Assistant is a great start to migrating an application from .NET Framework.
However, it’s important to understand the breaking changes between platforms as the
assistant cannot completely automate the process. Significant changes were made to the
following areas of ASP.NET Framework applications and will require attention:
later being removed from version
6.0 in favor of .NET.
.NET [Core] was designed to
be cross-platform, meaning it
• Project format – ASP.NET Core uses a different project file format than ASP.NET
Framework. You will need to convert your existing project file to the new .csproj
format. You can do this by creating a new ASP.NET Core project and copying the
relevant sections from your existing project file.
can run on Windows, Linux and
macOS, whereas .NET Framework
was designed specifically for
Windows. This cross-platform
architecture allows .NET Core
• New Dependency Model – Older Web Forms projects that may have used
a packages.config file to list the required NuGet packages will need to be
migrated. In the new SDK-style project, packages.config has been replaced with
<PackageReference> elements in the `csproj` project file.
to take advantage of platformspecific optimizations, resulting
in improved performance.
• New Startup Process – The startup process for Blazor has changed from Web Forms
and follows a similar setup for other ASP.NET Core services. The Global.asax.cs file
is the default startup page for Web Forms projects and will need to be rewritten to a
Program.cs file in an ASP.NET Core project.
12
© 2023 Progress. All Rights Reserved.
.NET [Core] has a modular design,
which allows developers to only
include the specific components
they need in their applications.
This reduces the overall size of
the application, resulting in faster
startup times and improved
• HTTP Modules vs. Middleware – HTTP modules and handlers are common patterns
in Web Forms to control the HTTP request pipeline. Web Forms configure modules
and handlers in the web.config file and depend on lifecycle events that are not part of
ASP.NET Core. Instead, ASP.NET Core uses a middleware pipeline with middleware
registered in the Configure method of the Startup class. If the module code is
applicable as middleware, it can be moved. Alternatively a module may need to be
completely rewritten to fit the new middleware pipeline.
performance.
.NET [Core] uses a new runtime,
which is lighter and more efficient
than the runtime used by .NET
Framework. The new runtime uses
just-in-time (JIT) compilation to
optimize code at runtime, resulting
in faster execution times.
.NET [Core] includes several
performance optimizations, such
as better garbage collection,
improved just-in-time (JIT)
• Bundling and Minification – Bundling and minification are performance optimization
techniques for reducing the number and size of server requests to retrieve certain file
types. JavaScript and CSS often undergo some form of bundling or minification before
being sent to the client. In ASP.NET Web Forms, these optimizations are handled at
runtime. The optimization conventions are defined inside the App_Start/BundleConfig.
cs files. In ASP.NET Core applications, frontend tooling such as Gulp and Webpack is
generally used for bundling and minification.
• Form Validation – Data annotations are used for validation in all scenarios. Most Web
Forms validation can be used without modification. A benefit to running in Blazor is
that the same validation logic can be executed without needing custom JavaScript as
C# runs both on the client and in the browser.
compilation and reduced memory
usage via Span<T>. These
optimizations can significantly
improve the performance of .NET
applications.
.NET [Core] includes support
for modern features such as
asynchronous programming,
• Configuration – In a Web Forms project, configuration data is commonly stored
in the web.config file and is accessed with ConfigurationManager. Services using
this configuration were often required to parse objects. With .NET Framework
4.7.2, composability was added to the configuration via ConfigurationBuilder.
ConfigurationBuilder allowed developers to add various sources for the configuration,
which could be composed at runtime to retrieve the necessary values. The
ConfigurationBuilder used in Web Forms was inspired by ASP.NET Core’s
ConfigurationManager, and some of this code may be easier to migrate from.
which can improve performance
by allowing applications
to perform multiple tasks
simultaneously.
Overall, .NET [Core] was designed
with performance in mind, and
its cross-platform architecture,
modular design, new runtime,
performance optimizations and
Migrating From Web Forms
Web Forms and Blazor are both frontend web frameworks based on .NET technology. Both
use HTML, CSS and C# to render components and web pages. Some components, code
and APIs in both Web Forms and Blazor often resemble each other, while their underlying
implementations are different.
support for modern features make
it faster than .NET Framework in
many cases.
Migrating from .NET Framework 4.8 (or less) and Web Forms will require a
complete rewrite of existing applications.
13
© 2023 Progress. All Rights Reserved.
Migrating from .NET Framework 4.8 (or less) with Web Forms to Blazor will require
a complete rewrite of the existing application. However, there are ways to take
advantage of the likenesses between Web Forms and Blazor. A prime example of this is
BlazorWebFormsComponents, an open-source library dedicated to recreating familiar
Web Forms components in Blazor. In figure 6a, using a repeater control in Web Forms
can be written nearly the same in figure 6b using a repeater component from the
BlazorWebFormsComponents library in Blazor.
Figure 6:[a] The ASP.NET Web Forms Repeater usage vs. [b] The BlazorWebComponents Repeater usage.
Web Forms and Razor Components (Blazor) also share similar concepts of codebehind files. Code-behind is a pattern for separating markup from business logic. In
both frameworks, code-behind files are supported through partial classes and follow
analogous naming conventions to identify partials: a component or page name with the
corresponding extension `razor|aspx.cs`. As shown in Figure 7, a Razor Component’s
markup is separated from the code-behind. Note the file names and extensions.
Figure 7: Code-behind using a Razor Component separates the markup from the component logic.
Overall, migrating from Web Forms to Blazor can be a lengthy process, but the benefits of
using Blazor make it worth the effort. Blazor provides a modern, efficient and easy-to-use
web framework that allows developers to create powerful web applications.
14
© 2023 Progress. All Rights Reserved.
Migration FromMVC on .NET
Framework
Migrating from MVC (model-view-controller) on .NET Framework to a Blazor application
with ASP.NET Core follows the same procedure as upgrading a .NET Framework. In
addition to the steps outlined in the section Migration From .NET Framework, the
Controllers and Views for the application will also need to be migrated or rewritten.
In ASP.NET Framework, controllers rely on the System.Web.Mvc namespace and are tightly
coupled to the System.Web assembly. Controllers in ASP.NET Framework also use the
HttpContext object to interact with the HTTP request and response pipeline, which can
make unit testing more difficult.
On the other hand, in ASP.NET Core, controllers are part of the Microsoft.AspNetCore.
Mvc namespace and designed to be more lightweight and modular. They do not rely on
the HttpContext object and instead use a more flexible and extensible RequestDelegate
pipeline. This makes it easier to unit test controllers and to create custom middleware.
Additionally, ASP.NET Core controllers can take advantage of the built-in dependency
injection (DI) system, which allows for more flexible and decoupled code. This means that
controllers can easily consume services and dependencies without having to manually
instantiate them or manage their lifetimes.
Once the application has been migrated, views can be addressed. Most view markup and
logic should transition to ASP.NET Core with little rework. At this point, the application can
transition to using Blazor.
15
© 2023 Progress. All Rights Reserved.
Transitioning From MVC on .NET
Core to Blazor
The ASP.NET Core framework includes several technologies for frontend web development
that can be used interchangeably. Because MVC Razor Views (Model-View-Controller),
Razor Pages and Blazor are all ASP.NET Core technologies, there isn’t a technical “migration”
between frontends. Both MVC and Razor Pages are capable of rendering multiple component
models: Html Helpers, Tag Helpers and Razor Components via the Component Tag Helper.
Blazor only utilizes Razor Components for technical reasons as it is the only component
model to support Blazor’s RenderTree. Therefore, Blazor cannot use HTML Helpers or Tag
Helpers. Figure 8 shows the technologies and their respective component models.
ASP.NET Core
Razor
Views
Razor
Pages
Blazor
HTML Helpers
Tag Helpers
Razor Components
Figure 8: The ASP.NET Core ecosystem of frontend frameworks and their overlapping component models.
By using Razor Views to render Razor Components, existing ASP.NET Core applications
can move individual components and views to Razor Components and completely
transition to Blazor.
To render Razor components from a Razor view in ASP.NET Core, you can use the
<component> tag helper. The <component> tag helper allows you to render a Razor
component as part of a Razor view or page.
16
© 2023 Progress. All Rights Reserved.
In the following example, the <component> tag helper is used to render a Razor
component in a Razor view. The `type` property defines which Razor Component to
render. In addition, the component tag helper chooses whether to render the component
on the client or server by setting the `render-mode` property to either Server or Client.
<component type=”typeof(MyComponent)” render-mode=”Server” />
The component tag helper can also pass data from the view to a component through
the `param` property by specifying a parameter name that matches the name of the
parameter in the Razor Component, prefixed with param-*.
@{
Person personData = new { Name = “John Doe”, Age = 30 };
}
<component type=”typeof(MyComponent)” render-mode=”Server” param-Person=”@
personData “ />
In this example, personData is a variable that contains an object that will be passed to the
MyComponent’s Person parameter. This allows the component tag helper to receive data
from a view and display a Razor Component utilizing Blazor in a single view.
From
Rewrite Views
Breaking Changes
Web Forms
Yes
Yes
MVC on .NET
Framework
Yes
Yes
MVC on ASP.NET Core
No
No
Razor Pages
(ASP.NET Core)
No
No
Table 4: Summary of transitions to Blazor from other .NET technologies.
Migrating from ASP.NET Framework to ASP.NET Core with Blazor requires planning,
rewriting code and careful testing. While the effort is not trivial, the advantages may be
worth the cost of the transition. ASP.NET Core can save valuable server performance,
and Blazor can increase developer productivity. To a lesser extent, ASP.NET Core MVC
applications can immediately take advantage of Blazor using the Component tag helper.
Once the transition is complete, developers can leverage Blazor productivity tools and
expedite app development. Table 4 shows a summary of each migration path to Blazor
and the changes required for a transition to Blazor.
17
© 2023 Progress. All Rights Reserved.
Tooling and Development
Blazor development is supported by a wide variety of tools, including Integrated
Development Environments (IDEs) and Command Line Interfaces (CLIs). Developer
productivity tools can also expedite the development of Blazor applications. Since Blazor is
a web framework that uses standard web technologies, mainstream software solutions can
be of assistance. Table 5 shows a summary of these tools.
Tool
Purpose
Progress® Telerik® REPL for Blazor
Build, Run, Share, Snippets
Microsoft Visual Studio
IDE (Templates, Compile, Debug)
Microsoft Visual Studio Code
IDE (Compile, Debug)
Ryder
IDE (Templates, Test, Compile, Debug)
.NET CLI
Templates, Test, Compile, Debug
Progress® Telerik® Fiddler™
Everywhere
HTTP/S debugging
Table 5: A list of productivity tools that support Blazor development.
Dotnet CLI
The .NET command-line interface (CLI) is a cross-platform toolchain for developing, building,
running and publishing .NET applications. The .NET CLI has evolved over the years to
become an indispensable tool for writing .NET applications. Using the .NET CLI, developers
can access the same templates Visual Studio offers, as well as run Blazor applications and
initialize tests. The .NET CLI makes a great companion when using Visual Studio Code.
Progress Telerik REPL for Blazor
Telerik REPL (Read–Eval–Print Loop) for Blazor is a web-based tool for the Blazor
community to write, run, save and share code snippets (interface shown in Figure 9).
The Telerik REPL for Blazor is an interactive online environment that reads, compiles and
immediately runs your Blazor code. The run-ready environment requires no installation,
account or special tooling—just jump in and start coding.
18
© 2023 Progress. All Rights Reserved.
Writing and running code is just the beginning. Additional features include:
• Saving your code
• Sharing examples with a simple URL
• Embedding with an interactive iframe
• Sharing from your IDE with the help of our Visual Studio extension and Visual Studio
Code extension
• Installing your favorite NuGet packages
• Referencing static files
• Seamless access to Progress® Telerik® UI for Blazor with 90+ truly native, easy-tocustomize UI components
Write, Run and Save
Figure 9: The Telerik REPL for Blazor user interface.
Telerik REPL for Blazor isn’t limited to simple snippets. It enables developers to write,
run and save everything from basic examples like a Counter page to more advanced
Blazor components. The built-in Monaco Editor, aka the code editor that powers VS Code,
provides syntax checking, code completion and error/warning console. In addition to
editing .razor files, the editor also supports using .cs files and static assets for detailed and
complete examples that can be easily shared.
19
© 2023 Progress. All Rights Reserved.
Compilation Diagnostics
Figure 10: The Error List panel shows compilation diagnostics.
No code runs from first attempt (or almost none). That’s why the Telerik REPL provides
compilation diagnostics to ease the processes of fixing problems—informational
messages, warnings and errors in the Error List located below the code pane, as seen
in Figure 10. You can easily find your error precisely with the file name and line number
shown in the Error List.
Sharing and Embedding
Once a REPL is saved, it can be shared by copying the URL directly or using the share icon
from the top toolbar. The share icon makes it easy to share to your favorite social platforms.
Figure 11: The sharing and embedding tabs of Telerik REPL for Blazor.
Sharing Blazor code is a quick way to communicate a creative idea, get help from
community members or teach others. In addition to sharing links, REPLs can be
embedded. As shown in Figure 11, selecting the Embed tab from the Share menu
20
© 2023 Progress. All Rights Reserved.
generates a configurable iframe. The iframe may include the code, the output and/or the
error console. Embedding is great for blog posts and documentation where you would like
to show a Blazor example and allow the user to interact without leaving your site.
Visual Studio
Visual Studio 2022 is the recommended IDE for building Blazor applications using ASP.
NET Core 6 or higher. Visual Studio is Microsoft’s premier IDE for building modern
applications. Every basic concept for building an application is supported by Visual
Studio, including templates, compilation, testing and debugging. In addition, Visual Studio
integrates with version control (Git/GitHub), CI/CD and third-party tools.
Microsoft has extended Blazor support to other editors as well through the Razor Language
Server Protocol. The Language Server Protocol (LSP) is an open-source protocol that defines
a standard way for an editor or IDE to enable features. The LSP enables productivity features
in editors, including code analysis, refactoring and code completion.
Visual Studio Code
Visual Studio Code (VS Code) is a lightweight editor that is a favorite among web and
JavaScript developers. VS Code also supports Blazor development. Much like Visual
Studio 2022, VS Code can help write, run and debug Blazor code. Support includes autocompletion for components and component parameters, data binding and event handlers.
While some tasks like running templates and testing require trips to the CLI, these features
may also be supported by plugins from the vast ecosystem of addons for VS Code.
Ryder
Rider is a fast, powerful cross-platform .NET IDE based on the IntelliJ platform and
ReSharper. It bundles tools for web development and database support so that you have
everything you need to develop Blazor applications. Ryder is a full- featured IDE that
includes templates, compilation, testing, refactoring, debugging and integration with
version control. Ryder is on par with the feature set of Visual Studio and provides a nonMicrosoft alternative to the ecosystem.
21
© 2023 Progress. All Rights Reserved.
Progress Telerik Fiddler Everywhere
Telerik® Fiddler™ Everywhere is a web debugging tool that monitors, inspects, edits and
logs all HTTP(S) traffic and issue requests between your computer and the Internet.
It also fiddles with incoming and outgoing data, enabling you to modify requests and
responses before they reach the browser and test the app without changing any code.
Fiddler Everywhere is a high-performance, cross-platform proxy for any browser, system or
platform, making it a perfect match for debugging Blazor applications.
The Fiddler client logs all HTTP and HTTPS traffic between your computer and the
Internet and helps you analyze and debug the incoming and outgoing traffic from Blazor
applications. You can use the captured traffic to debug issues, identify performance
bottlenecks or share it with your teammates.
HTTP Filters
Modern applications often generate hundreds or even thousands of requests, which
might pollute your session list with lots of unwanted information. Fiddler simplifies traffic
inspection through custom filters that allow developers to easily narrow down traffic to
the application under test. In Figure 12, the HTTP traffic is limited to GET requests on
`localhost` through the filter dialog box.
Figure 12: The Fiddler Filter dialog used to compose filters on traffic.
22
© 2023 Progress. All Rights Reserved.
Mocking Server Responses
Fiddler Everywhere provides a Rules tab tool, which is a powerful feature that enables you
to mock server responses and simulate various real-life scenarios.
You can use the Rule Builder to create rules and automatically trigger a specific request
that meets the criteria set in the rule, as seen in Figure 13. To mock a server response,
enable the Rules tab, set a rule and execute the request that will trigger that rule.
Figure 13: The Fiddler interface with the Rules tab used to mock a server response.
Modern .NET and Blazor work with various developer productivity tools. With several
options for IDEs, developers can choose which tool fits their preference without sacrifice.
In addition to general-purpose tools, Blazor excels in test coverage with libraries and tools
dedicated to ensuring app stability.
Test Coverage
Blazor is a new web application framework that promises to deliver native web client
experiences without the need for writing JavaScript. Developers can comfortably build full
stack web applications with the .NET framework and tools. While many highlight this .NET
foundation as Blazor’s strength, the story of Blazor testing may just be its biggest potential
upside. In this chapter, we’ll discuss the core concepts that make Blazor an ideal candidate
for testing and the tools that support: Unit Testing, Integration Testing and Automated
System Testing.
Because C# is a compiled language and utilizes strongly typed classes, it already ensures
some software stability through the compilation process. Trivial errors are immediately
caught by the compiler as Intellisense helps guide the development process. Now expand
23
© 2023 Progress. All Rights Reserved.
these capabilities full stack where not only server logic benefits but so does your web
client’s logic and UI component code. In addition to these fundamentals, we’ll add in Unit
Tests, Integration Tests, Component Tests, Mocking and Automated System Tests across
the entire application, without switching languages or methodologies.
When it comes to the .NET ecosystem, there are a lot of offerings to get the job done.
Below are tools that I’ve used personally throughout my career and achieved great success
with. Except for bUnit, these tools have existed prior to Blazor’s creation. Since the Blazor
release, these tools have expanded their usefulness to include the new .NET franchise.
Unit Testing
Unit testing is the first chance to ensure individual pieces of business logic are working
as designed. Unit testing is typically executed by the developer before committing a
feature to a larger codebase. These concise tests help find issues early on. When used
in conjunction with debugging, finding and fixing issues takes much less time than if
a bug is discovered later in the application development process. To further enhance
productivity, it’s important that the testing framework requires the least amount of setup
and configuration, while tightly integrating with the development environment.
xUnit
Test Target
Business Logic, View Models
Cost
Free
License
Open Source
Type
Framework
Interface: Test Runner
Visual Studio or CLI compatible
Table 6: A summary of xUnit’s role in testing.
xUnit has a long history in the .NET community and is a natural fit for all .NET projects,
including Blazor. xUnit.net is a free, open source, community-focused unit testing tool for
.NET. Written by the original inventor of NUnit v2, xUnit.net is the latest technology for unit
testing C#, F#, VB.NET and other .NET languages. A summary of xUnit’s role in testing can
be found in Table 6.
24
© 2023 Progress. All Rights Reserved.
One advantage of xUnit is its overall simplicity as the xUnit template is included in the
.NET Core cross-platform development workload. Adding an xUnit test project to your
application is as simple as right clicking a solution and choosing Add > New Project >
xUnit Test Project from the menu in Visual Studio, as seen in Figure 13. Alternatively, you
can run $ dotnet new xunit from the command line.
Figure 13: Create a new project dialog with xUnit.
The minimum requirement for defining a unit test is adding a [Fact] attribute to the
desired method, which includes an Assert statement. Facts are tests that are always true
and test invariant conditions, while Assert is used to verify that the conditions have been
met by the test. Fact and Assert are used in the example below to test the Add method. In
the PassingTest example, the Assert validates the expected output of 4 against Add(2,2).
To demonstrate a failing test, if the expected output were 5, the Assert would fail.
public class Class1
{
[Fact]
public void PassingTest()
{
Assert.Equal(4, Add(2, 2));
}
[Fact]
public void FailingTest()
{
Assert.Equal(5, Add(2, 2));
}
}
int Add(int x, int y)
{
return x + y;
}
25
© 2023 Progress. All Rights Reserved.
Since Blazor applications utilize .NET for the client and server, both server and client share
logic. xUnit is ideal for the shared layer of a Blazor application as the code here has a
generalized purpose. As seen in Figure 14, we can reference the entire application from
our xUnit project to test common code.
Figure 14: Adding project references.
In the following example, we’ll test validation logic that can be used to validate a correctly
formatted email address. The email validator could be used for checking UI input on the
client, validating a web endpoint post or safeguarding a database record about to be
written on the server.
[Fact(DisplayName = “Can validate correctly formatted email address”)]
public void ValidateCorrectlyFormattedEmail()
{
// Arrange
var validEmail = “fake@domain.com”;
var uut = new EmailValidator();
// Act
var isValid = uut.Validate(validEmail);
// Assert
Assert.True(isValid);
}
This would not be possible in a traditional web application written with a JavaScript client.
Instead, a separate validator and test would need to be created for the JavaScript client
using a completely different tool chain. The strengths of a full stack .NET application aren’t
just seen in shared logic either. With Blazor component testing, we can test the individual
UI components too.
26
© 2023 Progress. All Rights Reserved.
Blazor Component Testing
Web developers are accustomed to the instant feedback the browser provides. Many
frameworks even include built-in mechanics for refreshing the application in real-time, or
“hot reloading”. Hot reloading is often used to quickly prototype components or features
for a given application. While hot reloading is certainly a tool that has its place in web
development, it’s possible it is relied upon too much. Blazor has yet to provide such a tool,
although one is in development (https://github.com/dotnet/aspnetcore/issues/5456). An
arguably better alternative is unit testing UI components. A UI component can be isolated,
tested and quickly iterated upon just as application logic can be. Through unit testing,
we can achieve quick results without the overhead of loading the application, or even the
browser. To test Blazor components, we’ll use a new framework called bUnit, specifically
designed for this purpose.
bUnit
Test Target
UI Component
Cost
Free
License
Open Source
Type
Framework
Interface: Test Runner
Visual Studio or CLI compatible
Table 7: A summary of bUnit’s role in testing.
bUnit is a testing library for Blazor Components. A summary of its role is shown in Table
7. Its goal is to make it easy to write comprehensive, stable unit tests. bUnit builds on top
of existing unit testing frameworks such as xUnit, NUnit and MSTest, which run the Blazor
components test as any normal unit test. bUnit runs a test in milliseconds, compared to
browser-based UI tests that usually take seconds to run.
bUnit can set up and define components under tests using either C# or Razor syntax. bUnit
includes methods to verify component rendering using a semantic HTML comparer. Parameters,
cascading values and injecting services into components under test are made easy with bUnit’s
comprehensive helper methods. Even triggering event handlers and exercising a component’s
interactive features are supported by the library.
Adding a bUnit test project to your application can be done in several ways. One of the easiest
solutions is to install the bUnit project template and create a new project using the command line.
27
© 2023 Progress. All Rights Reserved.
dotnet new --install bunit.template
dotnet new bunit -o <NAME OF TEST PROJECT>
With the bUnit project created, we’ll add our project or component library as a reference.
dotnet sln <NAME OF PROJECT>.sln add <NAME OF TEST PROJECT>
dotnet add <NAME OF TEST PROJECT>.csproj reference <NAME OF COMPONENT
PROJECT>.csproj
Now we can test individual components in isolation with bUnit. Since we’re already
familiar with xUnit, we’ll continue with the xUnit [Fact] conventions and write our first
bUnit test. A common component test is to ensure that a component initializes correctly
and renders the appropriate HTML markup. This can be accomplished with bUnit’s
RenderComponent<TComponent> method.
Component Unit Test Example: Initialization and Rendering
In the following example, we’ll test the initially rendered Alert component. The component
should render a styled div element with an internal close button. The expected rendered
HTML is declared as the string expectedMarkup. Since bUnit relies on “semantic HTML
comparison”, the expectedMarkup doesn’t need to be exact match, but rather an HTMLequivalent match. Aspects that don’t affect component behavior or visual representation
are ignored, including comments, insignificant white space, css class order and implicit
attributes, just to name a few.
// bUnit library
using Bunit;
// xUnit framework
using Xunit;
// Component library or Blazor application
using TestableBlazor.Client;
[Fact(DisplayName = “Initial Alert renders correct markup”)]
public void AlertInit()
{
// Render an Alert component
var cut = RenderComponent<Alert>();
// Expected HTML rendered
string expectedMarkup = @”
<div class=””alert alert-danger””>
<button type=””button”” class=””close”” aria-label=””Close””>
<span aria-hidden=””true””>&times;</span>
</button>
</div>”;
}
// Did it match?
cut.MarkupMatches(expectedMarkup);
28
© 2023 Progress. All Rights Reserved.
In this test, an instance of the Alert component is created, and the component lifecycle
is fully completed. The final rendered component markup is then checked against the
expected markup.
Component Unit Test Example: Parameter Set
bUnit can go beyond initialization rendering tests and test specific sections of markup
based on component parameters. In the next example, we’ll assume we have a component
that has two distinct color themes represented by a CSS class. We can toggle the themes
by setting the IsInfo parameter to change the CSS class. The following example renders
the component with the parameter set to true and checks only the CSS class attribute on
the rendered component.
[Fact(DisplayName = “Alert has info theme”)]
public void AlertColor()
{
// Render the component with IsInfo = true
var cut = RenderComponent<Alert>(parameters =>
parameters.Add(p => p.IsInfo, true));
// The expected CSS value
var expectedCss = “alert alert-info”;
// The actual CSS value
var actualCss = cut.Find(“div”).GetAttribute(“class”);
// Did it match?
Assert.Equal(expectedCss, actualCss);
}
In this test, an instance of the Alert component is created and the component’s IsInfo
parameter is set to true. The rendered markup is then checked for the class attribute and
the value is validated against the expected CSS class.
Simple component unit tests like these can quickly accelerate the development process. Since
the application and browser are not involved, the process is much faster and more accurate
compared to manually loading the application and checking the rendered output visually.
Mocks
Some components require external dependencies, such as binding the component to a
data source. In addition, components can require other components or be assembled into
tightly coupled views like a form. Whether we’re writing complex unit tests or integration
tests, we need additional help setting the stage with controlled dependencies. As we
29
© 2023 Progress. All Rights Reserved.
expand the Blazor application’s testing capabilities, it’s helpful to have a mocking library
that works well with our test environment.
Mocking is a process used in unit testing when the unit being tested has external
dependencies. The purpose of mocking is to isolate and focus on the code being tested
and not on the behavior or state of external dependencies. In mocking, the dependencies
are replaced by closely controlled replacements objects that simulate the behavior of the
real ones.
A Mock is a sophisticated substitute that will still return a predetermined value. It can also
be programmed with expectations in terms of how many times each method should be
called, in which order and with what data.
Progress Telerik JustMock
Integration
xUnit, bUnit
Cost
Free, Bundled*, Commercial
License
Open Source
Type
Framework
Interface: Test Runner
Visual Studio or CLI compatible
*Included with some Telerik DevCraft™ licenses
Table 8: A summary of JustMock’s role in testing.
Telerik® JustMock is a library that can create a Mock with just one line of code, making
complex tests easy to arrange. A summary of its role in testing is shown in Table 8.
The free version of Telerik JustMock can be installed through NuGet. In addition, a
commercial version with advanced features (https://www.telerik.com/justmock/freemocking) is also available.
dotnet add package JustMock --version 2020.3.1019.2
With the JustMock dependency added to our test project, we can quickly begin mocking
services required by components.
30
© 2023 Progress. All Rights Reserved.
Component Unit Test Example: Mocking Injected Services
When using or testing Blazor components, it’s common to see the following error: “There
is no registered service of type ‘T’.” This usually means a service has not been registered
with dependency injection. Given a component under test that uses dependency injection,
rendering a component under test without a dependency will result in error.
Component:
@* Index.razor *@
@inject IDataService DataService
protected override async Task OnInitializedAsync()
{
Model.Regions = await DataService.GetRegions();
}
Test:
var cut = RenderComponent<Index>();
// Error: There is no registered service of type ‘IDataService’
In the case of component unit testing, we still need to register a service and should do
so with a means of isolating the results from that service to avoid nuances in non-static
data that can influence the test. Using JustMock, we can complete the test by mocking
the IDataService interface directly. In the following unit test code, an IDataService mock is
created by calling Mock.Create. With the mock created, we can then use JustMock to arrange
the method IDataService.GetRegions so it returns a predictable response. With the mock
arranged, the mocked service is then added to bUnit’s dependency injection container.
private void AddMockDataService()
{
var mockDataService = Mock.Create<IDataService>();
Mock.Arrange(() => mockDataService.GetRegions())
.Returns(
Task.FromResult(new string[] { “AA”, “BB”, “CC”, “DD” })
);
// Register service with bUnit
Services.AddSingleton(mockDataService);
}
31
© 2023 Progress. All Rights Reserved.
With the service available to the component, unit tests can now be executed to test
functionality. In the following test example, the Index component is rendered, then the
component’s Model property is checked to see if it: a) successfully calls the mocked
service, b) populates with data and c) the first data item is selected by default.
[Fact(DisplayName = “Region first item is selected on initialization.”)]
public void SettingRegionSelectsFirstItem()
{
AddMockDataService()
// Render the index page
// Component internally calls IDataService.GetRegions()
// and IDataService.GetTeamsByRegion()
var cut = RenderComponent<Index>();
// Get the Model property from the component Instance
var cutModel = cut.Instance.Model;
// Did our Model bind?
// Was the Regions collection filled?
Assert.Collection(cutModel.Regions,
i => i.Contains(“AA”),
i => i.Contains(“BB”),
i => i.Contains(“CC”),
i => i.Contains(“DD”));
// Was the first Region selected by default?
Assert.Equal(“AA”, cut.Instance.Model.SelectedRegion);
}
After each component is thoroughly tested individually, integration between components
can be tested to ensure they perform specific tasks or activities.
Component Integration Test Example: Cascading Dropdowns
Testing components as a group through integration testing ensures the features of an
application behave as expected and communication between units is faultless. In the
following example, we’ll test the integration between the component life cycle, form model
and multiple dropdown components. In a cascading dropdown scenario, one list influences
the results of another by fetching new data when the primary list is changed.
@page “/”
@inject TestableBlazor.Client.Services.IDataService DataService
<EditForm Model=”Model” OnValidSubmit=”HandleValidSubmit”>
<div class=”form-group”>
<label for=”regionSelect”>Region</label>
<TelerikDropDownList Value=”Model.SelectedRegion”
ValueChanged=”@((string s) =>
SelectedRegionChanged(s))”
ValueExpression=”@(() =>
32
© 2023 Progress. All Rights Reserved.
Model.SelectedRegion)”
Data=”Model.Regions”
Id=”regionSelect”
Class=”form-control”
Width=”100%” />
</div>
<div class=”form-group”>
<label for=”teamSelect”>Team</label>
<TelerikDropDownList @bind-Value=”Model.SelectedTeam”
Data=”Model.Teams”
Id=”teamSelect”
Class=”form-control”
Width=”100%” />
</div>
<TelerikButton Id=”formSubmit” Primary=”true”>Submit</TelerikButton>
<TelerikButton Id=”formReset” ButtonType=”ButtonType.Reset”>Cancel</
TelerikButton>
</EditForm>
@code {
[Parameter]
public UserFormViewModel Model { get; set; } = new UserFormViewModel();
public async Task SelectedRegionChanged(string value)
{
Model.SelectedRegion = value;
Model.Teams = await DataService.GetTeamsByRegion(Model.
SelectedRegion);
Model.SelectedTeam = Model.Teams[0];
}
protected override async Task OnInitializedAsync()
{
// init here
Model.Regions = await DataService.GetRegions();
await SelectedRegionChanged(Model.Regions[0]);
}
}
The following test will verify that the component’s Model property has the correct
SelectedTeam value when the primary dropdown, SelectedRegion, is changed. To do this,
JustMock provides a reliable output from IDataService.GetTeamsByRegion.
Mock.Arrange(() => mockDataService.GetTeamsByRegion(Arg.AnyString))
.Returns(
(string region) => Task.FromResult(
new string[] { $”Red {region}”, $”Green {region}”, $”Blue {region}”
})
);
bUnit is then used to invoke the dropdown’s change event, simulating form interaction.
33
© 2023 Progress. All Rights Reserved.
[Fact(DisplayName = “Selecting a region selects the first team value.”)]
async Task SettingRegionSelectsFirstTeam()
{
// Render the index component
var cut = RenderComponent<Index>();
// Invoke a drop-down change
await cut.Instance.SelectedRegionChanged(“BB”);
// Is the model the correct selected team?
Assert.Equal(“Red BB”, cut.Instance.Model.SelectedTeam);
}
This test confirms that the correct methods are invoked on IDataServce when the form
is initialized, and the dropdown is exercised. It also confirms that the Model persists the
correct value from the child dropdown through two-way data binding.
Integrated tests can be conducted by either developers or independent testers and are
usually comprised of a combination of automated functional and manual tests. Testing
with xUnit, bUnit and JustMock can cover many of the developer conducted tests, while
automated system testing can benefit from additional tooling.
Blazor Automated System Testing
System testing is a testing method used to evaluate the completed and integrated system.
When testing a Blazor application, a system test proves the end-to-end functionality of
the software is tested, which may include cross-browser compatibility, authentication, live
services and much more. Designing such tests and executing them in an automated way may
sound daunting, but the right tool for the job can greatly decrease the overhead and cost.
Progress Telerik Test Studio
Cost
Bundled*, Commercial
License
Open Source
Type
Framework
Interface: Integrated Test Environment (ITE)
Manage & Edit
Interface: Test Recorder
Browser Plugin
Interface: Test Runner
ITE, Visual Studio or CLI compatible
Interface: CI/CD
Azure DevOps, Jenkins, Bamboo,
compatible
* Test Studio Dev Edition included with some Telerik DevCraft licenses
Table 9: A summary of Test Studio’s role in testing.
34
© 2023 Progress. All Rights Reserved.
Telerik Test Studio is a commercial automated system testing suite of tools with built-in
integration with Telerik UI for Blazor. Test Studio includes both codeless and code-based
automation capabilities in a modular framework to enable both QA and developers
regardless of their expertise. Tests created in Test Studio, both recorded and code-based,
are native C# (.NET) code and can be edited within Visual Studio. A summary of Test
Studio’s role in testing can be found in Table 9.
Blazor Automated Test Recorder
Building tests manually is a time-consuming process. With Test Studio, the busy work of
building tests can be greatly reduced through its intuitive test recorder. Simply record your
test and use automated playback for an easy and fast way to craft your test and then run
that same script to test multiple browsers. The Test Studio recorder/player can be used as
a stand-alone application or Visual Studio plugin. The stand-alone application is ideal for
Quality Assurance (QA) as they’re not required to use Visual Studio (VS), thereby freeing
up VS licenses and allowing QA to focus on the task at hand.
Automated Test Example: Form IO
There are many test scenarios that could be created for a form. We could validate the
initial state of the form, ensure all the labels are rendered, default values are set and
verify validation on submission of a default form. We can also exercise the form, checking
its inputs and outputs and validating a successful submission. Let’s look at the former
scenario by recording a test that [shown in Figure 15]:
1. validates the default birthdate of “1/1/1980”
2. exercise the region drop down
3. validates the cascading Team drop down is loaded
4. exercise the submit button
Figure 15: The state of a form, validating
birthdate, region and team fields and
submit button.
35
© 2023 Progress. All Rights Reserved.
When the form’s submit button is exercised, we can also instruct our test to validate the
output or completed state of the form [shown in Figure 16]:
1. Thank You modal is displayed
2. The Thank You message is displayed
3. A close button is visible to the user
Figure 16: The state of a completed form, a thank you title and message and dismiss button are validated.
Recording the test with the Test Studio application or VS plugin is as simple as point and
click. We’ll start recording a test by [shown in Figure 17]:
1. Creating a new test
2. Choosing Record
3. Entering the desired URL to test
4. Clicking Record
Figure 17: The Test Studio interface and steps to record a new test.
36
© 2023 Progress. All Rights Reserved.
Test Studio will navigate to the application under test and begin recording user
interactions such as mouse and keyboard events. A test recorder panel is also displayed
beneath the running browser. This panel works similarly to the developer console included
with most browsers, most notably the element inspection tool developers are familiar with.
With the element selection tool enabled, the user can simply hover over HTML elements
for a contextually aware menu of potential test steps, seen in Figure 18. In addition, if the
application makes use of Telerik UI for Blazor, Test Studio will detect the UI components
and display their corresponding properties and values.
Figure 18: The Test Studio context menu and steps to validate a data input.
After each step is recorded and the browser is closed, a list of all test steps is displayed
in Test Studio, where they can be fine-tuned (by GUI or C# code), removed or executed.
In the following example, we can review the test created by the recorder with verification
steps for validating various inputs and outputs of the form. Shown in Figure 19 is a test
with 15 steps, including recorded actions and validations.
Figure 19: The Test Studio interface shows a test with 15 steps, including recorded actions and validations.
37
© 2023 Progress. All Rights Reserved.
This is just one example of how Test Studio can be used to test a Blazor application. The
Test Studio suite goes well beyond the scope of this chapter with features for:
• Cross Browser Testing
• Responsive Web Testing
• OCR and image comparison
• CI/CD integration
• API Testing
• Load Testing
• And much more
The examples shown in this chapter highlight four tools that cover the full spectrum of tests
generally used in application stability testing. Blazor and the .NET ecosystem provide a solid
base for unit testing, integration testing and automated system testing. Unit tests with xUnit
provide a quick feedback loop for testing common logic, while bUnit expands this ability
to Blazor components. As the application testing progresses into integration tests, Telerik
JustMock delivers an easy-to-use API set for mocking reliable dependencies. When it comes
to automated system testing, Telerik Test Studio embraces the .NET stack and further adds
value with tight integration with Telerik UI for Blazor. These testing tools not only cover
their intended niche but are robust enough to provide some overlap. Adopting these Blazor
stability testing tools is key to bullet proofing your next Blazor application.
Managing CSS Scope with
Razor Components
Choosing the architecture for styling a Blazor application will directly affect the ability
to customize the theme of a Razor component, or even the entire application. Deciding
to use a global CSS file to apply a theme or component CSS isolation will directly impact
the scope of customization that can be applied to elements in the application. In this
chapter, we’ll discuss the varying levels of CSS scope in a Blazor application and explore
opportunities to expand or restrict access to theme changes.
Let’s begin by defining what CSS scope is in this context—not to be mistaken for CSS
Scoped Styles. We’ll be discussing the scope of CSS properties and how they relate to
Razor components and the access level to CSS properties within Razor components.
There is currently a CSS spec that defines “CSS Scoped Styles”. CSS Scoped Styles
introduce a syntax to CSS, enabling developers to write scoped selectors using an @
syntax similar to CSS media queries.
38
© 2023 Progress. All Rights Reserved.
Global Scope
Generally, a property in CSS is globally accessible because, whenever a statement using
the same selector appears afterwards, it will override the previous value. In the following
example the color property is assigned the value red, then the second statement reassigns
the color property to blue.
.selector {
/* property: value; */
color: red;
}
.selector {
color: blue; /* blue replaces red */
}
The result is similar to what we might expect from a public class property in C# code.
Because the property is public, it can be arbitrarily reassigned by a developer by setting
the property.
public class Foo
{
public string Color { get;set; }
}
fooInstance.Color = “red”;
fooInstance.Color = “blue”;
// fooInstance.Color value is “blue”
In this example, color is essentially on the global scope. There is no way to control access
to color or when and how the property is assigned. If we need to control access, we’ll need
to apply some architectural patterns to do so. Through features of Blazor and CSS, we can
control the scope of style using CSS Isolation, but we first need to understand the impact
our decisions have.
CSS Isolation
In Blazor, CSS Isolation is a compiler feature that isolates CSS styles to individual pages,
views or components. Components using CSS Isolation can reduce or avoid dependencies
on global styles.
39
© 2023 Progress. All Rights Reserved.
CSS Isolation is also intended to assist with application maintenance in two ways. Isolated
CSS code is associated with a specific component through unique selectors that remove
the CSS from the global scope. In addition, all CSS generated by the compiler from CSS
Isolation is output to a file named {Namespace}.style.css. By auto-generating the CSS at
compile time, we get a fresh copy on the CSS. If a component is deleted, the CSS is also
removed from the compilation.
Style conflicts in CSS Isolation are managed by creating CSS selectors with a very high
specificity, making them impossible to override by mistake. The effect is an anti-pattern,
taking the “Cascading” out of Style Sheets (CSS). In the following example, a component
named Bubble is created. Bubble.razor is the markup for the component, while Bubble.
razor.css is the isolated CSS belonging to it. Together, they create a round shape with a
blue background and white text. The compiler will alter the HTML and CSS by adding
a unique id in the form of an HTML attribute for each element in the component. The
unique id is also incorporated into the selectors of the CSS so that the id must be used to
style the associated HTML.
@* Bubble.razor *@
<div>
<span class=”inner”>@Text</span>
</div>
/* Bubble.razor.css */
.inner {
font-size: 16px;
background-color: blue;
color: white;
/* properties make it round */
line-height: 3em;
height: 3em;
width: 3em;
border-radius: 50%;
display: block;
text-align: center;
margin: .5em;
}
The code from above is altered by the compiler to produce the example below as it
would be rendered by the browser. The unique identifier b-qfafx2ebd2 ensures that the
selector is not accidentally used to overwrite any of the element’s properties.
@* rendered Bubble HTML *@
<div b-qfafx2ebd2>
<span b-qfafx2ebd2 class=”inner”>@Text</span>
</div>
/* rendered Bubble.razor.css */
.inner[b-qfafx2ebd2] {
font-size: 16px;
40
© 2023 Progress. All Rights Reserved.
background-color: blue;
color: white;
/* properties make it round */
line-height: 3em;
height: 3em;
width: 3em;
border-radius: 50%;
display: block;
text-align: center;
margin: .5em;
}
In this example, one component uses a global scope, while the second uses CSS Isolation.
In the main page, a snippet of CSS overwrites the color value, which has no effect on the
isolated component in the corresponding Figure 20.
<main>
<Bubble Text=”B1”/>
<IsolatedBubble Text=”B2” />
</main>
<style>
.inner {
background-color: red;
}
</style>
Figure 20: Component B1 is affected by the style snippet, while component B2 remains unchanged due to CSS isolation.
Using CSS Isolation takes scope from global to completely private. To change the value of
a property used in isolated CSS, the source code must be updated. If we try to equate this
to C# code, it would resemble a const.
public class Foo {
const string color = “red”;
}
This approach creates a very rigid but safe design where nothing can influence the style of
the component. However, the source code must be edited and the application will need to
be recompiled.
41
© 2023 Progress. All Rights Reserved.
The Scale of CSS Scope
With Blazor, there are essentially two architecture patterns that exist by default: standard
CSS implemented in the global scope and the completely private scope of CSS Isolation.
The type of Razor Components you’re developing and their desired usage will decide what
approach works best. The decision should reflect what type of customization your end
users need.
For some scenarios, global customization might be ideal. One such example would be a
generic component library with many consumers. Organizations using a component library
will likely implement their own design system and theme the components to match. Teleik
UI for Blazor is one such library that offers this sort of customization.
On the opposite end of the spectrum, an organization developing an internal application
or line-of-business app would benefit from CSS Isolation. A single design system is
implemented and enforced through CSS Isolation. While rigid, it reduces the ability for
developers to circumvent the design system, especially by accident.
These polar-opposite architectures can also be blended to create another option. This option
would allow a sliding scale of scope so an organization can define where customization is
allowed and how broadly it is applied, using global, local and private scopes.
Global Customization:
1. Globally scoped CSS (or Sass) themes the application
2. No CSS Isolation is used
3. A
ny developer with access to the CSS source can change all aspects of the
application’s theme
Balanced Customization:
1. Global variables control the theme of the application and components
2. Some CSS Isolation is used
3. Components can be themed at the instance level through properties
Minimal Customization:
1. CSS Isoliation is used as much as possible
2. Theme is fully controlled through component properties
3. Changes to the theme through .razor.css files are managed
Customization features can be implemented through techniques using component
parameters and CSS custom properties. Together, we can control access much like C# can:
with private and public modifiers. Finding the right balance on the scale will depend on the
requirements, but the techniques provide flexibility.
42
© 2023 Progress. All Rights Reserved.
Customization Through Parameters
Allowing customization of components through parameters is fairly common when
creating Razor components. Parameters create an API for developers to make changes in a
controlled manner. Parameters can be loosely defined strings or use more restrictive types
like bools, enums or classes. When parameters are used to theme a component, it’s often
to give developers a choice between predetermined variations of the component’s theme.
Let’s begin with an example of restricting a components theme to simple variations using
an enum. For this example, we’ll create a button component with five theme options:
Default, Info, Warning, Danger and Success. These themes provide developers with options
via parameter, but also will restrict the customization to the included theme variations
using CSS Isolation.
First, we’ll create a Button component and define the enum values.
<button @onclick=”@HandleClick”>@Text</button>
@code {
//.. simplified for example purposes
public enum ButtonTheme {
Default,
Danger,
Warning,
Info,
Success
}
}
Next, we’ll add a Theme parameter and modify the button markup to write out a class
attribute based on the parameter’s value. The value will append the text button to the
lowercase value of the enum selected.
<button class=”button @Theme.ToString().ToLowerInvariant()” ...
@code {
//.. simplified for example purposes
[Parameter]
public ButtonTheme Theme { get; set; } = ButtonTheme.Default;
}
43
© 2023 Progress. All Rights Reserved.
Then a .razor.css file is created using CSS Isolation, Button.razor.css. In the CSS file,
we’ll write the corresponding CSS for the various button themes. The selectors will include
button with the appended theme selection, ex: button.{ButtonTheme}.
.button {
/* default && base button style */
color: #1d1942;
background-color: transparent;
border: 2px solid #1d1942;
/*... shortened for simplicity*/
border-radius: 5px;
}
.button:hover {
color: white;
background-color: #1d1942;
}
.danger {
color: #e55039;
background-color: transparent;
border: 2px solid #e55039;
}
.danger:hover {
background-color: #e55039;
}
/*... shortened for simplicity*/
The complete component offers a few theme options strictly defined by the API using
enums. Using the Button component is straightforward since the only acceptable values
come from ButtonThemes. The output of each instance can be seen in Figure 21.
<Button Text=”Default”/>
<Button Text=”Info” Theme=”@Button.ButtonTheme.Info”/>
<Button Text=”Danger” Theme=”@Button.ButtonTheme.Danger”/>
<Button Text=”Warning” Theme=”@Button.ButtonTheme.Warning”/>
<Button Text=”Success” Theme=”@Button.ButtonTheme.Success”/>
This pattern offers some flexibility and is easily understood by the developer using the
component. In addition, all the CSS code is protected by CSS Isolation, which means no
other button variations can be made and no accidental CSS collisions can occur.
Figure 21: A button component renders different colors based on the parameter value applied.
44
© 2023 Progress. All Rights Reserved.
Using parameters, we opened up our component to modification. With this approach, we’re
still required to edit the source code for any theme changes. If there was a need to use this
component with a new design system or a major re-branding, it would take a considerable
amount of work to update all the component’s individual CSS Isolation files. Fortunately, we
can expand our scope to be completely flexible while remaining prescriptive by leveraging
CSS custom properties.
CSS Custom Properties (Variables)
Adding CSS custom properties to your architecture will vastly improve the extensibility of
your CSS code. Custom properties (also referred to as CSS variables) are entities defined in
CSS that contain specific values to be reused throughout a document. To use variables, a
name is declared using the --* syntax, then the var() function is used to retrieve the value.
/* set */
--primary-color: blue;
/* get’s blue from --primary-color */
color: var(--primary-color);
When using the var function to get a value, a second parameter can be passed as default
to be used in case the variable is invalid. Default values help us write reliable code and
provide a scheme to make some values optional.
/* --my-var not declared */
color: var(--my-var, red);
/* result -> red */
/* set --my-var */
--my-var: blue;
color: var(--my-var, red);
/* result -> blue */
Variables are scoped to the element they are declared on and participate in the cascade. In
addition, variables inherit their values from their parent. Together, these rules form scope
within the stylesheet and can be leveraged to create additional scope within components.
Using CSS variables and CSS Isolation together, we can define scope in several ways,
getting the best of both global and private access. With this pattern, we’ll use CSS
variables and their scoping mechanics to define global scope, then use default values to
create private scope, and control access with component parameters.
45
© 2023 Progress. All Rights Reserved.
First, we’ll need to set up the global variables for our application. These variables will be
defined in a standard app.css file. The global variables need to be defined in a :root
selector and include things like our application theme colors.
:root {
font-size: 32px;
--app-background: antiquewhite;
--primary-color: #aeeaf8;
--primary-color-darker: #1d1942;
--primary-contrast-color: var(--app-background);
--secondary-color: #fd6012;
--secondary-color-darker: #ed451f;
--secondary-contrast-color: var(--app-background);
}
Next, we’ll create a component. For simplicity, we’ll reuse the previous Bubble component
that renders a circle with text inside.
<div>
<span>@Text</span>
</div>
@code {
[Parameter]
public string Text { get; set; } = “CSS”;
}
Then, we’ll use CSS Isolation by creating a .razor.css file for the Bubble component. Inside
the file, we’ll define the basic style of the span element.
span {
font-size: 16px;
background-color: blue;
color: white;
line-height: 3em;
height: 3em;
width: 3em;
border-radius: 50%;
display: block;
text-align: center;
margin: .5em;
}
46
© 2023 Progress. All Rights Reserved.
Now it’s time to define access to the component’s style. Let’s start by giving developers an
API for changing the components’ colors. Right now, the color and background-color
properties are hard coded to white and blue. Let’s update these with CSS variables that are
privately scoped. To make this effective, we’ll rely on inheritance by adding a class to the
outer container of the component. For this example, the class is named private-scope.
<div class=”private-scope”>
<span>@Text</span>
</div>
A new CSS selector .private-scope is added and inside CSS variables are defined.
Because of CSS isolation, these variables will be private to the component. Here we can
control how the theme is applied by using default values. A variable named --privatebubble-bg-color is added and uses a currently undefined variable --bubble-bgcolor as its color. A default is used to get the global color from the application theme
--primary-color. This setup enables the Bubble to receive color from the application’s
theme, or if a developer defines --bubble-bg-color, then all Bubble component instance
will receive that color instead.
--private-bubble-bg-color: var(--bubble-bg-color, var(--primary-color));
--private-bubblecolor: var(--bubble-text-color, var(--primary-colorcontrast));
}
To enable these new values, we’ll wire up the privately scoped values by updating the
span selector.
.private-scope { ... }
span {
background-color: var(--private-bubble-bg-color);
color: var(--private-bubble-color);
The Bubble component will render using its default settings if no color is specified by
the :root selector. The rendered component will be a blue background with white text, as
shown in Figure 22.
Figure 22: A rendered Bubble component with the default color scheme.
47
© 2023 Progress. All Rights Reserved.
If we would like to use the public setting to theme all Bubble component instances, we can
update the :root selector in app.css. In the example below, the Bubbles are now themed
using secondary instead of primary colors. Now when the Bubble component is rendered,
it will use the settings defined in :root. The result is shown in Figure 23.
:root {
…
/* set the Bubble color theme globally */
--bubble-bg-color: var(--secondary-color-darker);
--bubble-text-color: var(--secondary-contrast-color);
}
Currently our Bubble component uses a global and private scope. Let’s expand upon the
example by adding public scope. By adding parameters to our Bubble component, we can
allow individual customization per component instance.
Figure 23: A rendered Bubble component with the color scheme defined in :root.
We’ll start by adding properties to our Bubble component for setting the background color
and text color. Since these parameters will be CSS color values, we’ll use the string value type.
[Parameter]
public string? BackgroundColor { get; set; }
[Parameter]
public string? TextColor { get; set; }
Next, we need to translate the properties into an inline style. While it’s uncommon to see
inline styles used in modern HTML/CSS, in this case we’re leveraging their specificity to
override the variable values at the element level. This ensures we override CSS isolation
as well as any inherited values. The desired output from our code will be an inline style
with the format --private-bubble-bg-color: value; --private-bubble-color:
value. To simplify the process, we’ll use a NuGet package called BlazorComponentUtilities.
BlazorComponentUtilities’s StyleBuilder defines an inline style with a fluent API.
48
© 2023 Progress. All Rights Reserved.
The following code creates an inline style while eliminating the properties if they are null.
string inlineStyle => StyleBuilder.Empty()
.AddStyle(prop: “--private-bubble-bg-color”,
value: BackgroundColor,
when: !string.IsNullOrWhiteSpace(BackgroundColor))
.AddStyle(prop: “--private-bubble-color”,
value: Color,
when: !string.IsNullOrWhiteSpace(Color))
.NullIfEmpty()
;
Finally, we’ll update the markup with the inline style generated by StyleBuilder to a style
attribute.
<div class=”private-scope” style=”@inlineStyle”>
<span>@Text</span>
</div>
Now we can set the color values per component instance. In the following code, two
Bubble components are rendered. In Figure 24, the first uses the values defined in :root,
the second uses the instance colors set by the parameter.
<Bubble Text=”B1”/>
<Bubble BackgroundColor=”blue” TextColor=”yellow”/>
Figure 24: The first Bubble component uses the public :root values, while the second Bubble component
uses instance values.
If we compare the CSS code for this pattern to C#, some similarities emerge. The global
CSS variables loosely translate to a constructor assignment, while private CSS variables
work much like a private field, and properties expose the backing fields.
public class Bubble
{
49
© 2023 Progress. All Rights Reserved.
private string _bgColor;
private string _textColor;
public Bubble(
string textColor = “red”,
string backgroundColor = “white”)
{
_textColor = textColor;
_bgColor = backgroundColor
}
public string BackgroundColor {
get { return _bgColor; }
set { _bgColor = value; }
}
public string TextColor {
get { return _textColor; }
set { _textColor = value; }
}
A Completely Customizable UI
Library
We learned how to create a range of customization features in our own components using
CSS and CSS Isolation. Many of these customization features can be found in the Telerik
UI for Blazor component library. Telerik UI for Blazor incorporates Sass and CSS to match
your company brand or design system. The component library can be globally customized
through Sass, CSS or through a WYSIWYG online theme builder called Progress
ThemeBuilder.
In addition to global styles, components can be themed at the instance level by many built-in
style options as component parameters. Any built-in style option can be defined by a string
or using helpers provided by the API,the “ThemeConstants.Button.Rounded.Medium” for
example. The API helps with consistency and discoverability and prevents typos.
The parameters support preset theme options or can be completely customized. Telerik
UI for Blazor handles custom styles at the parameter level by building a CSS selector
based on the string used in the style property. For example, setting the component’s
Rounded property to: “small”, “medium”, “large” or “full” will use a built-in style. These styles
are added to the component as a class value of k-rounded-{value}. We can also use
the class to create a custom style by using a string that is not one of the built-in styles. If
Rounded is set to the string “custom”, it will produce the class k-rounded-custom. Then
we’ll write a selector in our style sheet to create the custom effect.
50
© 2023 Progress. All Rights Reserved.
.k-rounded-custom {
width:200px;
height:200px;
border-radius:50%;
}
<TelerikButton Rounded=”custom”
ThemeColor=”primary” >Button</TelerikButton>
In the example shown in Figure 25, a TelerikButton component is using various
parameters showing some of the preset theme options as well as a custom option.
Figure 25: A TelerikButton component utilizes parameters to customize the style of this component’s instance.
Blazor offers a wide range of solutions with application wide CSS, CSS Isolation and
leveraging CSS variables. Choosing the right architecture for styling components
requires knowing how the components will be used by developers. The right balance of
customization will be different for each application. With the techniques learned here, you’ll
be able to find the right fit.
Component Libraries
Component libraries are one way of speeding up Blazor application development and
reducing cost. With a component library, many of the larger aspects of app development
are provided as out-of-the-box solutions. This can include UI components, data
visualizations, design system elements, accessibility needs, globalization features,
document processing and more. Applying ready-to-use application elements all from one
source unifies documentation, support, UI theming and API composition. This unification
means developers spend less time searching through and reading documentation and
eliminates time wasted on making disparate components look alike. In addition, support is
guaranteed instead of relying on posting GitHub issues to open-source projects that may
not get immediate attention.
51
© 2023 Progress. All Rights Reserved.
Telerik UI for Blazor
Telerik UI for Blazor is a component library with productivity tools from Progress Software.
The component library includes over 100 UI components written specifically for Blazor
using native Razor Components. The native approach means each component was written
using the Blazor framework and only utilizes JavaScript when absolutely necessary. This
means less dependency on JavaScript and tighter integration with Blazor plus an overall
better experience.
Is This Low-Code?
Data grids are the gold standard for displaying large amounts of data in business
applications. The Telerik UI for Blazor Grid is built specifically for this task by incorporating
a wealth of features, most of which can be enabled in a simple and intuitive way through
low-code options.
“Low-code” is an industry term with a broad definition. Typically, we see the term “lowcode” used in the context of a drag-and-drop designer or a proprietary app development
tool that enables technical professionals to create apps.
However, low-code development techniques can help professional developers be more
efficient with repetitive tasks, or bridge the gap between development contexts such as
backend and UI systems. In the following examples, we’ll augment the need for UI code
by using data annotations. By using data annotations, a backend developer can be more
productive by writing less code in a familiar environment.
By using data annotations, a backend developer can be more productive by writing
less code in a familiar environment.
Blazor Data Grid
Data grids are the gold standard for displaying large amounts of data in business
applications. The Telerik UI for Blazor Grid is built specifically for this task by incorporating
a wealth of features, most of which can be enabled in a simple and intuitive way.
52
© 2023 Progress. All Rights Reserved.
Let’s begin by taking a look at an example written with Razor and HTML markup. In the
code example below, the grid is constructed of multiple HTML-like elements that define
the grid columns, data formats, data source and grid features.
<TelerikGrid Data=”@forecasts”>
<GridColumns>
<GridColumn Field=”Date” DisplayFormat=”{0:dddd, dd MMM yyyy}” />
<GridColumn Field=”TemperatureC” Title=”High Temp. C”
DisplayFormat=”{0:N1}” />
<GridColumn Field=”TemperatureCLow” Title=”Low Temp. C”
DisplayFormat=”{0:N1}” />
<GridColumn Field=”TemperatureF” Title=”High Temp. F”
DisplayFormat=”{0:N1}” />
<GridColumn Field=”TemperatureFLow” Title=”Low Temp. F”
DisplayFormat=”{0:N1}” />
<GridColumn Field=”Summary” />
</GridColumns>
</TelerikGrid>
Let’s focus for a moment on the first line of markup and see where simple adjustments can
make a big difference in how the grid operates. If we enable the following properties, we
can greatly improve the usability of the component. In the following snippet we’ll enable
paging, sorting, grouping and filtering by setting their respective properties.
<TelerikGrid Data=”@forecasts”
Pageable=”true”
Sortable=”true”
Groupable=”true”
FilterMode=”GridFilterMode.FilterMenu” >
...
We’ve easily increased the value of this component for our users with some markup. Even
with simple switches like these, it’s easy to imagine that a data grid with many features
could quickly balloon into many lines of markup.
With that in mind, let’s focus on the larger portion of the markup, where the grid columns are
defined. The grid columns are responsible for the bulk of our markup as each individual data
point must be defined along with the field, desired column title and formatting.
53
© 2023 Progress. All Rights Reserved.
<GridColumns>
<GridColumn Field=”Date” DisplayFormat=”{0:dddd, dd MMM yyyy}” />
<GridColumn Field=”TemperatureC” Title=”High Temp. C”
DisplayFormat=”{0:N1}” />
<GridColumn Field=”TemperatureCLow” Title=”Low Temp. C”
DisplayFormat=”{0:N1}” />
<GridColumn Field=”TemperatureF” Title=”High Temp. F”
DisplayFormat=”{0:N1}” />
<GridColumn Field=”TemperatureFLow” Title=”Low Temp. F”
DisplayFormat=”{0:N1}” />
<GridColumn Field=”Summary” />
</GridColumns>
This section of markup is repetitive and cumbersome to write. Thankfully, this can be
greatly reduced with the AutoGenerateColumns feature of the Telerik UI for Blazor Grid.
Auto-Generating Data Grid
Columns
When using the Telerik UI for Blazor Grid, auto-generation can greatly reduce or
completely remove the need for explicit GridColumns defined in markup. Since Blazor
uses .NET and our data models are strongly typed, the grid can infer information from
the model. In addition, the Grid component supports System.ComponentModel.
DataAnnotations that provide metadata when applied to the model.
Before we get acquainted with the auto-generation feature, let’s take a look at the model
class used in the example. The sample below is the model without any annotations.
public class WeatherForecast
{
public int Id { get; set; } // hidden
public DateTime Date { get; set; }
public double TemperatureC { get; set; }
public double TemperatureCLow { get; set; }
public double TemperatureF { get; set; }
public double TemperatureFLow { get; set; }
public string Summary { get; set; }
}
Using this model, each of the properties are mapped to a GridColumn component and
additional Title and DisplayFormat properties are set to achieve the desired output.
This pattern is followed for each data point shown in the grid.
54
© 2023 Progress. All Rights Reserved.
/* An explicit grid column */
<GridColumn Field=”TemperatureC” Title=”High Temp. C” DisplayFormat=”{0:N1}”
/>
Instead of defining each column, we can remove the GridColumns and its children
completely from the markup and set the AutoGenerateColumns property on the parent
grid component. When using auto-generation, the TelerikGrid component may even
be converted to a self-closing tag if no additional modifications are necessary. It’s quite
possible to add a fully featured grid component with a single element.
<TelerikGrid Data=”@forecasts”
Pageable=”true”
Sortable=”true”
Groupable=”true”
FilterMode=”GridFilterMode.FilterMenu”
AutoGenerateColumns=”true”/>
The Grid uses the model to generate the output using the property names and types.
Each property is mapped as-is using the property name as the grid column’s Title. In the
image below, we can compare the markup-defined grid to the auto-generated grid and
notice some differences, shown in Figure 26.
Figure 26: Two Telerik Grid components: the first using explicitly defined columns and the second using auto-generated
columns. Both grids render slightly different results.
55
© 2023 Progress. All Rights Reserved.
With the auto-generated grid, all properties are displayed, including the Id property
hidden in the markup-defined grid. The generated titles are not “human readable” on the
generated grid, and the data formats are raw output of the data type. Thus far, autogeneration “works” but doesn’t quite match the desired user experience.
Adding a little metadata can fix most parity issues with auto-generation. This is where
the context shift can play an important role with developers. While we do need to write
some code to complete the UI, a developer who is less comfortable with Razor and HTML
markup may find working in the model familiar territory, therefore improving productivity.
Let’s revisit the model and add data annotations to instruct the data grid to properly
generate the columns, titles and formatting. Each property in the model is decorated with
a Display attribute to set the column name. Fields that require data formatting use the
DisplayFormat annotation and use the same C# date format string that would have
been defined in markup. Finally, any columns we wish to hide are given the Display
attribute with the AutoGenerateField set to false. The updated annotated model can be
seen in the code sample below.
public class WeatherForecast
{
[Display(AutoGenerateField = false)]
public int Id { get; set; }
[DisplayFormat(DataFormatString = “{0:dddd, dd MMM yyyy}”)]
public DateTime Date { get; set; }
[Display(Name = “High Temp. C”)]
[DisplayFormat(DataFormatString = “{0:N1}”)]
public double TemperatureC { get; set; }
[Display(Name = “Low Temp. C”)]
[DisplayFormat(DataFormatString = “{0:N1}”)]
public double TemperatureCLow { get; set; }
[Display(Name = “High Temp. F”)]
[DisplayFormat(DataFormatString = “{0:N1}”)]
public double TemperatureF { get; set; }
[Display(Name = “Low Temp. F”)]
[DisplayFormat(DataFormatString = “{0:N1}”)]
public double TemperatureFLow { get; set; }
public string Summary { get; set; }
}
After annotating the model, we can compare the markup-defined grid to the autogenerated one and see there are no longer any differences between the two, as seen in
Figure 27.
56
© 2023 Progress. All Rights Reserved.
Figure 27: Two Telerik Grid components: the first using explicitly-defined columns and the second using auto-generated
columns. With the proper metadata, both render identical results.
The data grid is now completely auto-generated from the model complete with formatting.
In addition, the same model is used with the Telerik Grid’s auto-generated form, if editing
is enabled on the grid. If an external form is needed for modifying data, the Telerik Form
also exhibits the same auto-generation capabilities. In either form scenario, validation
attributes can be used to generate form validation on the UI.
Customization
Developers working with low-code solutions are often stuck at a crossroads when it comes to
customization. Fortunately, this isn’t a problem with the Telerik Grid auto-generation feature.
The Telerik Grid provides a way of exposing the generated columns so developers can mix and
match generated columns with markup-defined columns. This technique can be applied to
columns that can’t be customized with attributes like templates.
In the example below, we return to the markup definition of the grid and expand the element to
include the GridColumns component. Within the GridColumns, we instruct the grid where to
render the auto-generated columns by adding a GridAutoGeneratedColumns component.
Any columns defined before or after will be displayed in addition to what is generated.
57
© 2023 Progress. All Rights Reserved.
<TelerikGrid Data=”@forecasts”
Pageable=”true”
Sortable=”true”
Groupable=”true”
FilterMode=”GridFilterMode.FilterMenu”
AutoGenerateColumns=”true”>
<GridColumns>
<!-- Additional custom columns can go before -->
<GridAutoGeneratedColumns />
<!-- Additional custom columns can go below -->
</GridColumns>
</TelerikGrid>
Next, we can add our custom content. In this example, we’ll complete the grid by including
a custom column that displays an icon based on the data from the Summary field. A
Template is used within a grid column and data is passed to a method for finding the icon
rendered in the grid cell. To further customize the column, the FilterMenuType is set to a
CheckboxList automatically bound to unique summary values in the data set.
TelerikGrid Data=”@forecasts”
Pageable=”true”
Sortable=”true”
Groupable=”true”
FilterMode=”GridFilterMode.FilterMenu”
AutoGenerateColumns=”true”>
<GridColumns>
<GridColumn Field=”Summary” Width=”100px” Title=””
FilterMenuType=”FilterMenuType.CheckBoxList”>
<Template>
<span>@GetIcon(((WeatherForecast)context).Summary)</span>
</Template>
</GridColumn>
<!-- Additional custom columns can go before -->
<GridAutoGeneratedColumns />
<!-- Additional custom columns can go below -->
</GridColumns>
</TelerikGrid>
The rendered grid in Figure 28 uses both auto-generated columns and a custom template
column defined in markup. As we can see in the image below, the custom column shows icons
based on the data. In addition, an Excel-like filter interface is generated from the data set.
Figure 28: The Telerik Grid using auto-generated columns and templates to render a customized data column.
58
© 2023 Progress. All Rights Reserved.
The Telerik UI for Blazor Grid is a powerful component with many features. We can easily
enable UI functionality through simple properties, and further automate UI development
with a low-code model first approach.
Through example, we saw both a markup-defined grid and data-driven auto-generated
grid. Each approach fills a unique role depending on the developer’s needs while providing
flexibility to choose the right mix of low-code and customization.
The auto-generation feature is meant to speed up UI development by taking a strongly
typed model and using it to infer the UI. This means developers can spend less time
writing repetitive boilerplate code and focus on delivering solutions. This feature is not
limited to grid columns—grids with editing enabled will apply the same logic to autogenerated forms. In addition, the Telerik UI for Blazor Form and Tree List components
have similar capabilities.
Accessibility
There are several standards, policies and principles that govern how accessible
applications and components are created.
Section 508
Since 1998, Section 508 is part of the U.S. Rehabilitation Act of 1973. Section 508
represents a set of accessibility standards defined by the U.S. General Services
Administration (GSA) and initially applied to Federal agencies only with the aim to ensure
that their electronic and information technology is accessible to people with disabilities.
In 2017, Section 508 was reorganized to meet and reflect recent communication technology
innovations. Today, the Section 508 guidelines impact not only all U.S. Federal agencies but
also affect any company that does business with a Federal agency. Such companies include
vendors, private contractors, the financial industry, healthcare and legal organizations, as well
as partners of those agencies operating in the United States or abroad.
For more information, refer to:
• Rehabilitation Act of 1973 Section 508 (Latest Amendment)
• Telerik UI for Blazor Supports Accessibility Compliance
59
© 2023 Progress. All Rights Reserved.
W3C Web Content
Accessibility Guidelines
(WCAG) 2.1
The Web Content Accessibility Guidelines (WCAG) are set by the World Wide Web
Consortium (W3C) and define recommendations for making web content accessible to
people with physical and cognitive disabilities. WCAG defines accessibility principles with
their respective success criteria. Depending on the implemented success criteria by a web
application, the WCAG provide the A, AA and AAA levels of accessibility conformance.
For more information, refer to:
• WCAG 2 Quick Reference Guide
• WCAG 2.1 Guidelines
• Telerik UI for Blazor Supports Accessibility Compliance
WAI-ARIA
WAI-ARIA is a set of technical specifications developed by the W3C that provide the
semantics for the assistive technologies to access and interpret web content and web
applications. The WAI-ARIA recommendations (standards) divide the semantics into roles
and into the states and properties those roles support. For example, a checkbox role
supports the aria-checked state which indicates whether a checkbox, radio button or a
similar UI element is checked.
The WAI-ARIA framework targets web developers who create web applications by using
AJAX, scripting and other rich application techniques.
For more information, refer to:
• Accessible Rich Internet Applications (WAI-ARIA) 1.1
• WAI-ARIA Role Definitions
• WAI-ARIA State and Property Definitions
• Telerik UI for Blazor Supports Accessibility Compliance
60
© 2023 Progress. All Rights Reserved.
Keyboard Navigation
One of the most important aspects of web accessibility is keyboard accessibility. Users
with motor needs rely on a keyboard. This includes people with limited use of their hands
and users with modified keyboards or other hardware that mimics the functionality of a
keyboard. Low-vision users also often use a keyboard for navigation.
By default, users can only navigate to links, buttons and form controls with a keyboard.
The navigation order in which interactive items receive keyboard focus has to be logical
and intuitive. Generally, keyboard navigation logic needs to follow the visual horizontal and
vertical flow of the page. For example, left to right and top to bottom, header first followed
by the main and then the page navigation.
Keyboard accessibility is a category under the WCAG Operable principle.
In WCAG 2.1, the keyboard accessible category provides the following success criteria:
• Keyboard
• No Keyboard Trap
• Keyboard (No Exception)
• Character Key Shortcuts
Blazor applications should be built with a deep knowledge of accessibility so they can be
used by all users. If you are not an accessibility expert and your team doesn’t have access
to an expert, then an alternative is to use off-the-shelf components like Telerik UI for
Blazor that already incorporate keyboard and accessibility features.
Authorization and Identity
Blazor can use the existing ASP.NET authentication mechanisms to establish the user’s
identity. Blazor can also work with other Identity Providers (IdP), including Google,
Facebook and Auth0. The exact mechanism of authentication and authorization depends
on how the Blazor app is hosted, Blazor WebAssembly or Blazor Server.
Blazor WebAssembly: Authentication checks can be bypassed because all client-side
code can be modified by users. The same is true for all client-side app technologies,
including JavaScript SPA frameworks or native apps for any operating system.
Authentication should be implemented on the server for WebAssembly applications.
61
© 2023 Progress. All Rights Reserved.
Blazor Server: Apps operate over a real-time connection that’s created using SignalR.
Authentication in SignalR-based apps is handled when the connection is established.
Authentication can be based on a cookie or some other bearer token.
The built-in AuthenticationStateProvider service for Blazor Server apps obtains
authentication state data from ASP.NET Core’s HttpContext.User. This is how
authentication state integrates with existing ASP.NET Core authentication mechanisms.
Blazor comes with several built-in features to assist with displaying authorized content—
the authorization attribute, the AuthorizedView component, AuthenticationState object
and the AuthorizeRouteView.
Authorized View Component
The AuthorizeView component selectively displays UI content depending on whether the
user is authorized. This approach is useful when you only need to display data for the user
and don’t need to use the user’s identity in procedural logic.
The component exposes a context variable of type AuthenticationState (@context in
Razor syntax), which you can use to access information about the signed-in user.
<AuthorizeView>
<Authorized>
<h1>Hello, @context.User.Identity.Name!</h1>
<p>You can only see this content if you’re authorized.</p>
<button @onclick=”SecureMethod”>Authorized Only Button</button>
</Authorized>
<NotAuthorized>
<h1>Authentication Failure!</h1>
<p>You’re not signed in.</p>
</NotAuthorized>
</AuthorizeView>
@code {
private void SecureMethod() { ... }
}
62
© 2023 Progress. All Rights Reserved.
The AuthorizeView component also supports Role- and Policy-based authorization
through the component’s corresponding properties.
<AuthorizeView Roles=”Admin”>
<p>User: @context.User</p>
<p>You have the ‘Admin’ role claim.</p>
<AuthorizeView Policy=”Over21”>
<p>You’re and `Admin` and also satisfy the ‘Over21’
policy.</p>
</AuthorizeView>
</AuthorizeView>
AuthorizedView allows developers to control a specific piece of UI based on authorization.
This granular approach is great for individual components or child components. For full
page authorization, the [authorized] attribute may be the best option.
[Authorize] Attribute
The [Authorize] attribute is used to authorize a full page upon routing and navigation.
The [Authorize] attribute also supports role-based or policy-based authorization. Only use
[Authorize] on @page components reached via the Blazor Router. Authorization is only
performed as an aspect of routing and not for child components rendered within a page.
To authorize the display of specific parts within a page, use AuthorizeView instead.
@page “/”
@attribute [Authorize(Roles = “Admin, Superuser”)]
<p>You can only see this if you’re in the ‘Admin’ or ‘Superuser’ role.</p>
Procedural Logic
Authorization information can also be extracted through procedural logic. Blazor exposes
a Task<AuthenticationState> through a cascading parameter and the IAuthorizationService
through dependency injection. The AuthenticationState object can be used to obtain
a user’s ClaimsPrincipal and check the authenticated state and role. In addition, the
IAuthorizationService can be used to check a user’s claim against a policy.
63
© 2023 Progress. All Rights Reserved.
@page “/procedural-logic”
@inject IAuthorizationService AuthorizationService
<h1>Procedural Logic Example</h1>
<button @onclick=”@DoSomething”>Do something important</button>
@code {
[CascadingParameter]
private Task<AuthenticationState>? authenticationState { get; set; }
private async Task DoSomething()
{
if (authenticationState is not null)
{
var authState = await authenticationState;
var user = authState?.User;
if (user is not null)
{
if (user.Identity is not null && user.Identity.
IsAuthenticated)
{
// ...
}
if (user.IsInRole(“Admin”))
{
// ...
}
if ((await AuthorizationService.AuthorizeAsync(user,
“content-editor”))
.Succeeded)
{
// ...
}
}
}
}
}
User authorization is built into Blazor’s infrastructure with multiple components and APIs
designed to work with user claims, roles and policies. Blazor can integrate with IdPs such
as Google, Facebook and Auth0. Coupled with proper backend security, Blazor applications
meet the security requirements of modern-day web applications.
64
© 2023 Progress. All Rights Reserved.
Conclusion
Blazor offers a variety of choices for developers to use and strategize how applications
should be built and deployed. Depending on the needs and capabilities of the clients
being served, Blazor can adapt. If requirements call for ultra responsiveness and quick time
to market, Blazor Server can deliver. When the application needs to run offline or function
as a PWA, Blazor WebAssembly is a great choice. For applications that benefit from
running natively on mobile and desktop, Blazor Hybrid can do it all.
Bringing existing .NET Framework apps to the modern ASP.NET Core and Blazor frameworks
will require in-depth planning and hard work. However, the result of these efforts will not go
unnoticed as ASP.NET Core brings with it massive performance improvements. In addition,
Blazor has proven itself as a productive environment for developers to execute quickly
by leveraging Blazor productivity tools. Blazor excels in test coverage spanning unit tests,
integration tests and end-to-end tests ensuring system stability.
Blazor doesn’t just excel at architecture—it offers a wide range of solutions for dealing
with user interfaces. Not only does Blazor incorporate web standard CSS, CSS Isolation
and modern frontend CSS architecture, it also has a robust component library support
available from Progress. Telerik UI for Blazor can speed up Blazor application development
and reduce costs by standardizing UI components, data visualizations and design system
elements, while meeting accessibility needs.
Blazor is versatile, productive, secure and ready to run all your projects—existing and
new. Hopefully, this book serves as a brief guide to help you strategize and plan your next
Blazor application.
About the Author
Ed Charbeneau
Ed Charbeneau is a web enthusiast, speaker, writer, design admirer and Developer
Advocate for Progress. He has designed and developed web-based applications for
business, manufacturing, systems integration as well as customer facing websites. Ed
enjoys geeking out to cool new tech, brainstorming about future technology and admiring
great design.
65
© 2023 Progress. All Rights Reserved.
Telerik UI for Blazor is the most comprehensive library of 100+ Blazor UI components to
cover any dev scenario. Develop new Blazor apps and modernize legacy web projects in
half the time . Add professionally designed themes and powerful Visual Studio Productivity
tools for endless customization and productivity gains. Professional support, demos, learning
resources are also included. 30-days free trial is available.
Learn More:
www.telerik.com/blazor-ui
About Progress
Worldwide Headquarters
Dedicated to propelling business forward in a technology-driven world, Progress
(NASDAQ: PRGS) helps businesses drive faster cycles of innovation, fuel momentum and
accelerate their path to success. As the trusted provider of the best products to develop, deploy
and manage high-impact applications, Progress enables customers to build the applications and
experiences they need, deploy where and how they want and manage it all safely and securely.
Hundreds of thousands of enterprises, including 1,700 software companies and 3.5 million developers,
depend on Progress to achieve their goals—with confidence. Learn more at www.progress.com
Progress Software Corporation
15 Wayside Rd, Suite 400, Burlington, MA01803, USA
Tel: +1-800-477-6473
2023 Progress Software Corporation and/or its subsidiaries or affiliates. All rights reserved.
Rev 2023/05 RITM0201113
facebook.com/progresssw
twitter.com/progresssw
youtube.com/progresssw
linkedin.com/company/progress-software
progress_sw_
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )