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 part 1, I used the following setup (taking advantage of the Dotnet Core Dependency Injection) to manage state:
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();
}
}
}
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; }
}
}
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IStateService, StateService>();
}
@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
}
}
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:
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
}
}
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; }
}
}
services.AddScoped(x => new SurveyResponse());
services.AddTransient(provider =>
providerGetService<SurveyResponse>().Demographic);
@code
{
[Inject]
private Models.Demographic Model { get; set; }
}
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.