DEV Community

Cover image for Creating a .NET Core API
Matt Eland
Matt Eland

Posted on • Updated on

Creating a .NET Core API

While this article is technically part of my series in a fun gamedev lite side project I'm working on, my activities on the project this evening made for a good opportunity to share how to create a new ASP .NET Core Web API.

This article will walk you through some simple steps in creating, running, and testing a new ASP .NET Core Web API.

Prerequisites

I will be using .NET Core 2.1 as it's what I have installed on my machine, though today Release Candidate 1 of .NET Core 3 came out.

To get started:

Project Setup

Open Visual Studio 2019 and create a new project.

When prompted, choose ASP .NET Core Web Application and click Next.

New Project Dialog

Give your project a meaningful name. The solution name will auto-generate itself.

Name your project

Next Visual Studio will ask you what starting template you want to choose. These choices do not exclude you from going down other paths later. For now, we'll choose API and uncheck all the boxes on the right for the purposes of a simple demo application.

Select API Project

Click Create and your project should be created and opened.

Running the API

To verify that everything worked properly, go to the Debug menu on the top of the screen and click Start without Debugging. This will launch a web browser and give you a blank web page with the text ["value1", "value2"].

Believe it or not, this means everything is working. Your browser navigated to the ValuesController class and hit its HTTP GET route, which returned that content.

Here's a snippet of ValuesController.cs located in the Controllers folder:

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // Other code omitted...
}
Enter fullscreen mode Exit fullscreen mode

Here the browser navigated to /api/values which matched to ValuesController by name prefix (see the Route attribute on the ValuesController class). Inside of this controller, we mapped to the Get method listed above because the method used was GET (browser navigation does a GET request) and we weren't looking any further into api/values.

Because of this, ASP .NET Core ran the Get() method and returned a 200 OK result with the content defined in the string array in the listing above.

So, cool! Our code works. Now it's time to set up for some deeper development.

Adding Projects to the Solution

The first thing I like to do when adding a new project is create two new library projects and add them to the solution. The first will be a library to hold all of our application logic and the second will be a unit test library.

In Solution Explorer right click on your solution (the topmost item that contains the project) and choose Add and then New Project inside of that.

Add Project

Select Class Library (.NET Standard), Click Next, give it a meaningful name (I named mine MattEland.Starship.Logic) and click Create.

Now that the library is created, we'll right click on the main Web API project in Solution Explorer, select Add and then select Reference. From here, we'll check the name of the library we added and click OK.

This allows the main API project to use code defined in the library, which helps separate the API-specific logic from the domain logic and makes it easier to port the application logic over to a console, desktop, or mobile application if that is ever needed.


Now, click on the solution explorer and add another new project. This time we'll select either a new XUnit test project or an NUnit test project. For the purposes of this tutorial, I'll be demonstrating using the NUnit Test Project (.NET Core) template.

Name that project whatever you wish (mine is MattEland.Starship.Tests) and click Create.

Next we'll right click on the test project and add dependencies like we did above. This time we'll add a dependency to both the library and the web application. This way our tests can invoke methods directly on the controller for integration testing.

Adding classes to the library

Next, let's create a few sample domain classes and put them in our logic library. With the project selected, right click and click Add and then Class...

From here, leave the selection as a Class, but give it a meaningful name. Mine is going to be GameState.cs to represent the state of a turn-based game.

Put some simple code inside of this class - enough to test a simple object structure.

My data will be the following:

namespace MattEland.Starship.Logic
{
    public class GameState
    {
        public GameState(int id)
        {
            Id = id;
        }

        public int Id { get; }
        public int ClosedCount { get; set; }
    }
}

Enter fullscreen mode Exit fullscreen mode

I'm also going to create a GameRepository to store the GameState instances. This class is what our controller will interact with.

A very simple demo-oriented repository is listed below:

using System.Collections.Generic;
using System.Linq;

namespace MattEland.Starship.Logic
{
    public class GameRepository
    {
        private readonly IList<GameState> _games = new List<GameState>();

        public GameRepository()
        {
            // Start with some sample data
            CreateNewGame();
        }

        public IEnumerable<GameState> Games => _games;

        public GameState GetGame(int id) => _games.FirstOrDefault(g => g.Id == id);

        public GameState CreateNewGame()
        {
            int id = _games.Count + 1;
            var game = new GameState(id);
            _games.Add(game);

            return game;
        }

