Home Ticketing #6 – REST API

In this article, I will discuss about the REST API, what I have use for my home alerting service. This API is between my database engine and the incoming requests.

This article is part of a series. Other already public articles:
Home Ticketing #1 – What my back(end) at home!
Home Ticketing #2 – High level overview
Home Ticketing #3 – Tables and relationships

Home Ticketing #4 – Handle data – EF setup
Home Ticketing #5 – Server environment

Full code can be found in this github repo. I will highlight some details from it, where I discuss about that, but you can check the whole picture anytime. Those codes, what I discuss can be found in “HomeTicketing” project folder.

Position of this API

Purpose of this API is to make a “bridge” between my database and the incoming requests which made by clients. One of the client’s requests are coming from the web GUI, but some other are coming from monitoring scripts for example, or from any other automation or monitoring solution.

API objects

In a .NET REST API application we need to create “ApiController” classes. It contains the endpoints of the REST API. Every endpoint has URI which tells the path of the endpoint and a access method (e.g.: GET, PUT, POST, DELETE, etc.). Optionally it can contains more attributes (e.g.: Authentication) but those are optional. You can see and example for an endpoint from Controllers/CategoryController.cs file.

/// <summary>
/// List all categories
/// </summary>
/// <remarks>
/// This request is good to list all existing categories
/// </remarks>
/// <returns>List of categories in JSON</returns>
/// <response code="200">Request is completed</response>
/// <response code="400">Request is failed</response>
/// <response code="401">Not authorized</response>
/// <response code="403">Not authorized</response>
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[HttpGet("list/all")]
public async Task<IActionResult> GetCategories()
{
    _logger.LogDebug("GET categories are requested");
    var data = await _dbHandler.ListCategoriesAsync();
    if(data == null)
    {
        return BadRequest(new GeneralMessage() { Message = "Listing has failed" });
    }
    _logger.LogInformation("GET categories are completed");
     return Ok(data);
}

Although many people think that commenting functions is irreleveant, I am not agree with them. Besides, I am using OpenAPI (alias Swagger) and the input JSON is built by these commands and function parameters by Visual Studio.

As you can see it is an async function, as it is a web server. It returns with IActionResult interfance. Other functions like Ok, Bad Request or Not found are implementation if this interface. Using them are easier than using direct return codes (in my opinion).

Using database controller object

To use database controller object in this API, what I have discussed earlier, I am using depedency injection. I have setup it in Startup.cs file in ConfigureServices method:

/*-----------------------------------------------------------------------------------*/
/* Database configuration                                                            */
/*-----------------------------------------------------------------------------------*/
string connString = Configuration.GetValue<string>("ConnectionString:Db");
services.AddScoped<IDbHandler, DbHandler>(s => new DbHandler(connString));

As I defined this service on scope level mean that new object will be allocated for every connection which is coming. I adding service with IDbHandler interface, which also help me if I plan some changes in the future (e.g.: change DbHandler class) because I will only need to change this initialization not every controller of mine.

Now let us see, how the object is injected onto different classes. Below you can see my CategoryController.cs file constructor:

private readonly IDbHandler _dbHandler;
private readonly ILogger _logger;

public CategoryController(IDbHandler ticket, ILogger<CategoryController> logger)
{
    _dbHandler = ticket;
    _logger = logger;
}

As you can see, constructor has 2 argument: first one is a IDbHandler interface for my dataconnection object, second one is a logger interface for logging. Later in the script, I can use _dbHandler object to reach my data in database.

Implementing OpenAPI (Swagger)

For easier debug and documentation, I implemented Swagger. The fact that it is already supported by .NET as in-built feature makes it better. I had to make some customization in my projkect because:

  • This API is put behind a proxy, hosted by Apache web server
  • I use the same built output in more environment (sandbox, development and production)
  • I implemented authentication onto my service, so JWT tokens also needs to activated in Swagger

About the server setup and the different environments, you can read in this article. In Startup.cs file, there is a ConfigureServices method. This contains one part of Swagger initialization:

/*-----------------------------------------------------------------------------------*/
/* Swagger config                                                                    */
/*-----------------------------------------------------------------------------------*/
services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("2.0", new OpenApiInfo
    {
        Version = "2.0",
        Title = "Ticketing API Swagger UI",
        Description = "This is a place where the API can be try.",
        Contact = new OpenApiContact
        {
            Name = "Attila Molnár",
            Email = "[email protected]"
        }
    });
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
    {
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] { }
        }
    });

    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    c.IncludeXmlComments(xmlPath);
}); 

At the first look, a bit dense, but it is not in real. The SwaggerDoc part is defining some document information. For example my name, email address, description about this API, etc. Next part, AddSecurityDefinition and AddSecurityRequirement, definition is needed because most of the endpoint can be reach, when user is authenticated itself.

Next part of initialization defines where the swagger endpoint should be. It can be found in Startup.cs file at Configure method.

app.UseSwagger(c =>
{
    c.RouteTemplate = "swagger/{documentName}/swagger.json";
    c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
    {
        swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{Configuration["HttpProtocol"]}://{httpReq.Host.Value}{Configuration["ProxyPrefix"]}" } };
    });
});
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("2.0/swagger.json", "Ticketing API Swagger UI");
    c.RoutePrefix = "swagger";
});

What can be interested at the first look (compare to the default implementation), that I specified OpenApiServer list where I have given a URL. This URL is built by input data from appsettings.json file. I am using it, because depending which environment running the API it can be different.

Multiple appsettings.json files

Originally, there is only one appsettings.json file. But I automatically create new ones for each of my environments. I will show it how. It is defined in Program.cs file. I have created an function which is getting what is the current environment. Default value of _env variable is “Sandbox”:

private static void SetEnvironment()
{
    try
    {
        var config = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", false)
            .Build();
        _env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
    }
    catch (Exception)
    {

        throw;
    }
}

We are able to pass variables to application via environment variables. On my development and production environment, it is done due to my service file and systemd. When the main function runs, it call 2 functions:

public static void Main(string[] args)
{
    SetEnvironment();
    CreateHostBuilder(args).Build().Run();
}

CreateHostBuilder is basically exist, I just added a few extra line to create and/or use json file for the current environment:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureLogging(logbuilder =>
        {
            logbuilder.ClearProviders();
            logbuilder.AddConsole();
            logbuilder.AddTraceSource("Information, ActivityTracing");
        })
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            // Use general and environment specific JSON file
            config.AddJsonFile("appsettings.json");
            config.AddJsonFile($"appsettings.{_env}.json", optional: true);
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

Security

I am using this API only within my LAN, it also cannot be reach outside. Thus regarding the CORS policy I have setup a very “liberal” policy, everything is allowed. For user authentication and authorization I use the built-in implementation. I will not write about because Microsoft already did. If you are interested check this Microsoft article group: Introduction to authorization in ASP.NET Core | Microsoft Docs

Final words

This is how my REST API is built and work. It does what it must and it does not fail. There are some field what I would like to improve in the future, but for now it is okay for me.

Ati

Enthusiast for almost everything which is IT and technology. Like working and playing with different platforms, from the smallest embedded systems, through mid-servers (mostly Linux) and desktop PC (Windows), till the mainframes. Interested in both hardware and software.

You may also like...