\r\n

51Degrees Device Detection .NET  4.4

Device detection services for 51Degrees Pipeline

OnPremise/GettingStarted-Web/Startup.cs

The source code for this example is available in full on GitHub.

Required NuGet Dependencies:

Overview

The UseFiftyOne extension method is used to create a Pipeline instance from the configuration that is supplied. The AddFiftyOne extension method adds a middleware component that will intercept requests and perform device detection. The results will be stored in the HttpContext. The middleware will also handle setting response headers (e.g. Accept-CH for User-Agent Client Hints) and serving requests for client-side JavaScript and JSON resources.

services.AddFiftyOne(Configuration);
app.UseFiftyOne();

The results of detection can be accessed by adding a dependency on IFlowDataProvider. This can then be used to interrogate the data.

var flowData = _provider.GetFlowData();
var deviceData = flowData.Get<IDeviceData>();
var hardwareVendor = deviceData.HardwareVendor;

Results can also be accessed in client-side code by using the fod object. See the JavaScriptBuilderElementBuilder for details on available settings such as changing the fod name.

window.onload = function () {
fod.complete(function(data) {
var hardwareName = data.device.hardwarename;
alert(hardwareName.join(", "));
}
}

Configuration

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
// For a sample configuration file demonstrating all available options, see
// https://github.com/51Degrees/device-detection-dotnet-examples/blob/main/Examples/sample-configuration.json
"PipelineOptions": {
"Elements": [
{
// Required to unpack the GetHighEntropyValues base 64 encoded string into UACH HTTP headers.
"BuilderName": "UachJsConversionElement"
},
{
"BuilderName": "DeviceDetectionHashEngineBuilder",
"BuildParameters": {
"DataFile": "51Degrees-LiteV4.1.hash",
"CreateTempDataCopy": false,
"AutoUpdate": false,
"PerformanceProfile": "LowMemory",
"DataFileSystemWatcher": false,
"DataUpdateOnStartUp": false,
// Explicitly include just the properties used by the example.
"Properties": "DeviceId,JavascriptHardwareProfile,HardwareVendor,HardwareModel,HardwareName,IsMobile,JavascriptGetHighEntropyValues,Promise,Fetch,DeviceType,PlatformVendor,PlatformName,PlatformVersion,BrowserVendor,BrowserName,BrowserVersion,ScreenPixelsWidth,ScreenPixelsHeight,ScreenPixelsWidthJavascript,ScreenPixelsHeightJavascript"
}
},
{
// Required to add Javascript and JSON end points for 51Degrees.core.js and
"BuilderName": "JavaScriptBuilderElement",
"BuildParameters": {
"Minify": true
}
}
],
// Both these options default to true anyway.
// They are specified here for illustrative purposes.
"ClientSideEvidenceEnabled": true,
"UseAsyncScript": true
}
}

Controller

/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2023 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */
using Microsoft.AspNetCore.Mvc;
using FiftyOne.Pipeline.Web.Services;
using Microsoft.Extensions.Logging;
{
public class HomeController : Controller
{
private static bool _checkedDataFile = false;
private IFlowDataProvider _provider;
private ILogger<HomeController> _logger;
// The controller has a dependency on IFlowDataProvider. This is used to access the
// IFlowData that contains the device detection results for the current HTTP request.
public HomeController(IFlowDataProvider provider, ILogger<HomeController> logger)
{
_provider = provider;
_logger = logger;
}
public IActionResult Index()
{
// Log warnings if the data file is too old or the 'Lite' file is being used.
if(_checkedDataFile == false)
{
ExampleUtils.CheckDataFile(_provider.GetFlowData().Pipeline, _logger);
_checkedDataFile = true;
}
// Use the provider to get the flow data. This contains the results of device
// detection that has been performed by the pipeline.
return View(new IndexModel(_provider.GetFlowData(), Response.Headers));
}
}
}

View

@* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2023 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* *@
@using FiftyOne.DeviceDetection.Examples
@model FiftyOne.DeviceDetection.Examples.OnPremise.GettingStartedWeb.Model.IndexModel
@{
ViewData["Title"] = "Web Integration Example";
}
<link rel="stylesheet" href="~/css/site.css" />
<h2>Web Integration Example</h2>
<p>
This example demonstrates the use of the Pipeline API to perform device detection within a
simple ASP.NET Core web project. In particular, it highlights:
<ol>
<li>
Automatic handling of the 'Accept-CH' header, which is used to request User-Agent
Client Hints from the browser
</li>
<li>
Client-side evidence collection in order to identify Apple device models and properties
such as screen size.
</li>
</ol>
</p>
<h3>Client Hints</h3>
<p>
When the first request is made, browsers that support client hints will typically send a subset
of client hints values along with the User-Agent header.
If device detection determines that the browser does support client hints then it will request
that additional client hints headers are sent with future requests by sending the Accept-CH
header with the response.
</p>
<p>
Note that if you have visited this page previously, the value of Accept-CH will have been
cached so all requested client hints headers will be sent on the first request. Using features
such as 'private browsing' or 'incognito mode' will allow you to see the true first request
experience as the previous Accept-CH value will not be used.
</p>
<noscript>
<div class="example-alert">
WARNING: JavaScript is disabled in your browser. This means that the callback discussed
further down this page will not fire and UACH headers will not be sent.
</div>
</noscript>
@if (DateTime.UtcNow > Model.DataFile.DataPublishedDateTime
.AddDays(ExampleUtils.DataFileAgeWarning))
{
<div class="example-alert">
WARNING: This example is using a data file that is more than
@ExampleUtils.DataFileAgeWarning days old. A more recent data file may be needed to
correctly detect the latest devices, browsers, etc. The latest lite data file is available
from the
<a href="https://github.com/51Degrees/device-detection-data">device-detection-data</a>
repository on GitHub. Find out about the Enterprise data file, which includes automatic
daily updates, on our <a href="https://51degrees.com/pricing">pricing page</a>.
</div>
}
<div id="content">
<h3>Detection results</h3>
<p>
The following values are determined by sever-side device detection
on the first request:
</p>
<table>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
<tr class="lightyellow"><td><b>Hardware Vendor:</b></td><td> @Model.HardwareVendor</td></tr>
<tr class="lightyellow"><td><b>Hardware Name:</b></td><td> @Model.HardwareName</td></tr>
<tr class="lightyellow"><td><b>Device Type:</b></td><td> @Model.DeviceType</td></tr>
<tr class="lightyellow"><td><b>Platform Vendor:</b></td><td> @Model.PlatformVendor</td></tr>
<tr class="lightyellow"><td><b>Platform Name:</b></td><td> @Model.PlatformName</td></tr>
<tr class="lightyellow"><td><b>Platform Version:</b></td><td> @Model.PlatformVersion</td></tr>
<tr class="lightyellow"><td><b>Browser Vendor:</b></td><td> @Model.BrowserVendor</td></tr>
<tr class="lightyellow"><td><b>Browser Name:</b></td><td> @Model.BrowserName</td></tr>
<tr class="lightyellow"><td><b>Browser Version:</b></td><td> @Model.BrowserVersion</td></tr>
<tr class="lightyellow"><td><b>Screen width (pixels):</b></td><td> @Model.ScreenWidth</td></tr>
<tr class="lightyellow"><td><b>Screen height (pixels):</b></td><td> @Model.ScreenHeight</td></tr>
<tr class="lightyellow"><td><b>Device Id:</b></td><td> @Model.DeviceId</td></tr>
</table>
<br />
<div id="evidence">
<h3>Evidence used</h3>
<p class="smaller">Evidence was <span class="lightgreen">used</span> / <span class="lightyellow">present</span> for detection</p>
<table>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
@foreach (var entry in Model.Evidence)
{
<tr class="@(entry.Used ? "lightgreen" : "lightyellow")">
<td><b>@(entry.Key)</b></td>
<td>@(entry.Value)</td>
</tr>
}
</table>
</div>
<br />
<div id="response-headers">
<h3>Response headers</h3>
<table>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
@foreach (var entry in Model.ResponseHeaders)
{
<tr class="lightyellow">
<td><b>@(entry.Key)</b></td>
<td>@(entry.Value)</td>
</tr>
}
</table>
</div>
@if (Model.ResponseHeaders.ContainsKey("Accept-CH") == false)
{
<div class="example-alert">
WARNING: There is no Accept-CH header in the response. This may indicate that your
browser does not support User-Agent Client Hints. This is not necessarily a problem,
but if you are wanting to try out detection using User-Agent Client Hints, then make
sure that your browser
<a href="https://developer.mozilla.org/en-US/docs/Web/API/User-Agent_Client_Hints_API#browser_compatibility">supports them</a>.
</div>
}
<br />
<h3>Client-side Evidence and Apple Models</h3>
<p>
The information shown below is determined after a callback is made to the server with
additional evidence that is gathered by JavaScript running on the client-side.
The callback will also include any additional client hints headers that have been requested.
</p>
<p>
When an Apple device is used, the results from
the first request above will show all Apple models because the server cannot tell the
exact model of the device. In contrast, the results from the callback below will show
a smaller set of possible models.
This can be tested to some extent using most emulators, such as those in the
'developer tools' menu in Google Chrome. However, these are not the identical to real
devices so this can cause some unusual results. Using real devices will generally be more
successful.
</p>
<p>
If you want to work with Apple Model or other client-side information, such as screen
width/height on the server, it will be available on the next request.
This is achieved by storing the additional client-side evidence as cookies on the client.
When a future page is requested, these cookies will be included with the request and the
device detection API will include them when working out the details of the device.
Refreshing this page can be used to show this in action. Any values that are unique to the
client-side values below will appear in the evidence values used and server-side results
after the refresh.
</p>
@if (Model.Engine.DataSourceTier == "Lite")
{
<div class="example-alert">
WARNING: You are using the free 'Lite' data file. This does not include the client-side
evidence capabilities of the paid-for data file, so you will not see any additional
data below. Find out about the Enterprise data file on our
<a href="https://51degrees.com/pricing">pricing page</a>.
</div>
}
</div>
@* This View Component is embedded within the FiftyOne.Pipeline.Web package.
it adds a JavaScript include for 51Degrees.core.js.
The FiftyOne degrees middleware will intercept the request for this file and serve dynamically
generated JavaScript, which includes a JSON representation of the contents of flow data.
i.e. The results from device detection.
In addition, this JavaScript will look for properties that have a flag set indicating that
they contain executable script snippets.
These snippets will be executed and the values they obtain will be sent back to the server
in order for it to perform the detection process again with the new information.
This callback will also include any User-Agent Client Hints headers that have been requested
with the 'Accept-CH' header. (assuming the browser is willing to send them)
When the server responds, the JSON representation of the results will be updated with the
new values and the 'complete' event will fire.
Below, we subscribe to this complete event and display the values from the updated JSON.
*@
@await Component.InvokeAsync("FiftyOneJS")
<script>
window.onload = function () {
// Subscribe to the 'complete' event.
fod.complete(function (data) {
// When the event fires, use the supplied data to populate a new table.
let fieldValues = [];
var hardwareName = typeof data.device.hardwarename == "undefined" ?
"Unknown" : data.device.hardwarename.join(", ")
fieldValues.push(["Hardware Name: ", hardwareName]);
fieldValues.push(["Platform: ",
data.device.platformname + " " + data.device.platformversion]);
fieldValues.push(["Browser: ",
data.device.browsername + " " + data.device.browserversion]);
fieldValues.push(["Screen width (pixels): ", data.device.screenpixelswidth]);
fieldValues.push(["Screen height (pixels): ", data.device.screenpixelsheight]);
displayValues(fieldValues);
});
}
// Helper function to add a table that displays the supplied values.
function displayValues(fieldValues) {
var table = document.createElement("table");
var tr = document.createElement("tr");
addToRow(tr, "th", "Key", false);
addToRow(tr, "th", "Value", false);
table.appendChild(tr);
fieldValues.forEach(function (entry) {
var tr = document.createElement("tr");
tr.classList.add("lightyellow");
addToRow(tr, "td", entry[0], true);
addToRow(tr, "td", entry[1], false);
table.appendChild(tr);
});
var element = document.getElementById("content");
element.appendChild(table);
}
// Helper function to add an entry to a table row.
function addToRow(row, elementName, text, strong) {
var entry = document.createElement(elementName);
var textNode = document.createTextNode(text);
if (strong === true) {
var strongNode = document.createElement("strong");
strongNode.appendChild(textNode);
textNode = strongNode;
}
entry.appendChild(textNode);
row.appendChild(entry);
}
</script>

