DEV Community

loading...
Cover image for Building a Software Survey using Blazor - Part 2 - Better State

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

redfolder profile image Mark Taylor Originally published at red-folder.com ・3 min read

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.

Earlier 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();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Startup.cs - setup the dependecy injection

public void ConfigureServices(IServiceCollection services)
{
  ...

  services.AddScoped<IStateService, StateService>();
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
   }
}
Enter fullscreen mode Exit fullscreen mode

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; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Startup.cs - The dependency injection setup

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

Demographics.razor - accessing and using the state

@code
{
    [Inject]
    private Models.Demographic Model { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)

pic
Editor guide