Building a Software Survey using Blazor - Part 2 - Better State

I've decided to write a small survey site using Blazor. Part of this is an excuse to learn Blazor.

As I learn with Blazor I will blog about it is a series of articles.

This series of articles is not intended to be a training course for Blazor - rather my thought process as I go through learning to use the product.

All articles in this series:


From the last article

From part 1, I used the following setup (taking advantage of the Dotnet Core Dependency Injection) to manage state:

StateService.cs - a simple service


using SoftwareSurvey.Models;
using System.Collections.Generic;
using System.Linq;

namespace SoftwareSurvey.Services
{
    public class StateService : IStateService
    {
        private readonly List<IStateObject> _stateObjects = new List<IStateObject>();

        public T GetOrNew<T>() where T : IStateObject, new()
        {
            var state = Get<T>();

            return state ?? new T();
        }

        public void Save<T>(T state) where T : IStateObject
        {
            var existingState = Get<T>();

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

            _stateObjects.Add(state);
        }

        private T Get<T>() where T : IStateObject
        {
            return _stateObjects.OfType<T>().FirstOrDefault();
        }
    }
}

Demographic.cs - object to hold state


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; }
    }
}

Startup.cs - setup the dependecy injection


public void ConfigureServices(IServiceCollection services)
{
  ...

  services.AddScoped<IStateService, StateService>();
}

Demographics.razor - accessing and using the state


@code {
    protected Models.Demographic Model;

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

    protected override void OnInitialized()
    {
        base.OnInitialized();
        Model = _stateService.GetOrNew<Models.Demographic>();
    }

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

        // Do something
    }
}

The difference a day makes

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.

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.

So, instead I simplified it to:

SurveyResponse.cs - Parent state object


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
   }
}

Demographic.cs - object to hold state (unchanged)


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; }
    }
}

Startup.cs - The dependency injection setup


services.AddScoped(x => new SurveyResponse());
services.AddTransient(provider =>
    providerGetService<SurveyResponse>().Demographic);

Demographics.razor - accessing and using the state


@code
{
    [Inject]
    private Models.Demographic Model { get; set; }
}

As summary

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.

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.

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.

Per page, I basically then create a state object, add to the SurveyResponse and then make available via the Dependency Injection.

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.

To me this just made the state management that little bit cleaner.

About the author:

Mark Taylor is an experience IT Consultant passionate about helping his clients get better ROI from their Software Development.

He has over 20 years Software Development experience - over 15 of those leading teams. He has experience in a wide variety of technologies and holds certification in Microsoft Development and Scrum.

He operates through Red Folder Consultancy Ltd.