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

The purpose of this document is to describe the steps necessary to setup an Azure Static Web Apps application from scratch creating a development environment first using Blazor Web Assembly Client Front end and C# Http Functions back end. All of this will be done in Visual Studio where we can debug both the Client and API at the same time all the while using nearly purely C# for Client and Back End programing.

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
Three-Part Article Links:


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.

Give the solution any name that you like. I am going to use “swa-local-first-demo”, and then click on “Create”

Add a Blazor Web Assembly Project to your Solution

    1 – Right click on the Solution

    2 – select the option to Add -> New Project

NOTE: Be sure to select the “Blazor WebAssembly App”, and not the “Blazor Server App” option, and then click on “Next”
    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

TEST: If you like you can click the Play button / press F5 to test out your Blazor Web Assembly app.

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

For the project name give it the name of “Api”. In this case it is important to be exact and you will see why later. Click on Create.

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:

NOTE: – If you like you can do a right-click on the Api project and select “Debug -> Start New Instance.” Then go to the URL that it found in your Browser or postman to see the results.
Finally, Stop the debugger when done… if you choose to do this.

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.

The file should look something like this (your port might be different, and that is ok):
{
    "profiles": {
      "Api": {
        "commandName": "Project",
        "commandLineArgs": "--cors * --port 7088",
        "launchBrowser": false
      }
    }
}
At this point you can press F5 / press the play button and you should notice both the Client and API Projects start up, and you can go to their individual URLs to see their results.

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
This will create a package.json file in this directory.

Return to visual studio. Do a right-click on your solution and the select the option to Add an Existing Item.

Add the package.json file that just got created
Delete the “main” key, and within the scripts key update it to have a key for start with the following command: “swa start http://localhost:5198 –api-location http://localhost:7088”.

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"
}
Remember that 5198 and 7088 are my client / api ports. Your ports will probably be different. So be sure they match what is in your launchSettings.json files of your two projects of your solution.

What this will do is start the Azure Static Web Apps cli and will proxy requests sent to localhost:4280/ to either localhost:5198 orlocalhost: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"
        }
      }
    }
} 
Return to your command prompt where you did the npm init earlier. In that command prompt execute the following command (assuming that you are still in the correct directory where your package.json and solution files are).
npm start
Your screen should say something like this now:
Return to your visual studio and press F5 or the Green Play button to begin debugging.

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).

And your browser should have this now:
You can browser around this site for the Counter and Fetch Data to see how it goes.

You can also access localhost:4280/api/hello and 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.

Then go to your browser. Go to the counter page, and press the Click me button. Your visual studio should start flashing orange indicating that it has hit a breakpoint for you to inspect

Press F10 or Continue to move past that.

To the “Hello.cs” file within the Api project set a break point at line 20 by pressing F9
Browse to http://localhost:4280/api/hello and visual studio should flash orange indicating that you have a break point to inspect.
Press F10 or continue to go beyond that. Try I again with localhost:4280/api/hello?name=World and you will see that the name field is populated in the debugger this time.
Stop the debugger when you are ready.

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) });
There is something interesting in this line of code that actually makes it so that the HttpClient has a BaseAddress of the URL that the index.html page was loaded from. So, if you remember your client and api were on separate ports when we started, but when we run this through the SWA CLI the client and api are on the same port. So now that the api and client are on the same server, just at different routes, we no longer need to worry about configuring CORS or telling our application what the API URL is. It already knows form the “builder.HostEnviornment.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();
        }
    }
}
Start the debuggers back up, and then you will see at port 4280 the api url is where we browsed to.
If we browse to the original client ip address (in my case 5198) it says that is where the base address URL is, but obviously would be wrong since that URL does not have any proxy associated with it.

You can know for sure because if you browse to localhost:5198/api/hello, you will get nothing. However if you go to 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.

Give it the following code:
@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; }
    }
}
To review what is going on in this page. We are creating a model class for our form called HelloFormModel with one string property of “Name”. We are then creating an instance of that class to be used as our EditForm’s Model. We also created a string for the UI to use to display the response text from our API. We have a private method to get that response text which uses the injected HttpClient. When the page initializes, or the form is submitted an API Request is made and the ui response text is updated.

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;
    }
}
Start the debuggers and browse to the new Hello page.

When you first get there, you should get the generic message that you have been seeing at the /api/hello route.

Provide a name and hit submit and you will see the full response.
Stop the debuggers.

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.

At the end of the log you should see something like this
You can go back to the azure portal, and the application should be ready to browse.
When we go to the site our Index page tells us that it already knows our API URL is the one that was loaded
If we go to the Hello page, then we will see the following screen.
If we submit the form, then we will see the following screen.
Success!

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 should get a notice indicating that your repository was updated to a specific commit.
Return to the Solution Explorer. Click on the “Switch between solution and available views” button. Then browse through the folder view and notice that there is now a .github folder, with a workflows subfolder and a file that the azure static web apps made for us.
This is technically the file that is created for you when you use the “generator” on those Microsoft Online Documentation tutorials.

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 Intertech

Intertech 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. Intertech 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 Intertech 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, Intertech is a trusted partner of more than 4000 satisfied customers and has a 99.70% “would recommend” rating.

Three-Part Article Links:

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