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.
Motivation
As this is the first part of the series, I wanted to just start with the why.
Why build a survey site and why Blazor.
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.
Many business still appear to treat software development as a "bum-on-seat" kind of role. They treat developers (and all staff) with Theory X thinking.
Thus I would like to run a survey to establish if this is correct.
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.
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.
Thus, why not combine the two activities and use the survey as an excuse to build out a Blazor application.
Is this the best ROI?
Probably not.
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.
Is Blazor ready?
This was definitely my initial question when I started to look at Blazor.
And, at the time of writing, I'd probably say no.
It feels early version.
The development experience you would expect just isn't there yet (or I've failed to find it).
Visual Studio will report errors but the application will build and run correctly.
There doesn't seem to be any hot reloading.
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.
But is it good enough for certain application?
I'd give a guarded yes.
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.
Time will tell.
Blazor Server
Ok, a step back ... what is Blazor - from the Microsoft Page:
Blazor is a framework for building interactive client-side web UI with .NET:
- Create rich interactive UIs using C# instead of JavaScript
- Share server-side and client-side app logic written in .NET.
- Render the UI as HTML and CSS for wide browser support, including mobile browsers.
- Integrate with modern hosting platforms, such as Docker.
- Using .NET for client-side web development offers the following advantages:
Write code in C# instead of JavaScript.
- Leverage the existing .NET ecosystem of .NET libraries.
- Share app logic across server and client.
- Benefit from .NET's performance, reliability, and security
- Stay productive with Visual Studio on Windows, Linux, and macOS.
- Build on a common set of languages, frameworks, and tools that are stable, feature-rich, and easy to use.
In short - build you web pages using C# rather than JavaScript.
Blazor comes in two versions - Web Assembly and Server.
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.
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.
For me, the Blazor Server version seems the appropriate way to go for this project.
Based on the Microsoft Page; Blazor Server will be supported on most browsers:
State
The survey will follow a simply structure of 4-5 pages of questions with a save to persistent storage at the end.
There is no need for authentication or to persist the respondents answers "during" the survey.
I do however need to keep track of the answers "so-far" in the survey.
Moving from ASP.Net MVC to Blazor Server requires thinking about things a little differently. And state is one of them.
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.
And the scoped service is the choice I've gone for.
A scoped service is using the .NET Core Dependency Injection to provide the same instance of class for a users connection.
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).
The Microsoft Page describes it as:
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
My StateService
With this in mind I created 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();
}
}
}
This is a simple list of registered objects. Each "page" of the survey will have a state object which implements the IStateObject. For example:
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; }
}
}
Dependency Injection setup
The DI setup in basically the same as any .NET Core application. You add the Scoped class to the Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IStateService, StateService>();
}
Usage
Then within the relevant Blazor component, you inject the StateService and use to get & save the state object:
@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<Models.Demographic>();
}
protected void HandleValidSubmit()
{
_stateService.Save(Model);
NavigationManager.NavigateTo("/ThankYou");
}
}
The end result
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.
There would be some downsides to this approach for some applications:
- The state is in memory of the server - which is fine for a quick survey
- 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.
- 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).
On that last point, using the Azure SignalR Service 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.
Can see the full code for this stage of the project in Github
Top comments (0)