<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mark Taylor</title>
    <description>The latest articles on DEV Community by Mark Taylor (@redfolder).</description>
    <link>https://dev.to/redfolder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F469628%2F3cf7c45b-6479-4257-a156-17447c61c964.jpeg</url>
      <title>DEV Community: Mark Taylor</title>
      <link>https://dev.to/redfolder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/redfolder"/>
    <language>en</language>
    <item>
      <title>Building a Software Survey using Blazor - Part 12 - Putting it all together</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Mon, 21 Dec 2020 15:35:32 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-12-putting-it-all-together-246e</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-12-putting-it-all-together-246e</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it as a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;All articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state" rel="noopener noreferrer"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state" rel="noopener noreferrer"&gt;Part 2 - Better State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components" rel="noopener noreferrer"&gt;Part 3 - Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode" rel="noopener noreferrer"&gt;Part 4 - Render Mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-5-client-ip" rel="noopener noreferrer"&gt;Part 5 - Client IP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-6-azure-cosmos-db" rel="noopener noreferrer"&gt;Part 6 - Azure Cosmos DB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-7-azure-signalr-service" rel="noopener noreferrer"&gt;Part 7 - Azure SignalR Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-8-bunit" rel="noopener noreferrer"&gt;Part 8 - bUnit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-9-end-to-end-tests" rel="noopener noreferrer"&gt;Part 9 - End to End Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-10-bicep" rel="noopener noreferrer"&gt;Part 10 - Bicep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-11-azure-devops" rel="noopener noreferrer"&gt;Part 11 - Azure DevOps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 12 - Putting it all together - this article&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the survey
&lt;/h2&gt;

&lt;p&gt;A number of things occur simply by the user visiting &lt;a href="https://software-survey.red-folder.com/" rel="noopener noreferrer"&gt;https://software-survey.red-folder.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Firstly, the static HTML is provided by the application.&lt;br&gt;
  The &lt;code&gt;PreRenderLoadingMessage&lt;/code&gt; component I described in &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode" rel="noopener noreferrer"&gt;part 4&lt;/a&gt; allows me to provide a loading page while the Blazor Server app starts up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0gylwzez8xlg5ugvypiy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0gylwzez8xlg5ugvypiy.png" alt="Pre-Render Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During the Server app start up, it will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the IP address of the user - as discussed in &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-5-client-ip" rel="noopener noreferrer"&gt;part 5&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create the users state - as discussed in &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state" rel="noopener noreferrer"&gt;part 2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sets up the SignalR connection - as discussed in &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-7-azure-signalr-service" rel="noopener noreferrer"&gt;part 7&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app is then ready to use and the &lt;code&gt;PreRenderLoadingMessage&lt;/code&gt; will swap over from the loading page to the starting page.&lt;/p&gt;
&lt;h2&gt;
  
  
  The survey pages
&lt;/h2&gt;

