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 as 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:
One concern I has about running a survey, was how to avoid the results being swayed by a single party providing responses over and over again.
The simplest tactic to spot this was to record the client's IP address of the respondent and look for any bad behavior during the reporting stage.
In theory that should be simple, I just record the client's IP address and then save it along with the response.
Normally for ASP.Net applications you would get the client's IP from the HttpContext.
At the time of writing however, Blazor did not have access to the HttpContext - see this Github issue.
I suspect this is something that will be resolved in future versions (possibly .Net 5), but this left me with a problem, how to get the client's IP without having access to the HttpContext.
The solutions was actually reasonably simple - even if it took a few extra hops to achieve it.
First of all I grab the relevant details BEFORE starting the Blazor app and then pass them in:
@{
var requestDetails = new SoftwareSurvey.Models.RequestDetails
{
ConnectionIpAddress = HttpContext.Connection.RemoteIpAddress.ToString(),
ForwardedIpAddress = HttpContext.Request.Headers.ContainsKey("X-Forwarded-For") ? HttpContext.Request.Headers["X-Forwarded-For"].ToString() : null
};
}
<app>
<component type="typeof(App)" render-mode="ServerPrerendered" param-RequestDetails="requestDetails" />
</app>
So before I invoke the Blazor app, I use standard ASP.Net functionality to populate a model with the RemoteIpAddress
and any value in the X-Forwarded-For
header. The X-Forwarded-For
header should contain the original IP address if the request has come through a proxy.
While certainly not bulletproof, it was enough to capture silly attempts to game the responses.
The model was then passed in using the param-RequestDetails
attribute notation. The param-
prefix is a Blazor notation to then look for the parameter on the App
object and populate it with the model.
@inject SoftwareSurvey.Models.SurveyResponse _surveyResponse
...
@code {
[Parameter]
public SoftwareSurvey.Models.RequestDetails RequestDetails { get; set; }
protected override Task OnInitializedAsync()
{
_surveyResponse.ConnectionIpAddress = RequestDetails.ConnectionIpAddress;
_surveyResponse.ForwardedIpAddress = RequestDetails.ForwardedIpAddress;
return base.OnInitializedAsync();
}
}
The model is passed into the App
via the RequestDetails
property. I can then use those details to populate the _surveyResponse
object which is used to track the respondent's answers - and ultimately is saved away into Azure Cosmos DB.
In a similar manner, its also a useful technique to record query string values.
As part of my automated testing, I wanted to record that it was a test, thus could later be filtered out during the reporting phase.
To achieve this I wanted to be able to specify ?IsTest
in the url so that it was marked in the _surveryResponse
object - and saved away to Azure Cosmos DB for that later reporting.
So I simply added the following to the initialisation of the RequestDetails
model in the _Hosts.cshtml:
IsTest = HttpContext.Request.QueryString.HasValue && HttpContext.Request.QueryString.Value.Contains("IsTest", StringComparison.InvariantCultureIgnoreCase)
And then set the property on the _surveyResponse
object in the App.OnInitializedAsync
:
_surveyResponse.IsTest = RequestDetails.IsTest;
The HttpContext can often be a cause of problems for unit testing - especially if the unit testing is being added to existing code.
I often find that I create a wrapper around the HttpContext and then injecting the wrapper in to avoid direct HttpContext references.
The constraint of not having the HttpContext is arguably a good thing to avoid relying on direct references (which you really shouldn't).
By effectively treating the data as just another dependency to be passed in (via the [Parameter]
) you are forcing the App
to be cleaner - and thus easier to test.
So while I doubt it will be long before the HttpContext is made available with Blazor - for now it maybe a useful pattern.