        public bool DeleteGame(int id)
        {
            var game = _games.FirstOrDefault(g => g.Id == id);

            return game != null && _games.Remove(game);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now that we have some basic logic and a repository class to manage operations, lets see how this plugs in with the controller.

Creating our first controller

Next, let's delete the ValuesController.cs file (or leave it in if you want to keep it as a reference) and add a new controller to the Web API project. In my case, this is called GamesController to manage various game states available.

This class will hold on to a new instance of the repository class we created before and relay operations to it.

My controller is listed below:

using System.Collections.Generic;
using MattEland.Starship.Logic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace MattEland.Starship.ProcessingService.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class GamesController : ControllerBase
    {
        private readonly GameRepository _repository = new GameRepository();

        // GET api/games
        [HttpGet]
        public ActionResult<IEnumerable<GameState>> LoadGame()
        {
            return Ok(_repository.Games);
        }

        // GET api/games/42
        [HttpGet("{id}")]
        public ActionResult<GameState> LoadGame(int id)
        {
            var game = _repository.GetGame(id);

            if (game != null)
            {
                return Ok(game);
            }

            return new NotFoundResult();
        }

        // POST api/games
        [HttpPost]
        public CreatedResult NewGame()
        {
            var game = _repository.CreateNewGame();

            return new CreatedResult($"/api/games/{game.Id}", game);
        }

        // DELETE api/games/42
        [HttpDelete("{id}")]
        public StatusCodeResult Delete(int id)
        {
            bool deleted = _repository.DeleteGame(id);

            if (deleted)
            {
                return new StatusCodeResult(StatusCodes.Status204NoContent);
            }

            return new NotFoundResult();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Note that I define a standard GET method to GET all games as well as a specific GET method for getting a single game by its ID. These methods are differentiated by the parameters fed in to the HttpGet attribute with the get specific game one taking in a variable {id} that is mapped in as the int id parameter.

Also note that I define HttpPost and HttpDelete verbs for methods to create a new game and to delete an existing game.

Note again that this is extremely minimal. In a real application I'd have things like request validation and error handling baked into the API layer (if not handled by middleware).

Testing it in the browser

Now that the logic is all ready, you'd think we could just run without debugging and see our new response, but remember that Visual Studio navigated to the /api/values path on startup last time. This is because the project's debug settings are configured to look at that path.

We can change the default path by going to the Web API project's properties node and double clicking that, then choosing the Debug tab, then changing the URL in the Start Browser text box to match the name of your new controller.

Debug Start Path

Once everything is configured and you've saved the project (File > Save All), Run without Debugging and verify you see data as you'd expect.

In my case, I see: [{"id":1,"closedCount":0}] which looks correct based on my simple object definition.

At this point you can make HTTP requests against your local instance and it will respond with the appropriate response.

Testing it via code

I like to have a bit more security in testing my applications than having to manually test every API call, so I like to have at least one or two integration level tests that simulate direct calls on the Controller classes. The majority of my tests will be unit tests aimed at things like GameState or GameRepository, but it is helpful to test that the API logic is functioning as well.

In the UnitTest1.cs (which you can rename as you like), I'm going to modify the existing test to read as follows:

using MattEland.Starship.Logic;
using MattEland.Starship.ProcessingService.Controllers;
using NUnit.Framework;

namespace Tests
{
    public class Tests
    {
        [Test]
        public void Test1()
        {
            // Arrange
            var controller = new GamesController();

            // Act
            var result = controller.NewGame();

            // Assert
            Assert.IsNotNull(result);
            GameState state = (GameState) result.Value;
            Assert.AreEqual(2, state.Id);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This invokes the NewGame method on my controller (a HTTP POST verb in my case) and examines the result to see that a new game was created and returned and it had an ID consistent with what I'd expect.

Note that in order to support referencing the controller directly I had to follow a Visual Studio action suggestion and add a reference to Microsoft.AspNetCore.Mvc.Core.

I also found my tests were initially failing until I added a NuGet reference to Microsoft.AspNetCore.MVC.Abstractions. Do this by right clicking on the test project in Solution Explorer and choosing Manage NuGet Packages... and then searching for the assembly and clicking Install with it selected.

Adding a NuGet Reference

From here, you can run your test by clicking on Test in the upper menu, then Windows, then File Explorer. This will make the testing pane available and you can click the test case to run and run the selected test.

Test Results

Note: Your UI may look different than mine. I use ReSharper which adds on extra testing tools in the user interface

A simple unit test around the GameRepository starting with state inside of it might look like this:

using System.Linq;
using MattEland.Starship.Logic;
using NUnit.Framework;

namespace Tests
{
    public class RepositoryTests
    {
        [Test]
        public void RepositoryShouldStartWithGameState()
        {
            // Arrange
            var repository = new GameRepository();

            // Act
            var games = repository.Games;

            // Assert
            Assert.Greater(games.Count(), 0);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Closing

In this article we created a new API project, a shared logic library, and a test project and verified that everything functioned properly.

While there's still so much in this example that is very basic and could be drastically improved, this should help you get started. Stay tuned for subsequent articles on optimizing this application and dealing with common scenarios. There's a lot to learn in ASP .NET Core, but it's a fantastic platform for API development.

Top comments (0)