&lt;p&gt;As the user works through the survey, each page will use Dependency Injection to get the relevant part of the state.&lt;/p&gt;
&lt;h3&gt;
  
  
  Demographics page:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7h949peedth3oy3dknvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7h949peedth3oy3dknvg.png" alt="Demographics Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;State defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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; }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Injected as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    [Inject]
    private Models.Demographic Model { get; set; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Software Types page:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0ekt6u2xfltfe8cr1gbb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0ekt6u2xfltfe8cr1gbb.png" alt="Software Types Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;State defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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; }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Injected as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    [Inject]
    private Models.SoftwareTypes Model { get; set; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Your Experiences page:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhk2f9c0l82g5h456tqsl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhk2f9c0l82g5h456tqsl.png" alt="Your Experiences Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;State defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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; }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Injected as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    [Inject]
    private Models.Experiences Model { get; set; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  One Change page:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzz98fvinlkylwu1mlpp3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzz98fvinlkylwu1mlpp3.png" alt="One Change Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;State defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace SoftwareSurvey.Models
{
    public class OneChange
    {
        [JsonProperty(PropertyName = "text")]
        public string Text { get; set; }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Injected as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    [Inject]
    private Models.OneChange Model { get; set; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Further Contact page:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb5walvybexmozclsrr6s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fb5walvybexmozclsrr6s.png" alt="Further Contact Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;State defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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&amp;lt;ValidationResult&amp;gt; Validate(ValidationContext validationContext)
        {
            return IsEmailValid ? null : new ValidationResult[] { new ValidationResult("Please provided email", new string[] { "Email" }) };
        }

        private bool IsEmailValid =&amp;gt; !RequiresEmailAddress || !string.IsNullOrEmpty(Email);
        private bool RequiresEmailAddress =&amp;gt; SurveyResults || FollowUpQuestions || FurtherSurveys;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Injected as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    [Inject]
    private Models.Contact Model { get; set; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Saving the results
&lt;/h2&gt;

&lt;p&gt;On hitting the final page, the application saves the full state object for the user to Cosmos DB - as discussed in &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-6-azure-cosmos-db" rel="noopener noreferrer"&gt;part 6&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The survey is then complete for the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supporting cast
&lt;/h2&gt;

&lt;p&gt;While not directly related to the application itself, its obvious right to draw attention to the supporting functions.&lt;/p&gt;

&lt;p&gt;The following allowed me work quickly by providing a safety net (mainly to avoid regression):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bUnit for Unit testing - see &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-8-bunit" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;End to End testing - see &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-9-end-to-end-tests" rel="noopener noreferrer"&gt;Part 9&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Continous Integration, Delivery and Deployment - see &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-11-azure-devops" rel="noopener noreferrer"&gt;Part 11&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  And that's it
&lt;/h2&gt;

&lt;p&gt;The full source code (including tests, Bicep/ ARM templates and Azure DevOps pipeline YAML) can be found in &lt;a href="https://github.com/Red-Folder/software-survey" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>csharp</category>
      <category>azure</category>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 11 - Azure DevOps</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Mon, 14 Dec 2020 13:50:12 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-11-azure-devops-ce6</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-11-azure-devops-ce6</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it as a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Earlier articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state"&gt;Part 2 - Better State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components"&gt;Part 3 - Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode"&gt;Part 4 - Render Mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-5-client-ip"&gt;Part 5 - Client IP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-6-azure-cosmos-db"&gt;Part 6 - Azure Cosmos DB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-7-azure-signalr-service"&gt;Part 7 - Azure SignalR Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-8-bunit"&gt;Part 8 - bUnit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-9-end-to-end-tests"&gt;Part 9 - End to End Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-10-bicep"&gt;Part 10 - Bicep&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I generally use Azure DevOps for my personal projects.  Azure DevOps is described as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Azure DevOps provides developer services to support teams to plan work, collaborate on code development, and build and deploy applications." &lt;a href="https://docs.microsoft.com/en-gb/azure/devops/user-guide/what-is-azure-devops?view=azure-devops"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And for the purposed of Continious Integration, Delivery and Deployment, I use the Pipelines service of Azure DevOps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in a name?
&lt;/h2&gt;

&lt;p&gt;Personally I'd rather that Microsoft marketing hadn't decided to use the name "DevOps" for the collected services.&lt;/p&gt;

&lt;p&gt;I can understand why they chose it - but it only adds to the confusion over what DevOps is.&lt;/p&gt;

&lt;p&gt;DevOps is not a product.  DevOps is not a job role.  DevOps is much wider than that.&lt;/p&gt;

&lt;p&gt;I love the definition of DevOps from Donovan Brown (of Microsoft):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"DevOps is the union of people, process, and products to enable continuous delivery of value to our end users." &lt;a href="https://www.donovanbrown.com/post/what-is-devops"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I actually preferred the services pervious name "Visual Studio Team Services" or "VSTS" - but I can also see that as being rather limiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipelines
&lt;/h2&gt;

&lt;p&gt;The Pipelines service of Azure DevOps provides build and deployment services similar to Circle CI, TeamCity, Jenkins, Octopus Deploy, etc.&lt;/p&gt;

&lt;p&gt;It often seems to be overlooked as a CI/ CD tool - but is actually very good.&lt;/p&gt;

&lt;p&gt;For this project, I defined 3 "pipelines":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build, Test and Deploy&lt;/li&gt;
&lt;li&gt;Production Smoke Test&lt;/li&gt;
&lt;li&gt;E2E Test&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Build, Test and Deploy pipeline
&lt;/h2&gt;

&lt;p&gt;As the name suggests, on commit of any change to Github, the pipeline will build, test and deploy the Blazor Server app to an Azure App Service.&lt;/p&gt;

&lt;p&gt;The pipelines will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restore nuget packages&lt;/li&gt;
&lt;li&gt;Build the Software Survey solution&lt;/li&gt;
&lt;li&gt;Run the Unit Tests (the SoftwareSurvey.UnitTests and SoftwareSurvey.Models.UnitTests projects)&lt;/li&gt;
&lt;li&gt;Publish the Software Survey app&lt;/li&gt;
&lt;li&gt;Deploy to the Software Survey app to Azure App Services&lt;/li&gt;
&lt;li&gt;Copy and Publish the End to End Tests (the SoftwareSurvey.E2ETests project)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is all fairly standard stuff - the YAML for the pipeline can be found &lt;a href="https://github.com/Red-Folder/software-survey/blob/master/azure-pipelines.yml"&gt;here on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Production Smoke Test pipeline
&lt;/h2&gt;

&lt;p&gt;The Production Smoke Test was scheduled to run every hour while the survey was open (the month of September).&lt;/p&gt;

&lt;p&gt;The Production Smoke Test would use the End to End tests Published by the Buid, Test and Deploy pipeline to run them against the production site.&lt;/p&gt;

&lt;p&gt;I was effectively using the pipeline to run a synthetic transaction through the production app on a regular basis - using the standard pipeline notification capability to alert if the test failed.&lt;/p&gt;

&lt;p&gt;The pipeline will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download the E2E Tests (from the Build, Test and Deploy pipeline) to the agent&lt;/li&gt;
&lt;li&gt;Configure the Chrome Web Driver (needed for the End to End tests)&lt;/li&gt;
&lt;li&gt;Run the test&lt;/li&gt;
&lt;li&gt;Publish the final screen of the test - this allowed me to review what the app looked like if the test failed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The YAML for this pipeline can be found &lt;a href="https://github.com/Red-Folder/software-survey/blob/master/azure-pipelines-production-smoke-test.yml"&gt;here on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The E2E Test pipeline
&lt;/h2&gt;

&lt;p&gt;This is the most complicated of the pipelines - and the least used.&lt;/p&gt;

&lt;p&gt;I wanted a full end to end build of the environment, deploy the app, test it and destroy the environment pipeline - and this is it.&lt;/p&gt;

&lt;p&gt;I generally only run this manually - but in a client environment, I would probably look to incorporate into the Build, Test and Deploy.&lt;/p&gt;

&lt;p&gt;Because this is standing up Azure services (Cosmos DB and SignalR), this test does take longer to run (just over 20 minutes).&lt;/p&gt;

&lt;p&gt;The pipeline will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the Azure environment - using ARM template generated by &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-10-bicep"&gt;Bicep that I talked about in part 10&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get the output values from the ARM template - these are used later when running the E2E tests&lt;/li&gt;
&lt;li&gt;Download the app (from Build, Test and Deploy pipeline)&lt;/li&gt;
&lt;li&gt;Deploy the app new environment&lt;/li&gt;
&lt;li&gt;Download the E2E Tests (from the Build, Test and Deploy pipeline) to the agent&lt;/li&gt;
&lt;li&gt;Configure the Chrome Web Driver (needed for the End to End tests)&lt;/li&gt;
&lt;li&gt;Run the test&lt;/li&gt;
&lt;li&gt;Publish the final screen of the test&lt;/li&gt;
&lt;li&gt;Remove the Azure environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The YAML for this pipeline can be found &lt;a href="https://github.com/Red-Folder/software-survey/blob/master/azure-pipelines-end2end-tests.yml"&gt;here on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaway for Blazor Server
&lt;/h2&gt;

&lt;p&gt;In short, when it comes to CI/ CD - there isn't really anything special we need to do for Blazor Server.  It’s just like deploying any ASP.Net application.&lt;/p&gt;

&lt;p&gt;Yes we may need different services to support it (mainly the SignalR), but that is just part of the ARM setup (assuming you deploy from scratch).&lt;/p&gt;

&lt;p&gt;In the next article, I'll talk about pulling all of this together for the final solution.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>azure</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 10 - Bicep</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Mon, 07 Dec 2020 20:49:10 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-10-bicep-2j8n</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-10-bicep-2j8n</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it as a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Earlier articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state"&gt;Part 2 - Better State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components"&gt;Part 3 - Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode"&gt;Part 4 - Render Mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-5-client-ip"&gt;Part 5 - Client IP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-6-azure-cosmos-db"&gt;Part 6 - Azure Cosmos DB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-7-azure-signalr-service"&gt;Part 7 - Azure SignalR Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-8-bunit"&gt;Part 8 - bUnit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-9-end-to-end-tests"&gt;Part 9 - End to End Tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I host my Software Survey site on Azure - not that much of as surprise given the use of both Azure Cosmos DB and Azure SignalR Service.&lt;/p&gt;

&lt;p&gt;And I want to document my setup as Infrastructure as Code (IaC) so that it would repeatable and reliable.&lt;/p&gt;

&lt;p&gt;Normally I would use the Azure ARM template format.&lt;/p&gt;

&lt;p&gt;But given I was already being experimental using Blazor ... why not try something new for the infrastructure as well.&lt;/p&gt;

&lt;p&gt;Enter Bicep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bicep
&lt;/h2&gt;

&lt;p&gt;Bicep is an open-source project from the Microsoft Azure team - who describe it as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Bicep is a Domain Specific Language (DSL) for deploying Azure resources declaratively. It aims to drastically simplify the authoring experience with a cleaner syntax and better support for modularity and code re-use. Bicep is a transparent abstraction over ARM and ARM templates, which means anything that can be done in an ARM Template can be done in bicep" &lt;a href="https://github.com/Azure/bicep"&gt;Bicep Github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They also caveat it with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Note: Bicep is currently an experimental language and we expect to ship breaking changes in future releases. It is not yet recommended for production usage."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And yet for something very experimental, it really does seem to work very well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstractions over cloud templating languages
&lt;/h2&gt;

&lt;p&gt;I've found ARM templates, like AWS CloudFormation templates, to be very verbose - and can be rather cumbersome to work with.&lt;/p&gt;

&lt;p&gt;For AWS work, I've really enjoyed using their &lt;a href="https://github.com/aws/aws-cdk"&gt;Cloud Development Kit&lt;/a&gt; as a much better option.  The AWS CDK allows you to "develop" your infrastructure using a variety of development languages (although TypeScript is probably the best choice).&lt;/p&gt;

&lt;p&gt;For example (taken from &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/hello_world.html"&gt;AWS Documentation&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';

export class HelloCdkStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    new s3.Bucket(this, 'MyFirstBucket', {
      versioned: true
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines a new CloudFormation stack with an S3 bucket in it.&lt;/p&gt;

&lt;p&gt;AWS CDK then will compile this into a standard CloudFormation template.&lt;/p&gt;

&lt;p&gt;And at a high level, Azure Bicep operates in much the same way.&lt;/p&gt;

&lt;p&gt;It allows you to define your infrastructure in a domain specific language (rather than a general purpose programming language like AWS CDK) and then compiles to an ARM template.&lt;/p&gt;

&lt;h2&gt;
  
  
  My infrastructure
&lt;/h2&gt;

&lt;p&gt;Here is the Bicep file for a full infrastructure (&lt;a href="https://github.com/Red-Folder/software-survey/blob/master/Infrastructure/SoftwareSurveyInfrastructure.bicep"&gt;source&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param environment string = 'e2e'

var nameprefix = 'rfc-${environment}-software-survey'

resource signalr 'Microsoft.SignalRService/SignalR@2020-07-01-preview' = {
    name: '${nameprefix}-signalr'
    location: resourceGroup().location
    kind: 'SignalR'
    sku: {
        name: 'Free_F1'
    }
}

resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2020-06-01-preview' = {
    name: '${nameprefix}-db'
    location: resourceGroup().location
    kind: 'GlobalDocumentDB'
    properties: {
        enableFreeTier: true
        databaseAccountOfferType: 'Standard'
        consistencyPolicy: {
            defaultConsistencyLevel: 'Session'
        }
    }
}

resource applicationInsights 'microsoft.insights/components@2018-05-01-preview' = {
    name: '${nameprefix}-application-insights'
    location: resourceGroup().location
    kind: 'web'
    properties: {
        ApplicationType: 'web'
        publicNetworkAccessForIngestion: 'Enabled'
        publicNetworkAccessForQuery: 'Enabled'
    }
}

resource farm 'Microsoft.Web/serverfarms@2018-11-01' = {
    name: '${nameprefix}-app-plan'
    location: resourceGroup().location
    kind: 'linux'
    sku: {
        name: 'F1'
        capacity: 1
    }
}

resource website 'Microsoft.Web/sites@2018-11-01' = {
    name: '${nameprefix}-web-app'
    location: resourceGroup().location
    kind: 'app'
    properties: {
        serverFarmId: farm.id
        siteConfig: {
            appSettings: [
                {
                    name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
                    value: reference(applicationInsights.id).InstrumentationKey
                }
                {
                    name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
                    value: reference(applicationInsights.id).ConnectionString
                }
                {
                    name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
                    value: '~2'
                }
                {
                    name: 'Azure__SignalR__ConnectionString'
                    value: listKeys(signalr.id, '2020-07-01-preview').primaryConnectionString
                }
                {
                    name: 'Persistance:CosmosDbEndpoint'
                    value: cosmos.properties.documentEndpoint
                }
                {
                    name: 'Persistance:CosmosDbPrimaryKey'
                    value: listKeys(cosmos.id, '2020-06-01-preview').primaryMasterKey
                }
                {
                    name: 'XDT_MicrosoftApplicationInsights_Mode'
                    value: 'default'
                }
            ]
        }
    }
}

output appServiceName string = website.name
output appServiceUrl string = 'http://${website.name}.azurewebsites.net/'
output cosmosEndpoint string = cosmos.properties.documentEndpoint
output cosmosPrimaryKey string = listKeys(cosmos.id, '2020-06-01-preview').primaryMasterKey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In it I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a SignalR Service - free tier&lt;/li&gt;
&lt;li&gt;Create a Cosmos DB - free tier&lt;/li&gt;
&lt;li&gt;Create an Application Insights&lt;/li&gt;
&lt;li&gt;Create an App Service farm - free tier&lt;/li&gt;
&lt;li&gt;Create an App Service - passing in all the app settings needed by the Survey app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These 92 lines then get compiled into 129 line ARM template - which can be seen &lt;a href="https://github.com/Red-Folder/software-survey/blob/master/Infrastructure/SoftwareSurveyInfrastructure.json"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;p&gt;Mainly, I think that Bicep is considerably more readable than native ARM template.&lt;/p&gt;

&lt;p&gt;Bicep is also handling an amount of default values for you.&lt;/p&gt;

&lt;p&gt;It is however early days for the tool.  As of writing, they have only just moved to Bicep 0.2 - and I've yet to try (there could potentially be breaking changes).&lt;/p&gt;

&lt;p&gt;I currently wouldn't recommend for a client's production use - unless they had an appetite for the bleeding edge.&lt;/p&gt;

&lt;p&gt;For my purpose however, given it still generated a valid ARM template, I'm more than happy to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on Terraform
&lt;/h2&gt;

&lt;p&gt;If you have much experience with IaC, you maybe reading this and asking why I didn't go with Terraform.&lt;/p&gt;

&lt;p&gt;To be blunt, I've never really found the opportunity to use Terraform.  While I've studied it, my clients are generally working with native template formats.&lt;/p&gt;

&lt;p&gt;I would see no problems using Terraform to create the infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outputs
&lt;/h2&gt;

&lt;p&gt;You may have noted that I output a number of values at the end of my script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output appServiceName string = website.name
output appServiceUrl string = 'http://${website.name}.azurewebsites.net/'
output cosmosEndpoint string = cosmos.properties.documentEndpoint
output cosmosPrimaryKey string = listKeys(cosmos.id, '2020-06-01-preview').primaryMasterKey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those outputs are passed to my End to End tests (introduced in the &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-9-end-to-end-tests"&gt;last article&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Thank you for reading.  See you in the next article.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>csharp</category>
      <category>azure</category>
      <category>bicep</category>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 9 - End to End tests</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Mon, 30 Nov 2020 13:44:19 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-9-end-to-end-tests-1jn0</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-9-end-to-end-tests-1jn0</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it as a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Earlier articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state"&gt;Part 2 - Better State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components"&gt;Part 3 - Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode"&gt;Part 4 - Render Mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-5-client-ip"&gt;Part 5 - Client IP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-6-azure-cosmos-db"&gt;Part 6 - Azure Cosmos DB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-7-azure-signalr-service"&gt;Part 7 - Azure SignalR Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-8-bunit"&gt;Part 8 - bUnit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;While in the &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-8-bunit"&gt;last artice&lt;/a&gt; I talked about unit testing with bUnit - in this article I want to talk about end to end testing.&lt;/p&gt;

&lt;p&gt;I find a lot of value in automating at least the happy path test for user journeys.&lt;/p&gt;

&lt;p&gt;Firstly it save me running through the journey manually every time I make a code change - great for spotting regression.  And secondly, if possible, I like to use the same test for synthetic testing of production systems.  Using the test to run through the production journey on a regular interval as a way of highlighting any problems.&lt;/p&gt;

&lt;p&gt;Thus, for my Software Survey, I wanted to implement a simple happy path test that exercised every page of the survey and then validated the survey results has been correctly persisted to Azure Cosmos DB.&lt;/p&gt;

&lt;h2&gt;
  
  
  To SpecFlow or not to SpecFlow
&lt;/h2&gt;

&lt;p&gt;Normally I'd use SpecFlow to provide the test in its Gerkin style language.&lt;/p&gt;

&lt;p&gt;In this instance, as it is only me working on the project, I've just used an xUnit test.  I may add SpecFlow over the top at some point - but all it would add is increased readability - which has a lot of value when taking a client through the test - but not so much when it’s just for myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nothing special
&lt;/h2&gt;

&lt;p&gt;If I'm honest, there is nothing particularly special in creating the end to end test.&lt;/p&gt;

&lt;p&gt;The test is written in xUnit and uses Selenium Web Driver to interact with the website.&lt;/p&gt;

&lt;p&gt;Which is quite a positive.&lt;/p&gt;

&lt;p&gt;A Blazor Server is producing a HTML, CSS &amp;amp; JavaScript website, there appears to be no reason that the Selenium Web Driver can't be used to navigate it.  I would also suspect that Cypress should work - but I don't have as much experience with it, so stuck with the known combination of xUnit &amp;amp; the Selenium Web Driver.&lt;/p&gt;

&lt;p&gt;So, the test should be easy right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Here be dragons ...
&lt;/h2&gt;

&lt;p&gt;I have to admit that the end to end tests possibly cased me more problems than anything else with Blazor.&lt;/p&gt;

&lt;p&gt;The initial version of the test seemed to work fine.  &lt;/p&gt;

&lt;p&gt;I could run the test locally - it worked.&lt;/p&gt;

&lt;p&gt;I could run the test as part of Continuous Deployment - it worked.&lt;/p&gt;

&lt;p&gt;I could setup to run on an hourly schedule against production for synthetic testing - it sometimes worked.&lt;/p&gt;

&lt;p&gt;And this “sometimes” became a huge frustration to me.  It could work for hours - if not days, then the test would start reporting failures.  I would check manually, and it would look to be working fine.&lt;/p&gt;

&lt;p&gt;If you look at the &lt;a href="https://github.com/Red-Folder/software-survey/blob/master/SoftwareSurvey.E2ETests/End2End.cs"&gt;actual test class&lt;/a&gt; you'll see I've added various helper methods when trying to access page elements.&lt;/p&gt;

&lt;p&gt;Mainly the helpers are set to retry if the application is not in the expected state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        private async Task RetryActivity(By by, Func&amp;lt;IWebElement, bool&amp;gt; activity, string activityDescription)
        {
            _testOutputHelper.WriteLine($"Attempting '{activityDescription}'");

            // Allow time for Blazor to do its thing (fail after 60 attempt - 30 seconds)
            for (int i = 0; i &amp;lt; 60; i++)
            {
                await Task.Delay(500);

                try
                {
                    var element = _driver.FindElement(by);

                    if (activity(element)) return;

                    _testOutputHelper.WriteLine("Activity failed to return true");
                }
                catch (NoSuchElementException)
                {
                    _testOutputHelper.WriteLine("NoSuchElementException received");
                    continue;
                }
                catch (StaleElementReferenceException)
                {
                    _testOutputHelper.WriteLine("StaleElementReferenceException received");
                    continue;
                }
                catch (Exception ex)
                {
                    _testOutputHelper.WriteLine($"Exception {ex.GetType().Name} encountered - {ex.Message}");
                    throw ex;
                }
            }

            _testOutputHelper.WriteLine("Maximum retries reached");
            throw new Exception($"Maximum retries reached while attempting '{activityDescription}'");
        }

                private async Task WaitForElement(By by)
        {
            // Allow time for Blazor to do its thing (fail after 60 attempt - 30 seconds)
            for (int i = 0; i &amp;lt; 60; i++)
            {
                await Task.Delay(500);

                try
                {
                    _driver.FindElement(by);
                }
                catch (NoSuchElementException)
                {
                    continue;
                }
                catch (StaleElementReferenceException)
                {
                    continue;
                }
                return;
            }
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see ... lots of waits ... lots or retries.&lt;/p&gt;

&lt;p&gt;And this was because Selenium was struggling to find elements reliably.&lt;/p&gt;

&lt;p&gt;But, as discussed in &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode"&gt;part 4&lt;/a&gt;, I was looking in the wrong place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render Mode
&lt;/h2&gt;

&lt;p&gt;As I talk about in &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode"&gt;part 4&lt;/a&gt;, the &lt;code&gt;ServerPreRendered&lt;/code&gt; mode will initially serve a static copy of the page and then hydrate it once the Blazor (including SignalR) is fully available.&lt;/p&gt;

&lt;p&gt;And this was at the heart of my problem.&lt;/p&gt;

&lt;p&gt;For some reason the hourly tests seemed to be more suseptable to it than any other method of running the test - possible because the survey site was colder to start.&lt;/p&gt;

&lt;p&gt;Once I added the &lt;code&gt;PreRenderLoadingMessage&lt;/code&gt; to the survey site, the end to end tests consistently passed.&lt;/p&gt;

&lt;p&gt;And getting to that gem cost me a considerable amount of time - and sanity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tidy up
&lt;/h2&gt;

&lt;p&gt;I should be able to remove a fair amount of my retry logic from the end to end test now that I've found the true source of the problem.&lt;/p&gt;

&lt;p&gt;To be honest though, I don't see any benefit in doing that.  It is all working, so I'm inclined to leave it as it is.&lt;/p&gt;

&lt;p&gt;And that's it for the end to end tests.&lt;/p&gt;

&lt;p&gt;For something that was actually so easy to setup and operate, the overhead of understanding the &lt;code&gt;ServerPreRendered&lt;/code&gt; mode really caught me out.&lt;/p&gt;

&lt;p&gt;But I know it for next time.  This is why we try new things.&lt;/p&gt;

&lt;p&gt;Thank you for reading.  See you in the next article where I introduce Bicep – a DSL for configuring Azure.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 8 - bUnit</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Mon, 23 Nov 2020 13:52:56 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-8-bunit-1gh6</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-8-bunit-1gh6</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it as a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Earlier articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state"&gt;Part 2 - Better State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components"&gt;Part 3 - Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode"&gt;Part 4 - Render Mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-5-client-ip"&gt;Part 5 - Client IP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-6-azure-cosmos-db"&gt;Part 6 - Azure Cosmos DB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-7-azure-signalr-service"&gt;Part 7 - Azure SignalR Service&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;In modern development it would be difficult for any new technology to gain traction without an ability to unit test.&lt;/p&gt;

&lt;p&gt;For Blazor that is bUnit.&lt;/p&gt;

&lt;p&gt;I have to admit I was a little surprised to find that Microsoft didn't have its own tools for Blazor - rather they actually promote a 3rd party open-source tool:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"There's no official Microsoft testing framework for Blazor, but the community-driven project bUnit provides a convenient way to unit test Blazor components." &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/test?view=aspnetcore-5.0#test-components-with-bunit"&gt;Microsoft Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While I really like that Microsoft is embracing the open-source community, it does feel a little odd that they didn't have their own offering.&lt;/p&gt;

&lt;p&gt;That being said, bUnit seems an exceptionally well developed and maintained tool.&lt;/p&gt;

&lt;p&gt;I'd certainly recommend reading through the &lt;a href="https://bunit.egilhansen.com/docs/getting-started/index.html"&gt;bUnit documentation site&lt;/a&gt; before making a start.  The documentation is very good and quite quick to run through.&lt;/p&gt;

&lt;h2&gt;
  
  
  A summary
&lt;/h2&gt;

&lt;p&gt;So bUnit works with xUnit, NUnit or MSTest - providing a number of helpers tools for Blazor component testing.&lt;/p&gt;

&lt;p&gt;I believe that bUnit favours xUnit - which is fine for me as xUnit is my go-to unit testing framework for dotnet.&lt;/p&gt;

&lt;p&gt;bUnit allows you to write your tests in 3 ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;C# style&lt;/li&gt;
&lt;li&gt;Razor Fixture&lt;/li&gt;
&lt;li&gt;Razor Snapshot&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  C# Style
&lt;/h2&gt;

&lt;p&gt;This is a fairly traditional style which should be fairly recognisable to anyone with xUnit experience (below taken from the &lt;a href="https://bunit.egilhansen.com/docs/getting-started/writing-csharp-tests.html?tabs=xunit"&gt;bUnit Docs&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Xunit;
using Bunit;

namespace Bunit.Docs.Samples
{
  public class HelloWorldTest
  {
    [Fact]
    public void HelloWorldComponentRendersCorrectly()
    {
      // Arrange
      using var ctx = new TestContext();

      // Act
      var cut = ctx.RenderComponent&amp;lt;HelloWorld&amp;gt;();

      // Assert
      cut.MarkupMatches("&amp;lt;h1&amp;gt;Hello world from Blazor&amp;lt;/h1&amp;gt;");
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key here is the TestContext - it is part of bUnit and provides all the magic needed to interact with the Blazor components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Razor Fixture
&lt;/h2&gt;

&lt;p&gt;These tests are written differently, they are written in the Razor syntax.  The Fixture testing then allows you to make similar assertions that you can within C# style (sample taken from the &lt;a href="https://bunit.egilhansen.com/docs/getting-started/writing-razor-tests.html"&gt;bUnit Documentation Page&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@inherits TestComponentBase

&amp;lt;Fixture Test="HelloWorldComponentRendersCorrectly"&amp;gt;
  &amp;lt;ComponentUnderTest&amp;gt;
    &amp;lt;HelloWorld /&amp;gt;
  &amp;lt;/ComponentUnderTest&amp;gt;

  @code
  {
    void HelloWorldComponentRendersCorrectly(Fixture fixture)
    {
      // Act
      var cut = fixture.GetComponentUnderTest&amp;lt;HelloWorld&amp;gt;();

      // Assert
      cut.MarkupMatches("&amp;lt;h1&amp;gt;Hello world from Blazor&amp;lt;/h1&amp;gt;");
    }
  }
&amp;lt;/Fixture&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main difference here is that setup in done through Razor syntax.&lt;/p&gt;

&lt;p&gt;Personally I favour the C# style - and at this time, I've not found a reason to use the Razor Fixture style.  I suspect that it is largely a matter of personal taste.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blazor Snapshot
&lt;/h2&gt;

&lt;p&gt;These tests are also writen using Razor syntax - but rather than normal assertions, you specify the expected output (sample take from the &lt;a href="https://bunit.egilhansen.com/docs/getting-started/writing-razor-tests.html"&gt;bUnit Documentation Page&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@inherits TestComponentBase

&amp;lt;SnapshotTest Description="HelloWorld component renders correctly"&amp;gt;
  &amp;lt;TestInput&amp;gt;
    &amp;lt;HelloWorld /&amp;gt;
  &amp;lt;/TestInput&amp;gt;
  &amp;lt;ExpectedOutput&amp;gt;
    &amp;lt;h1&amp;gt;Hello world from Blazor&amp;lt;/h1&amp;gt;
  &amp;lt;/ExpectedOutput&amp;gt;
&amp;lt;/SnapshotTest&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now this works very well for regression style testing.&lt;/p&gt;

&lt;p&gt;I've used this across a number of my components to ensure that I pick up any accidental changes.  For this I think that Snapshot testing works very well.&lt;/p&gt;

&lt;p&gt;This has been exceptionally useful for testing my &lt;code&gt;OptionGrid&lt;/code&gt; component.  It’s probably my most complex component involving an amount of reflection and dynamic generation.  Ultimately it provides the grid during the survey:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QY2t7BwC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3smzd7s44z3ftpl2w88n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QY2t7BwC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3smzd7s44z3ftpl2w88n.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Technically my Snapshot test is more of an integration test - as it uses multiple components, but for regression purposes it’s great.  In the test, I define a sample model to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@inherits TestComponentBase

&amp;lt;SnapshotTest Description="Options Grid renders correctly"&amp;gt;
    &amp;lt;TestInput&amp;gt;
        &amp;lt;CascadingValue Value="EditContext"&amp;gt;
            &amp;lt;OptionGrid NotApplicableLabel="N/A" TValue="Model" /&amp;gt;
        &amp;lt;/CascadingValue&amp;gt;
    &amp;lt;/TestInput&amp;gt;
    &amp;lt;ExpectedOutput&amp;gt;
        &amp;lt;div class="option-grid"&amp;gt;
            &amp;lt;span class="header"&amp;gt;&amp;lt;/span&amp;gt;
            &amp;lt;span class="header"&amp;gt;N/A&amp;lt;/span&amp;gt;
            &amp;lt;span class="header"&amp;gt;&amp;lt;/span&amp;gt;
            &amp;lt;span class="header"&amp;gt;(Not) 1&amp;lt;/span&amp;gt;
            &amp;lt;span class="header"&amp;gt;2&amp;lt;/span&amp;gt;
            &amp;lt;span class="header"&amp;gt;3&amp;lt;/span&amp;gt;
            &amp;lt;span class="header"&amp;gt;4&amp;lt;/span&amp;gt;
            &amp;lt;span class="header"&amp;gt;(Very) 5&amp;lt;/span&amp;gt;
            &amp;lt;hr&amp;gt;
            &amp;lt;span&amp;gt;Value 1 Name&amp;lt;/span&amp;gt;
            &amp;lt;input type="radio" name="value-1" checked=""&amp;gt;
            &amp;lt;span&amp;gt;&amp;lt;/span&amp;gt;
            &amp;lt;input type="radio" name="value-1"&amp;gt;
            &amp;lt;input type="radio" name="value-1"&amp;gt;
            &amp;lt;input type="radio" name="value-1"&amp;gt;
            &amp;lt;input type="radio" name="value-1"&amp;gt;
            &amp;lt;input type="radio" name="value-1"&amp;gt;
            &amp;lt;i class="option-grid-row-description"&amp;gt;Value 1 Description&amp;lt;/i&amp;gt;
            &amp;lt;hr&amp;gt;
            &amp;lt;span&amp;gt;Value 2 Name&amp;lt;/span&amp;gt;
            &amp;lt;input type="radio" name="value-2" checked=""&amp;gt;
            &amp;lt;span&amp;gt;&amp;lt;/span&amp;gt;
            &amp;lt;input type="radio" name="value-2"&amp;gt;
            &amp;lt;input type="radio" name="value-2"&amp;gt;
            &amp;lt;input type="radio" name="value-2"&amp;gt;
            &amp;lt;input type="radio" name="value-2"&amp;gt;
            &amp;lt;input type="radio" name="value-2"&amp;gt;
            &amp;lt;i class="option-grid-row-description"&amp;gt;Value 2 Description&amp;lt;/i&amp;gt;
            &amp;lt;hr&amp;gt;
            &amp;lt;span&amp;gt;Value 3 Name&amp;lt;/span&amp;gt;
            &amp;lt;input type="radio" name="value-3" checked=""&amp;gt;
            &amp;lt;span&amp;gt;&amp;lt;/span&amp;gt;
            &amp;lt;input type="radio" name="value-3"&amp;gt;
            &amp;lt;input type="radio" name="value-3"&amp;gt;
            &amp;lt;input type="radio" name="value-3"&amp;gt;
            &amp;lt;input type="radio" name="value-3"&amp;gt;
            &amp;lt;input type="radio" name="value-3"&amp;gt;
            &amp;lt;i class="option-grid-row-description"&amp;gt;Value 3 Description&amp;lt;/i&amp;gt;
            &amp;lt;hr&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/ExpectedOutput&amp;gt;
&amp;lt;/SnapshotTest&amp;gt;

@code
{
    public EditContext EditContext = new EditContext(new Model());

    public class Model
    {
        [System.ComponentModel.DisplayName("Value 1 Name")]
        [System.ComponentModel.Description("Value 1 Description")]
        [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Value 1 is required")]
        public int Value1 { get; set; }

        [System.ComponentModel.DisplayName("Value 2 Name")]
        [System.ComponentModel.Description("Value 2 Description")]
        [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Value 2 is required")]
        public int Value2 { get; set; }

        [System.ComponentModel.DisplayName("Value 3 Name")]
        [System.ComponentModel.Description("Value 3 Description")]
        [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Value 3 is required")]
        public int Value3 { get; set; }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that use of the EditContext round the sample model.  This is provided into the component via the &lt;code&gt;&amp;lt;CascadingValue/&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I do have other tests that around the sub-components of this component, but having this Snapshot test allows for confidence on regression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unit Testing with the NavigationManager
&lt;/h2&gt;

&lt;p&gt;I'd have liked to think that Microsoft would have learnt by now to always use interfaces rather than concrete objects - and in a lot of Blazor that is true.&lt;/p&gt;

&lt;p&gt;Unfortunately, not with the NavigationManager.&lt;/p&gt;

&lt;p&gt;The NavigationManager is part of Blazor and used to change "page" within the application.&lt;/p&gt;

&lt;p&gt;So when it comes to unit testing a component that uses the NavigationManager we have a problem.&lt;/p&gt;

&lt;p&gt;Luckily, due to the necessity of having to deal with a history of Microsoft concrete implementations, it’s a fairly simple approach to resolve.&lt;/p&gt;

&lt;p&gt;I've simple created an interface for INavigationManager, then a simple wrapper that supports the interface and calls the concrete NavigationManager under the hood, then set up my component to use the INavigationManager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace SoftwareSurvey.Wrappers
{
    public interface INavigationManager
    {
        string Uri { get; }
       void NavigateTo(string uri);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Microsoft.AspNetCore.Components;

namespace SoftwareSurvey.Wrappers
{
    public class NavigationManagerWrapper : INavigationManager
    {
        private readonly NavigationManager NavigationManager;

        public NavigationManagerWrapper(NavigationManager navigationManager)
        {
            NavigationManager = navigationManager;
        }

        public void NavigateTo(string uri) =&amp;gt; NavigationManager.NavigateTo(uri);

        public string Uri =&amp;gt; NavigationManager.Uri;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="form-group navigation-buttons"&amp;gt;
    @if (HasPrevious)
    {
        &amp;lt;button type="button" class="btn previous-navigation-button" @onclick="HandlePrevious"&amp;gt;Previous&amp;lt;/button&amp;gt;
    }
    @if (HasNext)
    {
        &amp;lt;button type="submit" class="btn next-navigation-button" @onclick="HandleNext"&amp;gt;Next&amp;lt;/button&amp;gt;
    }

    &amp;lt;div class="clear"&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

@code {
    [Inject]
    private SoftwareSurvey.Wrappers.INavigationManager NavigationManager { get; set; }

    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then when it comes to testing, I can use the Moq library to create &lt;code&gt;Mock&amp;lt;INavigationManager&amp;gt;&lt;/code&gt; and test with ease.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unit Testing with StateHasChanged()
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode"&gt;part 4&lt;/a&gt;, I talked about my &lt;code&gt;&amp;lt;PreRenderLoadingMessage /&amp;gt;&lt;/code&gt; component; it showed a loading message while the app was starting up (establishing SignalR, etc), then would show the app content when everything was ready.&lt;/p&gt;

&lt;p&gt;Unit testing this became a problem due to the use of &lt;code&gt;StateHasChanged()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ideally I wanted two tests - one to validate the loading message was showing BEFORE the app was ready and one to validate the content was showing AFTER the app was ready.&lt;/p&gt;

&lt;p&gt;The problem with the StateHasChange() was that it was forcing the second render before I had a chance to validate the BEFORE state.&lt;/p&gt;

&lt;p&gt;So to test, I did something that I generally advise against - added unit testing specific code to the component in question.&lt;/p&gt;

&lt;p&gt;To the component I added an extra parameter (&lt;code&gt;UnitTestMode&lt;/code&gt;) which would control if the &lt;code&gt;StateHasChanged()&lt;/code&gt; would trigger or not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@if (HasRendered)
{
    @ChildContent
}
else
{
    &amp;lt;LoadingSpinner Title="Loading ..." /&amp;gt;
}


@code {
    [Parameter]
    public bool UnitTestMode { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private bool HasRendered { get; set; }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            HasRendered = true;

            if (!UnitTestMode)
            {
                StateHasChanged();
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in my test, I had more control over the renders.  By setting the &lt;code&gt;UnitTestMode&lt;/code&gt; to true, I could specify exactly when the second render occurred:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        [Fact]
        public void ShowsLoadingMessage_OnFirstRender()
        {
            using var context = new TestContext();
            context.Services.AddSingleton(new Mock&amp;lt;IEventLoggingService&amp;gt;().Object);

            IRenderedComponent&amp;lt;PreRenderLoadingMessage&amp;gt; cut = context.RenderComponent&amp;lt;PreRenderLoadingMessage&amp;gt;(parameters =&amp;gt;
                parameters
                    .Add(p =&amp;gt; p.UnitTestMode, true)
                    .AddChildContent("&amp;lt;div&amp;gt;Hello World&amp;lt;/div&amp;gt;")
            );

            Assert.Contains("Loading ...", cut.Markup);
        }

        [Fact]
        public void ShowsChildContent_OnSecondRender()
        {
            using var context = new TestContext();
            context.Services.AddSingleton(new Mock&amp;lt;IEventLoggingService&amp;gt;().Object);

            IRenderedComponent&amp;lt;PreRenderLoadingMessage&amp;gt; cut = context.RenderComponent&amp;lt;PreRenderLoadingMessage&amp;gt;(parameters =&amp;gt; 
                parameters
                    .Add(p =&amp;gt; p.UnitTestMode, true)
                    .AddChildContent("&amp;lt;div&amp;gt;Hello World&amp;lt;/div&amp;gt;")
            );

            Assert.DoesNotContain("&amp;lt;div&amp;gt;Hello World&amp;lt;/div&amp;gt;", cut.Markup);

            cut.Render();
            Assert.Contains("&amp;lt;div&amp;gt;Hello World&amp;lt;/div&amp;gt;", cut.Markup);
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then just to provide a level of confort, I also test for when the UnitTestMode is off - which we know will automatically re-render due to the &lt;code&gt;StateHasChanged()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        [Fact]
        public void ShowsChildContent_IfInDefaultMode()
        {
            using var context = new TestContext();
            context.Services.AddSingleton(new Mock&amp;lt;IEventLoggingService&amp;gt;().Object);

            IRenderedComponent&amp;lt;PreRenderLoadingMessage&amp;gt; cut = context.RenderComponent&amp;lt;PreRenderLoadingMessage&amp;gt;(parameters =&amp;gt;
                parameters.AddChildContent("&amp;lt;div&amp;gt;Hello World&amp;lt;/div&amp;gt;")
            );

            Assert.Contains("&amp;lt;div&amp;gt;Hello World&amp;lt;/div&amp;gt;", cut.Markup);
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I say, I would normally advise against adding this sort of change directly to a component - specifically for the purposes of testing - but in this case I believe it is valid and safe enough.&lt;/p&gt;

&lt;p&gt;I believe there are plans to allow for an async version of the &lt;code&gt;StateHasChanged()&lt;/code&gt; - partly for this, and partly to batch the rendering of multiple changes (for performance) - so I may change to that once it is available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other examples
&lt;/h2&gt;

&lt;p&gt;I've not gone into details of how to use bUnit as I believe the &lt;a href="https://bunit.egilhansen.com/index.html"&gt;documentation site&lt;/a&gt; does an excellent job of it.&lt;/p&gt;

&lt;p&gt;If however, you want to see more example of how I've used, then look at the &lt;a href="https://github.com/Red-Folder/software-survey/tree/master/SoftwareSurvey.UnitTests/Components"&gt;github for the Software Survey UnitTests project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next time
&lt;/h2&gt;

&lt;p&gt;Next time I'm going to take a look at end to end testing using Selenium and xUnit.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 7 - Azure SignalR Service</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Mon, 16 Nov 2020 13:52:47 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-7-azure-signalr-service-mcg</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-7-azure-signalr-service-mcg</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it as a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Earlier articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state"&gt;Part 2 - Better State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components"&gt;Part 3 - Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode"&gt;Part 4 - Render Mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-5-client-ip"&gt;Part 5 - Client IP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-6-azure-cosmos-db"&gt;Part 6 - Azure Cosmos DB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;SignalR is at the heart of Blazor server.  It forms the realtime connection so that the server can send updates to the browser.&lt;/p&gt;

&lt;p&gt;SignalR is describes as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"SignalR is a free and open-source software library for Microsoft ASP.NET that allows server code to send asynchronous notifications to client-side web applications. The library includes server-side and client-side JavaScript components."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But it is almost an invisible part of Blazor - it just happens.&lt;/p&gt;

&lt;p&gt;For production use however, you really want to be using an Azure SignalR Service as it takes a lot of the strain off the server and allows for greater scaling.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We recommend using the Azure SignalR Service for Blazor Server apps. The service allows for scaling up a Blazor Server app to a large number of concurrent SignalR connections. In addition, the SignalR service's global reach and high-performance data centers significantly aid in reducing latency due to geography." &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/host-and-deploy/server?view=aspnetcore-3.1"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Creating an Azure SignalR Service
&lt;/h2&gt;

&lt;p&gt;Again, like Azure Cosmos DB, there is a free tier.&lt;/p&gt;

&lt;p&gt;Its intended for development &amp;amp; test, but given the expected usage of my survey, it was to be sufficient for my use.&lt;/p&gt;

&lt;p&gt;Creating via the Azure Portal is easy and provides you with the relevant credentials.  (In a future article, I'll show how to create with Azure Bicep - a Domain-Specific-Language for producing ARM templates).&lt;/p&gt;

&lt;p&gt;You'll need the connection string from the service.  &lt;/p&gt;

&lt;p&gt;For production, I run the Blazor Server App as an Azure App Service, thus set the environment variable (Configuration -&amp;gt; Application Setting) &lt;code&gt;Azure__SignalR__ConnectionString&lt;/code&gt; to the connection string (more on this in the Azure Bicep article).&lt;/p&gt;

&lt;p&gt;For development, I set the User Secret of &lt;code&gt;Azure:SignalR:ConnectionString&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The important thing is to make sure that the connection string is &lt;em&gt;NOT&lt;/em&gt; stored in source control (especially as I'm using a public Github repository).&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Blazor app to use the Azure SignalR Service
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;Startup.cs&lt;/code&gt; add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        public void ConfigureServices(IServiceCollection services)
        {
            ...

            services.AddSignalR().AddAzureSignalR();

            ...
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And within &lt;code&gt;appsettings.json&lt;/code&gt;, then add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "Azure": {
    "SignalR": {
      "Enabled": "true",
      "ServerStickyMode": "Required"
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scaffolding
&lt;/h2&gt;

&lt;p&gt;A lot of this setup is done for you if you scaffold the SignalR Service connection - either when you create the Blazor Server app at the start or by the "Connected Services".&lt;/p&gt;

&lt;p&gt;Once all of this is done, it should just all work.&lt;/p&gt;

&lt;p&gt;Its almost too easy.  And so far I've found no problems with it.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>csharp</category>
      <category>azure</category>
      <category>signalr</category>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 6 - Azure CosmosDB</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Mon, 09 Nov 2020 13:50:03 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-6-azure-cosmosdb-1o89</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-6-azure-cosmosdb-1o89</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it as a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Earlier articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state"&gt;Part 2 - Better State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components"&gt;Part 3 - Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode"&gt;Part 4 - Render Mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-5-client-ip"&gt;Part 5 - Client IP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;When it came to storing the survey results, I wanted something simple.&lt;/p&gt;

&lt;p&gt;But in the same respect I wanted something cheap - and that would allow me report on the collected data.&lt;/p&gt;

&lt;p&gt;About this time, Azure launched a &lt;a href="https://docs.microsoft.com/en-us/azure/cosmos-db/optimize-dev-test#azure-cosmos-db-free-tier"&gt;free tier of Cosmos DB&lt;/a&gt; (or at least about this time I noticed it).&lt;/p&gt;

&lt;p&gt;I'd used Cosmos DB in its easlier incarnation - DocumentDB - but hadn't used Cosmos DB itself due to the cost.&lt;/p&gt;

&lt;p&gt;The free tier allows for a reasonable amount of data - more than enough for the survey, so I decided to trial it for the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Development
&lt;/h2&gt;

&lt;p&gt;Firstly, for development, its good to know that Microsoft provide a local Cosmos DB emulator.&lt;/p&gt;

&lt;p&gt;This can be downloaded from &lt;a href="https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator?tabs=cli%2Cssl-netstd21"&gt;here&lt;/a&gt; and is a good way to get started with Cosmos DB without the danger of incurring any Azure charges.&lt;/p&gt;

&lt;h2&gt;
  
  
  IPersistanceManager
&lt;/h2&gt;

&lt;p&gt;So I could inject Cosmos DB in, I wanted to hide the persistance being an interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public interface IPersistanceManager
    {
        Task&amp;lt;bool&amp;gt; Persist(SurveyResponse surveyResponse);
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which of course allowed me to mock out the PersistanceManager for integration testing.  So for example;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public class FakePersistanceManager : IPersistanceManager
    {
        public async Task&amp;lt;bool&amp;gt; Persist(SurveyResponse surveyResponse)
        {
            await Task.Run(() =&amp;gt; Thread.Sleep(5000));
            return true;
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CosmosDbPersistanceManager.cs
&lt;/h2&gt;

&lt;p&gt;The actual concrete implementation for Azure Cosmos DB was fairly simple.  On &lt;code&gt;Persist&lt;/code&gt;, it would create a &lt;code&gt;CosmosClient&lt;/code&gt; (using credentials from config), create the database &amp;amp; container if either didn't exist, and then save passed in model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Logging;
using SoftwareSurvey.Models;
using System;
using System.Net;
using System.Threading.Tasks;

namespace SoftwareSurvey.Services
{
    public class CosmosDbPersistanceManager : IPersistanceManager
    {
        private const string APPLICATION_NAME = "SoftwareSurvery";
        private const string DATABASE_NAME = "SoftwareSurvey";
        private const string CONTAINER_NAME = "Responses";

        private readonly PersistanceConfiguration _configuration;
        private readonly ILogger _logger;

        public CosmosDbPersistanceManager(PersistanceConfiguration configuration, ILogger&amp;lt;CosmosDbPersistanceManager&amp;gt; logger)
        {
            _configuration = configuration;
            _logger = logger;
        }

        public async Task&amp;lt;bool&amp;gt; Persist(SurveyResponse surveyResponse)
        {
            try
            {
                var client = new CosmosClient(_configuration.CosmosDbEndpoint,
                                              _configuration.CosmosDbPrimaryKey,
                                              new CosmosClientOptions() { ApplicationName = APPLICATION_NAME });

                Database database = await client.CreateDatabaseIfNotExistsAsync(DATABASE_NAME);
                Container container = await database.CreateContainerIfNotExistsAsync(CONTAINER_NAME, "/year");

                ItemResponse&amp;lt;SurveyResponse&amp;gt; response = await container.CreateItemAsync&amp;lt;SurveyResponse&amp;gt;(surveyResponse, new PartitionKey(surveyResponse.Year));

                if (response.StatusCode == HttpStatusCode.Created)
                {
                    return true;
                }
                else
                {
                    _logger.LogError($"Unable to save record to CosmosDB - received {response.StatusCode} - {response.ActivityId}");
                    return false;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to save to CosmosDB");

                return false;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ThankYou.razor
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;CosmosDbPersistanceManager&lt;/code&gt; could then be injected into the &lt;code&gt;ThankYou&lt;/code&gt; page (the final page).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ThankYou&lt;/code&gt; page would show a "saving" message while the response was being saved to Cosmos DB and provide retry prompt if the save failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page "/ThankYou"

@if (Saved)
{
    if (Error)
    {
        &amp;lt;h1 class="temporary-header"&amp;gt;SORRY&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;An error has occurred while trying to save your details.&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;Please press the button below to retry:&amp;lt;/p&amp;gt;
        &amp;lt;button type="submit" class="btn retry-button" @onclick="HandleRetry"&amp;gt;Retry&amp;lt;/button&amp;gt;
    }
    else
    {
        &amp;lt;PageTitle Title="THANK YOU" Position="7" /&amp;gt;
        ...
    }
}
else
{
    &amp;lt;LoadingSpinner Title="SAVING ..."&amp;gt;&amp;lt;/LoadingSpinner&amp;gt;
}

@code
{
    [Inject]
    private Models.SurveyResponse Model { get; set; }
    [Inject]
    private Services.IPersistanceManager PersistanceManager { get; set; }

    private bool Saved { get; set; }
    private bool Error { get; set; }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            SaveData();
        }
    }

    private void HandleRetry(EventArgs eventArgs)
    {
        Saved = false;
        Error = false;
        StateHasChanged();

        SaveData();
    }

    private void SaveData()
    {
        var task = PersistanceManager.Persist(Model);
        task.ContinueWith(async x =&amp;gt;
        {
            Error = !x.Result;
            Saved = true;
            await InvokeAsync(StateHasChanged);
        });

        Task.Run(() =&amp;gt; task);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the actual &lt;code&gt;Persist&lt;/code&gt; is run as a background task so that the user is shown the "Saving" message.  On completion of the &lt;code&gt;Persist&lt;/code&gt; then the UI is updated with with the Thank You message or the retry button.&lt;/p&gt;

&lt;h2&gt;
  
  
  And that's it
&lt;/h2&gt;

&lt;p&gt;Fairly simple really.&lt;/p&gt;

&lt;p&gt;And because I'm using the &lt;code&gt;IPersistanceManager&lt;/code&gt; interface, I can subsitute fakes/ mocks during both development and testing.&lt;/p&gt;

&lt;p&gt;It would also allow me to easily replace the persistance choice - moving to Azure SQL or even Blob storage if appropriate.&lt;/p&gt;

&lt;p&gt;But I did feel that Azure Cosmos DB would have provided me with the a really good platform for data analysis.  I'd certainly have liked to spend sometime with the Jupyter Notebook functionality - described by Microsoft as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Jupyter Notebook is an open-source web application that allows you to create and share documents that contain live code, equations, visualizations, and narrative text."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unfortunately due to the lack of survey repondents, that isn't going to happen.  Maybe next time.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>csharp</category>
      <category>cosmosdb</category>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 5 - Client IP</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Mon, 12 Oct 2020 17:06:39 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-5-client-ip-25jm</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-5-client-ip-25jm</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it as a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Earlier articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state"&gt;Part 2 - Better State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components"&gt;Part 3 - Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode"&gt;Part 4 - Render Mode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;One concern I has about running a survey, was how to avoid the results being swayed by a single party providing responses over and over again.&lt;/p&gt;

&lt;p&gt;The simplest tactic to spot this was to record the client's IP address of the respondent and look for any bad behavior during the reporting stage.&lt;/p&gt;

&lt;p&gt;In theory that should be simple, I just record the client's IP address and then save it along with the response.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Normally for ASP.Net applications you would get the client's IP from the HttpContext.&lt;/p&gt;

&lt;p&gt;At the time of writing however, Blazor did not have access to the HttpContext - see &lt;a href="https://github.com/dotnet/aspnetcore/issues/14167#issuecomment-533617987"&gt;this Github issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I suspect this is something that will be resolved in future versions (possibly .Net 5), but this left me with a problem, how to get the client's IP without having access to the HttpContext.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;The solutions was actually reasonably simple - even if it took a few extra hops to achieve it.&lt;/p&gt;

&lt;p&gt;First of all I grab the relevant details BEFORE starting the Blazor app and then pass them in:&lt;/p&gt;

&lt;h3&gt;
  
  
  _Hosts.cshtml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        @{
            var requestDetails = new SoftwareSurvey.Models.RequestDetails
            {
                ConnectionIpAddress = HttpContext.Connection.RemoteIpAddress.ToString(),
                ForwardedIpAddress = HttpContext.Request.Headers.ContainsKey("X-Forwarded-For") ? HttpContext.Request.Headers["X-Forwarded-For"].ToString() : null
            };
        }
        &amp;lt;app&amp;gt;
            &amp;lt;component type="typeof(App)" render-mode="ServerPrerendered" param-RequestDetails="requestDetails" /&amp;gt;
        &amp;lt;/app&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So before I invoke the Blazor app, I use standard ASP.Net functionality to populate a model with the &lt;code&gt;RemoteIpAddress&lt;/code&gt; and any value in the &lt;code&gt;X-Forwarded-For&lt;/code&gt; header.  The &lt;code&gt;X-Forwarded-For&lt;/code&gt; header should contain the original IP address if the request has come through a proxy.&lt;/p&gt;

&lt;p&gt;While certainly not bulletproof, it was enough to capture silly attempts to game the responses.&lt;/p&gt;

&lt;p&gt;The model was then passed in using the &lt;code&gt;param-RequestDetails&lt;/code&gt; attribute notation.  The &lt;code&gt;param-&lt;/code&gt; prefix is a Blazor notation to then look for the parameter on the &lt;code&gt;App&lt;/code&gt; object and populate it with the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  App.razor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@inject SoftwareSurvey.Models.SurveyResponse _surveyResponse

...

@code {
    [Parameter]
    public SoftwareSurvey.Models.RequestDetails RequestDetails { get; set; }

    protected override Task OnInitializedAsync()
    {
        _surveyResponse.ConnectionIpAddress = RequestDetails.ConnectionIpAddress;
        _surveyResponse.ForwardedIpAddress = RequestDetails.ForwardedIpAddress;

        return base.OnInitializedAsync();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model is passed into the &lt;code&gt;App&lt;/code&gt; via the &lt;code&gt;RequestDetails&lt;/code&gt; property.  I can then use those details to populate the &lt;code&gt;_surveyResponse&lt;/code&gt; object which is used to track the respondent's answers - and ultimately is saved away into Azure Cosmos DB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Added extra, the query string
&lt;/h2&gt;

&lt;p&gt;In a similar manner, its also a useful technique to record query string values.&lt;/p&gt;

&lt;p&gt;As part of my automated testing, I wanted to record that it was a test, thus could later be filtered out during the reporting phase.&lt;/p&gt;

&lt;p&gt;To achieve this I wanted to be able to specify &lt;code&gt;?IsTest&lt;/code&gt; in the url so that it was marked in the &lt;code&gt;_surveryResponse&lt;/code&gt; object - and saved away to Azure Cosmos DB for that later reporting.&lt;/p&gt;

&lt;p&gt;So I simply added the following to the initialisation of the &lt;code&gt;RequestDetails&lt;/code&gt; model in the _Hosts.cshtml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IsTest = HttpContext.Request.QueryString.HasValue &amp;amp;&amp;amp; HttpContext.Request.QueryString.Value.Contains("IsTest", StringComparison.InvariantCultureIgnoreCase)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then set the property on the &lt;code&gt;_surveyResponse&lt;/code&gt; object in the &lt;code&gt;App.OnInitializedAsync&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_surveyResponse.IsTest = RequestDetails.IsTest;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Is this a better approach than using the HttpContext directly?
&lt;/h2&gt;

&lt;p&gt;The HttpContext can often be a cause of problems for unit testing - especially if the unit testing is being added to existing code.&lt;/p&gt;

&lt;p&gt;I often find that I create a wrapper around the HttpContext and then injecting the wrapper in to avoid direct HttpContext references.&lt;/p&gt;

&lt;p&gt;The constraint of not having the HttpContext is arguably a good thing to avoid relying on direct references (which you really shouldn't).&lt;/p&gt;

&lt;p&gt;By effectively treating the data as just another dependency to be passed in (via the &lt;code&gt;[Parameter]&lt;/code&gt;) you are forcing the &lt;code&gt;App&lt;/code&gt; to be cleaner - and thus easier to test.&lt;/p&gt;

&lt;p&gt;So while I doubt it will be long before the HttpContext is made available with Blazor - for now it maybe a useful pattern.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 4 - Render Mode</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Mon, 05 Oct 2020 17:27:47 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-4-render-mode-3hfk</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-4-render-mode-3hfk</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it as a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Earlier articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state"&gt;Part 2 - Better State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components"&gt;Part 3 - Components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Your Blazor server side app will be initiated something like this in _Hosts.cshtml:&lt;/p&gt;

&lt;h3&gt;
  
  
  _Hosts.cshtml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        &amp;lt;app&amp;gt;
            &amp;lt;component type="typeof(App)" render-mode="ServerPrerendered" /&amp;gt;
        &amp;lt;/app&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I believe by default, the &lt;code&gt;render-mode&lt;/code&gt; will be set to &lt;code&gt;ServerPrerendered&lt;/code&gt; - and I think most examples will show &lt;code&gt;render-mode&lt;/code&gt; with that option - and I have to admit I didn't really pay much attention to it until I struggled with my automated end-to-end tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  My problem
&lt;/h2&gt;

&lt;p&gt;I had built an end-to-end happy path test using Selenium and orchestrated it to run every hour using Azure DevOps.  The test was fairly simple; it ran through every page of the survey with defined responses - then checked against CosmosDB to ensure that the survery results had been recorded.&lt;/p&gt;

&lt;p&gt;I could run the test locally and it worked perfectly everytime.&lt;/p&gt;

&lt;p&gt;And it would &lt;em&gt;mostly&lt;/em&gt; work correctly when being run on the hourly schedule - but it wasn't consistent.&lt;/p&gt;

&lt;p&gt;Occassionally I'd get failures.  When I investigated it looked as if my Selenuim test simple wasn't pressing the "Next" button on the home page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eox9QPf5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8osfqosx8ed8ufjq9gfh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eox9QPf5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8osfqosx8ed8ufjq9gfh.png" alt="Home Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I experimented with various techniques to track down the problem - but eventually I discovered the cause.&lt;/p&gt;

&lt;p&gt;It was the &lt;code&gt;render-mode&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ServerPrerendered
&lt;/h2&gt;

&lt;p&gt;What I hadn't taken the time to fully appeciate was how the &lt;code&gt;ServerPrerendered&lt;/code&gt; worked.&lt;/p&gt;

&lt;p&gt;From the &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.rendering.rendermode?view=aspnetcore-3.1"&gt;Microsoft Docs page&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Renders the component into static HTML and includes a marker for a Blazor server-side application. When the user-agent starts, it uses this marker to bootstrap a blazor application."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The important part here is the "Renders the component into static HTML".&lt;/p&gt;

&lt;p&gt;On first request, the &lt;code&gt;ServerPrerendered&lt;/code&gt; will return the the browser a static HTML representation to the page requested (with all its components).  The browser will display this HTML - then run the Blazor Javascript to establish a SignalR connection.  Once the SignalR connection is established THEN the Blazor page is re-rendered - this time with all the Blazor goodness.&lt;/p&gt;

&lt;p&gt;What you are effectively getting it a two stage render to the browser:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cosmetic render of the component - all the HTML - but none of the functionality&lt;/li&gt;
&lt;li&gt;Functional render of the components - replaces the cosmetic only HTML with all the intended functionality&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a techniques called hydration that has been popular in Javascript Single-Page-Apps (SPAs) for a while.  The intention is to provide the user with valuable content (the HTML) while the app is downloading, initializing and starting in the background.  Once ready the app would "take over" - replacing the HTML on screen.&lt;/p&gt;

&lt;p&gt;Done right it would look to the user that the app is ready much quicker that it actually is (plus the technique provides a considerable benefit for SEO - the clawler doesn't need to execute the Javascript to understand the content).&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to my problem
&lt;/h2&gt;

&lt;p&gt;So my end-to-end tests where failing due to recieving the cosmetic-only HTML and attempting to use it BEFORE the app had rehydrated and became functional.&lt;/p&gt;

&lt;p&gt;The test would open the website - get the HTML for the first page and click on the Next button.&lt;/p&gt;

&lt;p&gt;But this was BEFORE the app had rehydrated - thus there was no click handler behind the Next button.  Thus clicking on it did nothing.&lt;/p&gt;

&lt;p&gt;Thus when the end-to-end test asserted that the next page had been loaded, it failed because of course none of the logic had been run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;I could have changed the &lt;code&gt;render-mode&lt;/code&gt; (see below) but I chose to implement a loading spinner while in that pre-rehydrated state;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6iw1rG4C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fzaxe5q11cb66avs0vcw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6iw1rG4C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fzaxe5q11cb66avs0vcw.png" alt="Prerender Loading Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I felt this was best both for the user and for Selenium.  The user wouldn't become frustrated by a button that did nothing and Selenium would wait until the Next button appeared.&lt;/p&gt;

&lt;p&gt;For this I created the &lt;code&gt;PreRenderLoadingMessage&lt;/code&gt; component:&lt;/p&gt;

&lt;h3&gt;
  
  
  PreRenderLoadingMessage.razor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@if (HasRendered)
{
    @ChildContent
}
else
{
    &amp;lt;LoadingSpinner Title="Loading ..." /&amp;gt;
}


@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private bool HasRendered { get; set; }

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            HasRendered = true;
            StateHasChanged();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I wrapped the entire app within it:&lt;/p&gt;

&lt;h3&gt;
  
  
  App.razor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;PreRenderLoadingMessage&amp;gt;
    &amp;lt;Router AppAssembly="@typeof(Program).Assembly"&amp;gt;
        &amp;lt;Found Context="routeData"&amp;gt;
            &amp;lt;RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /&amp;gt;
        &amp;lt;/Found&amp;gt;
        &amp;lt;NotFound&amp;gt;
            &amp;lt;LayoutView Layout="@typeof(MainLayout)"&amp;gt;
                &amp;lt;p&amp;gt;Sorry, there's nothing at this address.&amp;lt;/p&amp;gt;
                @{ Is404 = true; }
            &amp;lt;/LayoutView&amp;gt;
        &amp;lt;/NotFound&amp;gt;
    &amp;lt;/Router&amp;gt;
&amp;lt;/PreRenderLoadingMessage&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus the first render (the prerender) shows the &lt;code&gt;LoadingSpinner&lt;/code&gt;, then subsequent (hydrated) renders would show the &lt;code&gt;@ChildContent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This technique was taken from &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-3.1#detect-when-the-app-is-prerendering"&gt;this Microsoft Docs page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This approach solved my problem nicely.  The end-to-end tests worked like a charm once I implemented it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other modes
&lt;/h2&gt;

&lt;p&gt;So there are two other modes available to &lt;code&gt;render-mode&lt;/code&gt; - &lt;code&gt;Server&lt;/code&gt; and &lt;code&gt;Static&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Static&lt;/code&gt; will just render static HTML - it doesn't provide any of the functionality.  I've not seen a good reason to use this mode - although I do wonder if it could be used with a static site generator - something like GatsbyJs.  I suspect its there for future use.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Server&lt;/code&gt; will operate the same as &lt;code&gt;ServerPrerendered&lt;/code&gt; but without the static HTML version on the first page request.  Rather it leaves the &lt;code&gt;app&lt;/code&gt; content empty until the SignalR connection is established and Blazor is running fully.&lt;/p&gt;

&lt;p&gt;The problem with &lt;code&gt;Server&lt;/code&gt; mode was that it can take a few seconds to establish that SignalR connection and to get everything "started".  I didn't want to provide an empty page to the user.  I'd end up adding some form of loading message in the _host.cshtml - only to then have to remove once the Bazlor app was ready.&lt;/p&gt;

&lt;p&gt;Thus it made much more sense to use the functionality baked into Blazor with the &lt;code&gt;ServerPrerendered&lt;/code&gt; - and I think that the &lt;code&gt;PreRenderLoadingMessage&lt;/code&gt; is a good way to handle that.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 3 - Components</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Tue, 29 Sep 2020 07:15:48 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-3-components-20mn</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-3-components-20mn</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it as a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Earlier articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state"&gt;Part 2 - Better State&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Blazor is very much based on components.  This was something I was very pleased to see.&lt;/p&gt;

&lt;p&gt;Having spent a lot of time with React, I've been quite a fan of the component structure.  The MVC structure enforced by ASP.Net, while familiar, has always seems a little clunky since working with React.&lt;/p&gt;

&lt;p&gt;So it was definitely good to see that Blazor was taking a lot of its direction from Javascript frameworks like React &amp;amp; Vue.&lt;/p&gt;

&lt;p&gt;We still have a little time to wait for CSS isolation by component - but that seem to be coming soon in &lt;a href="https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-5-preview-8/"&gt;.Net 5&lt;/a&gt;.  Something I'll take a look at again when released.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basics
&lt;/h2&gt;

&lt;p&gt;Creating a component is very much a first class activity within Visual Studio - simply add a new Razor Component.&lt;/p&gt;

&lt;p&gt;This creates our sample component ready to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test.razor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h3&amp;gt;Test&amp;lt;/h3&amp;gt;

@code {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can then pull that component into any other Razor component or page by adding &lt;code&gt;&amp;lt;Test /&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;One thing to watch out for is that you components namespace will be based on the folder you create it in.  Just remember to include that namespace in the apps _Imports.razor file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;While the markdown is easy - it just goes into the razor file.  Where to put the code I suspect will produce debate in teams for years to come.&lt;/p&gt;

&lt;p&gt;You basically have three choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the Test.razor file within the &lt;code&gt;@code { }&lt;/code&gt; block&lt;/li&gt;
&lt;li&gt;In a Test.razor.cs file as a partial class&lt;/li&gt;
&lt;li&gt;In a TestBase.cs "base" class (extending ComponentBase) and then &lt;code&gt;@inherits TestBase&lt;/code&gt; in the Test.razor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While I suspect that which version is used will be personal taste, I personally would advise picking one and trying to stick to it to avoid confusion.&lt;/p&gt;

&lt;p&gt;Personally I favour using the &lt;code&gt;@code { }&lt;/code&gt; block within the razor file.  It keeps the code &amp;amp; markup in one place and easy to see - similar to how I'd do it within React.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;@code { }&lt;/code&gt; block may get a bit unwieldy if you have a lot of code - but that maybe an indication that your trying to do too much in your component.&lt;/p&gt;

&lt;p&gt;For me, the &lt;code&gt;@code { }&lt;/code&gt; block should be fairly simple with any logic being injected in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composition over inheritance
&lt;/h3&gt;

&lt;p&gt;In early versions of the Software Survey I leaned heavily on the "base" class technique.  I used it to inhert functionality for the survey pages.&lt;/p&gt;

&lt;p&gt;You can see this in &lt;a href="https://github.com/Red-Folder/software-survey/tree/3cc49e13bd8f2d5f1811ab3b346f6b3142c1ce6c"&gt;this commit version&lt;/a&gt;; that I have two base classes used by all of the pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Red-Folder/software-survey/blob/3cc49e13bd8f2d5f1811ab3b346f6b3142c1ce6c/SoftwareSurvey/Components/SurveyPageBase.cs"&gt;SoftwareSurvey/Components/SurveyPageBase.cs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Red-Folder/software-survey/blob/3cc49e13bd8f2d5f1811ab3b346f6b3142c1ce6c/SoftwareSurvey/Components/SurveyFormBase.cs"&gt;SoftwareSurvey/Components/SurveyFormBase.cs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two bases classes provided a level of functionality for all the Survey pages - which technically worked, but definitely didn't "smell" correct.&lt;/p&gt;

&lt;p&gt;So I refactored the functionality out of the base classes into components.  The components are then composed together - which has subsequently made the code considerably easier to understand.&lt;/p&gt;

&lt;p&gt;If possible, always favour component composition over inheritance.&lt;/p&gt;

&lt;h2&gt;
  
  
  ChildContent
&lt;/h2&gt;

&lt;p&gt;Similar to React, Blazor supports children being passed into a component.&lt;/p&gt;

&lt;p&gt;An example of where I've used this is in the &lt;code&gt;&amp;lt;EnsureBeenThroughStart /&amp;gt;&lt;/code&gt; component:&lt;/p&gt;

&lt;h3&gt;
  
  
  EnsureBeenThroughStart.razor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@if (_valid)
{
    @ChildContent
}

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private bool _valid;

    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component is used to ensure that the user has visited the start of the survey (if not it redirects to the start).  It should avoid respondents accidentally deep linking part way through the survey.&lt;/p&gt;

&lt;p&gt;If valid, the component shows the &lt;code&gt;@ChildContent&lt;/code&gt;.  That &lt;code&gt;@ChildContent&lt;/code&gt; is automatically provided by Blazor as any thing defined between the &lt;code&gt;&amp;lt;EnsureBeenThroughStart&amp;gt; &amp;lt;/EnsureBeenThroughStart&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;As you can see below, I wrap the entire router within it.&lt;/p&gt;

&lt;h3&gt;
  
  
  App.razor
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...

&amp;lt;PreRenderLoadingMessage&amp;gt;
    &amp;lt;EnsureBeenThroughStart&amp;gt;
        &amp;lt;Router AppAssembly="@typeof(Program).Assembly"&amp;gt;
            &amp;lt;Found Context="routeData"&amp;gt;
                &amp;lt;RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /&amp;gt;
            &amp;lt;/Found&amp;gt;
            &amp;lt;NotFound&amp;gt;
                &amp;lt;LayoutView Layout="@typeof(MainLayout)"&amp;gt;
                    &amp;lt;p&amp;gt;Sorry, there's nothing at this address.&amp;lt;/p&amp;gt;
                    @{ Is404 = true; }
                &amp;lt;/LayoutView&amp;gt;
            &amp;lt;/NotFound&amp;gt;
        &amp;lt;/Router&amp;gt;
     &amp;lt;/EnsureBeenThroughStart&amp;gt;
&amp;lt;/PreRenderLoadingMessage&amp;gt;

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  More to learn
&lt;/h2&gt;

&lt;p&gt;Components can seem a simple concept, but like many simple things, I think they will take experience to get the best from them.&lt;/p&gt;

&lt;p&gt;I learnt that during the refactor away from the "base" classes.&lt;/p&gt;

&lt;p&gt;I suspect overtime (and usage) patterns of usage will emerge - as they have in React.  I suspect that many ideas will cross-pollinate between Blazor and Javascript frameworks.&lt;/p&gt;

&lt;p&gt;If you've not done so already, I'd recommend reading the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-3.1"&gt;Microsoft Create and use ASP.NET Core Razor components page&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 2 - Better State</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Mon, 21 Sep 2020 15:09:51 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-2-better-state-7d1</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-2-better-state-7d1</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it is a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Earlier articles in this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state"&gt;Part 1 - State&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  From the last article
&lt;/h2&gt;

&lt;p&gt;From part 1, I used the following setup (taking advantage of the Dotnet Core Dependency Injection) to manage state:&lt;/p&gt;

&lt;h3&gt;
  
  
  StateService.cs - a simple service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using SoftwareSurvey.Models;
using System.Collections.Generic;
using System.Linq;

namespace SoftwareSurvey.Services
{
    public class StateService : IStateService
    {
        private readonly List&amp;lt;IStateObject&amp;gt; _stateObjects = new List&amp;lt;IStateObject&amp;gt;();

        public T GetOrNew&amp;lt;T&amp;gt;() where T : IStateObject, new()
        {
            var state = Get&amp;lt;T&amp;gt;();

            return state ?? new T();
        }

        public void Save&amp;lt;T&amp;gt;(T state) where T : IStateObject
        {
            var existingState = Get&amp;lt;T&amp;gt;();

            if (existingState != null)
            {
                _stateObjects.Remove(existingState);
            }

            _stateObjects.Add(state);
        }

        private T Get&amp;lt;T&amp;gt;() where T : IStateObject
        {
            return _stateObjects.OfType&amp;lt;T&amp;gt;().FirstOrDefault();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Demographic.cs - object to hold state
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace SoftwareSurvey.Models
{
    public class Demographic: IStateObject
    {
        [Required(ErrorMessage = "Please provide company size")]
        [DisplayName("Company Size")]
        public string CompanySize { get; set; }

        [Required(ErrorMessage = "Please provide your job seniority")]
        [DisplayName("Job Seniority")]
        public string JobSeniority { get; set; }

        [DisplayName("Job Title (optional)")]
        public string JobTitle { get; set; }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Startup.cs - setup the dependecy injection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public void ConfigureServices(IServiceCollection services)
{
  ...

  services.AddScoped&amp;lt;IStateService, StateService&amp;gt;();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Demographics.razor - accessing and using the state
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@code {
    protected Models.Demographic Model;

    [Inject]
    protected SoftwareSurvey.Services.IStateService _stateService { get; set; }

    protected override void OnInitialized()
    {
        base.OnInitialized();
        Model = _stateService.GetOrNew&amp;lt;Models.Demographic&amp;gt;();
    }

    protected void HandleValidSubmit()
    {
        _stateService.Save(Model);

        // Do something
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The difference a day makes
&lt;/h2&gt;

&lt;p&gt;While there has been quite some time since the last article, I realized quite quickly after I posted the article I could make this cleaner - and thus much simpler.&lt;/p&gt;

&lt;p&gt;I was overcomplicated things by have a service which allowed for various types of state object.  I was adding unnecessary complexity to the system because I thought I'd need it later.&lt;/p&gt;

&lt;p&gt;So, instead I simplified it to:&lt;/p&gt;

&lt;h3&gt;
  
  
  SurveyResponse.cs - Parent state object
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Newtonsoft.Json;

namespace SoftwareSurvey.Models
{
    public class SurveyResponse
    {
        public SurveyResponse()
        {
            Demographic = new Demographic();

            // Other setup
        }

        [JsonProperty(PropertyName = "demographic")]
        public Demographic Demographic { get; set; }

        // Other properties
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Demographic.cs - object to hold state (unchanged)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Newtonsoft.Json;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

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; }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Startup.cs - The dependency injection setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services.AddScoped(x =&amp;gt; new SurveyResponse());
services.AddTransient(provider =&amp;gt;
    providerGetService&amp;lt;SurveyResponse&amp;gt;().Demographic);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Demographics.razor - accessing and using the state
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@code
{
    [Inject]
    private Models.Demographic Model { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  As summary
&lt;/h2&gt;

&lt;p&gt;The main difference was that I used the dependency injection to serve up the state models directly - rather than serving up a service which would then return the state.&lt;/p&gt;

&lt;p&gt;Thus the simplification within the Demographics.razor page - it doesn't need to have any knowledge how to get the model - its just injected directly in.  Which is cleaner code anyway.&lt;/p&gt;

&lt;p&gt;The dependency injection sets up the scoped instance of the SurveyResponse (which is ultimately what I persist) and then provides Transient access to the relevant state object.&lt;/p&gt;

&lt;p&gt;Per page, I basically then create a state object, add to the SurveyResponse and then make available via the Dependency Injection.&lt;/p&gt;

&lt;p&gt;With the SurveyResponse being "Scoped" it is unique per connection - the trick I learnt from the last post.  The dependency setup for the "state objects" within the SurveyResponse can be Transient as it returns references to that "Scoped" instance.&lt;/p&gt;

&lt;p&gt;To me this just made the state management that little bit cleaner.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Building a Software Survey using Blazor - Part 1 - State</title>
      <dc:creator>Mark Taylor</dc:creator>
      <pubDate>Tue, 15 Sep 2020 16:08:23 +0000</pubDate>
      <link>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-1-state-2693</link>
      <guid>https://dev.to/redfolder/building-a-software-survey-using-blazor-part-1-state-2693</guid>
      <description>&lt;p&gt;I've decided to write a small survey site using Blazor.  Part of this is an excuse to learn Blazor.&lt;/p&gt;

&lt;p&gt;As I learn with Blazor I will blog about it is a series of articles.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;As this is the first part of the series, I wanted to just start with the why.&lt;/p&gt;

&lt;p&gt;Why build a survey site and why Blazor.&lt;/p&gt;

&lt;p&gt;I want to run a survey of executive attitudes to custom software development (be it inhouse or outsourced).  While I'd like to believe that most business now understand the value achieved from software development - and how best to achieve that; the reality is very much different.&lt;/p&gt;

&lt;p&gt;Many business still appear to treat software development as a "bum-on-seat" kind of role.  They treat developers (and all staff) with &lt;a href="https://en.wikipedia.org/wiki/Theory_X_and_Theory_Y" rel="noopener noreferrer"&gt;Theory X&lt;/a&gt; thinking.&lt;/p&gt;

&lt;p&gt;Thus I would like to run a survey to establish if this is correct.&lt;/p&gt;

&lt;p&gt;While my initial thought would be to use a service like Survey Monkey for this, I found that most services seemed rather expensive for a one-off survey.&lt;/p&gt;

&lt;p&gt;In parallel to this, I was starting to investigate Blazor.  Its a technology that I've been aware of since its original demo - but I've only recently decided to spend time on it.&lt;/p&gt;

&lt;p&gt;Thus, why not combine the two activities and use the survey as an excuse to build out a Blazor application.&lt;/p&gt;

&lt;p&gt;Is this the best ROI?  &lt;/p&gt;

&lt;p&gt;Probably not.  &lt;/p&gt;

&lt;p&gt;My "billable" time is considerably greater than the cost of a service like Survey Monkey.  But it provides me an excuse to learn a new technology - which, given its my time I'm wasting, seems a better use than watching another series on Netflix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Blazor ready?
&lt;/h2&gt;

&lt;p&gt;This was definitely my initial question when I started to look at Blazor.&lt;/p&gt;

&lt;p&gt;And, at the time of writing, I'd probably say no.&lt;/p&gt;

&lt;p&gt;It feels early version.&lt;/p&gt;

&lt;p&gt;The development experience you would expect just isn't there yet (or I've failed to find it).&lt;/p&gt;

&lt;p&gt;Visual Studio will report errors but the application will build and run correctly.&lt;/p&gt;

&lt;p&gt;There doesn't seem to be any hot reloading.  &lt;/p&gt;

&lt;p&gt;The framework also seems to be missing obvious functionality for being a full featured framework - it doesn't seem to provide support for SEO until .Net 5 for example.&lt;/p&gt;

&lt;p&gt;But is it good enough for certain application?  &lt;/p&gt;

&lt;p&gt;I'd give a guarded yes.  &lt;/p&gt;

&lt;p&gt;I'll have a better idea as I work through the Survey Site.  I certainly wouldn't use for my main website yet (lack of SEO) - but for simple apps, then maybe.  &lt;/p&gt;

&lt;p&gt;Time will tell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blazor Server
&lt;/h2&gt;

&lt;p&gt;Ok, a step back ... what is Blazor - from the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-3.1" rel="noopener noreferrer"&gt;Microsoft Page&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Blazor is a framework for building interactive client-side web UI with .NET:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create rich interactive UIs using C# instead of JavaScript&lt;/li&gt;
&lt;li&gt;Share server-side and client-side app logic written in .NET.&lt;/li&gt;
&lt;li&gt;Render the UI as HTML and CSS for wide browser support, including mobile browsers.&lt;/li&gt;
&lt;li&gt;Integrate with modern hosting platforms, such as Docker.&lt;/li&gt;
&lt;li&gt;Using .NET for client-side web development offers the following advantages:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Write code in C# instead of JavaScript.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leverage the existing .NET ecosystem of .NET libraries.&lt;/li&gt;
&lt;li&gt;Share app logic across server and client.&lt;/li&gt;
&lt;li&gt;Benefit from .NET's performance, reliability, and security&lt;/li&gt;
&lt;li&gt;Stay productive with Visual Studio on Windows, Linux, and macOS.&lt;/li&gt;
&lt;li&gt;Build on a common set of languages, frameworks, and tools that are stable, feature-rich, and easy to use.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;In short - build you web pages using C# rather than JavaScript.&lt;/p&gt;

&lt;p&gt;Blazor comes in two versions - Web Assembly and Server.&lt;/p&gt;

&lt;p&gt;Web Assembly will download a special version of the .NET runtime to the browser and allow you to run DLLs on the browser as you would on your local machine.  This is all provided by Web Assembly and gives considerable level of power - but at the cost of a sizeable download.&lt;/p&gt;

&lt;p&gt;Server will interact with the browser of SignalR (a Microsoft abstraction over web sockets).  The "work" is carried out on the server with DOM changes being sent back to the browser.  This should give considerably smaller download size - but will be slightly less performant as the logic is run on the server rather than the browser.&lt;/p&gt;

&lt;p&gt;For me, the Blazor Server version seems the appropriate way to go for this project.&lt;/p&gt;

&lt;p&gt;Based on the &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/supported-platforms?view=aspnetcore-3.1" rel="noopener noreferrer"&gt;Microsoft Page&lt;/a&gt;; Blazor Server will be supported on most browsers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.red-folder.com%2Fblog%2Fbuilding-a-software-survey-using-blazor-part-1-state%2Fsupported-browsers.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcontent.red-folder.com%2Fblog%2Fbuilding-a-software-survey-using-blazor-part-1-state%2Fsupported-browsers.png" alt="Supported Browsers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  State
&lt;/h2&gt;

&lt;p&gt;The survey will follow a simply structure of 4-5 pages of questions with a save to persistent storage at the end.&lt;/p&gt;

&lt;p&gt;There is no need for authentication or to persist the respondents answers "during" the survey.&lt;/p&gt;

&lt;p&gt;I do however need to keep track of the answers "so-far" in the survey.&lt;/p&gt;

&lt;p&gt;Moving from ASP.Net MVC to Blazor Server requires thinking about things a little differently.  And state is one of them.&lt;/p&gt;

&lt;p&gt;Where as ASP.Net MVC you may use session state for this, Blazor Server provides some other options - storage on the browser, persistent storage on the server (database, blob storage, etc) or to use a scoped service.&lt;/p&gt;

&lt;p&gt;And the scoped service is the choice I've gone for.&lt;/p&gt;

&lt;p&gt;A scoped service is using the .NET Core Dependency Injection to provide the same instance of class for a users connection.&lt;/p&gt;

&lt;p&gt;For Blazor Server, the users connection is referred to as a circuit and is basically the SignalR connection.  If it gets broken, then a new circuit will be created (in this case losing the class instance).&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-3.1" rel="noopener noreferrer"&gt;Microsoft Page&lt;/a&gt; describes it as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Blazor Server hosting model supports the Scoped lifetime. In Blazor Server apps, a scoped service registration is scoped to the connection. For this reason, using scoped services is preferred for services that should be scoped to the current user&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  My StateService
&lt;/h2&gt;

&lt;p&gt;With this in mind I created a simple service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using SoftwareSurvey.Models;
using System.Collections.Generic;
using System.Linq;

namespace SoftwareSurvey.Services
{
    public class StateService : IStateService
    {
        private readonly List&amp;lt;IStateObject&amp;gt; _stateObjects = new List&amp;lt;IStateObject&amp;gt;();

        public T GetOrNew&amp;lt;T&amp;gt;() where T : IStateObject, new()
        {
            var state = Get&amp;lt;T&amp;gt;();

            return state ?? new T();
        }

        public void Save&amp;lt;T&amp;gt;(T state) where T : IStateObject
        {
            var existingState = Get&amp;lt;T&amp;gt;();

            if (existingState != null)
            {
                _stateObjects.Remove(existingState);
            }

            _stateObjects.Add(state);
        }

        private T Get&amp;lt;T&amp;gt;() where T : IStateObject
        {
            return _stateObjects.OfType&amp;lt;T&amp;gt;().FirstOrDefault();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simple list of registered objects.  Each "page" of the survey will have a state object which implements the IStateObject.  For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace SoftwareSurvey.Models
{
    public class Demographic: IStateObject
    {
        [Required(ErrorMessage = "Please provide company size")]
        [DisplayName("Company Size")]
        public string CompanySize { get; set; }

        [Required(ErrorMessage = "Please provide your job seniority")]
        [DisplayName("Job Seniority")]
        public string JobSeniority { get; set; }

        [DisplayName("Job Title (optional)")]
        public string JobTitle { get; set; }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dependency Injection setup
&lt;/h2&gt;

&lt;p&gt;The DI setup in basically the same as any .NET Core application.  You add the Scoped class to the Startup.cs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public void ConfigureServices(IServiceCollection services)
{
  ...

  services.AddScoped&amp;lt;IStateService, StateService&amp;gt;();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Then within the relevant Blazor component, you inject the StateService and use to get &amp;amp; save the state object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@code {
    protected Models.Demographic Model;

    [Inject]
    protected SoftwareSurvey.Services.IStateService _stateService { get; set; }

    [Inject]
    protected NavigationManager NavigationManager { get; set; }

    protected override void OnInitialized()
    {
        base.OnInitialized();
        Model = _stateService.GetOrNew&amp;lt;Models.Demographic&amp;gt;();
    }

    protected void HandleValidSubmit()
    {
        _stateService.Save(Model);
        NavigationManager.NavigateTo("/ThankYou");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The end result
&lt;/h2&gt;

&lt;p&gt;This gives me an ability to store page state objects to a service (simple class) during the lifetime of the users interaction with the survey.&lt;/p&gt;

&lt;p&gt;There would be some downsides to this approach for some applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The state is in memory of the server - which is fine for a quick survey&lt;/li&gt;
&lt;li&gt;If the connection is lost, the server goes down or the browser errors then the state "so-far" will be lost.  Again, I'd consider this acceptable for this use case.&lt;/li&gt;
&lt;li&gt;The server will have an overhead for every active user of the site.  This is unlikely to be a problem for this use case as I am unlikely to get high volumes of respondents at the same time (chance would be a fine thing).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On that last point, using the &lt;a href="https://azure.microsoft.com/en-gb/services/signalr-service/" rel="noopener noreferrer"&gt;Azure SignalR Service&lt;/a&gt; would address this issue.  This maybe something I look at later in the project - if nothing else than to have a go with the service.&lt;/p&gt;

&lt;p&gt;Can see the full code for this stage of the project in &lt;a href="https://github.com/Red-Folder/software-survey/tree/fe267f3dc3593178cdcc84a12a70792cfbfc3c11" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
