Building a Software Survey using Blazor - Part 5 - Client IP

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.

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

The Problem

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 Solution

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:

_Hosts.cshtml


        @{
            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.

App.razor


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

Added extra, the query string

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;

Is this a better approach than using the HttpContext directly?

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.

About the author:

Mark Taylor is an experience IT Consultant passionate about helping his clients get better ROI from their Software Development.

He has over 20 years Software Development experience - over 15 of those leading teams. He has experience in a wide variety of technologies and holds certification in Microsoft Development and Scrum.

He operates through Red Folder Consultancy Ltd.