Building an API using dot net core and Visual Studio Code

5 minute read

This tutorial was a great place to start before building my first dot not core application. What follows are my notes to come back to when I have to build an API in the future using dot net core. Getting started with a brand new project is made easy using the CLI as explained in the above tutorial.

Controllers and Routes

Controllers contain routes and the functions to call for each of those routes. The automatically created Controllers/ValuesController.cs contains the typical routes for a REST endpoint to carry out all the CRUD operations. All of this is very well explained in the above mentioned tutorial.

Environments

These docs show how to set and use environment variables to determine which environment the application is running in.

At a high level, the three most common environments are Development, Staging and Production. During development, this is automatically set in .vscode/launch.json.

...
"configurations": [
  {
    ...
    "env": {
        "ASPNETCORE_ENVIRONMENT": "Development"
    },
    ...
  }
]

On IIS, here is the way to set this. Below are the steps from that answer:

  • Go to your application in IIS and choose Configuration Editor.
  • Select Configuration Editor
  • Choose system.webServer/aspNetCore in Section combobox
  • Choose Applicationhost.config … in From combobox.
  • Click on enviromentVariables element and open edit window.
  • Set your environment variables - ASPNETCORE_ENVIRONMENT to Development, Staging or Production.
  • Close the window and click Apply.
  • Done

This environment variable can be used to inject the different application settings or services based on Startup.cs as shown later in this post.

Application settings

There should already be appsettings.json and appsettings.Development.json files. Reading values off these files into the code is good practice instead of hard-coding values into the code itself. This helps easily change these values for different environments by using different files - appsettings.Staging.json and appsettings.Production.json. Values in appsettings.json are common and for all environments but will be overwritten if they are set again in an environment specific settings files. Another advantage of doing this is that changing these values won’t require us to rebuild the application binaries. To use these values in the code, they first have to be loaded into the application during startup. Configuration is made static so it can be used to read values into controllers and services.

Startup.cs

...
public class Startup
{
  string currentEnv { get; set; }
  public Startup(IHostingEnvironment env)
  {
    currentEnv = env.EnvironmentName;

    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();
    Configuration = builder.Build();
  }

  // Make this static so it can be used to read values into controllers and services
  public static IConfiguration Configuration;
  ...
}

Here’s how to set values in these json files.

appsettings.json

{
  ...
  "baseUrl": "http://localhost:5000",
  "Emails": {
    "host": "gmail.com"
  },
  ...
}

Then, these values can be used in the code as shown below.

private string smtpHost = Startup.Configuration["Emails:host"];
private string baseUrl = Startup.Configuration["baseUrl"];

Services

Most of the application logic goes into Services. The controllers merely handle incoming requests and use the appropriate services to handle those requests.

It is helpful to create an interface for each service and create two implementations of it, especially when the app has to integrate with external services. One of the implementations will be used for mocking this service and the other would be the actual implementation. For example, here’s an example where an Email service is implemented to send reminder emails.

Services/IEmailService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace values.Services
{
  public interface IEmailService
  {
    void SendReminderEmail(string toAddress, string message);
  }
}

Services/MockEmailService.cs

using System;

namespace values.Services
{
  public class MockEmailService : IEmailService
  {
    public void SendReminderEmail(string toAddress, string message)
    {
        Console.WriteLine($"Mock - Send reminder email to {toAddress} with message {message}");
    }
  }
}

The actual logic then goes into Service/EmailService.cs similar to above.

Usage

First, inject the appropriate implementation of the service based on the environment the application is running in.

Startup.cs

...

public class Startup
{
  string currentEnv { get; set; }
  public Startup(IHostingEnvironment env)
  {
    currentEnv = env.EnvironmentName;
    ...
  }

  // This method gets called by the runtime. Use this method to add services to the container.
  public void ConfigureServices(IServiceCollection services)
  {
    ...
    if (currentEnv == "Staging" || currentEnv == "Production")
    {
      services.AddTransient<IEmailService, EmailService>();
    }
    else 
    {
      services.AddTransient<IEmailService, MockEmailService>();
    }
  }
}

The preferred way to check the environment seems to be env.IsEnvironment("environmentname") but I wasn’t able to pass IHostingEnvironment to ConfigureServices() and I was in too much of a hurry at that time to investigate further.

Here’s how the service can then be used in a controller.

public class TestController : Controller
{
  IEmailService _emailService;
  public TestController(IEmailService emailService)
  {
      _emailService = emailService;
  }
  
  [HttpGet("api/testEmail")]
  public IActionResult TestEmail()
  {
      _emailService.SendReminderEmail("to@gmail.com", "Test email!");
      return Ok();
  }
}

Logging

Here’s one way of adding logging to the application. It is so easy and saves so much of time debugging errors in Staging or Production. Just make sure to install the latest version of this package. To log to a file, put this is an appsettings file.

{
  "Logging": {
    "PathFormat": "Logs/appName-{Date}.txt",
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Error"
    }
  },
  ...
}

And load these settings.

Startup.cs

using Microsoft.Extensions.Logging;
...

public class Startup
{
  ...
  // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  {
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();
    loggerFactory.AddFile(Configuration.GetSection("Logging"));
    ...
  }  
}

Then use in any controller like so:

TestController.cs

using Microsoft.Extensions.Logging;
...


public class TestController : Controller
{
    ILogger<TestController> _logger;
    public TestController(ILogger<TestController> logger)
    {
        _logger = logger;
    }

    [HttpGet("api/hello")]
    public IActionResult Hello()
    {
        _logger.LogDebug("Hello");
        return Ok("Hello");
    }
}

Useful packages

One easy way to install nuget packages into the project using Visual Studio code is by using this NuGet Package Manager. Once that is installed, just fire it up using Ctrl+P, search for “>nuget” and select “NuGet Package Manager: Add Package”. Then search for a package, select a package and version from the list. This will add the package to the .csproj file and prompt you to Restore nuget packages doing which will install the package.

Here are some useful packages I have used.

  1. Sending emails
  2. Logging package used above
  3. FTP client
  4. HTTP client
  5. Evaluating Math expressions

More reading

  1. Why using AddMvcCore() is better?

Following all of these left me with a well organized project - logging, different settings for different environments, the core application logic nicely tucked away in services and also the ability to mock third party integrations while developing the core logic. They will also come in handy when I write some tests one day.

Leave a Comment