I've decided to write a small survey site using Blazor. Part of this is an excuse to learn Blazor.
As I learn with Blazor I will blog about it as a series of articles.
This series of articles is not intended to be a training course for Blazor - rather my thought process as I go through learning to use the product.
All articles in this series:
- Part 1 - State
- Part 2 - Better State
- Part 3 - Components
- Part 4 - Render Mode
- Part 5 - Client IP
- Part 6 - Azure Cosmos DB
- Part 7 - Azure SignalR Service
- Part 8 - bUnit
- Part 9 - End to End Tests
- Part 10 - Bicep
- Part 11 - Azure DevOps
- Part 12 - Putting it all together - this article
In this article I wanted to recap the previous articles in the series and how they fit together to produce the final Software Survey application.
Starting the survey
A number of things occur simply by the user visiting https://software-survey.red-folder.com/
Firstly, the static HTML is provided by the application.
The PreRenderLoadingMessage
component I described in part 4 allows me to provide a loading page while the Blazor Server app starts up:
During the Server app start up, it will:
- Get the IP address of the user - as discussed in part 5
- Create the users state - as discussed in part 2
- Sets up the SignalR connection - as discussed in part 7
The app is then ready to use and the PreRenderLoadingMessage
will swap over from the loading page to the starting page.
The survey pages
As the user works through the survey, each page will use Dependency Injection to get the relevant part of the state.
Demographics page:
State defined as:
namespace SoftwareSurvey.Models
{
public class Demographic
{
[Required(ErrorMessage = "Please provide company size")]
[DisplayName("Company Size")]
[JsonProperty(PropertyName = "companySize")]
public string CompanySize { get; set; }
[Required(ErrorMessage = "Please provide your job seniority")]
[DisplayName("Job Seniority")]
[JsonProperty(PropertyName = "jobSeniority")]
public string JobSeniority { get; set; }
[DisplayName("Job Title (optional)")]
[JsonProperty(PropertyName = "jobTitle")]
public string JobTitle { get; set; }
[Required(ErrorMessage = "Please provide if your business is UK Based")]
[DisplayName("Is your business UK based?")]
[JsonProperty(PropertyName = "ukBased")]
public string UKBased { get; set; }
}
}
Injected as:
[Inject]
private Models.Demographic Model { get; set; }
Software Types page:
State defined as:
namespace SoftwareSurvey.Models
{
public class SoftwareTypes
{
[DisplayName("eCommerce")]
[Description("Website(s) selling products or services")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "eCommerce")]
public int? ECommerce { get; set; }
[DisplayName("Information Website")]
[Description("Website(s) providing information (portfolio, blog, etc)")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "informationWebsite")]
public int? InformationWebsite { get; set; }
[DisplayName("Mobile App")]
[Description("Application(s) for install on mobile or tablet")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "mobileApps")]
public int? MobileApps { get; set; }
[DisplayName("Line Of Business")]
[Description("Application(s) to support specific business process")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "lineOfBusiness")]
public int? LineOfBusiness { get; set; }
[DisplayName("SaaS")]
[Description("Software as a Service - sold as a product (normally subscription)")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "softwareAsAService")]
public int? SoftwareAsAService { get; set; }
[DisplayName("Other")]
[Description("Any other type of software not listed above")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "other")]
public int? Other { get; set; }
}
}
Injected as:
[Inject]
private Models.SoftwareTypes Model { get; set; }
Your Experiences page:
State defined as:
namespace SoftwareSurvey.Models
{
public class Experiences
{
[DisplayName("ROI")]
[Description("Is it providing Return On Investment?")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "returnOnInvestment")]
public int? ReturnOnInvestment { get; set; }
[DisplayName("Keeping pace")]
[Description("Is it keeping pace with the business needs?")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "keepingPace")]
public int? KeepingPace { get; set; }
[DisplayName("Recruitment")]
[Description("It is easy to recruit the developers you want?")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "recruitment")]
public int? Recruitment { get; set; }
[DisplayName("Retention")]
[Description("Is it easy to retain your developers?")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "retention")]
public int? Retention { get; set; }
[DisplayName("Quality")]
[Description("Is the software of a high quality?")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "quality")]
public int? Quality { get; set; }
[DisplayName("Predicability")]
[Description("Is the software development predicatable?")]
[Required(ErrorMessage = "Please select a value")]
[JsonProperty(PropertyName = "predicability")]
public int? Predicability { get; set; }
}
}
Injected as:
[Inject]
private Models.Experiences Model { get; set; }
One Change page:
State defined as:
namespace SoftwareSurvey.Models
{
public class OneChange
{
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
}
}
Injected as:
[Inject]
private Models.OneChange Model { get; set; }
Further Contact page:
State defined as:
namespace SoftwareSurvey.Models
{
public class Contact : IValidatableObject
{
[DisplayName("With the results of the survery?")]
[JsonProperty(PropertyName = "resultsOfTheSurvey")]
public bool SurveyResults { get; set; }
[DisplayName("For any follow up questions?")]
[JsonProperty(PropertyName = "followUpQuestions")]
public bool FollowUpQuestions { get; set; }
[DisplayName("For future surveys?")]
[JsonProperty(PropertyName = "furtherSuvey")]
public bool FurtherSurveys { get; set; }
[DisplayName("Email Address")]
[EmailAddress]
[JsonProperty(PropertyName = "email")]
public string Email { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return IsEmailValid ? null : new ValidationResult[] { new ValidationResult("Please provided email", new string[] { "Email" }) };
}
private bool IsEmailValid => !RequiresEmailAddress || !string.IsNullOrEmpty(Email);
private bool RequiresEmailAddress => SurveyResults || FollowUpQuestions || FurtherSurveys;
}
}
Injected as:
[Inject]
private Models.Contact Model { get; set; }
Saving the results
On hitting the final page, the application saves the full state object for the user to Cosmos DB - as discussed in part 6
The survey is then complete for the user.
Supporting cast
While not directly related to the application itself, its obvious right to draw attention to the supporting functions.
The following allowed me work quickly by providing a safety net (mainly to avoid regression):
- bUnit for Unit testing - see Part 8
- End to End testing - see Part 9
- Continous Integration, Delivery and Deployment - see Part 11
And that's it
The full source code (including tests, Bicep/ ARM templates and Azure DevOps pipeline YAML) can be found in this repo.
There is however one further article to the series. I take a look at the different upgrading to .Net 5 makes to my Blazor Server application.
Top comments (0)