ASP.NET Core is a cross-platform framework for building web applications that lets you develop web services as back ends for various scenarios. It also supports containerization, which you can use for building cloud-native apps. These characteristics and its cross-platform design make ASP.NET Core an ideal tool for building Internet of Things (IoT) web servers.
Arm64 (sometimes written as AArch64 or ARM64) chip architecture allows you to create high-performance, power-efficient applications. Windows 11 can run directly on Arm64-powered devices, so you can use it similarly to Windows 10 IoT Core to develop IoT apps. For example, you can use ASP.NET Core to build a web API that your headless IoT device exposes to communicate with users or other devices.
By building ASP.NET apps to target Arm64 natively, you can achieve performance superior to an emulation approach while enjoying low power consumption. As we demonstrated in the first part of this series, building an app to use Arm64 natively can reduce the computation time almost threefold compared to emulation.
Also, Microsoft has introduced Windows Dev Kit 2023, which provides a convenient, compact, development-friendly device powered by an Arm64 Snapdragon chip. The device contains a neural processing unit and a GPU, enabling you to create high-performance, intelligent applications.
This article demonstrates how you can use ASP.NET Core with Windows 11 to build a web server for a headless IoT application. You will gain insights into harnessing Arm64-powered devices that offer high performance while consuming minimal power for your IoT applications.
This article uses Windows Dev Kit 2023 as a development PC. The kit does not contain any real sensors, so you will implement a temperature sensor emulator.
You can find the companion code here.
Project Setup
Start by installing the .NET 8.0 SDK. You must install an SDK for Windows running on Arm64 architecture. The installation is straightforward and does not require instructions. As explained in the previous article, the .NET 8.0 SDK provides you with the .NET command line interface (dotnet command), which you use to create and run the project. Note that when you install the SDK for Arm64 on a supported Arm64-powered device, the dotnet command automatically defaults to using Arm64 architecture for your project.
Next, install Visual Studio Code for Windows by selecting User Installer for Arm64. Then, launch the installer and use the default settings. After installation, pick your color theme. Alternatively, you can install Visual Studio 2022 with ASP.NET and the web development workload.
Now, you will create a new ASP.NET Core Web API project.
Open a command prompt window and type the following command:
dotnet new webapi -o Arm64.HeadlessIoT
This command generates the output shown below:
The command creates a project containing a web API controller implemented in the Controllers/WeatherForecastController.cs file. This controller returns a collection of simulated weather forecasts. Since you do not need this controller, remove it by deleting the entire WeatherForecastController.cs file. The project is now ready, and you can proceed with implementation.
Implementation
This section demonstrates how to implement the temperature sensor emulator, sensor service registration, and web API controller. Next, it outlines how to test the web API server.
Temperature Sensor Emulator
You start by implementing the temperature sensor emulator, which simulates readings from a temperature sensor connected to an IoT device running a web service.
To represent sensor readings, you use the SensorReading
class, defined in the code snippet below. To implement this class, create a Sensors folder in the Arm64.HeadlessIoT solution folder, then make a new file called SensorReading.cs, where you place the following code:
namespace Arm64.HeadlessIoT.Sensors;
public class SensorReading
{
public double Value { get; private set; }
public DateTime TimeStamp { get; }
public SensorReading(double value)
{
Value = value;
TimeStamp = DateTime.UtcNow;
}
}
The class above contains two properties: Value
and TimeStamp
. The Value property stores the sensor reading, while TimeStamp
represents the time the reading was obtained. The SensorReading
class also implements the constructor. You use this class to populate the Value
property with the constructor parameter. The TimeStamp
automatically provides the current date and time in UTC format.
Then, in the Sensors folder, create another file, ISensor.cs, which defines the following interface:
namespace Arm64.HeadlessIoT.Sensors;
public interface ISensor
{
public bool IsActive { get; set; }
public SensorReading GetCurrentReading();
}
This interface provides a common contract for classes implementing sensor emulators. In this case, all the classes implementing the interface must implement the following two members:
IsActive
— a Boolean property specifying whether the sensor is active and currently recording data GetCurrentReading
— a method returning an instance of the SensorReading
class containing a sensor reading and timestamp
Now, you can implement the actual class representing the temperature sensor. In the Sensors folder, add another file, TemperatureSensor.cs, which defines the following class:
namespace Arm64.HeadlessIoT.Sensors;
public class TemperatureSensor : ISensor
{
private const int minValue = -10;
private const int maxValue = 40;
private SensorReading lastKnownReading
= new SensorReading(random.Next(minValue, maxValue));
private static Random random = new();
public bool IsActive {get; set;} = true;
public SensorReading GetCurrentReading()
{
if(IsActive)
{
var currentSensorReading = new SensorReading(random.Next(minValue, maxValue));
lastKnownReading = currentSensorReading;
return currentSensorReading;
}
else
{
return lastKnownReading;
}
}
}
The TemperatureSensor
class implements the ISensor
interface, the IsActive
property, and the GetCurrentReading
method. The IsActive
property is a Boolean value, which is initially true
, indicating that the temperature sensor emulator is active.
The second method, GetCurrentReading
, checks if the IsActive
property is true. If so, the GetCurrentReading
method simulates a temperature reading using a pseudo-random number generator (the System.Random
class instance). Specifically, it uses the Next
method of this generator to pick an integer value from a range of values stored in the min
and max
fields of the TemperatureSensor
class. The lastKnownReading
field stores the sensor reading. Finally, the GetCurrentReading
method will return the temperature reading to the caller.
Alternatively, if the IsActive
property is false, the GetCurrentReading
method will return the last known sensor reading.
Sensor Service Registration
After implementing the temperature emulator, you use the dependency injection design pattern to register an instance of the TemperatureSensor
as a singleton. To do this, you must modify the Program.cs file by adding using Arm64.HeadlessIoT.Sensors
and builder.Services.AddSingleton<ISensor, TemperatureSensor>()
as they appear below:
using Arm64.HeadlessIoT.Sensors;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<ISensor, TemperatureSensor>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
This approach ensures that a single instance of the TemperatureSensor
class is available to the entire application. Any web API controller requiring access to that sensor can simply use constructor injection.
You will also use the Swagger toolset. As shown above, the default ASP.NET Core Web API project template also registers two services: EndpointsApiExplorer
and SwaggerGen
.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
You use these services to add Swagger middleware, which analyzes all the controllers in the project. It then generates a JSON-formatted file containing documentation of your web APIs and exposes the generated documentation on the HTTP endpoint using the following statements:
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
Web API Controller for the Headless IoT Device
Now you are ready to implement the web API controller. Create a new file, IoTController.cs, in the Controllers folder. Then, in the IoTController.cs file, add two using statements and define the namespace:
using Microsoft.AspNetCore.Mvc;
using Arm64.HeadlessIoT.Sensors;
namespace Arm64.HeadlessIoT.Controllers;
Next, define the IoTController
class, which derives from the ControllerBase and contains the required controller attributes:
[ApiController]
[Route("[controller]")]
public class IoTController : ControllerBase
{
}
Then, in the IoTController
class, define the read-only field, temperatureSensor
, which stores the reference to the TemperatureSensor
class instance registered in the dependency container in the Program.cs file:
private readonly ISensor temperatureSensor;
You can now define the IoTController
class constructor:
public IoTController(ISensor sensor)
{
temperatureSensor = sensor;
}
This code uses constructor dependency injection to obtain a reference to the TemperatureSensor
class instance. The temperatureSensor
field stores the reference to the TemperatureSensor
class as defined in the Program.cs file.
Now, you can implement the IoTController
method, which handles GET
requests:
[HttpGet]
[ProducesResponseType(typeof(SensorReading), StatusCodes.Status200OK)]
public SensorReading Get()
{
return temperatureSensor.GetCurrentReading();
}
This method returns the temperature sensor emulator’s current reading.
Finally, you implement the POST
handler, which enables a user to change the sensor emulator’s IsActive
property:
[HttpPost]
public IActionResult SetSensorStatus(bool isActive)
{
temperatureSensor.IsActive = isActive;
return Ok();
}
Now, you can change the internal state of the emulator through the web API.
Testing the Web API Server
To test the web API server, you must build and run the app.
Open the command prompt window, go to the project’s folder and type:
dotnet run
This command produces the following output:
Note that the command uses Arm64 architecture as a default (because you installed .NET SDK for Arm64). If you have multiple SDKs available, you must explicitly specify the architecture using the -a parameter. For example:
dotnet run -a arm64
You can ensure the app uses this architecture by opening the Task Manager and clicking the Details pane:
Now, open the web browser and navigate to http://localhost:<port>/iot, where <port> should match the port value shown after Now listening on in the command prompt screenshot above. Doing this invokes the GET
method of IoTController
, and you see the sensor reading rendered as a JSON-formatted string:
To test the POST
method, use curl, Postman, or Swagger. The last option is the simplest, as Swagger is available in the project template.
To access Swagger, type http://localhost:<port>/swagger in the web browser’s address bar. This URL takes you to the following screen:
Now, expand the POST section and click the Try it out button, which enables the isActive
drop-down list. Set isActive
to false, and then click Execute.
This action sends the POST
request to IoTController
and disables the emulator. So, all subsequent GET
calls to IoTController
return the last known sensor reading (which is the final reading generated before disabling the emulator), as shown below:
When you set isActive
back to true and send the GET
request to the web server, you will see the temperature setting update.
Summary
This article demonstrated using ASP.NET Core to implement a web API server for headless IoT apps running on Windows 11 on Arm. It visualized how the web server can retrieve simulated sensor readings from a temperature sensor emulator. It also showed how to control the emulator through a web server. Finally, it demonstrated using dependency injection in ASP.NET Core and Swagger to test a web API. ASP.NET Core is cross-platform, allowing you to use a similar approach to run web servers on non-Windows platforms.
By combining ASP.NET Core with Arm64, you can implement high-performance, low-power consumption web servers for all your applications. Although you can compile the project for any CPU, when you target Arm64, you improve the web server’s performance by running natively.
Try Arm64 yourself to learn more.