Startup

/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2023 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */
using FiftyOne.DeviceDetection.Hash.Engine.OnPremise.FlowElements;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed
// for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc();
// Add the hash engine builder to services so that the system can find the builder
// when it needs to.
services.AddSingleton<DeviceDetectionHashEngineBuilder>();
services.AddSingleton<UachJsConversionElementBuilder>();
// Configure the services needed by device detection and create the 51Degrees Pipeline
// instance that will be used to process requests.
services.AddFiftyOne(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request
// pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDeveloperExceptionPage();
// This is only needed when running under an ASP.NET test server.
app.UseMiddleware<UserAgentCorrectionMiddleware>();
// Add the 51Degrees middleware component.
// This will pass any incoming requests through the pipeline API, performing device
// detection. The IFlowData that is used will be stored in the data associated with
// the current HTTP session.
// The IFlowDataAccessor provides an easy way to retrieve this data. See HomeController
// for an example of this.
app.UseFiftyOne();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
private class UserAgentCorrectionMiddleware
{
private readonly RequestDelegate next;
public UserAgentCorrectionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var val = httpContext.Request.Headers["User-Agent"];
httpContext.Request.Headers.Remove("User-Agent");
httpContext.Request.Headers["User-Agent"] =
new Microsoft.Extensions.Primitives.StringValues(string.Join(" ", val));
await this.next(httpContext);
}
}
}
}