Building a Software Survey using Blazor - Part 5 - Client IP
Tags: blazor, csharp
Published: 2020-10-12 17:06:39

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:

* [Part 1 - State](https://red-folder.com/blog/building-a-software-survey-using-blazor-part-1-state)
* [Part 2 - Better State](https://red-folder.com/blog/building-a-software-survey-using-blazor-part-2-better-state)
* [Part 3 - Components](https://red-folder.com/blog/building-a-software-survey-using-blazor-part-3-components)
* [Part 4 - Render Mode](https://red-folder.com/blog/building-a-software-survey-using-blazor-part-4-render-mode)

---

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](https://github.com/dotnet/aspnetcore/issues/14167#issuecomment-533617987).

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.