Beginning Azure Static Web Apps Building and Deploying Dynamic Web
Beginning Azure Static Web Apps Building and Deploying Dynamic Web
Stacy Cashmore
Beginning Azure Static Web Apps: Building and Deploying Dynamic Web
Applications with Blazor
Stacy Cashmore
Amsterdam, The Netherlands
v
Table of Contents
vi
Table of Contents
vii
Table of Contents
viii
Table of Contents
ix
Table of Contents
x
Table of Contents
Index��������������������������������������������������������������������������������������������������������������������� 285
xi
About the Author
Speaker, author, and software developer, Stacy Cashmore
has been developing solutions since the mid-1990s in
various companies and industries ranging from facilitating
contract jobbing to allowing consumers to close a mortgage
without the help of a financial adviser – with lots in between.
She has a passion for sharing knowledge: using story telling
for sharing her experiences to help teams grow in the ways that
they develop software and work together and performing live
coding demonstrations to inspire others to try new technologies.
For her effort in the community, Stacy has been awarded the Microsoft MVP for
Developer Technologies since 2020.
xiii
About the Technical Reviewers
Marc Duiker is a Senior Developer Advocate at Ably with a
strong focus on event-driven architectures in the Azure cloud.
He loves helping developers to achieve more every day.
You might have seen Marc at a developer meetup
or conference since he's a regular speaker in the area of
Azure cloud and serverless technologies. He started Azure
Functions University, a free and open source learning
curriculum on GitHub, where everyone can learn about
Azure Functions at their own pace. In 2019, 2020, and
2021, Marc received the Microsoft Azure MVP award for his
community contributions.
In his spare time, Marc likes to give attention to the creative part of his brain. He likes
to create pixel art (check out VSCode Pets), code visuals and music, and an occasional
retro game.
xv
Acknowledgments
This book has been a while in the making. Not only the writing itself but also the journey
leading up to it. And there are many people to whom I owe a huge thank you.
I need to start with the amazing Jessica Engström. For your kindness to a terrified
conference attendee and starting me on this journey by convincing me to try my hand at
public speaking and for becoming an amazing friend.
To all the speakers from Swetugg 2019 for helping me get through that first talk, I’m
honored that I can call so many of you friends now.
And to Mattias Karlsson, for inviting me to write my first technical talk for your
conference in 2020. That really put me on the road to making technical content and
looking at new technologies – even if Covid got in the way and it had to be delivered
virtually! I’d still love to give a technical talk for your user group/conference at
some point.
To Cindy Velthuizen and Tom Ehren, for your support, advice, and time over the last
few years as I made this journey.
A huge thanks to Jonathan Gennick for reaching out, asking me if I would be
interested in writing a book, and helping me through those first stages.
To Shrikant Vishwakarma and Smriti Srivastava for your help while actually writing
the book and keeping me on track.
And everyone else at Apress who make getting books from Word documents on my
computer to being an actual book possible.
To my technical reviewers.
Marc Duiker for checking that I was doing the right things with the Azure Functions
through the chapters, and laughter at some of the comments I made.
And Jimmy Engström for doing the same with the Blazor code and helping me think
my way out of problems during the writing.
Of course, any issues that remain here are entirely down to me!
And to everyone else, too numerous to mention, who have helped me get this book
into readers’ hands.
xvii
Acknowledgments
Finally, my parents for always supporting me in what I wanted, and needed to do,
and cheering me on when it would have been so easy to give up at school, college, and
university. It means the world to me that you believed in me through those years.
And my partner and child, who wish to remain anonymous, but it wouldn’t be right
to not include them here. For your love, support, and helping me stay on track, I am
forever indebted.
xviii
PART I
Getting Started
To get an Azure Static Web App into production, we first have to do some groundwork.
There are online accounts that we need to create and set up, and there are
applications that we need to install.
In this part, we are going to make sure that we have everything that we need to create
our application and then make a sample application that we will use to create the Azure
Static Web App.
Once we have all of this ready, we can start to work on our application itself!
CHAPTER 1
Introduction
A couple of years ago, I decided that I’d been putting off making my own website for too
long. With the technology available to us, it doesn’t need to be difficult or complex to
develop our own space on the Web. I didn’t want to run my site on a simple home page
application though, I wanted to experiment with new technologies and learn new things!
With this in mind, I have used Azure Static Web Apps, with Blazor and .NET Azure
functions, to develop my personal site over the last couple of years.
I’ve spoken at many conferences and meetups about the subject, and when I hear
that someone has taken what I’ve said and tried something by themselves, it gives me a
real buzz of excitement that I’ve helped someone take that step.
And so, I decided to write this book to take a beginner on their journey of discovery!
Before we get started though, a little background on the technology that we’ll
be using!
JamStack
A method of developing applications that is becoming ever more popular is JamStack.
That is, using markup files and JavaScript to make a dynamic application that runs in a
browser and then supplementing these with APIs to fetch and store data.
Splitting up our applications in this way allows us to take advantage of new ways
of hosting sites. Our application is made of static files, so we no longer need a complex
web server. We can simply host our files statically so that they can run inside of a user’s
browser.
As our back end now only serves data requests, rather than complete web pages,
we can use the growing number of serverless possibilities to host our API, allowing easy
deployment and scaling using technologies like Microsoft Azure Functions, Google
Cloud Functions, or AWS Lambda Functions.
3
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_1
Chapter 1 Introduction
When we put this together, we get a hosting model that is simple and cheap – even
free – to put our application out into the world!
Over the last few years, this way of creating applications has been increasing in
popularity over traditional server-side generated applications that require dedicated web
servers to serve content.
BlamStack
JamStack is great for developers who use JavaScript technologies.
But there are lots of developers and development teams that use .NET with C#. Until
now, the JamStack way of developing applications was closed to these developers. Front-
end development in .NET was restricted to Web Forms or MVC. Both technologies have
their place – but both are also server technologies, with the final HTML being served to
the browser.
Then Blazor arrived! Originally released as a server technology, but with a browser
technology released soon after – Blazor WebAssembly – allowing us to take advantage of
using static files for our application.
Blazor WebAssembly runs on a .NET runtime compiled to run inside of a browser.
Not using any special plug-ins – this isn’t Silverlight for the 2020s – but using the built-in
WebAssembly runtime in the browsers themselves.
Blazor WebAssembly applications run the same way as JamStack applications. The
Blazor application handles the client application, running on the machine of the user
themselves, while an API provides the data to make the application dynamic. JamStack
but with Blazor: BlamStack!
Now we can take advantage of the paradigm using C# and .NET.
4
Chapter 1 Introduction
There are several ways to do this and with multiple providers. In 2021, Microsoft
introduced Azure Static Web Apps to make this possible in Azure.
Rather than deal with each of the components that make our application separately,
Azure Static Web Apps contain components all in one easy-to-manage resource.
The Azure Static Web App handles the global distribution of the static content and
even handles cache invalidation when a new version of the application is deployed.
They also have built-in Azure Functions to handle the API for our application.
Not only does this mean that we only need one Azure resource to handle storage,
distribution, and our Azure Functions, etc., but it also connects the static content to
those Azure Functions so that, to the outside world, it seems like it is one single resource.
This, again, simplifies our workflow by ensuring that we do not need to handle CORS
requests between the front end and back end.
And to make life even easier for us, we even get authentication out of the box with
several popular platforms, meaning that we can authenticate our users without ever
knowing their passwords!
If deploying from GitHub or Azure DevOps, we even get a simple continuous
integration and delivery (CI/CD) flow created at the same time as our application. The
fact that this works out of the box means that anyone can have a
high-quality CI/CD flow without having to invest time learning how to use those
pipelines, allowing for greater focus on the application itself!
It doesn’t mean that we are limited in our options though! As developers, we can take
more control over our Azure Static Web App if we need to, making it also suitable for the
enterprise environment.
Book Focus
On our journey through this book, we will be focusing on getting started with Azure
Static Web Apps, looking at what we can do to get an application into production, with
authentication.
At the end of our journey, I’ll leave you with some suggestions for you to carry on
your project after you have finished and allow yourself to learn even more.
The book is focused on a Windows development environment; it is possible to follow
along on any platform that supports .NET development – though some software will be
different to the descriptions in the books.
5
Chapter 1 Introduction
6
CHAPTER 2
Setting
Up the Environment
In this chapter, we will set up our development environment and required online
services so that we can create our first Azure Static Web App.
We will create the online accounts and repositories needed to host our Static Web
App and source code, clone the repo to our local machine, and install Visual Studio 2022
as an IDE to develop our applications locally. We can use any edition of Visual Studio for
the examples in this book, including the free Community Edition.
By the end of this chapter, we will have everything that we need, both online
accounts and resources on our local machine, to be ready to create and deploy our Azure
Static Web App.
If you already have all the required accounts and tools already installed and are
experienced in creating repositories in GitHub, then feel free to simply create a new
repository, clone it to your machine, and skip ahead to the next chapter!
7
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_2
Chapter 2 Setting Up the Environment
8
Chapter 2 Setting Up the Environment
This will bring us to the Microsoft Azure free trial screen. Click the green “Get started
for free” button as seen in Figure 2-2.
A prompt will appear to log in to a Microsoft Account. If you do not already have an
account, you can create one instead.
In the course of this flow, you will need to enter a credit card as a payment method for
the subscription.
The flow to create the Azure Account asks for standard personal data (name, email,
phone, address, etc.).
When the portal is visible, the account is set up and ready.
GitHub Account
Next, we need to set up a GitHub account. This will be where our code repository lives
and will be used to deploy our Static Web Application into Azure.
1. Go to https://github.com.
3. In the page that opens up, enter the email and click “Continue.”
9
Chapter 2 Setting Up the Environment
Click “Continue.”
After completing the puzzle, GitHub will send an email with a code. This must be
entered on the form seen in Figure 2-4 to prove that we have access to the email address
we used.
10
Chapter 2 Setting Up the Environment
Now the email has been verified, we can continue to set up our account!
11. We can ignore the feature selection screen for the purpose of
this book.
Click “Continue.”
After selecting a plan, we will be redirected to our new GitHub dashboard as shown
in Figure 2-5.
11
Chapter 2 Setting Up the Environment
GitHub Repo
Now that we have a GitHub account, we will need to create a repository to hold our code
and from where we will be building and deploying our Azure Static Web App.
From the GitHub dashboard at the end of the last session, click “Create repository”
as shown in Figure 2-6.
This will open the screen where we enter the information needed to create a
repository.
12
Chapter 2 Setting Up the Environment
When creating a repository, it’s important to check that the owner of the repository is
set correctly. It should be correct by default, but it is always good to make sure!
Beyond this, there are a number of steps that we need to take to ensure that our
repository behaves as expected.
13
Chapter 2 Setting Up the Environment
README files are often used to convey the purpose of a repository, how to use
the application, how to set up and run the application for development, and any extra
information that could be useful.
Add a .gitignore
When creating software, many files get created which should only exist on the developer
machine where they are created. Visual Studio, for example, creates files to hold user
information, along with “bin” and “obj” files when code is built. These files do not need
version control. Also, there are files which may contain secrets, local settings files, for
instance, that we need to run code locally. These should never be pushed to a repository
for security reasons.
Depending on the development language, and environment, that is used, different
patterns of files should be ignored.
For this book, Visual Studio 2022 is going to be used, so we need a .gitignore tailored
to that environment.
Open the drop-down box as seen in Figure 2-7.
14
Chapter 2 Setting Up the Environment
As there are many options available, it is generally quicker and easier to filter the
options than to search through the whole list.
Type “Visual” into the filter box, and the list will be more specific as seen in
Figure 2-8.
15
Chapter 2 Setting Up the Environment
Select “VisualStudio.”
Choose a License
The last option is for the license used for the repository. This isn’t mandatory, but if we
are making a repository public, then consider adding a license to it. When we do this, it
will be displayed at the top of the repository when people visit the page.
This book does not cover what license models are best – that is a choice that the
repository owner needs to make. However, there are resources online that can help make
the selection. For further reading, follow the following links:
https://docs.github.com/en/repositories/managing-our-repositorys-
settings-and-features/customizing-our-repository/licensing-a-repository
https://choosealicense.com/
If a license is needed, select the required license from the list shown in Figure 2-9.
16
Chapter 2 Setting Up the Environment
As with the .gitignore, we can filter the list of options to find our preferred license.
Default Branch
When the README, .gitignore, or license option is checked, GitHub will initialize
the repository with a default branch in order to store those files. Extra information is
displayed on the page about the default branch as seen in Figure 2-10.
Now that all options are selected, the “Create New Repository” page should look
something like Figure 2-11.
17
Chapter 2 Setting Up the Environment
18
Chapter 2 Setting Up the Environment
Git
In order to use the GitHub repository for our code, we will need to clone it to our local
machine. Installing Git is not complex, but there are many screens that we need to walk
through during the installation.
Go to https://git-scm.com/downloads in a new browser tab to download the install
file. For the purpose of the book walk-through, Windows is assumed – other OS installs
will vary.
Once downloaded, start the install process.
The defaults are fine to start with. However, it can be useful to include
the Git Bash Profile for Windows Terminal, seen in Figure 2-12.
19
Chapter 2 Setting Up the Environment
4. Select the name for the Start Menu folder; we can use the
default here.
Again, this isn’t something that we will be using in the book, but a
good rule of thumb is to override the default with “main” (this is
also the default alternative suggested by the installer) to make our
repositories more inclusive, as seen in Figure 2-13.
20
Chapter 2 Setting Up the Environment
7. Select how, or if, to add the Git command line to the PATH
variable for use in applications other than Git Bash.
We can keep the default value for this setting.
Click “Next” to continue.
21
Chapter 2 Setting Up the Environment
22
Chapter 2 Setting Up the Environment
• .gitignore
• README.md
The contents of the README file are also displayed under the list of files.
In the top right of the screen, there is a green button “Code.”
Click this to expand the code context menu, as seen in Figure 2-16.
23
Chapter 2 Setting Up the Environment
In order to make a connection with the repository, we are going to clone the code to
our machine using the Git command line.
Click the two overlapping squares to copy the URL for the repository, seen in
Figure 2-17.
Where “https://github.com/swa-for-beginners/beginning-static-web-apps.
git” is the URL of the GitHub repository.
Because we are trying to clone a private repository, we need to prove that we have
the rights to access it. On the GitHub Sign-In pop-up, click “Sign in with your browser”
seen in Figure 2-18.
24
Chapter 2 Setting Up the Environment
When the browser window opens to authorize the Git Credential Manager with
GitHub, click “Authorize Git Credential Manager” as seen in Figure 2-19.
25
Chapter 2 Setting Up the Environment
As we are making changes to GitHub that can have security implications, we may
need to reenter our GitHub password. If the pop-up seen in Figure 2-20 appears, fill in
the password and click “Confirm password.”
Git will then create a folder for the repository to be cloned into, clone the files in the
repository to our machine, check out the default branch, and make a connection to the
remote repository so that we can push any changes easily.
The console should look something similar to Figure 2-21.
26
Chapter 2 Setting Up the Environment
Before Visual Studio can be installed, the Visual Studio Installer first needs to be
installed. A pop-up as seen in Figure 2-23 should appear.
27
Chapter 2 Setting Up the Environment
4. Click “Continue.”
Once installation is complete, the Visual Studio Installer workload selection screen
is displayed, shown in Figure 2-24. This is where we can select the components that we
need in order to develop the solution from the book.
28
Chapter 2 Setting Up the Environment
The selections, seen in Figure 2-25, mentioned here are the minimum needed for the
examples in this book.
8. Click “Install.”
Visual Studio will now install. The time taken for the installation depends on the
speed of our network connection. Progress is displayed as in Figure 2-26. Once the install
is completed, Visual Studio will open. We will use this in the following chapter.
29
Chapter 2 Setting Up the Environment
Other Languages
The examples, and project, that this book uses to demonstrate Azure Static Web Apps
are written in C#, using Blazor WebAssembly, for the Client application, and .NET Azure
Functions, also written in C#, for the API project.
However, using these languages is not a requirement for Azure Static Web Apps
themselves. They are simply the language and technologies that have been selected.
Azure Static Web Apps support a multitude of front-end languages and frameworks
that generate static files for deployment – including several static site generators. There
are also many languages and frameworks supported for Azure Functions, so we are not
limited to just C#/.NET here either.
If you would like to follow along using one of these languages, that is possible.
The functionality of the Static Web App itself is unchanged, though accessing that
functionality in code will differ extensively from the examples and project used
throughout.
Conclusion
In this chapter, we have set up the cloud, and local, resources needed to get started
with development. We are now ready to start developing our application! In the next
chapter, we will create a base application and take a look at how to connect the front-end
application to the API ready for deployment.
30
CHAPTER 3
Technical Requirements
To run the examples in this chapter, Visual Studio 2022 should be installed as per the
instructions in Chapter 2, and the GitHub repository created in Chapter 2 should be
cloned to your local machine.
31
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_3
Chapter 3 Creating the Application
Clone
We used this command in the previous chapter to get the repository. The command is
executed with a link to a remote repository; Git then creates a folder for that repository
locally and clones it, so that it can be used locally.
This command is used before the repository exists locally.
Pull
This command is executed inside of a local repository that has already been cloned. The
command will go to the remote URL where the repository was cloned from and pull any
new changes into the local repository.
This is how we keep our local code up to date with changes that others have made.
Commit
This command takes changes that we have made and applies them to our repository.
When we edit a file, we have what is called an unstaged change. Git is not currently
tracking that change, and we can easily undo it. Once we stage a change so that Git
knows about it, we can then commit it to the Git repository.
Once committed, it becomes an immutable part of the repository, with a hash
referencing who changed it and when.
We need to commit changes if we want to push them to our GitHub repository.
Push
With the push command, we can take those changes that we have committed locally and
push them out to the remote repository so that others can pull them down onto their
machines.
This is how we share code that we have created or changed and is also how we are
going to get any changes we make to our application built and deployed.
32
Chapter 3 Creating the Application
2. In the start screen that opens (see Figure 3-1), click “Create a new
project.”
33
Chapter 3 Creating the Application
34
Chapter 3 Creating the Application
35
Chapter 3 Creating the Application
When the page has been scaffolded, Visual Studio should load and look like
Figure 3-5.
36
Chapter 3 Creating the Application
37
Chapter 3 Creating the Application
Program.cs
The Program.cs file is the entry point to our application and is run when the website is
first loaded.
In this file, our WebAssembly application is created, and the App.razor component
is added to the root components of the application and attached to the class “app.” See
Code 3-1.
38
Chapter 3 Creating the Application
To control “head” information of the HTML from within a Razor component, the
HeadOutlet is added and attached to the identifier “head::after”.
Also in this file, all services needed to run the application are initialized, including
any that we need to inject into components (Code 3-2).
await builder.Build().RunAsync();
_Imports.razor
Each component in C# only knows about code within the same namespace as itself.
To be able to use components from a different namespace, we have to tell the compiler
where to look to find the code.
For namespaces that you will use often in your razor files, you can avoid cluttering
every file with the same using statements by instead adding them to the “_imports.razor”
file. The using statements included here will be added to each razor file automatically.
We will take advantage of this later in the book!
An amount of using statements needed for most frequently are included by default
when a new application is made.
App.razor
The App.razor component is where the router for the application that contains the App
component is loaded (Code 3-4).
39
Chapter 3 Creating the Application
<Router AppAssembly="@typeof(App).Assembly">
This determines what razor components are loaded depending on the address of the
page being loaded (Code 3-5).
<Found Context="routeData">
<RouteView RouteData="@routeData" ➥
DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
The component handles loading route pages when they are found, as well as
handling when a page isn’t found (Code 3-6).
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert"> ➥
Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
wwwroot Folder
When a Blazor WebAssembly project is published, the wwwroot folder is copied into the
published folder. This is where the static files for the client application are located.
CSS Folder
Blazor applications are created with Bootstrap (https://getbootstrap.com/) installed.
The CSS files for Bootstrap, and for application-wide CSS, are created here. Isolated CSS
for individual Razor components is created within the files linked to the components
themselves.
40
Chapter 3 Creating the Application
index.html
This is the HTML entry point for the application. Each time the application is loaded,
this page is loaded by the browser.
The static CSS files are linked as in Code 3-7.
<link href="css/bootstrap/bootstrap.min.css" ➥
rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
As well as the bundled isolated CSS for the Razor components (Code 3-8).
In the body of the HTML, a placeholder is made where our application will be
created (Code 3-9).
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">❌</a>
</div>
And finally, the Blazor application is called via the “blazor.webassembly.js” script at
the end of the file (Code 3-11).
41
Chapter 3 Creating the Application
<script src="_framework/blazor.webassembly.js"></script>
It is good to know that this page exists, and what it does, but it isn’t something that
we are going to be changing during the course of the book.
Shared Folder
As the name implies, the shared folder contains code that is shared across the
application. By default, there are three files here to run the scaffolded application, but
only two of them are important to the scaffolded application that we are going to develop
further.
MainLayout.razor
This is the default layout of the application. Each page that is loaded will use this file for
the global template by default; it can be overridden.
The navigation menu is loaded into the sidebar as seen in Code 3-12.
<div class="sidebar">
<NavMenu />
</div>
In the main section of the page, the content from the route is loaded into the @Body
tag, as in Code 3-13. There is also a link to the ASP.NET documentation included before
the content; as this isn’t important for the running of the application, it isn’t shown in
the snippet.
42
Chapter 3 Creating the Application
NavMenu.razor
This is the navigation menu for our application, which is loaded in the MainLayout.
razor file. Each page that we have in our application has a NavLink component for easy
navigation; an example is shown in Code 3-14.
This NavLink component not only displays the button for navigation but also
matches the route currently loaded and highlights that link.
The navigation menu also has functionality to show or hide the navigation menu the
page is displayed on a smaller screen. We get a responsive design by default.
Pages Folder
Now that we have seen the parts that make the application work, let’s take a look at the
components that bring these together to make the pages we will actually see!
Index.razor
The Index.razor is the root page of our application. This is the content that our users will
see when they first visit our application. It’s the place where we can link to other parts of
the site and put important information for them to see as soon as they arrive.
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
43
Chapter 3 Creating the Application
When we navigate to the root of the site, this is the page that will be served. This is
controlled by the @page directive at the top of the page.
The scaffolded page sets the page title, displays the ubiquitous “Hello, world”
message, and has a link to the Blazor survey.
Counter.razor
Along with the Index.razor page, there are two other pages created when we scaffold a
Blazor application. The first of these is the Counter.razor.
This page shows how we can easily interact between the markup of the page that a
user sees and the C# code that runs behind.
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
@code {
private int currentCount = 0;
The Counter page loads from the route “/counter”, again set in the @page directive at
the top of the page.
This page doesn’t hold any exciting functionality, but it is a simple example of how to
link our code to events that happen on the page. There is a button on the page, and each
time it is clicked, the counter is incremented and shown to the users.
44
Chapter 3 Creating the Application
FetchData.razor
The second page that is scaffolded is the FetchData.razor page. It’s an example of both
how to fetch data from an API (or rather for the example, fetch data set from a JSON file
on the server) and how to display lists on a page.
This is the most complex page of the scaffolded application and the most important
to get a good feel of how to mix code and markup in a razor component.
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
45
Chapter 3 Creating the Application
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date. ➥
ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
46
Chapter 3 Creating the Application
As well as the same @page directive at the start of the file to give the route
(“/fetchdata”), there is a second directive here: @inject.
This, as the name suggests, injects an object when the page is loaded. In this case,
the HttpClient object that was added to the services in the Program.cs file.
This page makes a call to a JSON file on the server, using the injected HttpClient, and
then displays it in a tabular form on screen.
47
Chapter 3 Creating the Application
Figure 3-8. Windows Security Warning for Visual Studio SSL Certificate
The first page that loads is the Index.razor page; see Figure 3-9. The header and
navigation menu are created by the shared components in our application. The main
content is from the Index.razor file. Note the URL may have a different port number
(after “localhost:”) than in the screenshots.
48
Chapter 3 Creating the Application
Click the Counter button in the navigation menu, and the counter.razor is loaded
(Figure 3-10).
49
Chapter 3 Creating the Application
The page has not been reloaded, but the content has changed to our Counter page. If
you click the button, the counter increases.
However, if you return to the Home screen and then come back to the counter page,
the value of the counter is lost. Each time we navigate away from a page, the data inside
of it is lost.
Before we load the Fetch data page, open the developer tools of the browser using
F12 and select the Network tab as shown in Figure 3-11.
50
Chapter 3 Creating the Application
If not already selected, click the “Headers” tab. Here, the URL of the file is shown,
“sample-data/weather.json” (Figure 3-13). This folder and file can be found in the
wwwroot of the application.
Figure 3-13. Developer Tools Showing Request Headers for Fetching Sample Data
51
Chapter 3 Creating the Application
52
Chapter 3 Creating the Application
Figure 3-15. Visual Studio Add a New Project Screen (Azure Functions)
4. The location should be the root of your solution (in the case of
Figure 3-16, “C:\github\beginning-static-web-apps”).
5. Click Next.
53
Chapter 3 Creating the Application
54
Chapter 3 Creating the Application
The Azure Function project will now be created and added to the solution, similar to
Figure 3-18.
55
Chapter 3 Creating the Application
Figure 3-18. Visual Studio Solution Explorer for the Api Project
The Azure Function project is much simpler than the Blazor WebAssembly.
There is only one file that we are going to look at before running the application: the
“Function1.cs” file.
Inside the file, there is a single class “Function1.” Inside this class, we have our
function called “Run” with the signature seen in Code 3-18.
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, ➥
"get", "post", Route = null)] HttpRequest req,
ILogger log)
This function is decorated with a “FunctionName” attribute. This marks the method
as an entry point for an Azure Function. Ours is called “Function1” at the moment, the
same as the class name, but it doesn’t need to be.
FunctionName parameters can be any string with the limitations:
The function parameters define this as a function with an HttpTrigger for “get” and
“post” HTTP methods. As the route is “null,” it will simply be the name of the function itself.
Finally, there is an HttpRequest and an ILogger passed into the function.
56
Chapter 3 Creating the Application
Inside of the function, the code checks to see if a name has been passed as a
query parameter and returns some text based on that input. What it does exactly isn’t
important so we are not going to cover it here.
Finally, at the end of the function, the response is returned to the client as seen in
Code 3-19.
Before making the changes we need for our default application, let’s run the API to
see the response message that is made.
Right-click the “API” project in the solution explorer (Figure 3-19); click “Debug” and
then “Start New Instance.”
Figure 3-19. Debugging the Api Project from the Solution Explorer
57
Chapter 3 Creating the Application
A new command-line window should open with the Azure Functions Core Tools
showing the functions found in the application. In our case, Function1, along with the
URL to trigger it (Figure 3-20). As with our Blazor application, the port number may differ.
If a window opens for the firewall (see Figure 3-21), click “Allow access.”
Open a browser window at that location. The response should be as in Figure 3-22.
Figure 3-23. Azure Function Result with the Name Input Parameter
Congratulations, your Azure Function project is ready for use! We can close the
Azure Functions Core Tools window to stop debugging.
59
Chapter 3 Creating the Application
3. Check that the type of file being created is an Azure Function and
give it the name “WeatherForecast” as in Figure 3-25.
Click "Add."
60
Chapter 3 Creating the Application
61
Chapter 3 Creating the Application
A function will be created that looks the same as our first function. Replace the
contents of the new file with the code from Code 3-20.
Writing the code out can help with the understanding of what it’s doing. However,
this code can also be found in the GitHub repository for this book if you would rather
copy the code than write it out – see the end of the chapter for a link.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Collections.Generic;
62
Chapter 3 Creating the Application
namespace Api;
63
Chapter 3 Creating the Application
This code will create our random weather forecast. By default, for five days, but that
can also be overridden in the query string to return more or fewer days if wanted.
You can test the new function is working by debugging the Api and opening the
following URL in a browser, “localhost:7197/api/weather-forecast/1,” remembering to
replace the port number.
The JSON response should look like Code 3-21.
[{"date":"2021-12-30T14:55:00.4183011+00:00"➥
,"summary":"Cool","temperatureC":12}]
The values for “date,” “summary,” and “temperatureC” will probably be different;
that’s fine, as long as they are in the response.
We can stop the debugger again. Our API is now ready for use!
64
Chapter 3 Creating the Application
This will return the default five days of weather information. If a number is added
after the weather forecast, then that number of days will be returned.
Save the file and our application is now ready for deployment!
You may have noticed that we haven’t run this code locally yet to check that it works.
That is because the application as it stands right now will only run inside an Azure Static
Web Application. It won’t run locally without extra tools or modification. We will cover
this more in Chapter 5 debugging.
Pushing to GitHub
The final step that we need to take before we can create our Azure Static Web App
resource is push the code to GitHub so that it’s available for deploy.
1. In Visual Studio, open the “Git Changes” tab; see Figure 3-27.
65
Chapter 3 Creating the Application
2. If you have just installed Git, then the message to configure your
username and email address may appear at the top of the Git
Changes tab.
66
Chapter 3 Creating the Application
3. Fill in a username.
67
Chapter 3 Creating the Application
6. In the settings page, click “Emails” and find the “Keep my email
addresses private” checkbox, as shown in Figure 3-30.
68
Chapter 3 Creating the Application
7. If the setting isn’t checked (it should be by default), then check it.
Then copy the email address shown in the information text. Keep
this secret, and copy it carefully – it is used to identify you when
you make changes to code and push them to GitHub!
8. Paste this into the Git User Information window (Figure 3-28).
Click save.
69
Chapter 3 Creating the Application
Your code will then be pushed to your GitHub repository ready for the next step!
Conclusion
In this chapter, we have made the start to our application. We created our solution with a
Blazor WebAssembly Application. We looked at the way the application is scaffolded by
Visual Studio and how components communicate and saw it running.
We added an Azure Function project to our solution to work as our API and looked at
how the HTTP trigger is wired up into the code.
We added our first function and changed the code in the Client to use the API for
data rather than the static sample file.
70
Chapter 3 Creating the Application
Finally, we pushed our code to GitHub so that we can use it to create our Azure Static
Web Application.
In the following chapter, we will get started with that Azure Static Web itself, creating
the resource in the Azure Portal and deploying the code we just made into production!
The source code for this book is available on GitHub, located at https://
github.com/Apress/beginning-azure-static-web-apps. For this chapter, use the
“chapter-3” folder.
71
CHAPTER 4
Technical Requirements
In order to complete the steps in this chapter, you will need to have the Azure Account
created in Chapter 2 and the application from Chapter 3 available in a GitHub repository.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For this chapter, use the “chapter-3”
folder as a starting point.
73
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_4
Chapter 4 Creating the Static Web App
74
Chapter 4 Creating the Static Web App
In our example, we are using “beginning-swa-rg” for the name, just to keep the
naming simple and keep it recognizable to the subject of the book.
75
Chapter 4 Creating the Static Web App
6. When the screen has been filled in, so that it looks similar to
Figure 4-3, click “Review and create.”
After validation has been completed, Azure will say whether the details entered are
valid; see Figure 4-4.
76
Chapter 4 Creating the Static Web App
7. Click “Create.”
The resource group will now be created and be visible in the Resource Group list, as
per Figure 4-5.
77
Chapter 4 Creating the Static Web App
78
Chapter 4 Creating the Static Web App
The Create a Resource page will open (Figure 4-7): on the left-hand side, there is a
list of categories; on the right, there are quick links for a selection of resource types; and
a search box is at the top of the page to go directly to a resource type.
79
Chapter 4 Creating the Static Web App
Figure 4-8. Search Bar and Suggestion When Creating an Azure Resource
80
Chapter 4 Creating the Static Web App
Base Information
The first information needed for our Static Web App is similar to the Resource Group
itself, as seen in Figure 4-10.
81
Chapter 4 Creating the Static Web App
As we are creating our resource from inside of a resource group, the Subscription and
Resource Group should be filled in automatically. However, it’s good practice to always
double-check that it is correct!
With most other Azure resources that are accessible from the Internet, the names
must be unique to the whole of the Azure environment. This is because the name forms
part of the URL that is assigned to the resource.
Azure Static Web Apps do not have this limitation; if the name is unique inside of the
Resource Group, it can be used. The external URL is generated by Azure, which we will
see later in this chapter.
3. Select the Hosting Plan for our application.
We have two choices here: Free and Standard. See Figure 4-11.
82
Chapter 4 Creating the Static Web App
For hobby sites, and for learning about Azure Static Web Apps, the free tier is good
enough. It gives the functionality that we are going to need for this book and for many
applications.
For when enterprise functionality is needed, or when an SLA is important for your
application, the Standard tier is available. At the time of writing, this was only around $8
per month. Even in production form, Azure Static Web Apps are a cost-effective way to
get your application to production!
If you start with the free tier but find that you need features from the Standard tier, it is
possible to upscale later.
As with the Resource Group, you need to consider any data compliance rules
regarding data processing when picking the region for your Azure Static Web App. While
the static content is globally hosted, the Azure Functions will run the region specific for
the resource. On top of this, staging environments created are not globally hosted but
also run in this region.
83
Chapter 4 Creating the Static Web App
Logging In to GitHub
Now that we have set the details for the Azure Static Web App itself, we can start to define
where our code is coming from.
At first glance, it can seem off that we only have two options here: GitHub and Other;
see Figure 4-13. However, that comes from the tight integration between the Azure Static
Web App and GitHub. At the time of writing, GitHub is the only source that includes an
out-of-the-box deployment pipeline.
When deploying from other sources, the deployment pipelines must be set up by
hand – Azure does provide instructions for this, but it is not covered in this book:
1. Select “GitHub.”
To create the necessary files and settings inside of our GitHub repository, we need to
log in and authorize the Azure Portal to make changes to our repository.
After clicking “Sign in with GitHub,” a pop-up will open asking for authorization; see
Figure 4-14.
84
Chapter 4 Creating the Static Web App
Figure 4-14. Authorization Request for Creating Azure Static Web Apps
The rights being asked for look scary but are needed to create the workflows for
deployment.
If you have previously authorized the Azure Portal, then the login screen will look
slightly different.
85
Chapter 4 Creating the Static Web App
1. Pick the organization, repository, and branch that you have used
to create the application in previous chapters.
Once the repository has been selected, we can pick the preset that we want to use for
the build; see Figure 4-16.
If we open the drop-down, we can see all of the options available to us:
• Angular
• React
• Svelte
• Vue.js
• Blazor
86
Chapter 4 Creating the Static Web App
• Gatsby
• Hugo
• VuePress
• Custom
2. Select “Blazor.”
Now the final three options are available to us; see Figure 4-17.
These are where we tell the build process to find the Client application (app), the
Azure Functions (Api), and where the client build output can be found.
By default, and convention, these are filled in as “Client,” “Api,” and “wwwroot.”
This is the reason we named our projects “Client” and “Api” and created them in the
root folder of the GitHub repository. We could have named them anything, and created
them in whatever folder in the repository we wanted, but we would have also had to
make sure that we corrected these settings to the locations we used. This can be useful
when working in an environment with preexisting naming conventions that we need to
adhere to.
The output folder, for a Blazor project at least, can be anything as long as the folder
doesn’t exist in the repository. Leaving it as the default is a safe option!
This is only true for Blazor websites. For other frameworks, it’s far more important and
must be set to the output folder of the build process for whatever framework is used.
87
Chapter 4 Creating the Static Web App
The resource will now be created; this will take a little longer than the
resource group.
88
Chapter 4 Creating the Static Web App
5. Once completed (see Figure 4-19), click “Go to resource” and take
a look at our Azure Static Web App!
Later in the book, we will take a more in-depth look at the resource. At the moment,
we just need to find the URL of our resource to see the app in action. On the top right-
hand part of the screen, you can see the URL, as in Figure 4-20.
We can see that the URL is built using an adjective, a noun, and a unique number.
Azure uses these to ensure that the URL is unique, even when our resource name isn’t.
1. Open the URL of the Azure Static Web App in a new tab.
Depending on how long it has taken from the deployment being complete to the URL
being opened, you may see something similar to Figure 4-21; that isn’t the Blazor client
application we wanted to see!
The reason it’s so empty is that Azure Static Web Apps are created very quickly,
typically in a matter of seconds. However, it takes longer than that for our code to be
deployed there. What is available immediately is an empty resource, with a holding page,
waiting for the application!
So while we are waiting for the application to be deployed, let’s take a look at how it
got there. Don’t close the tab with the site just yet – we’ll be coming back before the end
of the chapter.
90
Chapter 4 Creating the Static Web App
Here, we can see that the workflow created by the Azure Portal is running (it may be
that it has already finished); see Figure 4-23.
91
Chapter 4 Creating the Static Web App
3. Click the “ci: add Azure Static Web Apps workflow file” to open the
workflow run detail page, as seen in Figure 4-24.
Workflows are built up of jobs. The workflow file that was created has two jobs
associated with it. “Close Pull Requests Job,” which we will look at in Chapter 12, and
“Build and Deploy Jobs” which, as the name suggests, builds and deploys our Azure
Static Web App for us. Click “Build and Deploy.”
4. Click that job to see the details of the job itself (Figure 4-25).
92
Chapter 4 Creating the Static Web App
A job is made of one or more actions – small, but complex, scripts that perform the
steps inside of a job. Right now, this is as in depth as we are going to go – but knowing
how to find this screen can be useful should you experience deployment issues!
Once green, continue to the next section to look at our application in production!
If it turns red, then go back into the actions and look at the error – it could be that the
“app_location” or “api_location” wasn’t set correctly. In this case, look at the instructions
in Appendix B.
2. Open the developer tools, and go to the Network tab to check the
change that we made to the FetchData page.
3. Click FetchData.
93
Chapter 4 Creating the Static Web App
Check the network tab and show that the data is coming from the same URL as the
client application; see Figure 4-26.
If you need to revisit how to access the network tab, take a look at Chapter 3, section
“Running the Application.”
The fact that the data is coming from the same URL as the client application is all
thanks to the glue hidden from us inside of the Azure Static Web App. This is also the
reason why we could not test the application locally. When we run the code on our
machine, the Client project is running on one port number and the Api project on
another. We will solve this problem in Chapter 5.
There is one last thing to see before moving on to that chapter. Refresh the Fetch
Data page using F5, and the site will return a 404 page (Figure 4-27). This is because
the page that we are looking at doesn’t exist on the server. We have deployed a Blazor
WebAssembly Single Page Application. The pages themselves only exist inside of the
Blazor application; they are not files served directly from the website. We will fix this
problem later in the book, but it is good to be aware of this problem from the start.
94
Chapter 4 Creating the Static Web App
Conclusion
In the last chapter, we created our Azure Static Web App resource that we’ll be using
throughout the rest of this book. We’ve seen the resource created in the Azure Portal and
explored some of the options available to us during the process.
We’ve deployed our application into the resource, exploring how that happens in the
GitHub action. We have seen the Client application running in a browser and that our
API is now running on the same domain name as the Client and delivering the weather
forecast data.
95
Chapter 4 Creating the Static Web App
The next step in our journey will be to run the application locally, both inside of
Visual Studio and by using the Static Web App CLI. We’ll cover this in Chapter 5.
The source code for this book is available on GitHub, located at https://
github.com/Apress/beginning-azure-static-web-apps. For this chapter, see the
“chapter-4” folder.
96
CHAPTER 5
Simple Debugging
In the previous chapter, we got our simple application live, hosted inside of an Azure
Static Web App and running in a production environment.
In this chapter, we will work to solve the problem mentioned at the end of Chapter 3 –
that we can only run our application in production at the moment. By the end of the
chapter, you will know how to debug inside Visual Studio, the changes needed to the
code and local settings. And we’ll look at using an external tool, the Static Web App CLI,
to replicate the Azure Static Web App on your local machine.
By using these tools and techniques, you will be able to run and debug your
application locally to enable you to quickly change and test before pushing your changes
to the cloud.
Technical Requirements
To complete the steps in this chapter, you will need to have the application from Chapter 4
available in a GitHub repository and cloned to your machine.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For this chapter, use the “chapter-4”
branch to start.
97
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_5
Chapter 5 Simple Debugging
Then we have the Azure Functions for our API to handle dynamic data for the
application. Azure Functions run in their own environment and on the server side. It is
totally separate from the Client.
So, we have code running in two locations, using two frameworks, and they need to
communicate with each other.
In production, the Azure Static Web App takes care of this for us. It provides the
glue between the Client and the API so that to us, from the outside, they seem to be one
coherent application running on the same URL.
When we run locally, we don’t have that glue by default. When you run the
application, the Client application will run on one HTTP port – in our test for the Client
from Chapter 3, this was 7097, and the Azure Functions will run on another. In our
example, this was 7197.
To debug both the front end and back end, we need to allow them to communicate
with each other as they do in production!
98
Chapter 5 Simple Debugging
5. In the window that opens, scroll down to the end of the page and
change “App URL” to the code from Code 5-1.
https://localhost:5000;http://localhost:5001
99
Chapter 5 Simple Debugging
8. Click “Properties.”
11. In the window that opens (see Figure 5-3), delete the contents of
“Command line arguments” – this will reset the port of the Azure
Functions back to the default of 7071.
If we run the application now, we should see that the Client project opens on port
5000, and the Azure Functions start on 7071.
Close all the tabs in Visual Studio but keep the solution open.
1. Find the “wwwroot” folder (see Figure 5-4) in the Client project.
Right-click it to open the context menu.
100
Chapter 5 Simple Debugging
4. In the window that opens, type “json” into the search bar shown
in Figure 5-5.
Figure 5-5. Add New Item Search Bar – App Settings File
7. Replace the contents of the new file with the code snippet from
Code 5-2.
101
Chapter 5 Simple Debugging
{
"API_Prefix": "http://localhost:7071"
}
9. Find the HttpClient line that matches the code from Code 5-3.
10. Replace the line with the code snippet from Code 5-4.
builder.Services.AddScoped(sp =>
new HttpClient {
BaseAddress =
new Uri(builder.Configuration["API_Prefix"]
?? builder.HostEnvironment.BaseAddress)
}
);
The preceding steps create a local settings file; this file is only used for local
development and is included in the .gitignore file. This means that it should never leave
the development machine.
In this file, we created a setting that points to the base URL of the API being used by
the Client application when running locally.
Finally, we use this setting when creating the HTTPClient that is injected into our
Razor components and used to make HTTP calls.
By default, we try to use the “API_Prefix” setting, but if it can’t be found, or is null,
then we fall back to the standard base address of the Client itself being used. This would
be the case when running in production.
102
Chapter 5 Simple Debugging
Because we make the change when the HTTPClient is created, we only need to make
this change in one location, and not each time we make a request to the API.
103
Chapter 5 Simple Debugging
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
},
"Host": {
"CORS": "*"
}
}
This file will ensure that when we launch the Azure Functions from Visual Studio
locally, it will start with CORS enabled for every origin. Not what we want in production,
but perfectly safe for local development.
And that is it. We’ve made all of the file changes that we need to be able to run our
application locally.
104
Chapter 5 Simple Debugging
4. Set both “Client” and “Api” projects to “Start”; see Figure 5-8.
5. Click “OK.”
That’s it! Now we can run our application. Click the debug start button, or press F5.
Because we changed our startup projects, three new windows should open:
105
Chapter 5 Simple Debugging
Figure 5-9. Client Browser, Client Terminal, and Azure Functions Core Tools
Terminal
106
Chapter 5 Simple Debugging
That’s how we can run our application; now to actually debug our code and see what
is happening when we run the code.
currentCount++;
3. In the browser, click the “Click Me” button of the “Counter” page.
The breakpoint we just set should now be hit; see Figure 5-11.
107
Chapter 5 Simple Debugging
We will come back and reuse these breakpoints later in the chapter. For now, we can
stop the Visual Studio Debugger and take a look at running our application using the
Static Web App CLI.
108
Chapter 5 Simple Debugging
2. Delete the code from Code 5-8 from the “local.settings.json” file in
the Api.
,
"Host": {
"CORS": "*"
}
If we were to try and run the application now, the “Fetch Data” will fail as the Client
won’t be able to find the Api. In the next section, we’ll solve that problem!
109
Chapter 5 Simple Debugging
Install NPM
Before installing the CLI itself, we first need to install the runtime environment needed
to run it on. If Node.js is already installed, check that it and npm are up to date and
proceed to the next section.
This is Node.js. Download Node.js from https://nodejs.org/en/download/. Pick
the recommended Long-Term Support (LTS) installer for your operating system, as
shown in Figure 5-13. The following install example is for a Windows 11 machine.
110
Chapter 5 Simple Debugging
Now that we have Node.js installed, we can install the Static Web App CLI itself.
1. Open a terminal window and run the command from Code 5-10.
111
Chapter 5 Simple Debugging
Code 5-10. npm Command to Globally Install the Azure Static Web App CLI
This command will either install the Static Web App CLI or upgrade it to the latest
version if already installed.
When complete, you should have a message similar but not necessarily the same as
Code 5-11.
found 0 vulnerabilities
When it has run, your terminal window should have lots of information, ending with
something similar to Code 5-13.
112
Chapter 5 Simple Debugging
113
Chapter 5 Simple Debugging
Figure 5-14. Debug Tools Showing WeatherForecast Coming from SWA CLI
Here, we can see the Static Web App redirecting the request to the Api for us, even
while running locally!
We can also see that the API breakpoints are still being hit, so we can still debug our
application in the same way as with the previous way of working.
But there is a problem with the Client application; if we try the “Click me” button on
the “Counter” page, we see that breakpoint hasn’t been hit. Don’t worry; we’ll go into
detail about why this is in the next section.
114
Chapter 5 Simple Debugging
Visual Studio has some clever tricks to be able to tunnel into this browser to be able to
link the code to the running application.
But that is limited only to that instance of the browser. We can even see that this is a
special instance of our browser – looking on the taskbar, the browser connected to our
running application appears as its own icon, rather than being grouped with the other
browser windows as normal. See Figure 5-15.
Figure 5-15. Multiple Browser Icons for the Visual Studio Browser
When we were running using the CLI, the instruction was to open a new browser.
This new browser does not have the tunneling into Visual Studio, and so none of our
breakpoints will be hit.
3. Navigate to the Counter page and click the “Click me” button.
We should now see that the breakpoints are hit as expected.
As said at the start of this section, we are only scraping the surface of the
functionality offered to us by the Static Web App CLI. But don’t worry – we will return
later in the book to see what other functionality it can offer us to help our developer flow!
For general development work, this is my preferred way of working. We can make
changes inside of Visual Studio, and the SWA CLI will simply keep on working as its only
function is to redirect the requests to Visual Studio.
115
Chapter 5 Simple Debugging
3. On the line with the breakpoint, indicated by the red dot, press F9
to remove the breakpoint.
5. On the line with the breakpoint, indicated by the red dot, press F9
to remove it.
116
Chapter 5 Simple Debugging
Conclusion
During this chapter, you have learned two techniques for running and debugging your
application locally. Inside of Visual Studio for when you have a simple application and
outside of Visual Studio using the Azure Static Web App CLI to be able to access more
Azure Static Web App functionality during the development process.
Going forward in the book, we will focus only on the CLI method for debugging as
that extra functionality will be important for us.
And with that, we have reached the end of Part 1! By following along, you have now
created your first app, deployed it into production, and seen how you can debug the
application for local development.
In Part 2, we will start developing our actual application. A blogging application
where you can create blog posts and display them for your users.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For this, see the “chapter-5” folder.
117
PART II
With these pages in place, you will have a good grounding for expanding your site to
other types of content that you would like your users to have access to.
Along the way, we will look at how the Azure Static Web App resource we are using
reacts to the new code that we are creating.
CHAPTER 6
Technical Requirements
In order to complete the steps in this chapter, you will need to have the Azure Account
created in Chapter 2, the application from Chapter 5 available in a GitHub repository,
and a deployed Azure Static Web App from Chapter 4.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For this chapter, use the “chapter-5”
folder for the start code.
Clean Up
Let’s start with cleaning the code to date. In previous chapters, we have added code to
demonstrate making a call from our Client application to our Api application. This isn’t
something that we are going to need going forward, so we are going to remove it.
We are also going to remove the example code scaffolded by Visual Studio when we
created our projects as it’s not needed for the blog application that we will be making
from this point onward.
121
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_6
Chapter 6 Retrieving Blog Data
Api
There are two files in our API that we need to remove.
Delete the following files:
• WeatherForecast.cs
• Function1.cs
In the Client project, we do have a file calling the WeatherForecast API endpoint,
but don’t worry, we’ll be removing that file as well, so this isn’t going to break our
application.
Client
In the Client project, there are more changes that we need to make.
• Shared\SurveyPrompt.razor
• Pages\Counter.razor
• Pages\FetchData.razor
• wwwroot\favicon.ico
• wwwroot\icon-192.png
122
Chapter 6 Retrieving Blog Data
Do not remove the <div> that contains the link – we will be using this later – just
remove the <a> tag.
6. From the “Pages” folder, open the “Index.razor” file.
123
Chapter 6 Retrieving Blog Data
<h1>Hello, world!</h1>
If we run the application now, we should see that it still runs, but we now have a
much emptier page – and our Counter and Fetch Data pages no longer exist.
With this housekeeping done, we can start to add to it again!
124
Chapter 6 Retrieving Blog Data
CosmosDB accounts can work with different APIs, which is a different way of
accessing the data. However, each account can only have one API used for accessing the
data. So, when creating our account, we need to select the SQL API.
4. Click the Create button for Core (SQL) – Recommended, as in
Figure 6-2.
5. Check that the Subscription and Resource Group are set correctly.
7. Set a location for the CosmosDB that matches the location of your
Resource Group/Azure Static Web App.
For the capacity mode, there are two options to choose from. The following
instructions are assuming that this is the first CosmosDB account created for the
subscription (and so we are going to use the free tier).
126
Chapter 6 Retrieving Blog Data
12. On the review page, double-check that the details are correct and
click create.
13. Once the CosmosDB is available, go to the resource and open the
Keys pane.
Here, we can see the URI for the CosmosDB and keys for accessing
the data. They should look something like Figure 6-5.
127
Chapter 6 Retrieving Blog Data
For security, never share the keys to any of your Azure resources. This account has only
been created for this book (by the time you are reading this, it will no longer exist) and is
not in use, so sharing the keys in the book is safe.
14. Make a note of the ReadWrite URI and the Primary Key – we will
be needing this in the coming section.
1. Run the command from Code 6-4 to clone the Database seeder
application.
128
Chapter 6 Retrieving Blog Data
In this class, there are two variables: EndpointUri and PrimaryKey; see Code 6-5.
5. Run the application; the output should look similar to Code 6-6.
C:\github\beginning-static-web-app-db-seeder\database➥
-seeder\database-seeder\bin\Debug\net6.0\database-seeder.exe➥
(process 9788) exited with code 0.
129
Chapter 6 Retrieving Blog Data
For the summary, it’s pretty much the same, the only difference being that we need
to know the ID of the blog post so that we can retrieve that as well; we don’t want the
full blog post – for the summary, we only need to send a snippet to display with the title.
However, it’s close enough to the full blog post that we can use the same model for both.
Because this model is going to be shared between the Client and Api projects, we are
going to add the model in a way that allows us to reuse the same code in both places. We
are going to create a class library for them.
130
Chapter 6 Retrieving Blog Data
4. Give the new project the name “Models” and click Next.
9. Replace the content of the class with the code in Code 6-7.
namespace Models;
131
Chapter 6 Retrieving Blog Data
The model is now ready for use to retrieve our blog posts!
132
Chapter 6 Retrieving Blog Data
4. Click OK.
Now that we have the models available, we can write our first real API to get data
from our CosmosDB. An Azure Function to fetch the blog post summaries for the Client!
We are going to be taking advantage of built-in Azure functionality to connect to our
CosmosDB so that we have to write as little code as possible.
To do this, we need to add a NuGet package so that we can talk to the CosmosDB.
In the Api project, right-click the “Dependencies” folder.
Figure 6-9. Manage NuGet Package Context Menu for the Api Project
133
Chapter 6 Retrieving Blog Data
Add a new HTTP function to the Api project; call the function “BlogPosts.”
2. Click “Add.”
3. Click “New Azure Function.”
6. Open the new file and replace the contents with the code in
Code 6-8.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
134
Chapter 6 Retrieving Blog Data
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using Models;
namespace Api;
[FunctionName($"{nameof(BlogPosts)}_Get")]
public static IActionResult GetAllBlogPosts(
[HttpTrigger(AuthorizationLevel.Anonymous,
"get", Route = "blogposts")] HttpRequest req,
[CosmosDB("SwaBlog", "BlogContainer",
Connection = "CosmosDbConnectionString",
SqlQuery = @"
SELECT
c.id,
c.Title,
c.Author,
c.PublishedDate,
LEFT(c.BlogPostMarkdown, 500)
As BlogPostMarkdown,
Length(c.BlogPostMarkdown) <= 500
As PreviewIsComplete,
c.Tags
FROM c
WHERE c.Status = 2")
] IEnumerable<BlogPost> blogPosts,
ILogger log)
{
return new OkObjectResult(blogPosts);
}
}
135
Chapter 6 Retrieving Blog Data
Let’s look at what this code does. Firstly, you can see that the body of the Azure
Function itself is empty. All we are doing is returning one value. So how does it work?
Well, this is using the Azure Cosmos integration built into Azure Functions that we
just added to the project. This integration means that the connection to CosmosDB and
running the query are all handled by the Azure Function. We get the results passed to us
so that we can do further processing, if needed, or simply return them as the response.
The first important parameter is the “Connection” setting. When making a
connection to the database, we use secrets to identify ourselves. It’s vital that we do not
share these secrets accidentally. Quite often, this can happen because we put connection
strings or keys into code, and that code ends up in source control – as would happen
with us. Then anyone with access to the source control, now or in the future, would have
access to our secrets. Not what we want.
To avoid this, we will store our connection string in an external setting. When
working locally, we will put this into the local settings file. Because we selected the Visual
Studio .gitignore file when we created our GitHub account, this file will not be checked
into our source code repository.
2. At the end of the values section, add a new value as in Code 6-9.
Don’t forget to replace the Cosmos URI and key with the values we
copied earlier.
"CosmosDbConnectionString": ➥
"AccountEndpoint=<Your CosmosDB URI>;➥
AccountKey=<Your CosmosDb Readwrite Primary Key>;"
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
136
Chapter 6 Retrieving Blog Data
"CosmosDbConnectionString": ➥
"AccountEndpoint=<Your CosmosDB URI>;➥
AccountKey=<Your CosmosDb Readwrite Primary Key>;"
}
}
Remember to add the setting inside the “Values” object and add a comma to the
preceding line; otherwise, we’ll get an error due to invalid JSON formatting.
The next parameter that we need to look at is the SqlQuery, shown in Code 6-11.
SqlQuery = @"
SELECT
c.id,
c.Title,
c.Author,
c.PublishedDate,
LEFT(c.BlogPostMarkdown, 500)
As BlogPostMarkdown,
Length(c.BlogPostMarkdown) <= 500 As
PreviewIsComplete,
c.Tags
FROM c
WHERE c.Status = 2"
The SQL query selects all the fields required for the summary from our collection of
blog posts, where the Status of the post is 2. This indicates that the blog post is published;
we’ll look at the other statuses later in the book.
Two fields are calculated – “BlogPostMarkdown” and “PreviewIsComplete.” These
are to select the snippet of the Post for display in the summary, rather than the full text,
and to indicate whether there is more text in the blog post to recover.
Now that we have everything we need, let’s try it out and see what we get back!
Run the application from within Visual Studio. Once running, open a browser and
go to http://localhost:7071/api/blogposts. We should see a JSON list of blog post
summaries. For the next part, we need to take a note of one of the IDs on the screen.
137
Chapter 6 Retrieving Blog Data
1. Copy the code from Code 6-12 underneath the existing function.
[FunctionName($"{nameof(BlogPosts)}_GetId")]
public static IActionResult GetBlogPost(
[HttpTrigger(AuthorizationLevel.Anonymous, "get",
Route = "blogposts/{author}/{id}")]
HttpRequest req,
[CosmosDB("SwaBlog", "BlogContainer",
Connection = "CosmosDbConnectionString",
SqlQuery = @"SELECT
c.id,
c.Title,
c.Author,
c.PublishedDate,
c.BlogPostMarkdown,
c.Status,
c.Tags
FROM c
WHERE c.id = {id} and c.Author={author}")]
IEnumerable<BlogPost> blogposts,
ILogger log)
{
if (blogposts.ToArray().Length == 0)
{
return new NotFoundResult();
}
138
Chapter 6 Retrieving Blog Data
The first thing that we’ll look at is the “route” parameter. After the “blogpost” route,
we now have “/{author}/{id}”. That is how the function knows to differentiate routes
between the two functions when a request is made. When we access the function with a
blog post id and author, anything after the “/” before {author} we can use in our code.
Next is the query. This one is slightly changed from the summary query. We are
retrieving the entire blog post and have lost the “PreviewIsComplete” flag. The where
clause has also changed. We don’t want all blog posts for this request. Rather, we only
want the individual post that we have requested.
The body of the function is also a little more complex. It could be that we request
a blog post that doesn’t exist. The SQL query will return us an empty list in that case.
But rather than return an empty list to the front end, we want to return a 404 error – not
found. That is much more useful to the users of the API than an empty list.
If there are blog posts, then we return the first in the list returned from the blog posts.
Rerun the application and in a browser go to http://localhost:7071/api/
blogposts/<Author>/<id>; replace <Author> with the corresponding author and <id>
with the ID that we copied from the summary response in the previous step (or rerun the
summary call and copy an ID again).
If you remove a few characters from the id and/or the author, we can check that a 404
error is returned for when a blog post can’t be found.
Once the action has completed and the Azure Static Web App has redeployed, we
can run the functions! Let’s run it and see what happens.
Yes – it fails because we missed a step. When we ran the functions locally, we used
the local setting file. This works for development but isn’t available to our production
app. As we discussed, we shouldn’t be putting our secrets into our source control.
Let’s add the settings in a safe way.
1. Open the Azure Portal and go to the Azure Static Web App created
in Chapter 4.
5. Paste the connection string from the value in the settings file into
the value field.
6. Click OK.
The setting has been added, and we can see that the value for the setting is hidden
unless we explicitly choose to see it.
Finally, we need to save the settings before we can use it.
140
Chapter 6 Retrieving Blog Data
Now that we have the setting in a safe place, our functions should work as expected.
Let’s rerun the production functions and see them working.
Great! We can now see the data from our database in production.
Conclusion
So, there we have it, our demo code from Part 1 has been removed, and we now have the
basis for a real application! We’ve seen how to retrieve data from an Azure CosmosDB
and return it using our Azure Function Api project.
Our application now has the ability to return a list of blog post summaries, as well as
an individual blog post by id.
In the following chapter, we will see how to use this data in the front end to display it
to the end user.
The source code for this book is available on GitHub, located at https://
github.com/Apress/beginning-azure-static-web-apps. For this chapter, see the
“chapter-6” folder.
141
CHAPTER 7
Displaying Data
In the previous chapter, we created our data source, seeded the data, and created two
functions to retrieve both a list of blog post summaries and a single, complete blog post.
Now to make the front end of the site do something useful!
In this chapter, we will update the Client application to make HTTP requests to the
Api to retrieve the summary list and individual blog posts. We’ll then display those blog
posts on the website using Razor components and even put our latest blog post on the
index page so that it is one of the first things our visitors see when they arrive.
Technical Requirements
In order to complete the steps in this chapter, you will need to have the Azure Account
created in Chapter 2, a deployed Azure Static Web App from Chapter 4, and the
application from Chapter 6 available in a GitHub repository.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For this chapter, use the “chapter-6”
folder for the start code.
143
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_7
Chapter 7 Displaying Data
4. Click “OK.”
Now that we have access to the BlogPost model, we can use it when retrieving data
from the API.
5. Click “Class.”
6. In the window that opens (see Figure 7-1), name the class
“BlogPostSummaryService.”
144
Chapter 7 Displaying Data
namespace Client.Services;
this.http = http;
}
}
145
Chapter 7 Displaying Data
8. Add a public property, under the HTTP Client, to hold the blog
post summaries that we retrieve; see Code 7-2.
As we are using the “BlogPost” from the Models project, we need to add the
namespace to this file to remove the red squiggly line from the last code snippet.
9. Add the using statement in Code 7-3 to the top of the file.
using Models;
If we look at the code, we see that the blog posts are not loaded in the constructor.
We are only going to load them when there is a need to. To provide access to that
functionality, we’ll add a new method to the service.
10. Add the code from Code 7-4 underneath the constructor.
We have another red squiggly line here; to use GetFromJsonAsync, we need to add
another using statement to the top of the file.
11. Add the using statement from Code 7-5 to the top of the file.
using System.Net.Http.Json;
146
Chapter 7 Displaying Data
The final thing we need to do for the service is to add it to our dependency injection
container so that we can use it in our pages.
12. Open Program.cs and add the code snippet from Code 7-6
underneath where the HTTP Client is added to builder.services.
builder.Services.AddScoped<BlogPostSummaryService>();
We also need to add the using statement so that the BlogPostSummaryService can be
recognized.
13. Add the code from Code 7-7 to the top of the file.
using Client.Services;
The service is now ready to use in our components. Let’s create them!
2. Click “Add.”
147
Chapter 7 Displaying Data
Depending on the version of Visual Studio 2022 that you have, you may also see the
option to add a Razor Page – which seems the obvious choice when we are adding a
page. However, these are not used for static Blazor applications. If you accidentally add
one, you can simply delete it.
To turn our component into a page, we need to add an “@page” directive to the top.
This directive will also tell the application what route to look for to load the page.
6. Add the directive from Code 7-8 to the top of the page.
@page "/blogposts"
This component will now be loaded when we visit the “/blogposts” URL of our
website.
148
Chapter 7 Displaying Data
We can see though that this causes a problem. The compiler doesn’t know where to
get the service from, so we get some red error highlighting on our code.
To fix that, we need to do one of two things. We can introduce a using statement
in our page to tell the compiler where to look. This will work, but as we are going to be
using this in multiple components, there is a better way.
In Blazor, there is a file that provides shared using statements for all Razor
components. This is what we are going to change.
9. At the end of the file, add the code from Code 7-10.
@using Client.Services
If we save the file and switch back to the “BlogPostSummaries.razor” page, we can
see that the error highlighting is now gone.
Finally, we can start on the content of the page itself.
Firstly, we are going to make it easy for users to see where they are. We are going to
change the page title in the browser tab and add a header to the page itself.
<PageTitle>Blog posts</PageTitle>
<h1>Blog posts</h1>
It looks like we have done the same thing twice here. Both the page title and the
H1 have the same text. But they do different things. The page title is a built-in Blazor
component that updates the title in the browser tab; the “h1” tag is to display the text on
the page itself.
149
Chapter 7 Displaying Data
Now we need to get hold of our blog post summaries. For that, we are going to add
a code block to our page. The great thing about Razor components is that they can do
both markup and C# code. We could also use a code-behind file, where our code is in
a separate file from the markup, but for code that we need on this page, that could be
considered too much. However, it is a personal choice so pick whichever feels more
natural.
This book does not cover the difference between code blocks and code-behind for Razor
components. For more information, see https://docs.microsoft.com/en-us/aspnet/
core/blazor/components.
11. Add the code from Code 7-12 at the end of the
BlogPostSummaries.razor file.
@code
{
protected override async Task OnInitializedAsync()
{
await service.LoadBlogPostSummaries();
}
}
This function is called, as we can see from the name, when the page is initialized.
It will fire off a call to the service to fetch the blog posts so that we can use them on
the page.
While we are waiting for them though, we need to show our users information to let
them know something is happening in the background.
12. Add the code from Code 7-13 after the h1 tags.
150
Chapter 7 Displaying Data
Think back to our service. The list of blog post summaries is not set until we get the
data from the API. So as long as it remains null, we know we are loading. When it has
loaded, we will have a value for summaries, even if it is an empty list – so we can make a
distinction between the two.
For now, we will just worry about showing the blog posts when they have loaded.
13. Add the code from Code 7-14 underneath the code from
Code 7-13.
else
{
foreach (var blogPostSummary in ➥
service.Summaries.OrderByDescending➥
(bps => bps.PublishedDate))
{
<div>@blogPostSummary.Title</div>
}
}
This code will run through all the blog post summaries and put the title on the
screen, newest first. Notice that we don’t need to know when the blog post summary has
a value; Blazor handles all of that automatically for us, so the loading text will disappear
and be replaced with the titles.
Run the app, start the application in Visual Studio and then start the Static Web App
CLI, and navigate to http://localhost:4280/blogposts to see the list of blog post titles.
151
Chapter 7 Displaying Data
<article>
<h2>@blogPostSummary.Title</h2>
<div>
@foreach(var tag in blogPostSummary.Tags)
{
<em>@tag, </em>
}
</div>
</article>
This will display everything except for the blog post itself. To do that, we need to
convert the Markdown that we have for the blog post into HTML. This isn’t something
that we are going to make ourselves, we are going to use a well-known tool instead:
Markdig.
You can find out more about Markdig on the GitHub repo, which can be found here:
https://github.com/xoofx/markdig.
To use this tool, we are going to need to add a new NuGet package.
Then we need to make the library available in our components. As we are going to
be using it in multiple components, we’ll add it in the _imports.razor as we did for the
Client.Services.
7. Open the _imports.razor, and at the end of the file, add the code
from Code 7-16.
@using Markdig
152
Chapter 7 Displaying Data
Now we have the code available to us, we just need to use it.
Back in BlogPostSummaries.razor, we are going to add one more line of markup to
our blog post summary div.
8. Under the Tags, add the code snippet from Code 7-17.
<div>
@((MarkupString)Markdown.ToHtml➥
(blogPostSummary.BlogPostMarkdown))
</div>
This will take the Markdown that we are retrieving from the API and turn it into
HTML to be displayed on the page.
Finally, for this page we need to add the link to the full blog post! We are going to
make the whole article tag clickable for ease of use.
To do this, we are going to change the opening article tag in the foreach loop.
9. Replace the opening “article” tag with the code snippet from
Code 7-18.
Whenever the div is clicked, we will call the Navigate command. Let’s add that next.
10. Inside of our code block, we need to add the code from Code 7-19.
153
Chapter 7 Displaying Data
11. Add the line from Code 7-20 underneath the @page directive at
the top of the page.
Our page is now complete. The only thing left now is to add an easy way to navigate
to the page. We are going to add a navigation option in the sidebar of our application
for this.
12. In the “Shared” folder of the Client project, open the “NavMenu.
razor” file.
13. Add the code snippet from Code 7-21 underneath the home
NavLink.
This will add a new navigation link for our page, with the text Blog Posts and a
document icon on the button.
Now we can run the application and test that the navigation works as expected and
that the summaries are displayed. If we click the blog post summary itself, we land on a
page that doesn’t yet exist. This is where our full blog post is going to be displayed. Let’s
add that now!
154
Chapter 7 Displaying Data
2. Replace the existing code with the code snippet from Code 7-22.
using Microsoft.AspNetCore.Components;
using System.Net.Http.Json;
using Models;
namespace Client.Services;
public BlogPostService(
HttpClient http,
NavigationManager navigationManager)
{
ArgumentNullException.ThrowIfNull(http, nameof(http));
ArgumentNullException.ThrowIfNull(
155
Chapter 7 Displaying Data
navigationManager,
nameof(navigationManager));
this.http = http;
this.navigationManager = navigationManager;
}
}
Now we can add the code that is going to fetch the blog posts for us.
4. Add the code from Code 7-24 before the final “}” in the file.
blogPost = await➥
result.Content.ReadFromJsonAsync<BlogPost>();
156
Chapter 7 Displaying Data
{
navigationManager.NavigateTo("404");
return null;
}
blogPostCache.Add(blogPost);
}
return blogPost;
}
This function first checks to see if there is a blog post in our cache with the Id and
author that we are looking for. If it is not found, we try to retrieve it from the API.
If we get an HTTP 404 or null response from the API, we know we are trying to access
a blog post that doesn’t exist and should redirect to page 404.
This page doesn’t exist, and so the Blazor router will display our content not found
information. There are better ways of doing this inside of Blazor, but they fall outside the
scope of this book.
If we get a blog post back from the API, then we store it in the blogPostCache.
Finally, we return the blog post to the caller.
That is our second service finished.
builder.Services.AddScoped<BlogPostService>();
Now that our BlogPostService is available, let’s put the actual blog post on the screen.
2. As with the page for the blog post summaries, remove all the code
in the page and start from scratch.
157
Chapter 7 Displaying Data
3. Add the @page directive from Code 7-26 to the top of the page.
@page "/blogposts/{author}/{id:guid}"
This makes our page available on the same URL as the summaries, with the id and
author of the blog post being added to differentiate it. This also makes the id and author
available to us in the code – we’ll get to that shortly.
Before that, we are going to inject our new service so that we can retrieve the
blog posts.
4. Add the code from Code 7-27 under the @page directive.
Now we can add the code to fetch the blog post. Whereas for the summaries we
accessed the list of summaries directly from the service, for the blog post we are going to
make a private variable for the blog post directly on the page.
We also check that the parameter we have is the correct one.
5. Add the code snippet from Code 7-28 under the @inject statement
from Code 7-27.
@using Models
@code
{
private BlogPost? blogPost;
[Parameter]
public Guid Id { get; set; }
[Parameter]
public string Author { get; set; }
158
Chapter 7 Displaying Data
{
blogPost = await service.GetBlogPost(Id, Author);
}
}
Now that we have the blog post, we can display it on the screen.
For this, we are going to be using similar code as for the summary.
159
Chapter 7 Displaying Data
While the blog post is being loaded, we display the “Loading…” page. Once we have
it, we display the post as we intended.
2. Add the code from the code snippet in Code 7-30 under the
@page directive.
To get the blog post summary, we need a couple of functions: one to load the
summaries if needed and one to fetch the latest summary.
3. Add the code from Code 7-31 at the end of the file.
Code 7-31. Index.razor Code for Loading Summaries and Selecting the Latest
@code {
private BlogPost? Summary =>
service.Summaries?
.OrderByDescending(bps => bps.PublishedDate)
.FirstOrDefault();
160
Chapter 7 Displaying Data
The load summaries in the OnInitializedAsync are the same as we have used before.
The Summary property is what we will be using in our code. It takes all the
summaries from the service, orders them by their published date in descending order
and then takes the first one.
Next, we are going to add code to display the loading message, a message for when
we have no blog posts, and finally to display the blog post summary itself.
4. Add the code from Code 7-32 above the @code directive.
161
Chapter 7 Displaying Data
Right now, the BlogPostSummary HTML tag doesn’t exist. This will be a new razor
component that we will use both here and in our blog post summary page.
The code for the component is very similar to the blog post summary page, with a
couple of small adjustments.
@using Models
@inject NavigationManager navigationManager
<article @onclick=@Navigate>
<h2>@Summary.Title</h2>
<div>
@foreach(var tag in Summary.Tags)
{
<em>@tag, </em>
}
</div>
<div>
@((MarkupString)Markdown.ToHtml➥
(Summary.BlogPostMarkdown))
</div>
</article>
162
Chapter 7 Displaying Data
@code{
[Parameter]
public BlogPost Summary { get; set; }
For the component, there is no @page directive at the top. That is what marks the
difference between a component and a page in Blazor.
A page is a component and can be used as such. But a component without the
directive cannot be used as a page.
At the end of the file, we have a small @code block. This defines the parameter that
is used to pass the BlogPost into the component and also replicates the Navigate method
needed to go to the blog post page. This is a little simpler than in the BlogPostSummaries
page as we no longer need to pass through the id and author of the BlogPost to the
Navigate function. We can just use the data directly from the BlogPost passed in the
parameter.
Before we add the code to the blog post summary and index pages, we need to add
the namespace to the “_imports.razor” file so that the component can be used. There is
just one problem – nowhere in this file do we say what the namespace is.
Thankfully, it’s easy to work out. The namespace for components is taken from the
folder structure where the component is located. As we called our folder “Components,”
and it is in the “Client” project, our namespace will be “Client.Components”.
4. Open the _imports.razor and add the code from Code 7-34 to the
end of the file.
@using Client.Components
163
Chapter 7 Displaying Data
2. Replace all the code inside of the foreach loop with the code from
Code 7-35.
@page "/blogposts"
@inject BlogPostSummaryService service
<PageTitle>Blog posts</PageTitle>
<h1>Blog posts</h1>
164
Chapter 7 Displaying Data
@code
{
protected override void OnInitializedAsync()
{
await service.LoadBlogPostSummaries();
}
}
Running Locally
Before we push to GitHub, and so to our production environment, let’s run this locally to
check that it’s working.
Start the application in Visual Studio, and then run the Static Web App CLI.
On the first page, you should see a page similar to Figure 7-3.
165
Chapter 7 Displaying Data
If we go to the blog post page in the navigation, we should see a list of blog posts, as
in Figure 7-4.
And finally, if we open one of the blog posts, we should see the full post, as in
Figure 7-5.
166
Chapter 7 Displaying Data
167
Chapter 7 Displaying Data
Conclusion
For the first time, we now have a website that does something useful!
When visitors visit our site, they will see the latest blog post, a page with summaries
of all the blog posts, and a page for the blog post itself.
There are still problems that we need to solve though, for example, ensuring that we
can refresh the page or deep link directly to the blog post.
In the next chapter, we’ll fix these problems and take a deeper look at the way that
we can control the Azure Static Web App from code using the staticwebapp.config.json
configuration file.
The source code for this book is available on GitHub, located at https://
github.com/Apress/beginning-azure-static-web-apps. For this chapter, use the
“chapter-7” folder.
168
CHAPTER 8
Technical Requirements
To complete the steps in this chapter, you will need to have the Azure Account created
in Chapter 2, a deployed Azure Static Web App from Chapter 4, and the application from
Chapter 7 available in a GitHub repository.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For this chapter, use the “chapter-7”
folder for the start code.
169
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_8
Chapter 8 Static Web App Configuration
2. Click Add.
170
Chapter 8 Static Web App Configuration
Now that we have a file, we can start by fixing the issue with refreshing the page!
Navigation Fallback
The problem that we have with refreshing our blog post page or sharing a link that goes
directly to the page rather than via the menu is that the page itself doesn’t exist in our
published website.
Traditionally when a browser loads a page, it goes to the server, requests a page,
downloads the contents, and displays it. When we navigate to a different page, there
is another request to the server for that page, and we download that page as well, as
described by Figure 8-3.
171
Chapter 8 Static Web App Configuration
Figure 8-3. Simplified Page Request Flow for Server Side Generated Applications
172
Chapter 8 Static Web App Configuration
A Single Page Application (SPA), such as our Blazor application, doesn’t work in this
way though.
That first request to the server doesn’t deliver a page; rather, it delivers application
code that runs inside the browser. The browser then builds the page itself.
When the navigation to the second page is received, the SPA builds this new page
without needing to make the round trip to the server.
However, if we try to call that second page directly, the SPA isn’t loaded and so the
browser does make that round trip to the server. Only the page isn’t a file on the server
and so we can’t find it.
It only exists inside of the SPA application itself – and so we get a 404, file not found,
error when trying to call it directly, as described by Figure 8-4.
173
Chapter 8 Static Web App Configuration
Figure 8-4. Simplified Page Request Flow for Single Page Applications
By using the navigation fallback though, we can control what happens when a path
isn’t found on the server. We are going to use the index.html as our navigation fallback.
So, when a request is sent to the Azure Static Web App for a route that doesn’t exist, it will
use the index.html for the contents.
174
Chapter 8 Static Web App Configuration
This will load our application and ensure that the correct route for the URL is loaded
(or display the contents of the not found route).
{
"navigationFallback": {
"rewrite": "index.html",
"exclude": [
"/images/*.{png,jpg,gif}",
"/css/*",
"/api/*"
]
}
}
As stated, this code will use the contents of the index.html page for any route
that isn’t known so that Blazor can then route the application to the correct page on
the client.
However, we also exclude a few routes from this. Images, CSS files, and API calls
are a special case. For these, we do not want to load the index.html and let Blazor try to
construct a page. Rather, we want to return the standard 404 response.
Before we move on to the other options that we have, let’s deploy the file so that our
site can redeploy with the fallback in place.
Commit the changes in the git changes window inside of Visual Studio, and push.
Once the deploy has finished, you can navigate back to one of the blog posts and refresh
the page to see our changes working.
Routes
To control resources in our application, we have the “routes” property in the
staticwebapp.config.json file. This property is an array of “route” rules. These control
rewriting and redirecting resources (along with the HTTP status code for the response),
175
Chapter 8 Static Web App Configuration
role-based access control (RBAC) to those resources, and custom control of the response
headers – both adding and removing headers.
The properties available for this control are displayed in Table 8-1.
route Required The route to be matched, this may include a wildcard at the
end of the path to match all subroutes
methods This property allows the route matching to be more fine-
tuned – not only on the route itself but also the HTTP method
used for access
redirect Mutually exclusive Points from the selected resource to a different place.
with rewrite The user sees the change in their browser. By default, this
is a 302 (temporary redirect) response code, but can be
overwritten to be a 301 (permanent redirect)
rewrite Mutually exclusive Returns a resource from a different location without updating
with redirect the user’s browser location
statusCode This can be used to override the status code returned or the
request
headers A set of HTTP headers added to the response. These can add
to, override, or remove one or more of the global headers
from the website. To remove a header, the value is set to an
empty string in the array
allowedRoles Used in role-based access to identify who can access a
resource
176
Chapter 8 Static Web App Configuration
Code 8-2. Restrict Access to the Azure Static Web App Authentication Resources
"routes": [
{
"route": "/.auth/*",
"statusCode": 404
}
],
This snippet will set the routes array to contain a single route, which will match
against all resources under the path “./auth” and return a 404 response. While the
resources will still exist in our Azure Static Web App, we cannot control that; it will mean
that they can no longer be accessed.
Protecting Resources
In the last section, we stopped access to a resource completely. However, there are times
when we don’t want to stop access for everyone, but do want to make sure that only
certain people can access our resources.
Next, we are going to make a resource only available to users with a specific role.
We are not going into how to assign the role needed to gain access yet – that is going to
be covered in the following chapter.
177
Chapter 8 Static Web App Configuration
Some resources, whether that is an API call or static file, should only be available to
certain users. Either only users that are authenticated on our site or maybe a subset of
those users.
The snippet from Code 8-3 specifies a `routes` json array with a single route that
would restrict all resources under the path “/protected” for users that are authenticated.
Those not authenticated would instead get a 401 not authorized response.
"routes": [
{
"route": "/protected/*",
"allowedRoles": ["authenticated"]
}
]
We can also restrict access based on the HTTP method being used for a route. For
example, you can let one group of users access a resource to “get” data for read-only
access, not allowing them to “post” or “put” data to write data back. In the example from
Code 8-4, we allow all users to read data, but only authenticated users to write.
"routes": [
{
"route": "/api/readwriteapi",
"methods": ["POST", "PUT", "PATCH", "DELETE"],
"allowedRoles": ["authenticated"]
}
]
This will allow all “safe” HTTP methods that should not change data (GET, HEAD,
and OPTIONS) to “/api/readwriteapi” but will deny access to the methods that can
change the state of the server unless the user is authenticated.
While we can also restrict direct access to pages in our Client application using these
routes, there is more work needed to make the application secure. This will be covered in
the next chapter.
178
Chapter 8 Static Web App Configuration
"routes": [
{
"route": "/resource",
"redirect": "/temporaryresource",
"statusCode": 302
}
]
This snippet will redirect all calls to “/resource” to “/temporaryresource” with a 302
response so that “/resource” does not lose its SEO significance.
179
Chapter 8 Static Web App Configuration
This looks almost the same as Code 8-5, except for the code used for the redirect.
This code informs the requester that the resource has been moved permanently and will
not return to the original location. This should not be used for temporary redirects as it
can cause permanent changes to the SEO of the original location.
Response Overrides
When something goes wrong with a request, an error code is returned. To improve the
user experience, and to keep the design of our application consistent, we can override
the error responses with our own custom pages, as shown in Code 8-7.
"responseOverrides": {
"403": {
"rewrite": "/forbidden.html"
}
}
Using this snippet, the user will see our custom page rather than the standard
forbidden page of the Azure Static Web App if they do not have the correct rights to
access that resource.
Example Configuration
We’ve looked at some of the components available individually; now let’s look at an
example with all the options we’ve used, as seen in Code 8-8.
{
"routes": [
{
"route": "/protected/*",
"allowedRoles": [ "authenticated" ]
},
180
Chapter 8 Static Web App Configuration
{
"route": "/api/readwriteapi",
"methods": [ "POST", "PUT", "PATCH", "DELETE" ],
"allowedRoles": [ "authenticated" ]
},
{
"route": "/oldresource",
"redirect": "/newresource",
"statusCode": 301
},
{
"route": "/resource",
"redirect": "/temporaryresource",
"statusCode": 302
},
{
"route": "/.auth/*",
"statusCode": 404
}
],
"navigationFallback": {
"rewrite": "index.html",
"exclude": [
"/images/*.{png,jpg,gif}",
"/css/*",
"/api/*"
]
},
"responseOverrides": {
"403": {
"rewrite": "/forbidden.html"
}
}
}
181
Chapter 8 Static Web App Configuration
We will be using these settings in the upcoming chapters to complete the base of
our blog application. Except for the redirect/rewrite functionality, which is just useful to
know as your application evolves.
Conclusion
The Static Web App Configuration provides us with control over our deployed
application. Not only for the simple things that we should always remember to
implement, navigationFallback, for example, to make our site more usable but also
deeper control of the application.
We’ve touched upon the configuration for functionality that we’ll be using in
upcoming chapters and some good-to-know settings. The staticwebapp.config.json
file is capable of much more though! For both the basic free tier version and the more
advanced standard tier. The full documentation for all options can be found at https://
docs.microsoft.com/en-us/azure/static-web-apps/configuration.
In the coming chapter, we’ll use these settings to work on the part of the application
that we will be using more than our blog post readers: creating and editing posts. In fact,
not more than our blog post readers – our blog post readers should not have access to
blog post creation and editing functionality at all!
That’s why we’ll begin with looking at the built-in authentication providers and
ensure that we can log in to our application and be assigned a role that means only
authorized users can edit the content.
The source code for this book is available on GitHub, located at https://
github.com/Apress/beginning-azure-static-web-apps. For this chapter, see the
“chapter-8” folder.
182
PART III
Authentication
In the previous part, we created an application that allowed users to read blog posts that
we have written.
That’s a great start, but to make it really useful, we want to be able to manage those
blog posts as well.
In this part, we will be looking at authentication in Azure Static Web Apps, making it
possible for users to authenticate, adding role-based access to ensure that only people
with the correct rights can manage data, and looking at the limitations of the free tier
with regard to authentication.
Once we have authentication in our application, we will add the management
functionality itself. By the end, we will have an application where we can safely and
securely manage the data in our application.
CHAPTER 9
Authentication
Up until now, we have been working anonymously in our application. To read blog posts,
we do not need to know who the user is – our blog post will be open to all for reading.
However, now we want to allow only certain users to be able to add new blog posts or
edit existing ones. To do this, we need authentication and authorization.
Authentication to know who is using our application, and authorization to know
what roles that user has, and what they should be allowed to do within the application.
In this chapter, we are going to be looking at the authentication options available
inside of Azure Static Web Apps for the free tier and how to add role-based authorization
to those users.
Finally, we will add the code needed to use this functionality in both our Client and
Api projects to secure our application.
Technical Requirements
To complete the steps in this chapter, you will need to have the Azure Account created
in Chapter 2, a deployed Azure Static Web App from Chapter 4, and the application from
Chapter 8 available in a GitHub repository.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For this chapter, use the “chapter-8”
folder for the start code.
Authentication Basics
The best way to manage your users’ passwords is to never know them in the first place.
But we still need to allow users to log in to our application. So how can we do that if we
do not know their passwords?
185
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_9
Chapter 9 Authentication
We’ve discussed the extras that we get with Azure Static Web Apps – the glue that
joins the static files and API to make one coherent application etc. Authentication and
authorization are another part of this, designed to make our lives simpler.
The Azure Static Web App integrates to several platforms, allowing us to use social
logins for our application without having to set up the connection ourselves. We let those
platforms deal with the users’ login information so that we don’t need to worry about it
ourselves.
Using this built-in authentication doesn’t only make it simpler for us to add
authentication, and help keep passwords safer, but it also means that we don’t have to
add code to our application to implement the actual authentication – only to make use of
it. That means less possibility for us to introduce bugs!
Standard Authentication
In the Azure Static Web App free tier that we are using for our application, we have
access to the standard authentication features.
At the time of writing, we have three authentication providers available to us, with
one more in preview (but usable). See Table 9-1 for details.
After using Azure Static Web Apps since they were in preview, the ability to use these
login providers out of the box is still one of the features that I find most amazing.
They are also implemented in such a way that our application doesn’t need to
know, for the most part, what provider we are using. The integration for our application
remains the same no matter which authentication provider the user chooses. And
should we upgrade to a standard tier Azure Static Web App in the future, it will still work
without changes.
186
Chapter 9 Authentication
To see this in action, let’s try this out on our production application. We don’t have
any code written to implement authentication, but we can access the built-in functions
via the URL.
All Azure Static Web App authentication functions are available via the “./auth” path
of our application. Each provider has their own endpoint within this route.
The login pages are located behind “./auth/login/<provider>”, where “<provider>” is
“aad,” “twitter,” “github,” or “google.”
We see that the web application redirects to the provider of choice and goes through
the authentication process of the provider, before displaying the consent page for
the user.
This is needed because the provider will pass some user details through to the Azure
Static Web App. An example can be seen in Figure 9-1.
187
Chapter 9 Authentication
Figure 9-1. Consent Screen for Azure Static Web App Authentication
Once consent has been given, we return to the application again. This consent
request only happens the first time a user authenticates with a provider for this Azure
Static Web App.
As we do not have any code to use this authentication information, we do not see
anything in the site to let us know that we have authenticated. But there is a page that
can display the information to us.
188
Chapter 9 Authentication
ClientPrincipal
We don’t only have the login pages available to use; there are also resources for us to see
the currently authenticated user.
{
"clientPrincipal": {
"identityProvider": "twitter",
"userId": "8d29475abc694fe69f7db06d4edcc234",
"userDetails": "Stacy_Cash",
"userRoles": [
"anonymous",
"authenticated"
]
}
}
Let’s take a short look at the information that we have available to us in this object.
identityProvider
The provider used to authenticate the user. For standard authentication, “aad,” “twitter,”
“github,” or “google.”
userId
When a user logs in to an Azure Static Web App using standard authentication, a user
id is generated for them. This Id is unique for the user, so we can use it to identify users
when they use our application.
This Id is also unique to this one Azure Static Web App. If we have multiple
applications, then the same user will have a different userId in each one.
189
Chapter 9 Authentication
userDetails
This is the identity of the user passed through from the authentication provider. What is
returned depends on the provider used. For “aad” and “google,” we receive the email of
the authenticated user. For “twitter” and “github,” we receive the user handle.
userRoles
Finally, we have an array of roles associated with our authenticated user. The two roles
that we can see in Code 9-2 are the default roles that every authenticated user is assigned
when they log in to an Azure Static Web app. We do not need to add these roles ourselves.
190
Chapter 9 Authentication
4. Select “.Net 6.0 (Long-term support)” for the framework and click
“Create.”
5. Delete the “Class1.cs” file.
10. Replace the code in the new class with the code snippet from
Code 9-3.
namespace StaticWebAppAuthentication.Models;
191
Chapter 9 Authentication
As the method that we will make to extract the ClientPrincipal needs access to the
request headers, we will add the “Microsoft.AspNetCore.Http” NuGet package.
4. Click “Install.”
Now we can create the method we will be using in the Api project.
5. Replace the code with the code snippet from Code 9-4.
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using StaticWebAppAuthentication.Models;
namespace StaticWebAppAuthentication.Api;
192
Chapter 9 Authentication
{
if (!headers.TryGetValue ➥
("x-ms-client-principal", out var header))
{
return new ClientPrincipal();
}
This function takes in an “IHeaderDictionary,” which we will take from the request in
our API call.
We try to extract the "x-ms-client-principal" header from the dictionary – this is the
header that the Azure Static Web App adds to our request before it arrives at our Api.
If the header doesn’t exist, then we return an empty ClientPrincipal, which means
that the user is not authenticated.
This header is a StringValues object, but we are only interested in the first entry.
This is the Base64 encoded JSON string. After converting to a standard JSON string, we
deserialize the JSON to our C# object.
Finally, we check if the ClientPrincipal exists before returning it. If it doesn’t exist, we
return a new ClientPrincipal. Again, this means that our user will not be authenticated
for the request.
193
Chapter 9 Authentication
Client-Side Authentication
The client-side application requires works differently to the API. Before we can start
coding, we need another NuGet package in our “StaticWebAppAuthentication” project.
194
Chapter 9 Authentication
Ensure that you install the latest stable 6.x version of the package
and not the prerelease version 7.x. Ensure that the “Include
prerelease” checkbox is unchecked.
4. Click “Install.”
3. Replace the contents of the file with the code snippet from
Code 9-5.
namespace StaticWebAppAuthentication.Models;
Next, we need the code to process the ClientPrincipal for the Blazor application.
5. Replace the code with the code snippet from Code 9-6.
195
Chapter 9 Authentication
using Microsoft.AspNetCore.Components.Authorization;
using StaticWebAppAuthentication.Models;
using System.Net.Http.Json;
using System.Security.Claims;
namespace StaticWebAppAuthentication.Client;
public StaticWebAppsAuthenticationStateProvider ➥
(HttpClient httpClient)
{
ArgumentNullException.ThrowIfNull ➥
(httpClient, nameof(httpClient));
this.http = httpClient; }
}
In the constructor of the class, we are injecting the HttpClient that we also used to
retrieve the blog posts in an earlier chapter and checking that it’s not null.
The class itself is inherited from “AuthenticationStateProvider.” This allows us to
inject the state into our Client application.
We have a red squiggly line on the screen now; this is because we have not
implemented the method required by the “AuthenticationStateProvider.” We’ll
implement that later in this section.
Next, we need to retrieve the ClientPrincipal itself.
6. Add the code snippet from Code 9-7 to the class below the
constructor.
196
Chapter 9 Authentication
7. Add the code snippet from Code 9-8 to the class after the
constructor.
197
Chapter 9 Authentication
if (!principal.UserRoles.Any())
{
return new ClaimsPrincipal();
}
ClaimsIdentity identity = ➥
AdaptToClaimsIdentity(principal);
return new ClaimsPrincipal(identity);
}
Earlier in the chapter, we covered that an authenticated user will always have two
roles, “anonymous” and “authenticated.” The “anonymous” role doesn’t really give us
much information about the user. Should the user only have that role, then we want to
treat them as unauthenticated because we do not really know who they are.
The first step is to strip out that role. At the same time, we are also checking that we
have a UserRoles list at all – and if we don’t just create an empty list.
Next, we check how many UserRoles the user has. If it is zero, then we return an
empty ClaimsPrincipal. This means that the user will not be treated as authenticated
inside of the application.
If we do have UserRoles, then we want to adapt the ClientPrincipal to a
ClaimsIdentity and use that ClaimsIdentity to create the ClaimsPrincipal that we will use
in the application.
We still need the code to adapt the ClientPrincipal to a ClaimsIdentity.
198
Chapter 9 Authentication
identity.AddClaim(
new Claim(
ClaimTypes.Name,
principal.UserDetails!));
identity.AddClaims(
principal.UserRoles!
.Select(r => new Claim(ClaimTypes.Role, r)));
return identity;
}
9. Add the code snippet from Code 9-10 below the constructor in
the class.
199
Chapter 9 Authentication
catch
{
return new AuthenticationState ➥
(new ClaimsPrincipal());
}
}
By using the methods that we already wrote, we can make this function short.
First, we get the ClientPrincipal and convert it to the ClaimsPrincipal. Then we
return a new AuthenticationState using that ClaimsPrincipal.
If there are any errors, we revert to classing the user as not authenticated in the
“Catch” block.
Now we have an authentication step that we can use in our application.
3. Replace the code in the class with the code snippet from
Code 9-11.
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
namespace StaticWebAppAuthentication.Client;
200
Chapter 9 Authentication
.AddAuthorizationCore()
.AddScoped<AuthenticationStateProvider, ➥
StaticWebAppsAuthenticationStateProvider>();
}
}
This class adds two things to the Services available in our application.
“AddAuthorizationCore” adds, as the name suggests, core authorization to our
application so that we can use it inside of our code.
The “AddScoped” function injects our new
“StaticWebAppsAuthenticationStateProvider” to be used when the application needs
an “AuthenticationStateProvider.” This allows us to use our ClaimsPrincipal in our
application.
This was a lot of code for us to create, but we are finally ready to use it in our
application!
3. Search for and select the latest stable version 6.x of “Microsoft.
AspNetCore.Components.Authorization”.
4. Click “Install.”
201
Chapter 9 Authentication
4. Add the code snippet from Code 9-12 to the top of the file under
the existing “using” statements.
using StaticWebAppAuthentication.Client;
builder.Services.AddStaticWebAppsAuthentication();
The last thing we need to do before we start adding code is to add authentication to
the router for our application.
But we also need to update our “_imports.razor” file for this.
2. Add the code snippet from Code 9-14 to the end of the file.
@using Microsoft.AspNetCore.Components.Authorization
4. At the top of the page, add the tag from Code 9-15.
202
Chapter 9 Authentication
</CascadingAuthenticationState>
These tags ensure that we have our authentication state available in every razor
component in our application. Let’s put it to use!
Login Screen
Now we need to add the interactive element so that our users can log in and log out with
more ease and to show their status without manually going to the “./auth/me” resource.
3. Replace the contents of the file with the code snippet from
Code 9-17.
@page "/login"
<PageTitle>Login</PageTitle>
@{
var providers = new Dictionary<string, string>
{
{ "aad", "Microsoft" },
{ "github", "GitHub" },
{ "twitter", "Twitter" },
{ "google", "Google"}
};
}
<h1>Login</h1>
203
Chapter 9 Authentication
<div>
@foreach(var provider in providers)
{
<div>
<a class="btn btn-block btn-lg btn-social ➥
[email protected]" ➥
href="/.auth/login/@provider.Key">
<span class="fab [email protected]"> ➥
</span>
Sign in with @provider.Value
</a>
</div>
}
</div>
This page will put four different login buttons on the screen. One for each of the
providers that we have. The buttons link to the built-in login resources that Azure Static
Web Apps provide us out of the box.
We are using this extra page just to give easy access to each of the providers; if only one
provider is needed, then we can just link directly to it from the navigation menu and skip
that extra page.
We’ll provide a link to this page in the navigation menu of our website.
2. Under the navigation for the blog post overview page, add the
code snippet from Code 9-18.
<AuthorizeView>
<Authorized>
<div class="nav-item px-3">
<NavLink class="nav-link" ➥
href="/.auth/logout?➥
post_logout_redirect_uri=/">
<span class="oi oi-account-logout" ➥
204
Chapter 9 Authentication
aria-hidden="true"></span>
@(context.User.HasClaim(➥
c => c.Value == "admin") ? ➥
"(Admin) Logout" : ➥
"Logout")
</NavLink>
</div>
</Authorized>
<NotAuthorized>
<div class="nav-item px-3">
<NavLink class="nav-link" ➥
href="/login ">
<span class="oi oi-account-login" ➥
aria-hidden="true"></span> Login
</NavLink>
</div>
</NotAuthorized>
</AuthorizeView>
205
Chapter 9 Authentication
The last thing that we are going to look at in this chapter is how to add that special
“admin” role that we are using in the application. This is also how we are going to ensure
that now all authenticated users can create/edit blog posts in the next chapter.
Role-Based Access
At the start of the chapter, we looked at the roles that each user has when authenticating
in an Azure Static Web App: “anonymous” and “authenticated.” These are important to
allow us to recognize if the user is known or not.
But when we need extra control, we can add extra roles, like the “admin” role used
for the login button.
When using the free tier, as we are doing, these roles are controlled in the Azure Portal.
1. Open the Portal and go to the Azure Static Web App created for
this book.
Figure 9-3. Azure Portal Settings for Azure Static Web Apps
206
Chapter 9 Authentication
In the Role management screen, Figure 9-4, we can invite new users to the site,
manage users that are already invited, or remove users.
Using this screen, we can, at the time of writing, assign special roles to up to 25
users using the Azure Portal. This doesn’t seem much, but we need to remember that
we only need to add users with something other than the standard “anonymous” and
“authenticated” roles. None of our other users need to be added to the portal. For many
applications, 25 users with special roles will be enough.
For those applications where it is not, a standard tier app can be used with an Azure
Function to handle roles. But that is outside of the scope for this book.
3. To add a user, click the invite link, seen in Figure 9-4, to open the
“Create invitation link” fly-in.
This can be seen in Figure 9-5.
207
Chapter 9 Authentication
208
Chapter 9 Authentication
5. Click “Generate”; the screen should look a little like Figure 9-6.
6. Copy the invite link using the copy icon in the link text box and
paste it into a new tab in the browser.
If the user is not already logged in to the account specified in the fly-out, then log in
and grant consent to the Azure Static Web App if requested, and the website should load.
If our changes have been deployed, then we should now see a logout button with the
“(Admin)” text inside of it as in Figure 9-7.
209
Chapter 9 Authentication
Conclusion
Over the course of this chapter, we have learned about using the standard authentication
that we get out of the box with Azure Static Web Apps.
We’ve looked at how authentication is available without having to write any
code, what information they give to us about the user, and what providers we have
available to us.
We’ve looked at role management in the Azure Portal, allowing us to use role-based
access control inside of our application.
Finally, we have added a library that allows us to use this functionality inside of our
application – both on the Client, where we now have login functionality available, and in
the API.
Now that we can secure our application, we are ready to add the functionality that
will create and edit blog posts in the next chapter.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For the completed code for this
chapter, see the “chapter-9” folder.
210
CHAPTER 10
Technical Requirements
To complete the steps in this chapter, you will need to have the Azure Account created
in Chapter 2, a deployed Azure Static Web App from Chapter 4, and the application from
Chapter 9 available in a GitHub repository.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For this chapter, use the “chapter-9”
folder for the start code.
211
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_10
Chapter 10 Creating Blog Posts
We are building a REST (Representational State Transfer) interface for our Azure
Functions; the same endpoint we already built to retrieve blog posts is going to manage
our data differently depending on how it is being called.
The methods that we have available are
2. Add the code snippet from Code 10-1 under the “using”
statements at the top of the file.
212
Chapter 10 Creating Blog Posts
using System;
using Microsoft.Azure.Cosmos;
using System.Threading.Tasks;
using StaticWebAppAuthentication.Api;
using StaticWebAppAuthentication.Models;
Code 10-2. Azure Function Method Declaration for Creating a Blog Post
[FunctionName($"{nameof(BlogPosts)}_Post")]
public static IActionResult PostBlogPost(
[HttpTrigger(AuthorizationLevel.Anonymous, "post",
Route = "blogposts")]
BlogPost blogPost,
HttpRequest request,
[CosmosDB("SwaBlog",
"BlogContainer",
Connection = "CosmosDbConnectionString")]
out dynamic savedBlogPost,
ILogger log)
{}
This declares the Azure Function, using an HTTP POST on the “blogposts” endpoint.
We pass three parameters into the Azure Function:
Whatever object we put into the document will be saved to the CosmosDB when the
Azure Function exits.
213
Chapter 10 Creating Blog Posts
4. Add the code snippet from Code 10-3 into the function created
using Code 10-2.
if (blogPost.Id != default)
{
savedBlogPost = null;
return new BadRequestObjectResult("id must be null");
}
var clientPrincipal =
StaticWebAppApiAuthorization
.ParseHttpHeaderForClientPrincipal(request.Headers);
blogPost.Id = Guid.NewGuid();
blogPost.Author = clientPrincipal.UserDetails;
savedBlogPost = new
{
id = blogPost.Id.ToString(),
Title = blogPost.Title,
Author = blogPost.Author,
PublishedDate = blogPost.PublishedDate,
Tags = blogPost.Tags,
BlogPostMarkdown = blogPost.BlogPostMarkdown,
Status = 2
};
return new OkObjectResult(blogPost);
We check that there isn’t an Id already set for the blog post because we are using the
POST function to create our blog post.
If a blog post already has an Id, then we set the CosmosDB document to null and
return a 400 response indicating that there is a problem with the request. Setting the
document to null will stop it from being saved to the CosmosDB.
Next, we extract the ClientPrincipal from the request header and use it to fill the
Author field.
214
Chapter 10 Creating Blog Posts
We are setting the Author directly from the authorization details here. This is not
something that we should do in the real world. Rather, there should be functionality to link
the UserId to a display name, but that is outside of the scope of this book.
Be aware that if a post is created for a user authenticated with either Google or Azure
Active Directory, the email will be exposed when displaying the blog post.
And the final preparation step is to create a new Id for the blog post.
Then we set the document, including the author and Id, so that it can be saved to the
CosmosDB when the function completes.
The updated BlogPost object is then returned from the function so that it can be
used, with the extra information for the Id and Author, by the calling party.
1. Add the code from Code 10-4 and paste it under the POST
function we just created.
Code 10-4. Azure Function Method Declaration for Replacing a Blog Post
[FunctionName($"{nameof(BlogPosts)}_Put")]
public static IActionResult PutBlogPost(
[HttpTrigger(AuthorizationLevel.Anonymous, "put",
Route = "blogposts")]
BlogPost updatedBlogPost,
[CosmosDB("SwaBlog",
"BlogContainer",
Connection = "CosmosDbConnectionString",
Id = "{Id}",
PartitionKey = "{Author}")]
BlogPost currentBlogPost,
[CosmosDB("SwaBlog",
"BlogContainer",
Connection = "CosmosDbConnectionString")]
215
Chapter 10 Creating Blog Posts
This Azure Function declaration sets the HTTP trigger for the PUT method. As with
the POST method, we pass in three parameters:
As we do not need the ClientPrincipal for this function, we do not need the full
HttpRequest object.
We also try to load the current BlogPost from the CosmosDB, based on the Id and
Author of the inbound document.
We have the same output dynamic document as we had in our previous function.
Now we can build the body of the function to do the work.
2. Copy the code from Code 10-5 into the function body at the end of
Code 10-4.
if (currentBlogPost is null)
{
savedBlogPost = null;
return new NotFoundResult();
}
savedBlogPost = new
{
id = updatedBlogPost.Id.ToString(),
Title = updatedBlogPost.Title,
Author = updatedBlogPost.Author,
PublishedDate = updatedBlogPost.PublishedDate,
Tags = updatedBlogPost.Tags,
216
Chapter 10 Creating Blog Posts
BlogPostMarkdown = updatedBlogPost.BlogPostMarkdown,
Status = 2
};
return new NoContentResult();
The PUT function is used to replace data in our database, so we will replace the
entire CosmosDB document when we make a change, for example, when we edit a
blog post.
To ensure that we are not saving data incorrectly, we check that the currentBlogPost
parameter isn’t null. If it is, then we return a “Not Found” result, setting the output
document variable to null so that nothing is saved to the CosmosDB.
If the currentBlogPost parameter is set, then we can replace the data. We set the
output document in the same way as we do when creating a blog post. As the ID and the
author are the same, the original blog post will be overwritten with the new data.
As there is no new information to return to the calling party when changing data,
we return a no content result. This indicates that we have successfully completed the
request, but that there is no data to return.
1. Copy the code snippet from Code 10-6 and paste it under the
function we created with Code 10-5.
Code 10-6. Azure Function Method Declaration for Deleting a Blog Post
[FunctionName($"{nameof(BlogPosts)}_Delete")]
public static async Task<IActionResult> DeleteBlogPost(
[HttpTrigger(AuthorizationLevel.Anonymous, "delete",
Route = "blogposts/{author}/{id}")]
HttpRequest request,
string author,
string id,
217
Chapter 10 Creating Blog Posts
[CosmosDB(
"SwaBlog",
"BlogContainer",
Connection = "CosmosDbConnectionString",
Id = "{id}",
PartitionKey = "{author}")]
BlogPost currentBlogPost,
[CosmosDB(Connection = "CosmosDbConnectionString")]
CosmosClient client,
ILogger log)
{}
The HTTP trigger is linked to the DELETE HTTP method, but we pass in more
parameters for this function:
Those URL parameters are also used to match the route of the request, as we do
when fetching a full blog post using the ID.
Using these parameters, we can safely delete the BlogPost.
We have a different return type for this function, rather than the IActionResult that
we have used in the other functions; this time, we are returning a Task<IActionResult>.
This is because for the delete we need to use asynchronous development.
2. Copy the snippet from Code 10-7 into the function created with
Code 10-6.
if (currentBlogPost is null)
{
return new NoContentResult();
}
218
Chapter 10 Creating Blog Posts
Container container =
client.GetDatabase("SwaBlog")
.GetContainer("BlogContainer");
await container
.DeleteItemAsync<BlogPost>(id, new PartitionKey(author));
We again check to see if we have found the blog post that we are trying to delete. We
do it for a different reason when deleting though. The function of the delete is to ensure
that the data isn’t in our CosmosDB. If we don’t find the data, then we can simply return
the NoContentResult to indicate success – after all, the document doesn’t exist, which is
our target state.
We need this extra step to ensure that the CosmosDB doesn’t return an error when
trying to remove nonexistent data.
If the data is found, we can send the request to delete it using the CosmosClient
parameter we passed into the function. If this returns without error, we can return the
NoContentResult.
2. Add the code from Code 10-8 above the navigation fallback object.
219
Chapter 10 Creating Blog Posts
"routes": [
{
"route": "/api/blogposts",
"methods": ["POST", "PUT", "DELETE"],
"allowedRoles": ["admin"]
}
],
This configuration ensures that any user can access the HTTP GET functions, either
to retrieve the blog post summaries or a full individual blog post, but cannot call the
POST, PUT, or DELETE methods to change the data unless they have the “admin” role.
Having this rule in place will ensure that the API is kept secure, without having to
handle the authentication ourselves in our code. This makes our code simpler, more
secure, and easier to maintain.
These are all the changes needed to secure the Api project. Next, we’ll add the create
and edit functionality to the Client application.
BlogPostService Changes
Inside of the BlogPostService, we need to add the code to communicate with the Azure
Function to create the BlogPost.
Before we add the function though, we need to make some changes to the class to
prepare for it.
220
Chapter 10 Creating Blog Posts
2. Add the using statement from Code 10-9 to the top of the file.
using System.Text;
using System.Text.Json;
Copy the code snippet from Code 10-10 under the last private variable
in the BlogPostService class.
public BlogPostService(
HttpClient http,
NavigationManager navigationManager,
BlogPostSummaryService blogPostSummaryService)
{
ArgumentNullException.ThrowIfNull(http, nameof(http));
ArgumentNullException.ThrowIfNull(
navigationManager,
nameof(navigationManager));
ArgumentNullException.ThrowIfNull(
blogPostSummaryService,
nameof(blogPostSummaryService));
221
Chapter 10 Creating Blog Posts
this.http = http;
this.navigationManager = navigationManager;
this.blogPostSummaryService = blogPostSummaryService;
}
Now the BlogPostService has been prepared, we can add the functions themselves.
return savedBlogPost!;
}
First, we check that valid data has been passed into the service; if not, we throw
an error.
Then we convert the BlogPost to StringContent that we can use as the body of the
POST request that we send to the Azure Function.
We deserialize the data that we get back and add it to the Client application
collection of full blog posts and summaries.
222
Chapter 10 Creating Blog Posts
BlogPostSummaryService Changes
So that we can find the blog post on our list of summaries, we also need to add the blog
post to our BlogPostSummaryService. We already have the call in the BlogPostService
class; we just need to create the method now.
2. Add the code from Code 10-13 to the end of the class.
The first thing that we need to do in the service is to check that there is anything that
we need to do.
We check that a blog post has been passed to the service; if not, we throw an error.
Then we check that the summaries object has been filled – if the user has called the
create page directly, then we will not have filled the list from the API yet.
If not, then we simply exit – the first time that the user sees the list of blog posts, it
will be loaded from the API, including the blog post we have just written.
223
Chapter 10 Creating Blog Posts
3. Add the code snippet from Code 10-14 to the end of the function
we created with the code snippet from Code 10-13.
if (!Summaries.Any(summary =>
summary.Id == blogPost.Id
&& summary.Author == blogPost.Author))
{
var summary = new BlogPost
{
Id = blogPost.Id,
Author = blogPost.Author,
BlogPostMarkdown = blogPost.BlogPostMarkdown,
PublishedDate = blogPost.PublishedDate,
Tags = blogPost.Tags,
Title = blogPost.Title
};
Summaries.Add(summary);
}
First, we check to see if the blog post already exists in the summaries list.
If not, then we create a new BlogPost using the data we pass in.
We check the length of the blog post itself, and if it’s longer than the 500 characters
that we have for our summaries, then trim it to the first 500.
We can do this safely because we are using Markdown as the format for our blog posts
when storing them. If we add any HTML to our blog post in the future, then this code will
need to change as cutting off at 500 characters could mean that the HTML is corrupted.
Finally, we add the blog post to the Summaries list itself.
224
Chapter 10 Creating Blog Posts
BlogPostService Changes
1. Open the “BlogPostService.cs” file.
2. Add the code snippet from Code 10-15 to the end of the class.
var result =
await http.PutAsync("api/blogposts", data);
result.EnsureSuccessStatusCode();
blogPostSummaryService.Replace(blogPost);
}
225
Chapter 10 Creating Blog Posts
We start, as with the add functionality, by checking that we have a blog post passed
into the method, and if not, then we throw an error.
Then we prepare the PUT statement by converting the BlogPost object to a
StringContent object for the body of the PUT request.
Once the blog post has been saved, we search for the index of the original version in
the list of BlogPosts in the service. If found, we replace the blog post with the updated
version passed into the function.
Finally, we ensure that the list of summaries has also been updated with the new
content.
Again, we get a red squiggly line here as we haven’t yet made the change to the
BlogPostSummaryService. We’ll do that in the next section.
BlogPostSummaryService Changes
1. Open the “BlogPostSummaryService.cs” file.
2. Add the code snippet from Code 10-16 to the end of the class.
As with our other functions, we first make sure that we have a blog post passed into
the function; otherwise, we throw an error.
226
Chapter 10 Creating Blog Posts
Then we check to see if the Summaries have been filled yet. As with the Add
function, if these have not yet been loaded, then we don’t need to do anything – when
the visitor goes to the blog post list, it will be filled with all blog posts, including our
edited blog post.
3. Add the code snippet from Code 10-17 at the end of the Replace
function from Code 10-16.
First, we try to find the existing summary for the blog post.
If we find it, then we change Title, Tags, and Markdown to be the same as the blog
post passed into the function.
Finally, we check the length of the Markdown, as we did when adding it originally. If
longer than 500 characters, we trim it to the first 500.
227
Chapter 10 Creating Blog Posts
BlogPostService Changes
1. Open the “BlogPostService.cs” file.
2. Add the code snippet from Code 10-18 to the end of the class.
We call the Delete HTTP Method of the API using the id and author passed into the
function.
We then check to see if the post is in the list of BlogPosts held in the service. If it is,
we remove it.
Finally, we can remove the associated summary from the BlogPostSummaryService.
As with the previous two functions, we have a red squiggly line. We’ll solve this in the
next section.
BlogPostSummaryService Changes
1. Open the “BlogPostSummaryService” file.
2. Add the code snippet from Code 10-19 to the end of the class.
228
Chapter 10 Creating Blog Posts
var summary =
Summaries.First(➥
summary => summary.Id == id ➥
&& summary.Author == author);
Summaries.Remove(summary);
}
We check to see if we have any summaries at all, or if the blog post that we are trying
to remove is in the list. If not, we just return – there is no work for us to do.
We then retrieve the summary from the list and remove it.
Now that our services are complete, we can move on to the user interface so that we
can finally use our functionality!
3. Replace the contents of the file with the code snippet from
Code 10-20.
229
Chapter 10 Creating Blog Posts
@page "/blogposts/{author}/{id}/edit"
@page "/blogposts/{id}/edit"
@using Microsoft.AspNetCore.Authorization
@using Models
@inject BlogPostService service
@inject NavigationManager navigationManager
The @page directive sets the URL needed to access our page. Unlike the FullBlogPost
page, we are not setting the id parameter to be a GUID. This is because when creating a
page, we may not have a GUID to use.
We also have two routes set up. One with the author, and one without. When we are
editing a blog post, we have all the information needed to load the blog post, the same as
when we are displaying one.
When creating a blog post though, we won’t have the author information, and so we
need the extra route to take that into account.
We don’t force the Id to be a GUID here as when creating a blog post, it will be
a string.
Then we add our using statements so that we have access to authentication and
model classes.
Finally, we inject the two services that we will be using in the code of the page.
The BlogPostService, used for loading, creating, and updating blog posts, and the
NavigationManager used to redirect users to the appropriate page after editing has been
completed.
We are going to start by adding the code needed to make our page functional.
4. Copy the code snippet from Code 10-21 to the end of the page.
@code
{
private BlogPost? blogPost;
private string mode = "edit";
private string tags = string.Empty;
230
Chapter 10 Creating Blog Posts
[Parameter]
public string? Id { get; set; }
[Parameter]
public string? Author {get; set; }
}
• tags: A variable to hold our processed list of tags for the blog post
We also specify the parameters, Author and Id, passed into the page from the
URL. As discussed already, this needs to be a string for when we are creating a new
blog post.
5. Add the code snippet from Code 10-22 into the @code block,
underneath the Author parameter.
ArgumentNullException.ThrowIfNull
(Author, nameof(Author));
231
Chapter 10 Creating Blog Posts
If the Id is set to “new,” then we set the mode of the page to create. This means
that we do not need to load a blog post; we can just set the blogPost object to be a new
Blogpost.
If the Id is not “new,” then we need to check that we have an Author and valid GUID
passed into the page. If not, we throw an error.
If we do have an Author and GUID, then fetch it from the BlogPostService and join
the list of Tags from the blog post into one comma-separated string.
After the user has created the blog post, we need to save it.
To ensure that we have a clean set of tags to save, we are going to do some cleanup
work before saving them. This will remove any empty tags that the user may have
accidentally entered.
6. Copy the code from Code 10-23 to the end of the code block.
7. Copy the code snippet from Code 10-24 underneath the code
snippet from Code 10-23.
232
Chapter 10 Creating Blog Posts
{
blogPost.PublishedDate = DateTime.Now;
blogPost =
await service.Create(blogPost);
}
else
{
await service.Update(blogPost);
}
navigationManager.NavigateTo➥
($"blogposts/{blogPost.Author}/{blogPost.Id}");
}
When the user clicks save, the first thing that we need to do is split the tags up from a
comma-separated string and back into a list of strings. To make life easier for the user, we
are cleaning the input before we save it.
If we are in “create” mode, then we set the PublishedDate to DateTime.Now and then
save the blog post using the BlogPostService.
The Author and Id fields are not set before we save the blog post as that will be done
in the API. We do set the Id with the value returned from the save function so that we can
use it for redirecting later in the function.
If we are updating a blog post, then we can simply call the Update function of the
BlogPostService.
Finally, after the saving has been completed, we redirect back to the view page for
the blog post.
Now we can start displaying the edit form on the page.
8. Add the code snippet from Code 10-25 above the @code block.
<AuthorizeView Roles="admin">
<Authorized>
else
{
<EditForm
Model="blogPost"
OnValidSubmit="Save"
Context="EditContext">
</EditForm>
}
</Authorized>
</AuthorizeView>
We are wrapping the code for the editing in an AuthorizeView, again to ensure that
only users with the admin role can see the edit form.
While we are waiting for the blog post to be loaded, we display a loading text.
Once loaded, we show the EditForm itself. We pass the object being edited, the
blogPost and set the function to call when the form is valid and a Submit button is
pressed.
Finally, we name the Context of the EditForm to be EditContext. We need to do the
last step because both the AuthorizeForm and EditForm have a built-in object called
Context, and they will interfere with each other if the naming is left unchanged.
Now we can add the HTML controls for the editing itself.
9. Add the code snippet from Code 10-26 into the EditForm
component on the page.
<div>
<label>Title</label>
<div><InputText @bind-Value=blogPost.Title /></div>
</div>
<div>
<label>Tags</label>
<div><InputText @bind-Value=tags /></div>
</div>
234
Chapter 10 Creating Blog Posts
<div>
<label>Post</label>
<div>
<InputTextArea
@bind-Value=blogPost.BlogPostMarkdown
style=" width: 100%; ➥
height: 60vh; min-height: 100px;" />
</div>
</div>
<button type="submit">Save</button>
• The title
The title and tags are just plain input controls. The post itself though is a text area –
this allows multiline input and a bigger typing area.
The tags control isn’t connected directly to the blogPost object specified in the
EditForm, rather it is accessing a private variable for the page. This is because the tags
are stored as a list of strings, but here we are allowing users to simply enter a comma-
separated list of values in a single string.
We are also adding some styling to the blog post control. This styling will make the
control as wide as the screen, and take up 60% of the visible view area, with a minimum
height of 100 pixels.
This should be put into a CSS class but is kept inline here for simplicity.
Our create and edit functionality is now complete – the only thing left is to provide
access to it from our existing pages!
235
Chapter 10 Creating Blog Posts
2. Add the code snippet from Code 10-27 below the H1 tag on
the page.
<AuthorizeView Roles="admin">
<Authorized>
<a href="blogposts/new/edit">Create</a>
</Authorized>
</AuthorizeView>
This code checks to see if the user has the admin role and, if so, displays the link to
the create URL.
2. Add the code snippet from Code 10-28 to the page under the
injection of the BlogPostService.
We will use the NavigationManager to redirect away from the page when we delete a
blog post.
Next, we need to add the two links themselves.
1. Add the code snippet from Code 10-29 above the <article> tag
displaying the blog post.
236
Chapter 10 Creating Blog Posts
Code 10-29. Edit and Delete Links for the FullBlogPost.razor Page
<AuthorizeView Roles="admin">
<Authorized>
<a href="blogposts/@blogPost.Author➥
/@blogPost.Id/edit">Edit</a>
<a href="javascript:;" @onclick="Delete">Delete</a>
</Authorized>
</AuthorizeView>
Again, we are making sure that the user has the correct admin role to see the links.
The Edit link simply redirects to the edit page, using the Id of the blog post being viewed.
The Delete functionality is a little more complex. There is an onclick event linking
the Delete method that we will write next. But there is also a piece of JavaScript in the
href attribute. This JavaScript allows us to set a href property (so that the delete link
displays the same as the Edit link) but ensures that the browser does nothing with it
when clicked.
Now for the final code. The Delete method itself.
This method first calls the delete function on the BlogPostService and then redirects
the user back to the full list of blog posts.
237
Chapter 10 Creating Blog Posts
Once the site has been deployed, we can log in with the account with the “admin”
role, which we set in Chapter 9.
When we browse to the “blog posts” page, we should now see the Create link at the
top of the page, as seen in Figure 10-1.
When we click the link, we will be redirected to our edit page, with all fields empty.
Fill in some text to create a new blog post, as seen in Figure 10-2.
238
Chapter 10 Creating Blog Posts
Click “save”; we are now redirected to the blog post display page. Here, we can see
the two new buttons: edit and delete, as seen in Figure 10-3.
Figure 10-3. Edit and Delete Functionality in Blog Post Display Page
When we click “edit,” we open the edit page, which looks the same as Figure 10-2.
Make a change and then click “save.” We return to the blog post display page.
If we refresh this page, we can see that the changes are still visible.
If we click delete, the blog post is removed, and we are redirected to the list of
blog posts.
We can now create and edit posts!
Conclusion
And with that, the code for our blog application is complete. We have a fully functional
application for both users to read blog posts and for us to create and edit new ones.
We’ve completed the functionality in this book by adding the edit functionality,
adding new Azure functions to handle the “POST,” “PUT,” and “DELETE” HTTP methods
we use to create and edit data, then securing those routes using the “staticwebapp.
config.json” file so that only “admin” users have access to them.
We’ve changed the “BlogPostService” in the Client application to use those
endpoints and to ensure that our cached data is up to date when we make changes.
Finally, we added a page for creating and editing blog posts and provided access to
this functionality in the user interface.
There are many improvements that we can make to our application going forward,
see “appendix A” for ideas for self-study, but our coding for the project is complete.
In the remainder of the book, we will look at how we can use the Azure Static Web
App, and its Static Web App CLI, to help our developer workflow when working with our
application and help us turn our website into our own brand using custom domains.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For the completed code for this
chapter, use the “chapter-10” folder.
239
PART IV
SWA Functionality
At the end of the previous chapter, we were finished with our application. It has all of
the functionality that we need to create, update, and delete blog posts and for users to
browse those blog posts.
In this final part, we are going to look at some last functionality to make the Azure
Static Web App easier to work with.
We will start with a deeper look at how we can use the Azure Static Web App CLI to
support our developer inner loop and to allow us to better test our application locally.
We’ll then look at how we can test our application in an Azure environment without
deploying to production.
And finally, we’ll look at adding custom domains to our application to give it an
easier-to-remember URL.
CHAPTER 11
Technical Requirements
To complete the steps in this chapter, you will need the application from Chapter 10
available and the SWA CLI that we installed in Chapter 5.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For this chapter, use the “chapter-10”
folder for the start code.
243
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_11
Chapter 11 Static Web App CLI
To do this, we are going to use three separate commands to see everything needed to
run the application.
3. In the second window, go to the Api folder of our solution and run
the command from Code 11-2.
This command will check the code in the folder for Azure
Functions and what language has been used to create it.
It will then compile and run the Azure Functions.
func start
4. In the last terminal window, we can run the SWA CLI itself to join
these two together.
Go to the root folder of the solution (which contains both the Client and Api
projects) and run the command from Code 11-3.
This command, the same as we used in Chapter 5, will start the Node.js server that
replicates the Azure Static Web App that we have in production.
When running, we can visit http://localhost:4280 and see our application
running without using Visual Studio.
But this is still three terminals, and it’s not convenient. Let’s improve this!
First, we are going to run the Azure Functions for our API differently, so that we no
longer need that terminal.
244
Chapter 11 Static Web App CLI
1. Close the terminal window where we ran the API Azure Functions
using “func start.”
Code 11-4. Static Web App CLI Start Command with Azure Functions
This command will run the “func start” command with the contents of the “Api”
folder when starting the Node.js server.
Once running, we can see that our application is still running on the same http://
localhost:4280 URL.
Now we’ll remove the need to start the Client application separately. This is a little
more complex than running the Api inside of the Static Web App CLI.
3. Restart the Static Web App CLI with the command from
Code 11-5.
Code 11-5. Static Web App CLI Start Command with Client and Api
We now also include the “dotnet watch run” command when we start the Node.js
server. It looks a little different from when we ran it in the terminal though.
Because we start the Static Web App CLI in the root folder for the solution, the run
command is also executed with the root folder as the working directory. We add the
“--project .\Client” to the command so that the dotnet CLI starts in the correct location
to find the code.
Using this command, we can now run the application locally using only one
terminal window.
If we visit the https://localhost:4280 URL, we can see that our application
still works.
245
Chapter 11 Static Web App CLI
Remember to check the correct URL; when “dotnet watch run” is executed, it opens a
new browser with the Client application not running via the Static Web App CLI. I have
made the mistake multiple times of looking at this browser rather than running the Static
Web App CLI URL, and then wondering why the code was not working.
The command is a little long and complex – but we will look at improving that later
in the chapter!
Code 11-6. Static Web App CLI Overriding the Default Api Port
swa start https://localhost:5000 --api-location ./Api ➥
--run "dotnet watch run --project ./Client" ➥
--api-port 7073
246
Chapter 11 Static Web App CLI
The command still starts both the Client and Api projects as before, but now sets the
Api to start on port 7073. If we look at the terminal running the Static Web App CLI, we
can see that requests for the API are forwarded to this port now.
As we included the “--api-location” parameter, this command will start the Azure
Functions itself. It also passes the port number to the Azure Functions Core Tools to start
the Azure Functions on the required port.
If we do not specify the API location explicitly, as when running the solution using
Visual Studio, it simply uses the port number to know where to send API requests to the
application.
1. Stop the Static Web App CLI from the previous section.
Code 11-7. Static Web App CLI Overriding the Default Port
This command starts the application as normal, only now the application is available
on 4281. Open http://localhost:4281 to see the application working.
247
Chapter 11 Static Web App CLI
Code 11-8. Static Web App CLI with Different Client Port Number
In this example, we are using the HTTP port associated with our Client application;
until now, we have always used the HTTPS port.
Because we point directly at the full URL, we do not need to add anything else to our
command.
If we check the http://localhost:4280 URL, as we have not overridden the Static
Web App CLI port, we need to revert to the 4280 port; we can see that the application is
still working, only now the requests are directed to the http://localhost:5001 port.
1. Stop the Static Web App CLI from the previous section.
248
Chapter 11 Static Web App CLI
These files will be generated in the “dist/wwwroot” folder in the root folder of the
solution.
1. Stop the Static Web App CLI from the previous section.
2. Run the code from Code 11-10 in the root folder of the solution.
Running this command doesn’t start the compilation of the Client project, as we
have previously used. Instead, we now get a message telling us that the Static Web App
CLI is serving static content from the “./dist/wwwroot” folder, as seen in Code 11-11.
Code 11-11. Example Static Web App CLI Output Showing Static Content
Being Served
[swa]
[swa] Serving static content:
[swa] C:\github\beginning-static-web-apps\dist\wwwroot
[swa]
When we open the app now, there are two main differences:
• The application should run faster because the published static files
are optimized for delivery to the browser.
While we don’t need to use this method of running our application every time we
deploy, it’s good to know how to run the application in this way.
249
Chapter 11 Static Web App CLI
1. Stop the Static Web App CLI from the previous section.
2. Open Visual Studio Code using the command from Code 11-12.
This will open the current folder with Visual Studio Code.
code .
3. Check how many files are currently classed as modified by Git; see
Figure 11-1 as an example with 295 changes.
Figure 11-1. Git Icon Showing Modified Files in Visual Studio Code
250
Chapter 11 Static Web App CLI
5. Add the line from Code 11-13 to the end of the file.
This rule tells Git to ignore any files and folders in the “dist” folder.
Any files added here, or altered here, will therefore not be
included when we next commit changes.
dist/
251
Chapter 11 Static Web App CLI
The Azure Static Web App CLI can also work with a configuration file, where we can
create one or more profiles for how it should run.
2. Click the new file icon in the file explorer, shown in Figure 11-3.
4. Paste the code from Code 11-14 into the new file.
{
"configurations": {
"static": {
"appLocation": "./dist/wwwroot",
"apiLocation": "./Api"
},
"run-all": {
"appDevserverUrl": "https://localhost:5000",
"apiLocation": "./Api",
"run": "dotnet watch run --project ./Client"
},
"debug": {
"appDevserverUrl": "https://localhost:5000"
252
Chapter 11 Static Web App CLI
}
}
}
This configuration gives us access to three different ways of running our application
from the command line:
• “static” runs the site using the static files that we have just created in
the “/dist” folder and runs the Api project from the “./Api” folder.
• “run-all” runs the .NET development server for the Client application
and runs the Api project from the “./Api” folder.
• “debug” connects the Static Web App CLI to the Client development
server, and the Api, running already. This allows us to connect to the
Visual Studio application.
Each configuration takes the command-line arguments we used previously and uses
them as properties of the configuration object.
There are some differences though. On the command line, we can use the location of
the published files and the URL of the development server interchangeably. When using
the configuration, we need to explicitly state whether we are linking to a file location or
development server.
Let’s try our new configuration file to see how it works.
First, we are going to test the “debug” configuration.
1. Open Visual Studio and run both the Client and Api projects.
Code 11-15. Static Web App CLI Using the “debug” Configuration
When the Static Web App CLI starts, we can see that it picks up that we are using a
configuration setting from our file; see Code 11-16.
253
Chapter 11 Static Web App CLI
Code 11-16. Static Web App CLI Output Snippet for Configuration
1. Stop the Static Web App CLI from the previous example.
Code 11-17. Static Web App CLI Using the “run-all” Configuration
As we can see in the terminal, it is now using the “run-all” configuration and has
started both the Client and Api projects.
Code 11-18. Static Web App CLI Using the “static” Option
We can now see that the static configuration option has been used, and we are
serving the content of the “./dist/wwwroot” folder.
4. Open http://localhost:4280 in a browser and check that it is
working.
254
Chapter 11 Static Web App CLI
• .gitignore
• swa-cli.config.json
Now that these files are on GitHub, anyone who we allow access to our repository
can run the same three commands that we have just used to run the application locally.
Conclusion
Over this chapter, we have looked at how the SWA CLI can help us in our developer inner
loop. From running the application outside of Visual Studio to running the application
as it will be deployed.
We’ve looked at how we can run multiple applications side by side locally without
interfering with each other.
Finally, we have set up a configuration file so that we can easily run our application
using simple commands.
We’ve only scratched the surface of what the SWA CLI, and associated
configurations, can do for us – but what we have used can dramatically simplify our
development workflow and allow us to have a better idea how our application is going to
work in a production environment.
In our next chapter, we’ll look at how we can check how our application works in a
production-like environment in Azure itself using the built-in staging environments and
pull request functionality inside of GitHub.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For the completed code for this
chapter, see the “chapter-11” folder.
255
CHAPTER 12
Testing in Azure
In the previous chapter, we looked at how we can improve our development flow locally.
By using the Static Web App CLI, we could replicate the functionality found in Azure
Static Web Apps on the command line. To ensure that we could also check how our
published application was going to run, we also looked at how to use the published files.
However, this does not ensure that our application is going to run the same way
inside of the Azure resource itself. As the Static Web App CLI itself says, there could be
differences.
In this chapter, we are going to look at the last stage of deploying new functionality to
production. That is testing in Azure itself.
We will look at the GitHub workflow in a little more detail, make a change, and see it
running in Azure before we move it to production.
And we’ll look at the limitations that we have when using this functionality.
Technical Requirements
To complete the steps in this chapter, you will need to have the Azure Account created
in Chapter 2, a deployed Azure Static Web App from Chapter 4, and the application from
Chapter 11 available in a GitHub repository.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For this chapter, use the “chapter-11”
folder for the start code.
257
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_12
Chapter 12 Testing in Azure
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
This section will ensure that the workflow file is triggered whenever there is a pull
request opened, synchronized, reopened, or closed against the main branch.
In the “build_and_deploy_job,” there is a check to ensure that the job is run not only
when a change is made to the main branch but also whenever a pull request triggers the
workflow, and it is not being closed. See Code 12-2.
build_and_deploy_job:
if: github.event_name == 'push' || (github.event_name == ➥
'pull_request' && github.event.action != 'closed')
The build job works differently for a pull request though. When we push a change to
our main branch, it is built and deployed to our production environments, as we have
seen throughout the book.
When we build a pull request, an entirely new staging environment is created.
The code from the pull request is built and deployed into the new environment and a
comment linking to the environment is left on the pull request itself.
258
Chapter 12 Testing in Azure
Making a Branch
Let’s make some changes to our application using pull requests to see this in action!
5. In the screen that opens, fill in the name of a branch, and for
“Based on:” select the main branch as shown in Figure 12-2.
Now that we have our branch, we just need to make some changes to push to GitHub
for our pull request.
259
Chapter 12 Testing in Azure
Save the change, commit the file, and push the change to GitHub.
We can see the message that there has been a change in a branch; see
Figure 12-3.
3. On the pull request page, as seen in Figure 12-4, enter a title and
description of the change.
260
Chapter 12 Testing in Azure
5. In the page that opens, we will see after a few seconds that a
workflow starts; see Figure 12-5.
Once the workflow has finished, the outline will change from orange to green.
261
Chapter 12 Testing in Azure
If we look at the URL, we can see that it looks similar, but not the same as the normal
URL for the production Azure Static Web App:
<base-url>-<pull-request-number>.<azure-swa-region>.<number>.
azurestaticapps.net
This means that every time we make a pull request, it will get its own unique URL
that we can use for testing.
If we look at the title of the page in the browser tab, we should see the change that we
made in our code. See Figure 12-7 for a comparison to the live site which is unchanged.
262
Chapter 12 Testing in Azure
Ensure that the branch for the change is selected, commit the change, and push it
again to GitHub.
263
Chapter 12 Testing in Azure
Just as our main branch is built and deployed on every change, the pull request code
will also be built and redeployed each time our new branch is updated.
Once the workflow has finished, reopen the pull request URL and check that the
change made is visible on the index page.
264
Chapter 12 Testing in Azure
If we select the staging environment, we can see that the values for the staging
environment are automatically copied from the production environment when it is
created.
But once it has been created, we can change those values so that the environment
can use different resources.
We are not going to do this for our application, but it is good to know that the
functionality exists.
265
Chapter 12 Testing in Azure
Free Tier
In our demonstration, we are using the free tier of Azure Static Web Apps. This is great
for hobby projects or getting started. But there are, of course, limitations. Staging
environments are one of these.
There are two limitations that we need to deal with.
266
Chapter 12 Testing in Azure
close_pull_request_job:
if: github.event_name == 'pull_request' && ➥
github.event.action == 'closed'
runs-on: ubuntu-18.04
name: Close Pull Request Job
steps:
- name: Close Pull Request
id: closepullrequest
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ ➥
secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_GREEN_RIVER➥
_00413E103 }}
action: "close"
We can see in the job that this one only runs for pull requests, and only when they
are closed, as mentioned in the job name and description.
It uses the same action as the build and deploy job, only now it is called with a
“close” action. This action cleans up the staging environment that was created inside of
Azure for our pull request.
Let’s close the pull request that we have open.
1. Open the pull request we just created.
We have two options. We can just use the “Close pull request” button. In which case
our staging environment will be removed, but the code will not be merged into the main
branch and deployed to production.
Or we can merge the pull request. In which case the code changes that we have made
will be merged to the main branch – triggering a separate workflow run to deploy those
changes to production and also performing the cleanup.
267
Chapter 12 Testing in Azure
In Figure 12-10, we can see the two buttons: “Merge” and “Close pull request.”
Now when we look at the actions in GitHub, we can see two workflows running, as
we see in Figure 12-11.
Figure 12-11. GitHub Actions Running After Closing the Pull Request
268
Chapter 12 Testing in Azure
If we select a staging environment, we can then manually delete it using the “delete”
button visible in Figure 12-12. This will free up a slot inside of our application.
If the environment is needed in the future, for an ongoing pull request, for example,
we can rerun the action linked in the pull request and recreate the environment.
Conclusion
We can use the built-in staging environments to easily check that our application runs as
well in the real Azure resource in the cloud as it does locally when using the Static Web
App CLI.
We’ve looked at creating pull requests for changes and the workflow file associated
with it.
269
Chapter 12 Testing in Azure
We’ve seen how those environments differ from our production environment and
discussed the limitations that we have when using them.
We are almost at the end of our journey. There is only one thing left to look at – the
URL we have right now is not very user-friendly! In our last chapter, we will fix that.
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For the completed code for this
chapter, see the “chapter-12” folder.
270
CHAPTER 13
Custom Domains
We’ve reached the last stop in our journey. We have an application that we can build on
now. We can create, edit, and delete blog posts.
There is however still one thing that we need to do to make our users’ life easier.
Our domain name. While the domain names that Azure Static Web Apps create with the
resource are fun and unique, they are neither easy to say nor remember.
We need a domain name that better fits our application.
In this chapter, we will look at two ways of adding a custom domain to our
application. We’ll use an externally managed domain and look at how to use Azure DNS.
At the end of this chapter, our blog application will have a URL that is much simpler
to remember!
Technical Requirements
To complete the steps in this chapter, you will need to have the Azure Account created in
Chapter 2 and a deployed Azure Static Web App from Chapter 4.
To complete the external domain section of the chapter, access is needed to the DNS
records of a chosen domain.
To complete the steps, an externally hosted domain and a domain managed with
Azure DNS are required.
The source code for this book is available on GitHub via the book’s product page,
located at https://github.com/Apress/beginning-azure-static-web-apps. For this
chapter, see the “chapter-12” folder for the start code.
271
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8_13
Chapter 13 Custom Domains
• “beginningazurestaticwebapps.dev”
• “www.beginningazurestaticwebapps.dev”
We take it for granted as visitors that these two URLs will probably go to the same
place, but technically they do not.
We are going to use an apex domain as our example, so setting up both.
If only the subdomain is needed, for example, “blog.beginningazurestaticwebapps.
dev,” follow the instructions for “www.beginningazurestaticwebapps.dev”.
272
Chapter 13 Custom Domains
1. Open the Azure Portal and go to the Azure Static Web App
resource.
In this screen, we can see the custom domains available for our application. By
default, there is one domain always available – the one created by the Azure Portal itself
when we created our Azure Static Web App. This does not count toward our quota of two
custom domains.
3. Click the add button, shown in Figure 13-1, to see the two options
available to us, as seen in Figure 13-2.
Clicking this button gives us access to both the routes for adding a custom domain.
273
Chapter 13 Custom Domains
Self-Managed Domain
1. To add a self-managed domain, click the “Custom domain on
other DNS,” shown in Figure 13-2.
This link opens a slide-in pane on the right-hand side of the pane.
First, we need to enter the domain that we want to enter. Enter the apex domain that
you want to enter, for our example: “beginningazurestaticwebapps.dev”. See Figure 13-3
for reference.
274
Chapter 13 Custom Domains
Set up the DNS records of the domain as per the instructions given. For the apex
domain, we need to create a “TXT” record for the host “@” with the value generated for
us. See Figure 13-5.
The Azure Portal will now check that the correct records exist on
the domain.
In the instructions, we can see that we need to add a “CNAME” record for the
“www” host with the value given on the panel. See Figure 13-6.
Depending on how long it takes for the DNS records to propagate on the Internet,
this process can take seconds or hours.
Once finished, return to the “Overview” panel of the Azure Static Web App. If we look
at the URL of the application, it no longer shows the Azure-generated URL of the website,
but rather the custom domain that we have just entered.
If we visit the site on either the apex or www domain now, we should see our application.
2. Enter one of the custom domains (either the apex or the “www”).
4. When the form is correctly filled, click the apply button; see
Figure 13-7.
276
Chapter 13 Custom Domains
The Azure Portal will now create the correct records in the Azure DNS Zone. This can
be quick, but can also take more than ten minutes to complete.
In the preceding instructions, it was said that either the apex domain or “www”
domain could be used, rather than saying to use one or the other – as we did with the
DNS domains from the self-hosted DNS instructions. That is because when using Azure
DNS, it is the same process for each one.
Once the custom domain has been added, add the second custom domain in the
same way.
Once the second domain has been added, we should be able to open the custom
domain and see our site working.
Azure-Generated URL
While we now have multiple ways to add custom domains to our application, it’s
important to know that this does not stop the original URL from still being active.
If we take the original URL from the custom domain panel and visit the site, we can
see that it still works.
Also, when a staging environment is created, it also uses the original URL as the base
for the URL used by the environment, not a domain based on our new custom domain.
277
Chapter 13 Custom Domains
Conclusion
And here we are at the end of our journey together.
We now have a functional blog application where we can create, edit, read, and
delete blog posts. We have our inner workflow for developing new functionality and can
test these in production as well.
And now our website has an easy-to-say, easy-to-remember URL.
Of course, there is much more that we can do with our application – from the look
and feel to extra functionality to make both our life creating posts and our user’s life
reading content better.
In Appendix A there are some more projects that you can complete to make the
application your own using the excellent documentation available to us at aka.ms/swa.
Thank you for allowing me to take this journey with you. I hope that you have
enjoyed this journey and can see the possibilities that Azure Static Web Apps can
give to us!
The source code for this book is available on GitHub, located at https://github.
com/Apress/beginning-azure-static-web-apps. For the completed code for this
chapter, see the “chapter-12” branch.
278
APPENDIX A
Next Steps
We’ve completed our journey looking into Azure Static Web Apps. The application,
however, can still be improved! Here are some ideas for you to implement.
279
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8
Appendix A Next Steps
Search
When there are not many posts to display, it’s easy for users to have an overview of what
they are doing. But as the list grows, it gets harder to navigate.
What would help the users is some simple search functionality – on tags and the
author level.
The page displaying the post summaries will need changing to allow users to select
the tags/authors they wish to see. And the list of blog posts needs to be filtered as these
are selected.
With the functionality as it is in the book, this can all be implemented on the Client
(as we have a complete list of blog posts). But this challenge can also be taken further by
reducing the data needed and only displaying a small number of blog posts at one time,
which will mean that both paging and filtering need to be applied to the Azure Function
as well.
Tag Lookup
When writing or editing a blog post, we can add tags to help the user understand the
contents of the post.
This is a free text entry – so as we need a new tag, we can create one. But to make the
process easier, it would also be useful to have a list of tags previously used, either as a
lookup field or even something that shows suggestions as we type the tag.
If you have already implemented the search functionality, then you should already
have a complete list of tags that you can reuse.
281
Appendix A Next Steps
This status can be expanded so that we can add draft posts or allow for “soft”
deletion of posts.
New parameters will be needed when fetching the blog posts to make this work, and
users will need to be able to filter on what they are looking for.
Remember that users should only be able to fill in those details if they have the
correct level of rights in the system! Standard users should not see draft or deleted posts
that do not belong to them!
Styling
Throughout the book, we have only used the standard styling that we get with Blazor out
of the box. It doesn’t look bad, but it can look much better!
By using extra styling rules, take the website that we have built together and make it
your own!
To complete this, you will need to change the CSS for the application and, optionally,
change the structure of the application.
282
APPENDIX B
3. Click on the workflow file. For a new Azure Static Web App there
should only be one file in this folder, see Figure B-2.
283
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8
Appendix B Changing the GitHub Workflow
4. To edit the file, click the pen icon, seen in Figure B-3.
app_location: "Client"
api_location: "Api"
output_location: "wwwroot"
9. In the pop-up that opens for the commit, fill in a title and
comment for the change.
10. Select whether to make the change directly to the main branch or
to create a new branch for testing the change.
If the changes were made to the main branch, then the workflow should now fire
with the updated settings. If a new branch was created, then a pull request is needed to
test the changes; see Chapter 12 for more information on pull requests.
284
Index
A security alert, 58
Visual Studio
API application, 52, 121
add new project screen, 53, 54
Api project, 30, 56, 57, 94, 103–105, 109,
Api Project, 56
110, 112, 116, 130–134, 136, 192,
create Azure Function Screen, 55
194, 220, 243
Weather Forecast, 65
Application root folder, 38
Azure functions, 212, 213, 215, 216, 220,
App.razor component, 38, 39
246, 247
Authentication, 185, 186, See also
Azure Functions Core Tools, 58, 59, 105,
Standard authentication
106, 110, 247
and authorization, 186
Azure Portal, 73, 74, 80, 84, 91, 95, 124,
library to client project, 201–203
139, 140, 207, 210, 269, 273,
login screen, 203–206
275, 277
role-based access control, 206–210
Azure Portal Settings, 206
users’ passwords, 185
Azure Static Web App creation
AuthenticationStateProvider, 196, 201
base information, 81–83
Authorization, 186
Blazor build setup, 86–89
Api project, 194
creation Page, 81
Azure account, 211
deployment complete, 89
application, 259
deployment details, 84
GitHub repository, 257
empty Resource Group, 79
production environment, 257
GitHub, 78
staging slot, 260
GitHub repository details, 86
workflow file, 258
GitHub workflow, 91–93
Azure Active Directory, 186, 215, 279
hosting plan options, 83
Azure DNS Zone, 276, 277
logging into Github, 84, 85
Azure Function project
new resource, 90
API, 52
404 page, 95
Blazor WebAssembly, 56
placeholder site, 90
core tools, 58
project Details, 82
FunctionName parameters, 56
region, 83
result with name input parameter, 59
resource group creation, 73–77
result with no input, 59
285
© Stacy Cashmore 2022
S. Cashmore, Beginning Azure Static Web Apps, https://doi.org/10.1007/978-1-4842-8146-8
INDEX
286
INDEX
287
INDEX
288
INDEX
289
INDEX
T, U
Tag Lookup, 281
W
Windows security warning, 48
wwwroot folder, 170
V
Visual Studio 2022, 7, 14, 27, 28, 47,
117, 165
X, Y, Z
Visual studio code file explorer, 251 x-ms-client-principal, 190
290