Blazor Webassembly Succinctly
Blazor Webassembly Succinctly
Succinctly
Michael Washington
Foreword by Daniel Jebaraj
Copyright © 2023 by Syncfusion, Inc.
If you obtained this book from any other source, please register and download a free copy from
www.syncfusion.com.
The authors and copyright holders provide absolutely no warranty for any information provided.
The authors and copyright holders shall not be liable for any claim, damages, or any other
liability arising from, out of, or in connection with the information in this book.
Please do not use this book if the listed terms are unacceptable.
3
Table of Contents
Introduction .............................................................................................................................10
Parameters ......................................................................................................................14
Events .............................................................................................................................16
Unauthenticated users..........................................................................................................46
4
Client project ........................................................................................................................51
Layouts ................................................................................................................................52
Serialization ..........................................................................................................................56
Forms ..............................................................................................................................73
Validation .........................................................................................................................73
5
Deleting a record ..................................................................................................................89
Syncfusion Dialog.................................................................................................................89
EmailSender class..............................................................................................................103
Route parameters...............................................................................................................107
Conclusion ............................................................................................................................111
6
The Story behind the Succinctly Series
of Books
Daniel Jebaraj, CEO
Syncfusion, Inc.
taying on the cutting edge
S As many of you may know, Syncfusion is a provider of software components for the
Microsoft platform. This puts us in the exciting but challenging position of always
being on the cutting edge.
Whenever platforms or tools are shipping out of Microsoft, which seems to be about
every other week these days, we have to educate ourselves, quickly.
While more information is becoming available on the Internet and more and more books are
being published, even on topics that are relatively new, one aspect that continues to inhibit us is
the inability to find concise technology overview books.
We are usually faced with two options: read several 500+ page books or scour the web for
relevant blog posts and other articles. Just as everyone else who has a job to do and customers
to serve, we find this quite frustrating.
We firmly believe, given the background knowledge such developers have, that most topics can
be translated into books that are between 50 and 100 pages.
This is exactly what we resolved to accomplish with the Succinctly series. Isn’t everything
wonderful born out of a deep desire to change things for the better?
Free forever
Syncfusion will be working to produce books on several topics. The books will always be free.
Any updates we publish will also be free.
7
Free? What is the catch?
There is no catch here. Syncfusion has a vested interest in this effort.
As a component vendor, our unique claim has always been that we offer deeper and broader
frameworks than anyone else on the market. Developer education greatly helps us market and
sell against competing vendors who promise to “enable AJAX support with one click,” or “turn
the moon to cheese!”
We sincerely hope you enjoy reading this book and that it helps you better understand the topic
of study. Thank you for reading.
8
About the Author
9
Introduction
Blazor is an exciting technology that allows you to create web-based applications using C#
instead of JavaScript. However, you still have the ability to implement custom JavaScript when
you desire. Blazor WebAssembly is a component that allows browsers to run binary code for
enhanced performance and security.
In this book, we will cover the core elements of Blazor and then explore additional features by
building a sample application called Syncfusion Help Desk.
The code for this e-book is available on GitHub. The step-by-step instructions use Visual Studio
2022 Community edition, which is available for free at this link. In addition, SQL Server
Developer edition is recommended, and it is available for download for free at this link.
You may want to bookmark the official Microsoft Blazor documentation available at
https://blazor.net.
10
Chapter 1 What Is Blazor?
Blazor applications are composed of components that are constructed using C#, HTML-based
Razor syntax, and CSS.
Blazor has two different runtime modes: server-side Blazor and client-side Blazor, also known
as Blazor WebAssembly. Both modes run in all modern web browsers, including web browsers
on mobile phones.
Server-side Blazor
Server-side Blazor renders the Razor components on the server and updates the webpage
using a SignalR connection. The Blazor framework sends events from the web browser, such as
button clicks and mouse movements, to the server. The Blazor runtime computes changes to
the components on the server and sends a diff-based webpage back to the browser.
11
Client-side Blazor (WebAssembly)
Client-side Blazor is composed of the same code as server-side Blazor; however, it runs entirely
in the web browser using a technology known as WebAssembly.
The primary difference in Blazor applications that are created in server-side Blazor versus
client-side Blazor is that the client-side Blazor applications need to make web calls to access
server data, whereas the server-side Blazor applications can omit this step, as all their code is
executed on the server.
One way to think of Blazor is that Blazor is a framework for creating SPA webpages using one
of two architectures (client side or server side), using Razor technology written with the C#
language.
Because client-side Blazor with WebAssembly executes entirely on a user's browser, it is very
fast for many applications.
The Syncfusion Help Desk sample application covered in this book will be built using client-side
Blazor.
Blazor features routing, where you can provide navigation to your controls using the @page
directive followed by a unique route in quotes preceded by a slash.
12
The following is an example of a simple Razor component called ComponentExample.razor.
@page "/componentexample"
@code {
The following shows what the component looks like in a running application.
A Razor component is contained in a .razor file and can be nested inside of other components.
For example, we can create a component named ComponentOne.razor using the following
code.
<h4 style="background-color:goldenrod">
This is ComponentOne
</h4>
@code {
@page "/componentexample"
13
<ComponentOne />
@code {
The following shows what ComponentExample.razor now looks like when running in the
application.
Parameters
Razor components can pass values to other components using parameters. Component
parameters are defined using the [Parameter] attribute, which must be declared as public.
<h5 style="color:red">@Title</h5>
@code {
[Parameter]
public string Title { get; set; }
}
14
Code Listing 5: ParameterExample.razor
@page "/parameterexample"
<h4>Parameter Example</h4>
@code {
Data binding
Simple, one-way binding in Blazor is achieved by declaring a parameter and referencing it using
the @ symbol. An example of this is shown in the following code.
<b>BoundValue:</b> @BoundValue
@code {
private string BoundValue { get; set; }
15
This displays the following when rendered.
Two-way, dynamic data binding in Razor components is implemented using the @bind attribute.
@code {
private string BoundValue { get; set; }
}
When we run the code, it displays the value entered into the text input box as text is typed in.
Events
Raising events in Razor components is straightforward. The following example demonstrates
using the @onclick event handler to execute the method IncrementCount when the button is
clicked.
@code {
16
private int currentCount = 0;
When the control is rendered and the button is clicked six times, the UI looks like this.
17
Chapter 2 The Help Desk Application
To demonstrate the features of Blazor, and how controls such as the suite available from
Syncfusion can make development faster and easier, we will create a simple help desk
application.
Users of the application will see a form that allows them to create a new help desk ticket.
18
Figure 11: Syncfusion Toast
With the Syncfusion Toast control, a pop-up window is displayed to let the user know that their
action was successful.
An email is sent to the administrator with a link that will navigate directly to the help desk ticket.
19
Figure 13: Administrator Adding Details
When the administrator is logged in and they click the link in the email, they will have the ability
to edit all fields and enter help desk ticket detail records at the bottom of the form by entering
the desired text and clicking Add.
20
Figure 14: Administrator Saving Details
After adding the desired help desk ticket details, the administrator clicks Save to save the
record and send a notification email to the user who created the help desk ticket.
21
Figure 15: User Adding Details
The user who created the help desk ticket receives an email with a link that navigates them to
the help desk ticket and allows them to enter details.
However, the fields at the top of the help desk ticket are grayed out and disabled for them. They
can only add new details.
22
Help desk administrators
A user logged in as the administrator will see an Administration link that will take them to the
section of the application that will allow them to administer all help desk tickets.
This will display the Syncfusion DataGrid. The administrator can edit records and sort, page,
and resize the data grid.
23
Figure 18: Syncfusion Dialog
Clicking the Edit button next to a record in the data grid will open it up in the Syncfusion Dialog
control.
This dialog will allow the administrator to edit all fields of the help desk ticket, as well as add
help desk ticket detail records at the bottom of the form.
When the administrator saves the record, the user who created the help desk ticket receives an
email with a link that navigates them to the help desk ticket.
24
Figure 19: Delete Confirmation
Clicking the Delete button next to a record in the data grid will open the delete confirmation
dialog box.
Clicking Yes will delete the record and clicking No will cancel the action.
25
Chapter 3 Create the Help Desk Application
In this chapter, we will cover the steps to create the Help Desk application.
26
Install SQL Server
The application requires a database to store the data. Download and install the free SQL Server
2019 Developer edition here, or use the full SQL Server, if you have access to it.
Note: The requirements for creating applications using Blazor are constantly
evolving. See the latest requirements.
Note: If you install Visual Studio 2022 and select the ASP.NET or .NET workload
during installation, the .NET Core SDK and runtime will be installed for you. Learn
more.
27
Create the project
Open Visual Studio and select Create a New Project > Blazor WebAssembly App > Next.
28
Figure 24: Additional Information
On the Additional information dialog, select .NET 6.0 (Long-term support) for Framework.
Click Create.
29
Figure 25: In Visual Studio
30
Figure 26: Add SQL Server
Click Add SQL Server to add a connection to your database server, if you don’t already have it
in the SQL Server list.
Note: For this example, we do not want to use the localdb connection (for SQL
Express) that you may see in the list.
Expand the tree node for your SQL Server, then right-click Databases and select Add New
Database. Name the database SyncfusionHelpDeskClientBook.
31
Figure 28: Copy Connection String
After the database has been created, right-click it and select Properties. In the Properties
window, copy the connection string.
32
Figure 29: Paste Connection String
Open the appsettings.json file in the Server project and paste the connection string in the
DefaultConnection property.
33
Blazor WebAssembly security
The default code the Visual Studio wizard creates will allow us to create new users. However,
we want some users to be administrators. To do this, we must enable role management.
Open the Program.cs file in the Server project and remove the following code.
builder.Services.AddDefaultIdentity<ApplicationUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
Replace the code with the following code to remove the requirement to confirm new user
accounts and to enable role management.
builder.Services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<Microsoft.AspNetCore.Identity.IdentityRole>() // Add roles
.AddEntityFrameworkStores<ApplicationDbContext>();
// From: https://github.com/dotnet/AspNetCore.Docs/issues/17649
// Configure identity server to put the role claim into the id token
// and the access token and prevent the default mapping for roles
// in the JwtSecurityTokenHandler.
builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options
=>
{
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
In Visual Studio, press F5 to run the application and open it in your web browser.
34
Figure 30: Register User
Click Register and create a user with the email address Test@Email.
Click Apply Migrations. After a few moments, you will see a prompt to refresh the page.
Refresh the page in your web browser.
35
Figure 32: Logged In
The required tables will be created in the database, and the application will display the home
page and indicate that the Test@Email account is logged in.
Close the web browser to stop the application and return to Visual Studio.
Create an administrator
We will now create code that will programmatically create an administrator role and add the
Administrator@Email account to the administrator role.
36
Figure 33: Add Custom Register Page
To do this, we need to override the registration page with a custom registration page.
In the Server project, create an Account folder in the Areas/Identity/Pages folder. Create a
Register.cshtml page and a Register.cshtml.cs page using the following code.
37
Code Listing 11: Register.cshtml
@page
@model SyncfusionHelpDeskClient.Server.Areas.Identity.Pages.RegisterModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-
control" />
<span asp-validation-for="Input.ConfirmPassword"
class="text-danger"></span>
</div>
<button type="submit" class="btn btn-
primary">Register</button>
</form>
</div>
<div class="col-md-6 col-md-offset-2">
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
38
Code Listing 12: Register.cshtml.cs
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using SyncfusionHelpDeskClient.Server.Models;
namespace SyncfusionHelpDeskClient.Server.Areas.Identity.Pages
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
const string ADMINISTRATION_ROLE = "Administrators";
const string ADMINISTRATOR_USERNAME = "Admin@email";
private readonly
SignInManager<ApplicationUser> _signInManager;
private readonly
UserManager<ApplicationUser> _userManager;
private readonly
RoleManager<IdentityRole> _roleManager;
public RegisterModel(
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_signInManager = signInManager;
_roleManager = roleManager;
}
[BindProperty]
public InputModel Input { get; set; }
39
public string ReturnUrl { get; set; }
public IList<AuthenticationScheme>
ExternalLogins
{ get; set; }
[Required]
[StringLength(100, ErrorMessage =
"The {0} must be at least {2} and " +
"at max {1} characters long.",
MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage =
"The password and confirmation " +
"password do not match.")]
public string ConfirmPassword { get; set; }
}
if (ModelState.IsValid)
40
{
var user = new ApplicationUser
{ UserName = Input?.Email, Email = Input?.Email };
var result =
await _userManager.CreateAsync(
user, Input?.Password);
if (result.Succeeded)
{
// Set confirm Email for user.
user.EmailConfirmed = true;
await _userManager.UpdateAsync(user);
if (RoleResult == null)
{
// Create ADMINISTRATION_ROLE role.
await _roleManager
.CreateAsync(
new IdentityRole(ADMINISTRATION_ROLE));
}
if (user.UserName?.ToLower() ==
ADMINISTRATOR_USERNAME.ToLower())
{
// Put admin in Administrator role.
await _userManager
.AddToRoleAsync(
user, ADMINISTRATION_ROLE);
}
return LocalRedirect(returnUrl);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(
string.Empty, error.Description);
}
}
41
return Page();
}
}
Next, add a _ViewImports.cshtml file in the Pages folder using the following code.
@using Microsoft.AspNetCore.Identity
@using SyncfusionHelpDeskClient.Server.Areas.Identity
@using SyncfusionHelpDeskClient.Server.Areas.Identity.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
42
Finally, to test that we can restrict access to a user in the Administrators role, in the Server
project, remove the following code in the Controllers\WeatherForecastController.cs file.
[Authorize]
Even though security cannot be enforced in the client project, it is still helpful to implement code
that will display differently according to the roles of the current user.
We will also implement code in the client project that will indicate if the current user is in the
administrator role.
43
Figure 35: Update Index.razor
Open the Index.razor page in the Client project and replace all the code with the following
code.
@page "/"
<AuthorizeView Roles="@ADMINISTRATION_ROLE">
<p>You are an Administrator</p>
</AuthorizeView>
@code {
string ADMINISTRATION_ROLE = "Administrators";
44
Figure 36: Register Administrator
45
Figure 38: Administrator Display
The home page of the application will indicate that the user is an administrator.
When we log in, the code in the Index.razor page runs to create the administrator role and to
add the Admin@Email account to that role.
Unauthenticated users
Sometimes we want to allow unauthenticated users to call server-side controllers.
46
To demonstrate this, in the Server project, remove the following code in the
Controllers\WeatherForecastController.cs file.
However, when we run the application and log in as a user who is not an administrator
(Test@Email), we will be directed to the login page.
This happens because the Fetch data page is marked as authorized, and the default
HttpClient, which makes the calls to the server-side code, requires the user to be
authenticated.
To rectify this, we will need to create a second HttpClient that does not require authentication.
In the Client project, open the Program.cs file and insert the following code before
builder.Services.AddApiAuthorization().
47
Code Listing 19: Create NoAuthenticationClient
Next, open the FetchData.razor page and remove the following code, which requires users to
be logged in to view the page.
@attribute [Authorize]
Add the code with the following that allows a named HttpClient to be used.
48
Finally, delete the following code.
forecasts =
await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
Replace it with the following code that uses the new named HttpClient.
forecasts =
await client.GetFromJsonAsync<WeatherForecast[]>
("WeatherForecast");
When we run the application, the Fetch data page now works for unauthenticated users.
49
Chapter 4 Explore the Project
Server project
The Program.cs file in the Server project is the entry point for the application and sets up things
for server-side configurations such as the database connection.
var connectionString =
50
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
It also invokes the routing, authentication, and authorization. Finally, before the final app.Run()
call, it calls the method MapFallbackToFile("index.html") that sets up the index.html page,
in the Client project, as the root page of the application.
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");
app.Run();
Client project
The Program.cs file in the Client project specifies that the App component contained in the
App.razor file is to be rendered as the root component of the application.
The routing of page requests in the application is configured in the App.razor file, like this.
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
@if (context.User.Identity?.IsAuthenticated != true)
{
<RedirectToLogin />
51
}
else
{
<p role="alert">
You are not authorized to access this
resource.
</p>
}
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this
address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
This component intercepts web browser navigation and renders the page and the layout after
applying any configured authorization rules.
Layouts
The App.razor control specifies MainLayout (contained in the MainLayout.razor file) as the
application’s default layout component.
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4 auth">
<LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/"
target="_blank">About</a>
</div>
52
@Body
</article>
</main>
</div>
The MainLayout component inherits from LayoutComponentBase and will inject the content of
the Razor page that the user is navigating to at the location of the @Body parameter in the
template.
The remaining page markup, including the NavMenu control (located in the NavMenu.razor file)
and the LoginDisplay control (located in the LoginDisplay.razor file), will be displayed around
the content, creating a consistent page layout.
53
Chapter 5 Add Syncfusion
The Syncfusion controls allow us to easily implement advanced functionality with a minimum
amount of code. The Syncfusion controls are contained in a NuGet package. In this chapter, we
will cover the steps to obtain that package and configure it for our application.
Note: See the latest system requirements for using Syncfusion here.
Right-click the Solution node and select Manage NuGet Packages for Solution.
54
Figure 45: Install NuGet Packages
Select the Browse tab and install the following NuGet packages:
Additional configuration
In the Client project, open the _Imports.razor file and add the following.
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Inputs
@using Syncfusion.Blazor.Popups
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.DropDowns
@using Syncfusion.Blazor.Layouts
@using Syncfusion.Blazor.Calendars
@using Syncfusion.Blazor.Navigations
@using Syncfusion.Blazor.Lists
@using Syncfusion.Blazor.Grids
@using Syncfusion.Blazor.Buttons
@using Syncfusion.Blazor.Notifications
Open the Program.cs file in the Client project and add the following using statement.
55
Code Listing 30: Server Project Startup.cs Using Statement
using Syncfusion.Blazor;
Add the following code to the Main method, before the builder.Build().RunAsync(); line.
// Add Syncfusion
builder.Services.AddSyncfusionBlazor();
Replace {{ ENTER SYNCFUSION LICENSE HERE }} with the Syncfusion license you obtained
in the previous step.
Finally, add the following to the <head> element of the wwwroot/index.html page in the Client
project.
<link href="_content/Syncfusion.Blazor/styles/material.css"
rel="stylesheet" />
<script src="_content/Syncfusion.Blazor/scripts/syncfusion-blazor.min.js"
type="text/javascript"></script>
Serialization
The help desk ticket object, which we will create in later steps, contains a nested collection of
HelpDeskTicketDetail objects. We need to add special code that uses the
Microsoft.AspNetCore.Mvc.NewtonsoftJson assembly (added earlier) to properly serialize and
deserialize the objects.
In the Program.cs file in the Server project, delete the following code.
builder.Services.AddControllersWithViews();
56
Replace it with the following code.
builder.Services.AddControllersWithViews()
.AddNewtonsoftJson(
options =>
options.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
57
Chapter 6 Creating a Data Layer
We will now add additional tables to the database to support the custom code we plan to write.
To allow our code to communicate with these tables, we require a data layer. This data layer will
also allow us to organize and reuse code efficiently.
In the SQL Server Object Explorer, right-click the database and select New Query.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[HelpDeskTicketDetails](
[Id] [int] IDENTITY(1,1) NOT NULL,
[HelpDeskTicketId] [int] NOT NULL,
[TicketDetailDate] [datetime] NOT NULL,
[TicketDescription] [nvarchar](max) NOT NULL,
58
CONSTRAINT [PK_HelpDeskTicketDetails] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[HelpDeskTickets](
[Id] [int] IDENTITY(1,1) NOT NULL,
[TicketStatus] [nvarchar](50) NOT NULL,
[TicketDate] [datetime] NOT NULL,
[TicketDescription] [nvarchar](max) NOT NULL,
[TicketRequesterEmail] [nvarchar](500) NOT NULL,
[TicketGUID] [nvarchar](500) NOT NULL,
CONSTRAINT [PK_HelpDeskTickets] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[HelpDeskTicketDetails]
WITH CHECK
ADD CONSTRAINT [FK_HelpDeskTicketDetails_HelpDeskTickets]
FOREIGN KEY([HelpDeskTicketId])
REFERENCES [dbo].[HelpDeskTickets] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[HelpDeskTicketDetails]
CHECK CONSTRAINT [FK_HelpDeskTicketDetails_HelpDeskTickets]
GO
59
Figure 47: Click Execute
Close the SQLQuery1.sql window and refresh the view of the tables in the database.
You will see that the new tables have been added.
60
Figure 49: Database Diagram
The preceding diagram shows the relationship between the newly added tables. A
HelpDeskTickets record is created first. As the issue is processed, multiple associated
HelpDeskTicketDetails records are added in a one-to-many relationship.
61
Figure 51: EF Core Power Tools
In the Server project, right-click the project node in the Solution Explorer and select EF Core
Power Tools > Reverse Engineer.
62
Click Add to create a connection to the database if one does not already exist in the dropdown
list.
After you do that, or if the connection to the database is already in the dropdown list, select the
database connection in the dropdown and click OK.
63
Figure 54: Configure DataContext
Click OK.
64
Figure 55: DataContext Created
In the Solution Explorer, you will see the DataContext has been created.
In the Server project, open the Program.cs file and add the following using statement.
using SyncfusionHelpDesk.Data;
65
Code Listing 37: Database Connection
In the Controllers folder of the Server project, add a new file called
SyncfusionHelpDeskController.cs with the following code.
#nullable disable
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace SyncfusionHelpDesk.Data
{
[ApiController]
[Route("[controller]")]
public class SyncfusionHelpDeskController : ControllerBase
{
private readonly SyncfusionHelpDeskContext _context;
public SyncfusionHelpDeskController(
SyncfusionHelpDeskContext context)
{
_context = context;
}
66
// Only an Administrator can query.
[Authorize(Roles = "Administrators")]
[HttpGet]
public object Get()
{
StringValues Skip;
StringValues Take;
StringValues OrderBy;
string orderby =
(Request.Query.TryGetValue("$orderby", out OrderBy))
? OrderBy.ToString() : "TicketDate";
return new
{
Items = _context.HelpDeskTickets
.OrderByDescending(orderby)
.Skip(skip)
.Take(top),
Count = TotalRecordCount
};
}
else
{
System.Reflection.PropertyInfo prop =
typeof(HelpDeskTickets).GetProperty(orderby);
return new
{
Items = _context.HelpDeskTickets
.OrderBy(orderby)
.Skip(skip)
.Take(top),
Count = TotalRecordCount
};
67
}
}
[HttpPost]
[AllowAnonymous]
public Task
Post(HelpDeskTickets newHelpDeskTickets)
{
// Add a new Help Desk Ticket.
_context.HelpDeskTickets.Add(newHelpDeskTickets);
_context.SaveChanges();
return Task.FromResult(newHelpDeskTickets);
}
[HttpPut]
[AllowAnonymous]
public Task
PutAsync(HelpDeskTickets UpdatedHelpDeskTickets)
{
// Get the existing record.
// Note: Caller must have the TicketGuid.
var ExistingTicket =
_context.HelpDeskTickets
.Where(x => x.TicketGuid ==
UpdatedHelpDeskTickets.TicketGuid)
.FirstOrDefault();
if (ExistingTicket != null)
{
ExistingTicket.TicketDate =
UpdatedHelpDeskTickets.TicketDate;
ExistingTicket.TicketDescription =
UpdatedHelpDeskTickets.TicketDescription;
ExistingTicket.TicketGuid =
UpdatedHelpDeskTickets.TicketGuid;
ExistingTicket.TicketRequesterEmail =
UpdatedHelpDeskTickets.TicketRequesterEmail;
ExistingTicket.TicketStatus =
UpdatedHelpDeskTickets.TicketStatus;
68
UpdatedHelpDeskTickets.HelpDeskTicketDetails)
{
if (item.Id == 0)
{
// Create new HelpDeskTicketDetails record.
HelpDeskTicketDetails newHelpDeskTicketDetails
=
new HelpDeskTicketDetails();
newHelpDeskTicketDetails.HelpDeskTicketId =
UpdatedHelpDeskTickets.Id;
newHelpDeskTicketDetails.TicketDetailDate =
DateTime.Now;
newHelpDeskTicketDetails.TicketDescription =
item.TicketDescription;
_context.HelpDeskTicketDetails
.Add(newHelpDeskTicketDetails);
}
}
}
_context.SaveChanges();
}
else
{
return Task.FromResult(false);
}
return Task.FromResult(true);
}
if (ExistingTicket != null)
{
// Delete the Help Desk Ticket.
_context.HelpDeskTickets.Remove(ExistingTicket);
69
_context.SaveChanges();
}
else
{
return Task.FromResult(false);
}
return Task.FromResult(true);
}
}
// From: https://bit.ly/30ypMCp
public static class IQueryableExtensions
{
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source, string propertyName)
{
return source.OrderBy(ToLambda<T>(propertyName));
}
70
In the Server project, open the ApplicationDbContext.cs file in the Data folder, and delete this
code.
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) :
base(options, operationalStoreOptions)
{
}
public ApplicationDbContext(
DbContextOptions<ApplicationDbContext> options,
IOptions<OperationalStoreOptions> operationalStoreOptions) :
base(options, operationalStoreOptions)
{
}
71
Chapter 7 Creating New Tickets
In this chapter, we will create a page that will contain a form to create new help desk tickets.
In the Pages folder of the Client project, open the index.razor page and add the following
under the @page directive.
@using Microsoft.AspNetCore.Components.Authorization;
@using SyncfusionHelpDeskClient.Shared
@inject HttpClient Http
@inject IHttpClientFactory ClientFactory
Add the following code markup under the existing closing AuthorizeView tag.
SfToast? ToastObj;
private string ToastContent { get; set; } = "";
The ToastObj will provide programmatic access to the control, for example, to open it and close
it. The ToastContent property will allow us to set the text that is displayed in the control.
72
Forms
Blazor provides an EditForm control that allows us to validate a form using data annotations.
These data annotations are defined in a class that is specified in the Model property of the
EditForm control.
Validation
The EditForm control defines a method to handle OnValidSubmit. This method is triggered
only when the data in the form satisfies all the validation rules defined by the data annotations.
Any validation errors are displayed using the DataAnnotationsValidator and/or the
ValidationSummary control.
HelpDeskTicket Class
To support the forms and validation that we will implement, add a new class called
HelpDeskTicket.cs in the Shared project.
Because this class is in the Shared project, it will be usable by both the Server and Client
projects.
73
Code Listing 44: HelpDeskTicket.cs
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace SyncfusionHelpDeskClient.Shared
{
public class HelpDeskTicket
{
public int Id { get; set; }
[Required]
public string TicketStatus { get; set; }
[Required]
public DateTime TicketDate { get; set; }
[Required]
[StringLength(50, MinimumLength = 2,
ErrorMessage =
"Description must be a minimum of 2 and maximum of 50
characters.")]
public string TicketDescription { get; set; }
[Required]
[EmailAddress]
public string TicketRequesterEmail { get; set; }
public string TicketGuid { get; set; }
• TextBox: Allows us to gather data from the user and later display existing data.
74
• DatePicker: Allows the user to enter a date value and later display an existing date
value.
• DropDownList: Presents a list of predefined values (in our example, a list of ticket status
values) that allows the user to choose a single value. Later we will use this control to
also display an existing selected value.
The DropDownList control requires a data collection for the display options. To enable this, in
the Shared project, add the following class.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SyncfusionHelpDeskClient.Shared
{
public class HelpDeskStatus
{
public string ID { get; set; }
public string Text { get; set; }
Note: You can get more information on the Syncfusion Blazor controls on their
website.
75
OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator></DataAnnotationsValidator>
<div>
<SfDropDownList TItem="HelpDeskStatus" TValue="string"
PopupHeight="230px"
Placeholder="Ticket Status"
DataSource="@HelpDeskStatus.Statuses"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="@objHelpDeskTicket.TicketStatus">
<DropDownListFieldSettings Text="Text"
Value="ID">
</DropDownListFieldSettings>
</SfDropDownList>
</div>
<div>
<SfDatePicker ID="TicketDate" Placeholder="Ticket Date"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="@objHelpDeskTicket.TicketDate"
Max="DateTime.Now"
ShowClearButton="false"></SfDatePicker>
<ValidationMessage For="@(() => objHelpDeskTicket.TicketDate)" />
</div>
<div>
<SfTextBox Placeholder="Ticket Description"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="@objHelpDeskTicket.TicketDescription">
</SfTextBox>
<ValidationMessage For="@(() =>
objHelpDeskTicket.TicketDescription)" />
</div>
<div>
<SfTextBox Placeholder="Requester Email"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="@objHelpDeskTicket.TicketRequesterEmail">
</SfTextBox>
<ValidationMessage For="@(() =>
objHelpDeskTicket.TicketRequesterEmail)" />
</div>
<br /><br />
<div class="e-footer-content">
<div class="button-container">
<button type="submit" class="e-btn e-normal e-
primary">Save</button>
</div>
</div>
</EditForm>
76
Add the following to the @code section.
Finally, add the following code to insert the data into the database. This will call the Post
method (because it is using PostAsJsonAsync) in the SyncfusionHelpDeskController (in the
Server project) when the form has successfully passed validation.
if (NoAuthenticationClient != null)
{
await NoAuthenticationClient.PostAsJsonAsync(
"SyncfusionHelpDesk", objHelpDeskTicket);
}
77
TicketDate = DateTime.Now
};
StateHasChanged();
if (ToastObj != null)
{
await this.ToastObj.ShowAsync();
}
}
catch (Exception ex)
{
ToastContent = ex.Message;
StateHasChanged();
if (ToastObj != null)
{
await this.ToastObj.ShowAsync();
}
}
}
78
Figure 57: New Help Desk Ticket Form
The user can select a ticket status using the DropDownList control.
79
Figure 59: Select Ticket Date
The user can select a ticket date using the DatePicker control.
If the user tries to save an incomplete record, they will see validation errors.
80
Figure 61: Save Ticket
With a properly completed form, the user can click Save to save the data.
The user will see a brief confirmation message by the Toast control.
81
Figure 63: View Data in the Database
If we look in the database, we will see the data has been added.
Enter data for at least six help desk tickets so that you will have enough data to demonstrate
paging in the Syncfusion DataGrid covered in the following chapter.
82
Chapter 8 Help Desk Ticket Administration
In this chapter, we will detail the steps to create the screens that allow the administrators to
manage the help desk tickets. In the ticket administration that we will construct, help desk
tickets can be updated and deleted, but ticket details that are part of a help desk ticket can only
be added and deleted.
Add the Administration page to the Client project by right-clicking the Pages folder and selecting
Add > New Item.
83
Figure 65: Add New Item
Select the Razor Component template, name the control Administration.razor, and click Add.
@page "/administration"
@using SyncfusionHelpDeskClient.Shared
@inject HttpClient Http
@inject IHttpClientFactory ClientFactory
</AuthorizeView>
@code {
#nullable disable
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
84
Add Link in NavMenu.razor
We will now add a link to the Administration control in the navigation menu.
Open the NavMenu.razor control in the Shared folder of the Client project and remove the
following code (for the links to the counter and fetchdata pages).
<AuthorizeView Roles="Administrators">
<div class="nav-item px-3">
<NavLink class="nav-link" href="administration">
<span class="oi oi-plus" aria-hidden="true"></span>
Administration
</NavLink>
</div>
</AuthorizeView>
This will display a link to the Administration.razor page, but this link will only display for
administrators because it is wrapped in an AuthorizeView control with the Roles property set
to Administrators.
85
Figure 66: Administration Link
When we run the application and log in as the administrator, we see the Administration link.
When we click on the link, we are navigated to the Administration page (currently blank).
It has a DataSource property that we will bind to a collection of the help desk tickets. It also has
properties to allow for paging and sorting.
The control also allows us to define the columns that will contain the tabular data, as well as
Edit and Delete buttons.
<div>
<div id="target" style="height: 500px;">
<SfGrid ID="Grid"
DataSource="@colHelpDeskTickets"
AllowPaging="true"
AllowSorting="true"
AllowResizing="true"
AllowReordering="true">
<SfDataManager Url="SyncfusionHelpDesk"
Adaptor="Adaptors.WebApiAdaptor">
</SfDataManager>
<GridPageSettings PageSize="5"></GridPageSettings>
<GridEvents CommandClicked="OnCommandClicked"
TValue="HelpDeskTicket">
</GridEvents>
<GridColumns>
86
<GridColumn HeaderText="" TextAlign="TextAlign.Left"
Width="150">
<GridCommandColumns>
<GridCommandColumn Type=CommandButtonType.Edit
ButtonOption="@(new CommandButtonOptions()
{ Content = "Edit" })">
</GridCommandColumn>
<GridCommandColumn Type=CommandButtonType.Delete
ButtonOption="@(new CommandButtonOptions()
{ Content = "Delete" })">
</GridCommandColumn>
</GridCommandColumns>
</GridColumn>
<GridColumn IsPrimaryKey="true"
Field=@nameof(HelpDeskTicket.Id)
HeaderText="ID #" TextAlign="@TextAlign.Left"
Width="70">
</GridColumn>
<GridColumn Field=@nameof(HelpDeskTicket.TicketStatus)
HeaderText="Status" TextAlign="@TextAlign.Left"
Width="80">
</GridColumn>
<GridColumn Field=@nameof(HelpDeskTicket.TicketDate)
HeaderText="Date" TextAlign="@TextAlign.Left"
Width="80">
</GridColumn>
<GridColumn Field=@nameof(HelpDeskTicket.TicketDescription)
HeaderText="Description"
TextAlign="@TextAlign.Left"
Width="150">
</GridColumn>
<GridColumn Field=@nameof(HelpDeskTicket.TicketRequesterEmail)
HeaderText="Requester" TextAlign="@TextAlign.Left"
Width="150">
</GridColumn>
</GridColumns>
</SfGrid>
</div>
</div>
87
public IQueryable<HelpDeskTicket> colHelpDeskTickets { get; set; }
The SfDataManager control populates the colHelpDeskTickets collection that is bound to the
DataSource property of the DataGrid.
When running the application, log in as an administrator and navigate to the Administration
page. We will see the help desk ticket records displayed in the data grid.
88
Figure 68: Sorting
Deleting a record
We will implement the functionality to delete a help desk ticket record.
To enable this, the DataGrid control allows us to create custom command buttons using the
CommandClicked property that we currently have wired to the OnCommandClicked method.
We will use the OnCommandClicked method to display a dialog box, using the Syncfusion
Dialog control, which will require the user to confirm that they want to delete the record.
Syncfusion Dialog
The Dialog control is used to display information and accept user input. It can display as a
modal control that requires the user to interact with it before continuing to use any other part of
the application.
<SfDialog Target="#target"
Width="100px"
Height="130px"
IsModal="true"
ShowCloseIcon="false"
89
@bind-Visible="DeleteRecordConfirmVisibility">
<DialogTemplates>
<Header> DELETE RECORD ? </Header>
<Content>
<div class="button-container">
<button type="submit"
class="e-btn e-normal e-primary"
@onclick="ConfirmDeleteYes">
Yes
</button>
<button type="submit"
class="e-btn e-normal"
@onclick="ConfirmDeleteNo">
No
</button>
</div>
</Content>
</DialogTemplates>
</SfDialog>
Change the OnCommandClicked method to the following to respond to the Delete button being
clicked for a help desk record.
90
this.DeleteRecordConfirmVisibility = true;
StateHasChanged();
}
}
Add the following method, which will simply close the dialog if the user clicks the No button on
the dialog.
@ref="gridObj"
SfGrid<HelpDeskTicket> gridObj;
Now, we can add the following method to delete the record if the user clicks the Yes button in
the dialog.
91
This method will refresh the data grid by calling gridObj.Refresh(), which uses the gridObj
object defined with the @ref attribute.
When we run the application, we can click Delete to open the dialog.
Clicking the No button will simply close the dialog. Clicking the Yes button will delete the
selected record and refresh the data grid.
92
Edit ticket control
We will construct an EditTicket control that will be placed inside a dialog in the Administration
page and displayed when an administrator wants to edit a help desk ticket. We do this to allow
this control to be reused in the EmailTicketEdit.razor page (covered in the following chapter).
In the Pages folder of the Client project, create a new control called EditTicket.razor using
the following code.
93
Code Listing 60: EditTicket.razor
@using System.Security.Claims;
@using Syncfusion.Blazor.DropDowns
@using SyncfusionHelpDeskClient.Shared
@inject HttpClient Http
@inject IHttpClientFactory ClientFactory
<div>
<SfDropDownList TItem="HelpDeskStatus" Enabled="!isReadOnly"
TValue="string" PopupHeight="230px"
Placeholder="Ticket Status"
DataSource="@HelpDeskStatus.Statuses"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="@SelectedTicket.TicketStatus">
<DropDownListFieldSettings Text="Text"
Value="ID">
</DropDownListFieldSettings>
</SfDropDownList>
</div>
<div>
<SfDatePicker ID="TicketDate" Enabled="!isReadOnly"
Placeholder="Ticket Date"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="@SelectedTicket.TicketDate"
Max="DateTime.Now"
ShowClearButton="false">
</SfDatePicker>
</div>
<div>
<SfTextBox Enabled="!isReadOnly" Placeholder="Ticket Description"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="@SelectedTicket.TicketDescription">
</SfTextBox>
</div>
<div>
<SfTextBox Enabled="!isReadOnly" Placeholder="Requester Email"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="@SelectedTicket.TicketRequesterEmail">
</SfTextBox>
</div>
@if (SelectedTicket.HelpDeskTicketDetails != null)
{
@if (SelectedTicket.HelpDeskTicketDetails.Count() > 0)
{
<table class="table">
<thead>
<tr>
<th>Date</th>
94
<th>Description</th>
</tr>
</thead>
<tbody>
@foreach (var TicketDetail in
SelectedTicket.HelpDeskTicketDetails)
{
<tr>
<td>
@TicketDetail.TicketDetailDate.ToShortDateString()
</td>
<td>
@TicketDetail.TicketDescription
</td>
</tr>
}
</tbody>
</table>
}
<SfTextBox Placeholder="NewHelp Desk Ticket Detail"
@bind-Value="@NewHelpDeskTicketDetailText">
</SfTextBox>
<SfButton CssClass="e-small e-success"
@onclick="AddHelpDeskTicketDetail">
Add
</SfButton>
}
<br />
@code {
#nullable disable
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
[Parameter]
public HelpDeskTicket SelectedTicket { get; set; }
95
// Enable editing.
isReadOnly = !CurrentUser.Identity.IsAuthenticated;
}
NewHelpDeskTicketDetail.HelpDeskTicketId =
SelectedTicket.Id;
NewHelpDeskTicketDetail.TicketDetailDate =
DateTime.Now;
NewHelpDeskTicketDetail.TicketDescription =
NewHelpDeskTicketDetailText;
// Add to collection.
SelectedTicket.HelpDeskTicketDetails
.Add(NewHelpDeskTicketDetail);
Notice that this exposes a SelectedTicket parameter (of type HelpDeskTicket) that will
accept a reference of a help desk ticket record.
In the Administration.razor control, add the following markup to display the EditTicket.razor
page in a Dialog control.
<SfDialog Target="#target"
Width="500px"
Height="500px"
IsModal="true"
ShowCloseIcon="true"
@bind-Visible="EditDialogVisibility">
<DialogTemplates>
<Header> EDIT TICKET # @SelectedTicket.Id</Header>
<Content>
<EditTicket SelectedTicket="@SelectedTicket" />
</Content>
<FooterTemplate>
96
<div class="button-container">
<button type="submit"
class="e-btn e-normal e-primary"
@onclick="SaveTicket">
Save
</button>
</div>
</FooterTemplate>
</DialogTemplates>
</SfDialog>
Note that this instantiates the EditTicket control (the EditTicket.razor code page) and passes
a reference to the currently selected help desk ticket record (@SelectedTicket) through the
SelectedTicket attribute.
Add the following to the @code section to implement the functionality for the Save button on the
dialog.
Next, add the following code to the OnCommandClicked method to open the dialog when the
Edit button is clicked on a row in the data grid.
if (args.CommandColumn.ButtonOption.Content == "Edit")
{
// Get the selected Help Desk Ticket.
SelectedTicket =
await Http.GetFromJsonAsync<HelpDeskTicket>(
"Email?HelpDeskTicketGuid=" +
97
args.RowData.TicketGuid);
Finally, in the Server project, create a new class in the Controllers folder called
EmailController.cs using the following code.
#nullable disable
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using SyncfusionHelpDeskClient.Shared;
namespace SyncfusionHelpDesk.Data
{
[ApiController]
[Route("[controller]")]
public class EmailController : ControllerBase
{
private readonly IConfiguration configuration;
private readonly IHttpContextAccessor httpContextAccessor;
private readonly SyncfusionHelpDeskContext _context;
public EmailController(
IConfiguration Configuration,
IHttpContextAccessor HttpContextAccessor,
SyncfusionHelpDeskContext context)
{
configuration = Configuration;
httpContextAccessor = HttpContextAccessor;
_context = context;
}
[HttpGet]
[AllowAnonymous]
public object Get()
{
98
// Return only one ticket.
StringValues HelpDeskTicketGuidProperty;
string HelpDeskTicketGuid =
(Request.Query.TryGetValue("HelpDeskTicketGuid",
out HelpDeskTicketGuidProperty))
? HelpDeskTicketGuidProperty.ToString() : "";
return ExistingTicket;
}
}
}
This class will later be expanded when we add email functionally, in the next chapter.
99
Figure 72: Edit Ticket Dialog
When we run the application, we can click Edit next to a record to open it up in the dialog.
Any of the help desk ticket values at the top of the form can be edited and saved by clicking
Save at the bottom of the dialog.
Near the bottom of the form, help desk ticket detail records can be added by entering text in the
text box and clicking Add.
100
Figure 73: Edit Ticket
The help desk detail record will be added, but it will not be saved until the Save button is
clicked.
101
Chapter 9 Sending Emails
In this chapter, we will create code that will allow ticket creators and administrators to update
help desk tickets by simply clicking a link in an email.
We will create the code that will email the administrators when a new help desk ticket has been
created, as well as email help desk ticket creators and administrators when help desk tickets
are updated.
Open the appsettings.json file and add the following two lines below the opening curly bracket
(above the "ConnectionStrings": line), entering your SendGrid key for the
SENDGRID_APIKEY property and your email address for the SenderEmail property.
102
Figure 75: SendGrid NuGet Package
EmailSender class
We will update the EmailController.cs class to read the settings from the appsettings.json
file and send emails.
Add a new class to the Shared project named HelpDeskEmail.cs using the following code.
#nullable disable
using System;
using System.Collections.Generic;
using System.Text;
namespace SyncfusionHelpDeskClient.Shared
{
public class HelpDeskEmail
{
public string EmailType { get; set; }
public string EmailAddress { get; set; }
103
public string TicketGuid { get; set; }
}
}
To actually send the email, open the EmailController.cs file in the Controllers folder of the
Server project and add the following using statements.
using SendGrid;
using SendGrid.Helpers.Mail;
Add the following method to read the SENDGRID_APIKEY from the appsettings.json file and
send the emails.
[HttpPost]
[AllowAnonymous]
public Task Post(HelpDeskEmail objHelpDeskEmail)
{
try
{
// Email settings.
SendGridMessage msg = new SendGridMessage();
var apiKey = configuration["SENDGRID_APIKEY"];
var senderEmail = configuration["SenderEmail"];
var client = new SendGridClient(apiKey);
string strHtmlContent =
$"<b>{objHelpDeskEmail.EmailType}:</b> ";
strHtmlContent = strHtmlContent +
$"<a href='" +
$"{GetHelpDeskTicketUrl(objHelpDeskEmail.TicketGuid)}" +
$"'>";
strHtmlContent = strHtmlContent +
$"{GetHelpDeskTicketUrl(objHelpDeskEmail.TicketGuid)}</a>";
104
if (objHelpDeskEmail.EmailType == "Help Desk Ticket Created")
{
msg = new SendGridMessage()
{
From = FromEmail,
Subject = objHelpDeskEmail.EmailType,
PlainTextContent = strPlainTextContent,
HtmlContent = strHtmlContent
};
// Send email.
msg.AddTo(new EmailAddress(
objHelpDeskEmail.EmailAddress,
objHelpDeskEmail.EmailType)
);
}
else
{
Task.FromResult("Error - Bad TicketGuid");
105
}
}
client.SendEmailAsync(msg);
}
catch
{
// Could not send email.
// Perhaps SENDGRID_APIKEY not set in
// appsettings.json
}
return Task.FromResult("");
}
// Utility
return url;
}
#endregion
Next, open the Index.razor file in the Client project and add the following code to the end of the
HandleValidSubmit method (under the PostAsJsonAsync line).
// Send email
HelpDeskEmail objHelpDeskEmail = new HelpDeskEmail();
objHelpDeskEmail.EmailType = "Help Desk Ticket Created";
objHelpDeskEmail.EmailAddress = "";
objHelpDeskEmail.TicketGuid = objHelpDeskTicket.TicketGuid;
await NoAuthenticationClient.PostAsJsonAsync(
"Email", objHelpDeskEmail);
106
This code will use PostAsJsonAsync to call the Post method of the EmailController.cs file to
send the emails. This code will be called when a user creates a new help desk ticket.
// Send email
HelpDeskEmail objHelpDeskEmail =
new HelpDeskEmail();
objHelpDeskEmail.EmailType =
"Help Desk Ticket Updated";
objHelpDeskEmail.EmailAddress =
SelectedTicket.TicketRequesterEmail;
objHelpDeskEmail.TicketGuid =
SelectedTicket.TicketGuid;
await Http.PostAsJsonAsync(
"Email", objHelpDeskEmail);
Route parameters
When a help desk ticket is initially created and saved to the database, it is assigned a unique
GUID value. When an email is sent to notify the help desk ticket creator and administrator, the
email will contain a link that passes this GUID to the Blazor control that we will create. This
control will be decorated with an @page directive that contains a route parameter.
In the Pages folder of the Client project, create a new control called EmailTicketEdit.razor
with the following code.
@page "/emailticketedit/{TicketGuid}"
This line, together with a field in the @code section called TicketGuid (of type string), will
allow this control to be loaded and passed a value for TicketGuid from a link in the email.
Enter the following code as the remaining code for the file.
107
Code Listing 72: EmailTicketEdit Code
@using SyncfusionHelpDeskClient.Shared
@inject HttpClient Http
@inject IHttpClientFactory ClientFactory
@code {
#nullable disable
[Parameter] public string TicketGuid { get; set; }
HttpClient NoAuthenticationClient;
private HelpDeskTicket SelectedTicket = new HelpDeskTicket();
private bool EditDialogVisibility = true;
108
// Create an httpClient to use for non-authenticated calls.
NoAuthenticationClient =
ClientFactory.CreateClient("ServerAPI.NoAuthenticationClient");
StateHasChanged();
}
}
// Send email.
HelpDeskEmail objHelpDeskEmail = new HelpDeskEmail();
objHelpDeskEmail.EmailType = "Help Desk Ticket Updated";
objHelpDeskEmail.EmailAddress =
SelectedTicket.TicketRequesterEmail;
objHelpDeskEmail.TicketGuid = SelectedTicket.TicketGuid;
await NoAuthenticationClient.PostAsJsonAsync(
"Email", objHelpDeskEmail);
return;
}
}
Notice that this page also includes the EditTicket control, effectively reusing that control in
both this page and the Administration page.
109
Email link
When we run the application and create a new help desk ticket, the administrator is sent an
email with a link that will take the administrator directly to the help desk ticket.
110
Conclusion
We've now seen how Blazor technology enables you to create sophisticated, manageable, and
extensible single-page applications using C# and Razor syntax. Now, try it for yourself!
111