Building a Software Survey using Blazor - Part 2 - Better State
Tags: blazor, csharp
Published: 2020-09-21 15:09:51

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:

* [Part 1 - State](https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state)

---

## 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.