Often we come across feedback forms shared with us from our employers or organizations to get our feedback to take informed decisions. With the onset of Covid, the forms were shared by digital mediums with the help of products like Microsoft Forms and Google Forms. It usually contains questions which you need to answer by selecting any of the options shown as radio buttons in the forms. Typically, It ranges from 0-5 where the higher rating shows more satisfaction and vice versa or else we have an option to select from a list of options including bad, neutral, good and so shown.
But does this solution actually work? In my opinion, It’s a big no. People tend to select a random option to share the ratings and get over with it as soon as possible which in turn makes the data collected using such forms of hardly any use. But what if we can take these data in textual format and process it by using sentiment analysis to find out about user satisfactions. And if there are negative feedbacks we can store them in different tables to analyze and find out the pain points of our customers or end users.
Problem Statement
Let’s consider a scenario which is quite relevant in current times where students are taking online classes. But gathering student’s satisfaction with their classes on different subjects and their respective faculties has been challenging. Student’s being student’s just randomly fill up these feedback forms by selecting random options from the choices available in the form provided to them by their institutions to gather insights from them. This makes it really difficult for the organizations and management of these institutions to work into improving the students experience by identifying the subjects which require special attention and work on making the student experience better on these subjects.
Proposed Solutions
In this article, We are going to build a solution using Blazor, Azure functions, Azure Static Web Apps and Azure Cognitive Services to solve the problems highlighted in the problem statement. We are going to deploy a Azure Static Web App which will consist of a Blazor WebAssembly App as the front-end application and an Azure Functions App as the Backend APIs which will be interacting with our Azure SQL database to store the feedback submitted by the students. The azure functions will leverage the power of the azure cognitive services to build intelligent solutions to solve the problem statement.
Architecture of the Application
As shown in the above figure, We will be building the client-side application using Blazor WebAssembly. This application will consist of a SubmitFeedback component and GetFeedbackReport Component. The backend will be an function app consisting of two azure functions named Submit Feedback and Get All Feedback
Submit Feedback component will be used by the students to submit their feedback for different subjects with the help of submit feedback function. This component will consist of all the required fields like Student ID, Subject Name and the Feedback on the subject. Feedback on the Subject will be of Text Area type where students need to write about their opinions on the subject in no less than 50 words. Once the student has filed all the required fields and clicks on submit, the SubmitFeedback function is called by the component. The SubmitFeedback function leverages the power of Azure Text Analysis and finds out the sentiment of the feedback and stores the feedback data submitted by students along with the sentiment of the feedback in our Azure SQL Database.
GetFeedbackReport Component will be used by the admin to gather the insights for all the subjects by processing the feedback shared by the students. This component will call the GetAllFeedback function which will fetch all the feedback from our Azure SQL Database and will return all the records stored in our database but will only retrieve the value of SubjectName and FeedbackSentiment to keep the Student Information anonymous. We will find out the percentage of positive,negative,neutral and mixed sentiment from the feedback received for all the subjects individually in the GetFeedbackReport Component. This will help the admin gather insights about how the students feel about the subjects and identify the subject which needs some work to be done to make the student experience better.
Prerequisites
- Install .NET 5 SDK from https://dotnet.microsoft.com/download/dotnet/5.0
- Install the latest version of Visual Studio 2019 from https://visualstudio.microsoft.com/downloads/
- Install the Azure Development Workload using Visual Studio Installer
- Create an Azure Cognitive, Azure SQL Database and Azure Static Web App Resource in Azure Portal
- Push your app in a GitHub Repository
Why Azure Static Web Apps?
There can be a question arising in our minds, using azure cognitive services makes sense as we need the cognitive capabilities like Text Analytics in our app but why should we use Azure Static Web Apps. Below are the features because of which I choose to build this solution using Azure Static Web Apps.
- Custom Binding + Free SSL certificates
- Static File Hosting + Integrated API support provided by Azure Functions
- Support for multiple input and output binding
- Build and Deploy with Github Actions
- Globally Distributed
- Integrated Authentication and Routing
We can definitely build a Blazor WebAssembly app and deploy it in a storage account as a static website and binding with our serverless APIs using azure functions but this would require CORS configurations, building CI/CD pipelines along with generating/buying SSL certificate for our websites to name a few. Azure Static Web Apps ticks for all the above mentioned configuration which we would have to do if we had deployed the front-end app i.e., Blazor App in a storage account and the Backend APIs inside a function app using azure function.
Creating the required resources in Azure for the project
Create a Azure Cognitive Service Resource in Azure and get the API key and endpoint
Go to Azure Portal. Type Cognitive Services in the search bar and click on it.
Now click on create as highlighted in the below attached image. This will pop up a side screen which would show the marketplace.
Type Text Analytics in the search and click on Text analytics offered by Microsoft to create the resource.
Now we need to click on create.
Fill in all the required fields as shown in the below attached image and click on review + create.
If the validation check for your entered value was successful, then you will be able to see a screen similar to the one shown below consisting of all the values you had entered. Click on create to create the resource.
Once the resource is created, go to the Text Analytics resource and get the key and end point from the Keys and Endpoint section as highlighted in the image below.
We will be using this key and endpoint in our azure functions to interact with the Text Analytics API to find the sentiment of the feedback.
Create a Azure SQL Database to store the feedback data
Go to Azure Portal. Type Azure SQL in the Search bar and Click on it.
Click on Create as highlighted in the below shown image. If you previously had any Azure SQL databases, you will see all of it listed in this screen.
Select the SQL deployment option as SQL databases and the resource type as Single Database as it would be sufficient for our requirement.
Now fill in all the required fields as highlighted in image below and click on Review + Create.
In case you don’t have an Azure Sql Server, click on create new link available below the Sever’s textbox. It will pop a new side screen where you will have to fill in the server name, user id and password. Once you fill in all the details click on Ok to create a Azure SQL Server on the fly.
Once you have filled all the details, Click on Review + Create, this will take us to the validation screen where we will see the summary of all the details we had previously filled in. Once the validation check is successful, we can provision our database by clicking on create.
Once the resource is provisioned, go to the resource. We can get the Server Name and option to get the Connection String from here in the overview section. Let’s click on the set server firewall to define the traffic which is authorized to access this resource. It is advised to allow only the application to access this resource while using it as a part of a production workload. We could have configured it while creating the resources but often the infrastructure is provisioned by someone else and you might need to add your workstation’s IP address to access this resource if your IP is not configured in the firewall.
Now add a new firewall rule by defining the rule name and IP address of your workstation. In my case, I have defined a rule named allow-all with Start IP as 0.0.0.0 and End IP as 255.255.255.255 but you can also replace the Start IP and End IP with your workstation's IP address to gain access to this resource and click on Save to save the firewall rule which you have defined. The problem with this way is that if you don’t have a static IP then you will have to reconfigure the firewall rule by modifying the Start IP and End IP of your workstation again. With the rule I have added, Anyone can access our database over the internet which should be avoided while developing enterprise applications. Since I don’t have a static IP, I’m using it to avoid the pain modifying my firewall rule every time my IP changes. I would recommend you to not define a firewall rule as the one created above for a production workload.
Now that we have configured the firewall, let’s open the SSMS and connect with the database we had created in this section. Once connected, let’s execute the below mentioned SQL Script to create the Feedback table to store the data.
CREATE TABLE [dbo].[Feedback](
[Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[StudentId] [int] NOT NULL,
[SubjectName] [varchar](100) NOT NULL,
[StudentFeedback] [varchar](max) NOT NULL,
[FeedbackSentiment] [varchar](18) NOT NULL,
)
GO
Let’s build the solutions using Blazor and Azure Functions in Visual Studio 2019
Now as we have created all the required resources for the solutions, let's start building our student feedback analyzer app. Before we get any further, let’s clone the repo provided by Microsoft which contains a blazor starter template from the below mentioned link.
Link - https://github.com/staticwebdev/blazor-starter
Once you have cloned the repo, open the solution using Visual Studio 2019. You will see three projects present in the solutions which are as follows:
- API
- Client
- Shared
API project is an azure functions project which contains HTTP-Triggered Azure functions that acts as the APIs for our client application.
Client project is a Sample Blazor WebAssembly project which visual studio creates out of the box when you create a new Blazor WebAssembly project
Shared project is C# library project which contains the data models used by both the Client and API project.
Note - Currently azure static web apps support only supports HTTP-Triggered functions but all the bindings are supported. So please don't add functions of a different Trigger Type in Api project.
As we know the purpose of all the projects included in the solution now, Let’s create the Models in the Shared project which we will be using in our application.
We will be using three models across the projects in our applications. The first one being, Feedback class. We will be using this class inside the Client project to send the data to api where we will perform sentiment analysis on the feedback and then use it to store the feedback data in our database which we had created earlier in the previous section.
public class Feedback
{
public int StudentId { get; set; }
public string SubjectName { get; set; }
public string StudentFeedback { get; set; }
public string FeedbackSentiment { get; set; }
}
Next, We need to create the FeedbackReport class. We will be using this inside the API project to retrieve the data from the database and later we will use it to process and wrangle the data inside the Client project.
public class FeedbackReport
{
public string SubjectName { get; set; }
public string FeedbackSentiment { get; set; }
}
And finally we need to create the DisplayReportModel class to display the data in terms of percentage categorically for the sentiment type for each individual subject in the reports screen.
public class DisplayReportModel
{
public string SubjectName { get; set; }
public int TotalPositiveFeedback { get; set; }
public int TotalNegativeFeedback { get; set; }
public int TotalNeutralFeedback { get; set; }
public int TotalMixedFeedback { get; set; }
public int TotalFeedbacks { get; set; }
}
Once we have added the above mentioned classes in our shared project. Let’s start adding HTTP Triggered azure functions in our API project to interact with our database.
We will be creating two azure functions namely SubmitFeedback and GetAllFeedback inside our Api project and two razor component inside our client project namely SubmitFeedback and SubmitFeedback for this application.
Before adding functions inside our api project, We need to add two nuget packages as mentioned below:
- Azure.AI.TextAnalytics - We will use this package to perform sentiment analysis to find the sentiment of the feedback submitted by the student. Use the below mentioned line to install the package using the package manager console.
Install-Package Azure.AI.TextAnalytics -Version 5.0.0
- System.Data.SqlClient - We will use this package to interact with our Azure SQL database from our azure function. Use the below mentioned line to install the package using the package manager console.
Install-Package System.Data.SqlClient -Version 4.8.2
Once we have installed all the above mentioned dependencies in our Api project, we need to rename the local.settings.example.json to local.settings.json and add our connection string of the database, Text Analytic’s Key and Endpoint value inside the Values. The local.settings.json should look similar to the one shown below.
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=false",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"connectionString": "our-connection-string",
"key":"our-text-analytics-key",
"endpoint":"our-text-analytics-endpoint"
},
"Host": {
"LocalHttpPort": 7071,
"CORS": "*",
"CORSCredentials": false
}
}
There can be questions arising in our mind. Earlier while discussing the reasons why we choose Azure Static Web App, I had mentioned that we don’t need to configure cors anymore but we are still configuring the port and cors here. The reason is when we run this project locally our api has a different domain then that of our client project but when we deploy it in azure static web app the domain remains the same for the client as well as the api project. For example, When I run the projects locally, my api project will be available in http://localhost:7071/ while my client project will be available at https://localhost:44351/, but when I deploy our app to azure static web app, the domain remains the same for the both the project. And you don’t need to do anything explicitly to enable interaction between your client and api.
Let’s create the SubmitFeedback function which we will use to perform sentiment analysis on the feedback submitted by the student using the GetFeedbackSentiment method and later use the InsertData method to insert all the data submitted by the student along with the feedback sentiment inside our database.
The code for SubmitFeedback function is mentioned below:
public static class SubmitFeedback
{
private static readonly AzureKeyCredential credentials = new AzureKeyCredential(Environment.GetEnvironmentVariable("key"));
private static readonly Uri endpoint = new Uri(Environment.GetEnvironmentVariable("endpoint"));
private static TextAnalyticsClient client = new TextAnalyticsClient(endpoint, credentials);
[FunctionName("SubmitFeedback")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
ILogger log)
{
Feedback data;
int insertRecord;
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
try
{
data = JsonConvert.DeserializeObject<Feedback>(requestBody);
data.FeedbackSentiment = GetFeedbackSentiment(data.StudentFeedback);
insertRecord = InsertData(data);
return new OkObjectResult(data);
}
catch (Exception ex)
{
log.LogError(ex.Message);
return new BadRequestObjectResult(ex.Message);
}
}
public static string GetFeedbackSentiment(string feedback)
{
DocumentSentiment documentSentiment = client.AnalyzeSentiment(feedback);
return documentSentiment.Sentiment.ToString();
}
public static int InsertData(Feedback feedback)
{
int recordsInserted;
try
{
using (SqlConnection connection = new SqlConnection(Environment.GetEnvironmentVariable("connectionString")))
{
string queryString = @"INSERT INTO [Feedback](StudentId,SubjectName,StudentFeedback,FeedbackSentiment)
VALUES(@StudentId,@SubjectName,@StudentFeedback,@FeedbackSentiment)";
using (SqlCommand cmd = new SqlCommand(queryString))
{
cmd.Parameters.AddWithValue("@StudentId", feedback.StudentId);
cmd.Parameters.AddWithValue("@SubjectName", feedback.SubjectName);
cmd.Parameters.AddWithValue("@StudentFeedback", feedback.StudentFeedback);
cmd.Parameters.AddWithValue("@FeedbackSentiment", feedback.FeedbackSentiment);
cmd.Connection = connection;
connection.Open();
recordsInserted = cmd.ExecuteNonQuery();
connection.Close();
}
}
}
catch (Exception ex)
{
recordsInserted = 0;
}
return recordsInserted;
}
}
The above function is like any normal HttpTrigger Azure function. The route for the function remains as domain/api/{FunctionName} i.e., www.{function-name}.azurewebsites.net/api/SubmitFeedback. We can modify the endpoint of our function by overding the route parameter. We have passed only one request method type, i.e, POST to HttpTriggerAttribute’s constructor as we will be using this function to store data in the database. This restricts the function to only respond to POST requests.
We have defined three static variables inside our SubmitFeedback class as we will be using them to connect with our Azure Text Analytics Service using it’s SDK which we had installed earlier. There’s a reason why I have defined these variables as static variables. Defining static variables allows us to reuse these variables for different function invocations inside the same server instead of creating a new object for every invocation. This gives us performance benefits and saves our memory. Though if our function scales out and spins up a new server instance, we will have a new static variable created for this server.
Inside the azure function, We are first deserializing the data we received from the request object and storing it inside the data variable of Feedback Type then we update FeedbackSentiment property of the data object by assigning it the value of the Sentiment found after performing sentiment analysis on the value StudentFeedback property of the data object and then call the InsertData method to insert a new record in our database with the values of properties of the data object.
GetFeedbackSentiment uses the AnalyzeSentiment method of Azure Text Analytics SDK to get the sentiment of the Feedback submitted by the student. The sentiment identified and returned can be any one of the following mentioned below:
- Positive
- Negative
- Mixed
- Neutral
InsertData uses a SqlCommand type object, cmd to insert the data to our Feedback table present inside our Azure SQL database by calling the ExecuteNonQuery method after adding the all the parameters values using the AddWithValue method. We could have used EF core or any other ORM/Micro ORMs like dapper too in this project to interact with the database.
Now as we have discussed what the code inside our SubmitFeedback function does, let’s discuss one point we haven’t discussed yet. We are fetching the secrets from our local.settings.json file, Is it safe. To answer that, It’s a big no and this file will be ignored when we push this project to our github repository to deploy our application in Azure Static Web App. We will have to add all the secrets inside the application setting to make our application work smoothly as our functions will fetch the secrets from the application setting of our Azure Static Web App instead of the local.setting.json file. It is recommended to use a key vault to store your app secrets though.
Now let’s have a look at code for the SubmitFeeback razor component of our client app which will be used by the students to submit their feedback which will later send this as a POST request to our SubmitFeeback function present inside our API project.
@page "/SubmitFeedback"
@using BlazorApp.Shared
@inject HttpClient http
<h3>Submit Feedback</h3>
<EditForm Model="feedback">
<div class="form-group">
<label for="studentId">Student Id</label>
<InputNumber @bind-Value="feedback.StudentId" class="form-control" id="studentId" />
</div>
<div class="form-group">
<label for="subjectName">Subject Name</label>
<InputText @bind-Value="feedback.SubjectName" class="form-control" id="subjectName" />
</div>
<div class="form-group">
<label for="feedback">Enter Feedback</label>
<InputTextArea @bind-Value="feedback.StudentFeedback" class="form-control" id="feedback" />
</div>
<div>
<button type="submit" @onclick="SaveFeedback" class="btn btn-success">Save</button>
</div>
<div>
<p class="alert-info">@message</p>
</div>
</EditForm>
@code {
private Feedback feedback = new Feedback();
string message = "";
private async Task SaveFeedback()
{
try
{
message = "Sending the feedback";
var response = await http.PostAsJsonAsync("/api/SubmitFeedback", feedback);
message = "Feedback stored in the destination";
}
catch (Exception ex)
{
message = "Failed to store your feedback";
}
}
}
I have created this SubmitFeedback component inside the Pages folder of our client project which contains all other razor components for our project. I have defined the route for this component by using @page directive and then injected the HttpClient inside our component using @inject directive. You may wonder when did I register the HttpClient inside our Blazor app’s DI Container. Well I didn’t do it because it was already done in the template which we are customizing to build the application. If you are making one from scratch then you will need to configure it inside the program.cs file your blazor project. I’m using input components here to get the data from all the input elements and bind it to the feedback model’s properties. We are associating the input forms value with the property of the feedback model by the help of @bind-value. Finally we have a button named Save which makes a call to our SaveFeedback method which we had defined inside our @code section of the component, everytime a student clicks on it. I haven’t done any validation checks on the input form elements here but I highly recommend you to perform validation checks in your implementation of such applications.
The SaveFeedback method sends a POST request to our SubmitFeedback function. I have defined a message variable which helps the user stay updated about the state of their feedback as we are sending asynchronous calls.
Now as we have covered grounds for the SubmitFeedback Component and API function, Let’s look into the GetAllFeedback function’s code which is mentioned below.
public static class GetAllFeedback
{
[FunctionName("GetAllFeedback")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
ILogger log)
{
List<FeedbackReport> data = new List<FeedbackReport>();
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
using (SqlConnection connection = new SqlConnection(Environment.GetEnvironmentVariable("connectionString")))
{
string oString = "select SubjectName,FeedbackSentiment FROM Feedback";
SqlCommand oCmd = new SqlCommand(oString, connection);
connection.Open();
using (SqlDataReader oReader = oCmd.ExecuteReader())
{
while (oReader.Read())
{
FeedbackReport feedbackReport = new FeedbackReport();
feedbackReport.SubjectName = oReader["SubjectName"].ToString();
feedbackReport.FeedbackSentiment = oReader["FeedbackSentiment"].ToString();
data.Add(feedbackReport);
}
connection.Close();
}
}
return new OkObjectResult(data);
}
}
In the GetAllFeedback Function, We are only accepting GET requests as we have only passed GET to HttpTriggerAttribute’s constructor. We are using a simple ADO.NET code to fetch all the records from the feedback table of our database by passing our query in the oCmd object and later populate the fetched records inside the data variable of List type which we will be passing back as a response to the client. We are only bringing the values from the SubjectName and feedbackSentiment columns from the table to keep the student and his feedback annonymous.
This was a fairly simple function which was performing read operations by using the oCmd and oReader object. But the heavy lifting is done on the GetFeedbackReport component of our client project which consumes this function.
Let’s have a look at the code for the GetFeedbackReport component.
@page "/GetFeedbackReport"
@using BlazorApp.Shared
@inject HttpClient http
<h3>Get Feedback Report</h3>
<p>This component demonstrates fetching data from the server.</p>
@if (feedbackReports == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Subject</th>
<th>Postive Feedback</th>
<th>Negative Feedback</th>
<th>Mixed Feedback</th>
<th>Neutral Feedback</th>
</tr>
</thead>
<tbody>
@foreach (var report in displayReportModels)
{
<tr>
<td>@report.SubjectName</td>
<td>@((report.TotalPositiveFeedback*100)/report.TotalFeedbacks)%</td>
<td>@((report.TotalNegativeFeedback*100)/report.TotalFeedbacks)%</td>
<td>@((report.TotalMixedFeedback*100)/report.TotalFeedbacks)%</td>
<td>@((report.TotalNeutralFeedback*100)/report.TotalFeedbacks)%</td>
</tr>
}
</tbody>
</table>
}
@code {
private List<FeedbackReport> feedbackReports;
List<DisplayReportModel> displayReportModels = new List<DisplayReportModel>();
protected override async Task OnInitializedAsync()
{
try
{
feedbackReports = await http.GetFromJsonAsync<List<FeedbackReport>>("/api/GetAllFeedback");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
foreach (var report1 in feedbackReports)
{
foreach (var report2 in feedbackReports)
{
if ((report1.SubjectName == report2.SubjectName) && displayReportModels.FindAll(x => x.SubjectName == report1.SubjectName).Count == 0)
{
DisplayReportModel displayReportModel = new DisplayReportModel();
displayReportModel.SubjectName = report2.SubjectName;
displayReportModel.TotalFeedbacks = feedbackReports.FindAll(x => x.SubjectName == report2.SubjectName).Count;
displayReportModel.TotalPositiveFeedback = feedbackReports.FindAll(x => x.FeedbackSentiment == "Positive" && x.SubjectName == report2.SubjectName).Count;
displayReportModel.TotalNegativeFeedback = feedbackReports.FindAll(x => x.FeedbackSentiment == "Negative" && x.SubjectName == report2.SubjectName).Count;
displayReportModel.TotalNeutralFeedback = feedbackReports.FindAll(x => x.FeedbackSentiment == "Neutral" && x.SubjectName == report2.SubjectName).Count;
displayReportModel.TotalMixedFeedback = feedbackReports.FindAll(x => x.FeedbackSentiment == "Mixed" && x.SubjectName == report2.SubjectName).Count;
displayReportModels.Add(displayReportModel);
}
}
}
}
}
We have created this component inside the Pages folder of our client project. We have defined the route for this component using the @page directive and injected the HttpClient service using the @inject directive. We have overridden the OnInitializedAsync lifecycle method of this component as we wanted to populate a table with the processed data representing the sentiments of students for each subject whenever this component loads. In the OnInitializedAsync , we are consuming the GetAllFeebacks function of the API project.. After getting the records, we are storing it inside feedbackReports which is a List object. And then we are wrangling the fetched records to get the total no. feedback received along with the total no. of positive, negative, mixed and neutral feedback for all the subjects respectively. We will be storing the wrangled data inside displayReportModels object which is of List type. We are using this displayReportModels object to display it in a tabular format in terms of percentage as shown in the table below.
We are iterating through the displayReportModels inside our component using razor syntax to display the data inside a table by calculating the percentage for Positive, Negative, Mixed and Neutral Feedbacks received for the subject.
Let’s add two menu items inside the navmenu component which we can use to go to the respective components directly. We can find the navmenu component inside the Shared folder of the Client project. Open the navmenu component and add the below mentioned code snippet to add menu items to access the SubmitFeedback and GetFeedbackReport component inside the div tag which has the class @NavMenuCssClass.
<li class="nav-item px-3">
<NavLink class="nav-link" href="SubmitFeedback">
<span class="oi oi-signal" aria-hidden="true"></span> Submit Feedback
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="GetFeedbackReport">
<span class="oi oi-project" aria-hidden="true"></span> Feedback Report
</NavLink>
</li>
Now as we have written all the code required for the app to run smoothly in the API, Client and Shared project, Let’s run it locally and see if it’s working as expected. We need to launch API and Client projects simultaneously. To do this, Go to the properties of your solution then select multiple startup projects and select the action as start for the API and Client project. Now click on Ok and Click on Start to run the application locally.
Now an instance of our client web app and the api project will get launched. The client project which is a Blazor WebAssembly will run on a browser and the api project will launch the azure function core tool to run. We can see the list of all the functions present in our api project in the azure function core tool window along with the logs as shown in the below attached window. Let’s click on the Feedback Report from the Menu. It shows us the feedback report for the all individual subject in terms of percentage for all the sentiment categories and see the function calls,invocation id and log message of the functions inside the azure function core tool window.
Since we clicked on the Feedback Report menu item, it routed us to the GetFeedbackReport component. And as we wrote the code to make a call to the GetAllFeedback function every time this component is loaded, the GetAllFeedback function was called by our client which is visible in the azure function core tool window as highlighted in the image below.
Now that we have run and tested the application locally, let’s deploy it in a Azure Static Web App resource in Azure.
Deploy the solution to Azure using Azure Static Web Apps
To deploy this application in a Azure static web app resource, We will have to create a repository in our github account and commit all the code of this project over there. Once we have committed all the codebase to a github repository. Go to Azure Portal and search for Azure Static Web Apps.Click on the Static Web Apps(Preview) as highlighted below.
Click on Create to create your Static Web App. If you already had a few prior to creating this static web app you will be able to see all of them on this screen.
Now you will be prompted to fill in some details required to create your static web app as mentioned below.
- Subscription - We need to select the subscription which we want to use to create this resource. You can select any one of your existing subscriptions
- Resource group - Create a new resource group for this resource or use an existing one
- Name - Give your static web app a name. I have named my app as blazor-swa
- Region - Select any of the available region which is nearest to you
- SKU - since static web apps are still in preview, the default SKU will be free but when it goes to GA we can see a few more SKUs.
- Now you will need click on sign in with Github to authorize your app to access your Github account to access your repositories
- Organization - Select the organization inside of which your repository for this project is present, in my case the organization is AshirwadSatapathi
- Repository - Select the repository containing your project. In my case it is blazor-swa
- Branch - Select the Branch which you want to be deployed. For this repository I only have branch so it will be master for this.
- Build Preset - Select Blazor as the build the preset. This would define the default project structure of our app.
- App Location - Let the app location as Client since our client-side project is Client, if we had our client-side project as testClient in our solution then we would have to select the app location as testClient.
- API Location - Let the Api location as Api since our function project name is Api.
- Output Location - Let the Output location as wwwroot.
After you have entered all the above required information click on Review + Create. Now we see a screen similar to the one shown below. Here we will see a summary of all the details we had filled in the previous screen. Click on Create to create our static web app to host our app.
Once the resource is created, click on go to resource and click on browse to see your app in action.
Don’t panic if you see a screen similar to the one shown below as it usually takes some time to deploy your app. In the background, your application is deployed using Github Action. To see the status of your applications deployment, you can either click on Github action runs as highlighted in above picture or. Go to your repository where this app is committed and then go to the actions to see the deployment status of your app.
There’s a catch here though. Our student feedback analyzer app won’t work as expected even after successful deployment as our app won't be able to find our app secrets like db connection string, key and endpoint of our text analytics resource as we had stored them inside the local.settings.json file which gets ignored while committing our project to github.
So how do we make our project work now ?
It's quite simple to be honest. We need to click on configuration and then add our app secrets in the application setting. Once we had added all our secrets, let’s click on save and now our application will be able get the app secrets with the help GetEnvironmentVaribale method of the environment class and our app should work as expected.
Now if we go to our app using the url of the static web app or by clicking on browse from the Overview section and click on the Feedback report menu item. We will get the feedback report of each subject categorically based on the sentiment type of the feedback now.
And we have created and deployed our student feedback analyzer app using Azure static web app and azure cognitive services. We can use the below mentioned URL to test this application.
Link for the app - https://purple-sea-062c01410.azurestaticapps.net/
Fun Fact - Azure Static Web Apps creates a generated hostname for your app without using the resource name unlike the ways we get in other resources in azure like azure web apps or storage account. It is possible to have an azure static web app with the same name across different subscriptions and resource groups. The only restriction being, No two azure static web apps resources present inside one resource group can have the same name at the time, this article was written as can be seen in the screen below.
Conclusion
We can customize this application to solve different use cases similar to the one discussed in this article, one of them being a product feedback analyzer. Azure static web apps offer a lot of services which makes our life a lot easier as developers. While deploying our application, we no longer need to worry about configuring CORS or about building a CI/CD pipeline for our app to give automated deployments. Azure static web app creates a github workflow out of the box for automated deployments using github action for our app which deploys our application to azure. With due respect to all the amazing feature it provides I don't recommend to use it for production workloads yet as it is still in preview at the time of writing the article. But I must agree that, in a short time it has become one of my favorite azure service.
Top comments (7)
Thanks for sharing this article - I've found it really helpful. I've used your tutorial as the basis for a simpler use case to help me learn (utilising Dapper as well), but have come into an issue. Calls to the Api functions get a 503 error ("Function host is not running"). I get a similar issue running locally. Did you have this issue at all?
For ref have included what I've done below.
my code: github.com/oliverwolffe/hundredcli...
the site: lemon-moss-044d90203.azurestaticap...
Am fairly new to C# and Blazor so perhaps I've gone wrong in where I've deviated from your build.
Hi @oliverwolffe , Glad you found it resourceful. Did you configure your solution to start multiple projects. With reference to error, I'm assuming that your function project i.e, API project has not started while the client project has started. I will definitely give it a look later tomorrow. In the meanwhile, can you please configure your solution and try to start multiple projects in your solution.
Thank you for getting back to me! I didn’t realise that this configuration was required but that’s very helpful to know - I will have a look at this. I’m using VS Code rather than VS, which I think is meant to start both projects at once.
Yes. That's what I meant. Hope it helps 😇
I managed to figure out the issue by running the Api project on it's own as an Azure function. There were a couple of .net v5 dependencies which don't work with Functions, so the Api project wasn't starting up. After I downgraded these to 3.1.x the Api works!
Great Article Ashirwad!
Thanks, Sumit. Glad you like it.