Setting Up Local Development Environment, Setting Up Client and API Communications, and Publishing to GitHub and Azure
Part 1 of 3: Azure Static Web Apps – Blazor Web Assembly Front End and C# Azure Functions Backend – Local Development First Method
Azure Static Web Apps is a very flexible and powerful tool on Azure. You can mix and match different frontend frameworks with many different backend frameworks. This series will focus on using C# for both the Frontend and Backend.
Prerequisites:
-
-
- A GitHub Account
- An Azure Account
- Visual Studio 2022 (Community Edition will be just fine)
- Nodejs and NPM installed
-
Part 1 of 3: Setting Up Local Development Environment, Setting Up Client and API Communications, Publishing to GitHub and Azure
Part 2 of 3: Understanding Preview Environments
Part 3 of 3: Authentication and Authorization
Go Directly To The Code: Github
Part 1 – Setting Up Local Development Environment, Setting Up Client and API Communications, Publishing to GitHub and Azure
You may have seen this article when discovering information about Azure Static Web Apps. (Article: https://docs.microsoft.com/en-us/azure/static-web-apps/getting-started This is fine and all, but it is a little strange because it has you create your CI / CD pipeline before you really write any code. Also, if you are like me and discovered that you can write front end code in C# using Blazor WebAssembly and know that Azure Functions can be written in C# as well, you would like to keep both the front end and back end in C#, and in a Monorepo pattern as well, and that is just what we are going to do today.
Create an Empty Solution Project
-
1 – Open your Visual Studio
2 – Click on File -> New -> Project.
3 – Select the “Blank Solution” option and click on Next.
Add a Blazor Web Assembly Project to your Solution
-
1 – Right click on the Solution
2 – select the option to Add -> New Project
-
3 – Give the project name “Client”.
This time it is important to be exact, and you will see why later.
-
4 – Click on “Next”
5 – Uncheck the configure for HTTPS.
Ensure that the authentication type is none, and that no other options are selected and then click on Create
Add an Azure Functions Project to your Solution
-
1 – Right click on the Solution and select the option to Add a New Project.
2 – Search for and select “Azure Functions” and then click on Next
When prompted state that you want to create an “Http trigger” and then click on Create
-
3 – Rename the Function1.cs file to Hello.cs
4 – Next, change the FunctionName attribute that says “Function1” to say “Hello”
Your project / screen should look something like this now:
Configure your Client and API to launch at the same time
-
1 – Right-click on your solution and select the Set Startup Projects Option.
2 – Change to Multiple startup project. And set both Api and Client to start
Furthermore, we need to enable cors for our http triggers.
Go in to your launchSettings.json file within the Properties folder of the Api project. To the commandLineArgs property and “–cors * ” to the beginning of the value for that property.
{
"profiles": {
"Api": {
"commandName": "Project",
"commandLineArgs": "--cors * --port 7088",
"launchBrowser": false
}
}
}
You will notice that they are now running on different ports. In my case my API / Azure Functions are running on 7088, and my client is running on 5198.
You can take note of these port numbers by examining the launchSettings.json files in Properties folders of the Client and API projects.
For us to debug the Client and API at the same URL / Port Number we will need the Azure Static Web Apps CLI.
Install the Azure Static Web Apps CLI
To do this, open a command prompt and execute the following command:
npm install -g @azure/static-web-apps-cli
Adjust Launch Setting and run Static Web Apps CLI
Change directories so that you are in the folder where your solution file is at. For me that means I changed directories to c:\Users\swoodward\source\repos\swa-local-first-demo. While at that folder execute the following command:
npm init -y
Return to visual studio. Do a right-click on your solution and the select the option to Add an Existing Item.
Your package.json should look something like this:
{
"name": "swa-local-first-demo",
"version": "1.0.0",
"description": "",
"scripts": {
"start": "swa start http://localhost:5198 --api-location http://localhost:7088"
},
"keywords": [],
"author": "",
"license": "ISC"
}
What this will do is start the Azure Static Web Apps cli and will proxy requests sent to http://localhost:4280/ to either http://localhost:5198/ or http://localhost:7088/ depending on if the request is for the client or api. Furthermore, it will add additional route for authentication that are similar to the authentication routes built in when it is deployed in Azure.
Next, go to the launchSettings.json file within your Client application. In the profiles key and the Client key add an additional key of “launchUrl” to have the value of “http://localhost:4280”. Your launchSettings.json file should look something like this now (remember that your applicationUrl port will probably be different).
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:43722",
"sslPort": 0
}
},
"profiles": {
"Client": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "http://localhost:4280",
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5198",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
npm start
As your application starts to come up your CLI will start proxying many requests for the dll files that the Web Assembly Framework requires the client to download (Don’t worry not all of these files are downloaded in production, rather they are compiled).
You can also access http://localhost:4280/api/hello and http://localhost:4280/api/hello?name=World on a separate browser tab and watch what happens in the command prompt as you see it proxies these api requests as well.
Test Break Points
On the Counter.razor file go to line 16, or wherever you can see that the IncrementCount method is being executed, and press F9 to set a break point.
Press F10 or Continue to move past that.
Client and API Communications
Examine the Program.cs file in the Client project.
You should have a line that reads as follows.
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
We can even check this by adding the following code to the Index.razor file:
@page "/"
@inject HttpClient _httpClient;
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<p>API URL: @apiBaseUrl</p>
@code {
private string? apiBaseUrl;
protected override void OnInitialized()
{
base.OnInitialized();
if(_httpClient.BaseAddress is not null)
{
apiBaseUrl = _httpClient.BaseAddress.ToString();
}
}
}
You can know for sure because if you browse to http://localhost:5198/api/hello, you will get nothing. However if you go to http://localhost:4280/api/hello you will get what you want.
I just wanted to point this out because we will be using dependency injection on our page that communicates with our API, but sometimes knowing what is happening with dependency injection can be unclear.
Stop the debuggers and create a new Razor Component called Hello.razor in the “Pages” folder of the Client Project.
@page "/hello"
@using System.Web
@inject HttpClient _httpClient;
<h1>Hello</h1>
<PageTitle>Hello</PageTitle>
<EditForm Model="@helloFormModel" OnValidSubmit="SubmitHelloForm">
<div class="form-group">
<label>Name</label>
<InputText @bind-Value="helloFormModel.Name" class="form-control" />
</div>
<input class="btn btn-primary my-2" type="submit" value="Submit" />
</EditForm>
<p>Response Text: @responseText</p>
@code {
private HelloFormModel helloFormModel { get; set; } = new HelloFormModel();
private string responseText { get; set; } = "";
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await GetHelloResponseAsync();
}
private async Task SubmitHelloForm()
{
await GetHelloResponseAsync();
}
private async Task GetHelloResponseAsync()
{
if(_httpClient.BaseAddress is not null)
{
var uriBuilder = new UriBuilder(_httpClient.BaseAddress);
uriBuilder.Path = "/api/hello";
if(!string.IsNullOrEmpty(helloFormModel.Name) && !string.IsNullOrWhiteSpace(helloFormModel.Name))
{
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query["name"] = helloFormModel.Name;
uriBuilder.Query = query.ToString();
}
var builtUri = uriBuilder.ToString();
Console.WriteLine($"builtUri: {builtUri}");
var response = await _httpClient.GetStringAsync(builtUri);
responseText = response;
}
}
private class HelloFormModel
{
public string? Name { get; set; }
}
}
Build a link to the Hello page by modifying the NavMenu.razor page.
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">Client</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="hello">
<span class="oi oi-phone" aria-hidden="true"></span> Hello
</NavLink>
</div>
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
When you first get there, you should get the generic message that you have been seeing at the /api/hello route.
In the command shell that you executed the npm start, do a CTRL+C to stop the SWA CLI.
I would like to point out that as of this writing I had to open the command prompt and do this npm start before starting the Visual Studio debuggers any time I need to come back and code this application. I tried Task Runners, but couldn’t get them to work. I also tried using the command shell in Visual Studio, but could never get the CTRL+C to work to turn off the swa cli.
Deploy Your Static Web App
At this point we are ready to upload our code to GitHub, and then register our Static Web App
Do a right-click on the solution and select the option to Create Git Repository. Sign in to GitHub if need be, and check the box for Add a README, then click on Create and Push when ready.
Pay attention to your notifications and once you see one that says that it “Created and pushed repository to GitHub successfully” browse to the Azure Portal (https://portal.azure.com/).
-
1 – Click on the “Create a Resource” button
2 –Search for and select “Static Web App”
3 –Click on “Create”
-
4 – Give the project any name that you like. I am using swa-local-first-demo.
Depending on how many times you have been at this screen you may need to sign in and authorize GitHub for your GitHub account.
Once GitHub is authorized select the repository that you just created in Visual Studio earlier.
Set the build preset to blazor. If you followed this tutorial exactly then you should be able to leave the default values for App location, API Location and Output location.
Remember: These folder names are case sensitive.
Here is what mine looked like before clicking on Review and Create.
-
5 – Click on “Review and Create” and then click on “Create”
6 – When it says that things are done click on “Go to resource”
Next – In another tab (A) go to github.com, (B) go to the repository that was created for this project, (C) click on the Action tab, and (lastly) click on the running workflow to watch the actual code deployment in action.
Download the GitHub Workflow File to your Local Repository
In Visual Studio click on the “Git Changes” tab, and then click on the “Pull” button.
You are now ready to perform your code, build, deploy workflow as you would with any other application, and because you set it up this way you will have full debugger support for both the client and api in a single repository.
Conclusion
Azure Static Web Apps is a very powerful feature of Azure allowing you to focus more on code, and less on infrastructure. It also makes it very easy to manage the code, build, and deploy pipelines for both your Client and API applications. Setting things up through a local first approach makes it easier to setup debugging support for both your Client and API as well.
About Black Slate
Black Slate is a Software Development Consulting Firm that provides single and multiple turnkey software development teams, available on your schedule and configured to achieve success as defined by your requirements independently or in co-development with your team. Black Slate teams combine proven full-stack, DevOps, Agile-experienced lead consultants with Delivery Management, User Experience, Software Development, and QA experts in Business Process Automation (BPA), Microservices, Client- and Server-Side Web Frameworks of multiple technologies, Custom Portal and Dashboard development, Cloud Integration and Migration (Azure and AWS), and so much more. Each Black Slate employee leads with the soft skills necessary to explain complex concepts to stakeholders and team members alike and makes your business more efficient, your data more valuable, and your team better. In addition, Black Slate is a trusted partner of more than 4000 satisfied customers and has a 99.70% “would recommend” rating.