[WebApi Tutorial] Library Check – Services and DbContexts

Jamie Taylor1 comment
A WebApi For Books - First Steps - Header ImageSource: https://stocksnap.io/photo/G5UQS7WRFS Copyright 2017: Clem Onojeghuo

Today’s header image was created by Clem Onojeghuo, the original source for the image is available here

Over the next few weeks, I’m going to be building a WebApi project and I hope you’ll join me. The WebApi project that we’ll build together will be a microservice (or sorts) that we can send an HTTP GET request to and it will respond the details of a book from the Discworld canon.

Those who know me will know that I’m a big Terry Pratchett fan, so it seemed like a good idea to base at least one tutorial around the Discworld books.

Our WebApi is going to expose a few GET enpoints, one which takes a book id and one which takes a search string. The Id endpoint will return the book and the search endpoint will return a list of 0 or more books. Each book that is returned will include a truncated list of main characters who make appearances in that book.

We’ll use Entity Framework Core as a ORM and SQLite as the database technology.

I decided on SQLite because I’m doing this cross platform, and thought ‘why not use something new for the database?’

The database records will represent Books and Characters, with a Many to Many relationship between them (meaning that we’ll need a join table).

Before We Begin

As a head’s up, I’ve already uploaded all of the code that we’re about to write to a GitHub repository. You can head over there if you want to take a sneak peak at what we’re going to create in this tutorial.

Data Models

In order to map our problem domain, we’ll need three models:

  • Book
  • Character
  • BookCharacter

We could have named the final model CharacterBook, or JoinTable, or anything really. But BookCharacter seems like it makes the most sense to me – after all, it links a Book to a Character.

Book

The primary key will be an integer,

We could use something else, but Primary Keys are (usually) easier to handle when they’re integers – especially for smaller data sets.

we’ll need to store an ordinal (the release order, because we can’t guarantee that Entity Framework Core will persist them to the database in sequential order), the name of the book, ISBN details (both ISBN-10 and ISBN-13), and a short synopsis. Each of these properties (apart from the primary key and ordinal) will be strings.

Character

This will be the easier of our models to create: we need a primary key (integer) and a character name.

BookCharacter

This will be our join table, and will have two foreign keys:

  • One for the Book Id
  • One for the Character Id
Relationship Diagram

Although the domain model isn’t that complex, here’s a relationship diagram showing how our three data models fit together:

Domain Model
Our domain modal (generated with DbVisualiser)

I’ve included Audit Data in the image, we’ll come back to this in a later post, but we’ll achieve this using Shadow Properties.

Which I’ve written about previously.

One last thing about the books is that the models are designed such that we can swap the data out for any other books. We could just as easily have them represent list a set of development books (swapping characters for authors), or perhaps a list of horror books. The point is that I’ve only chosen Discworld here because I’m familiar with them, but once we’ve gotten to the point of seeding our database (as it will be readonly), we can change the seed data for anything we want.

Prerequisites

There’s a few things you’ll need to know and have installed in order to follow this tutorial.

Software

The most important thing will be that you have the latest version of the .NET Core SDK installed on your development machine. Head over here, to get the latest version for your operating system: https://www.microsoft.com/net/core

On top of that you’ll either need Visual Studio Code and the C# language bindings, or Visual Studio.

If you’re not running Windows, then you won’t be able to use Visual Studio, so you’ll have to get Visual Studio Code and the C# bindings.

If you’re going down the Visual Studio Code route, then I’ve already written about the steps you’ll need to follow.

If you’re going down the Visual Studio route, then you’ll need to visit https://www.visualstudio.com/ to get the latest version. There may be a cost involved, depending on which version you want to install.

Community Edition is free, and fully featured enough to complete this tutorial – so I’d install that version if you don’t have want to buy a license right now.

Creating the WebApi Application

I’ve only briefly covered creating a WebApi application in the past, so this will be a chance for me to cover the process of creating them in greater detail.

If you want to use Visual Studio Code and the Terminal (which you’ll have to do if you’re running MacOS or one of the officially supported Linux distributions), then you should use the section called ‘Visual Studio Code and the Terminal – MacOS, Linux or Windows’.

However, if you are running Windows and would like to use Visual Studio, then please use the section called ‘Visual Studio – Windows Only’.

Visual Studio Code and the Terminal – MacOS, Linux or Windows

So the first thing we need to do is create a directory for the webApiProject

mkdir webApiTutorial
cd webApiTutorial/
view raw shell.sh hosted with ❤ by GitHub

Now we need to add a global.json along with an appsettings.json into that directory

touch global.json
touch appsettings.json
view raw shell.sh hosted with ❤ by GitHub

As a side note: we should always have a global.json file. As Brad Wilson points out:

This is due to how the SDK versions differ:

Brad Wilson tweet on global.json
Direct link to Tweet: https://twitter.com/bradwilson/status/800821160401399808

Once you’ve created these files, open the top level directory (I named it webApiTutorial in the previous steps) with Visual Studio Code.

Initial Directory opened with VS Code
Our initial directory opened with VS Code

In the above image, I’ve already created the src directory but we don’t want to do that yet. You’ll see why in a moment

In our global.json, we’ll need to paste the following code:

{
"projects": [
"src"
],
"sdk": {
"version": "1.0.0-preview2-final"
}
}
view raw global.json hosted with ❤ by GitHub

This tells .NET Core that our project code will be located in the src directory (which we’ll create in a moment), and that we want to target .NET Core version 1.0.0-preview2-final (which means that we can’t use MSBuild).

We’re going with preview-2 here because preview-3 (at the time of writing) is only available for Visual Studio 2017, which some people might not want to install as it hasn’t been full released yet.

In the appsettings.json, we need to paste the following code:

{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}
},
"Data": {
"SqliteConnection":{
"ConnectionString":"Data Source=dwDatabase.db"
}
}
}

This gives .NET Core a heads-up on what we’ll be doing. First, we’re telling .NET Core that we want standard trace logging (which will be outputted to the terminal window once we start sending requests), then we’re telling .NET Core what our connection string is going to be.

Heading back to the terminal, run the following command (assuming that it’s still in the webApiTutorial directory):

yo aspnet
view raw shell.sh hosted with ❤ by GitHub

Choose “WebApi Application” and give it the name “src”.

src is a bit of a silly name, but we’re bootstrapping using yeoman and the files that it gives us (which makes this step quicker). By running yeoman, we’re short circuiting a lot of the project setup.

dwCheckApi yo aspnet command
Here is the output I received from running yo aspnet and choosing Web Api Application

Once yeoman has finished, go back to VS Code and open the following files from within the src directory:

  • Program.cs
  • Startup.cs

Change the namespaces in these files to ‘webApiTutorial’. Then open the ValuesController.cs file, and change the namespace to ‘webApiTutorial.Controllers’

We’ll need it to test that everything went well. But likely delete it, in one of the later posts in this series.

We also need to change the default name of the compiled binary, by default it will be the name of the source directory.

And having a binary called ‘src’ is a little naff.

Add the following to the top of your project.json:

"name": "webApiTutorial"
view raw project.json hosted with ❤ by GitHub

The final step here is to check that everything went well so far. So let’s run the application:

dotnet restore
dotnet watch run
view raw shell.sh hosted with ❤ by GitHub

‘dotnet watch’ will tell Roslyn to watch for file alterations and re-build and re-run the application whenever a file is saved.

Then make a note of the url listed in the output of dotnet run

By default, it comes out as ‘http://localhost:5000’ for me.

and head over to the url + /api/values.

dwCheckApi Initial Get Check
You should receive a response similar to this for a GET request sent to /api/values

If everything went right so far, then you should get a response exactly like the above one. If not, then I’d recommend going back and reading over everything so far.

Visual Studio – Windows Only

It is a lot easier to use Visual Studio to create the project, as we don’t have to use yeoman to create a bunch of files then futz with them.

In Visual Studio go to File > New Project:

Visual Studio - New Project Menu Location
Just like I’m doing here

In the dialogue window that opens, select .NET Core from the left hand portion of the window (this is the templates list), then select ‘ASP.NET Core Web Application (.NET Core)’ from the main portion of the dialogue.

Don’t forget to set the name of the project and solution to ‘webApiTutorial’

Visual Studio - New Project Dialogue
Again, just like I’m doing here

Then click ‘OK’ and in the final dialogue select ‘Web API’ (leaving all other options as they are)

Visual Studio - New Project Wizard
Once we’ve done this, we’re done with the dialogues

You should end up with a project directory structure similar to this (in the Solution Explorer):

Visual Studio - New Project Directory Layout

I added the appsettings.json file to the solutions directory before taking this screen shot, so you’ll need to do that:

  • Right click on the ‘Solution Items’ directory
  • Hover over Add, then click on New Item

Visual Studio - Add New File

There may not be an option for json files in the dialogue that appears, this is a common issue with Visual Studio. Leave the selections as they are and enter ‘appsettings.json’ into the file name.

Visual Studio - Add New File Dialogue
Visual Studio will use the filename and type that you enter to figure out what kind of file it is

The last thing we need to do is add the contents for our global.json and appsettings.json files. As with the section on using VS Code and the Terminal (above), we’re just going to paste the contents of the files in.

In our global.json, we’ll need to paste the following code:

{
"projects": [
"src"
],
"sdk": {
"version": "1.0.0-preview2-final"
}
}
view raw global.json hosted with ❤ by GitHub

This tells .NET Core that our project code will be located in the src directory, and that we want to target .NET Core version 1.0.0-preview2-final (which means that we can’t use MSBuild).

A configuration error can happen here, if you haven’t got the correct version of the SDK installed. If that happens, see the Prerequisites section for details

In the appsettings.json, we need to paste the following code:

{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
}
},
"Data": {
"SqliteConnection":{
"ConnectionString":"Data Source=dwDatabase.db"
}
}
}

This gives .NET Core a heads-up on what we’ll be doing. first, we’re telling .NET Core that we want standard trace logging, then we’re telling .NET Core what our connection string is going to be.

Adding Entity Framework Core

We need to add some things to our project.json to enable us to use Entity Framework Core. Firstly we need to add Entity Framework Core (and it’s SQLite Data Provider) to the dependencies section:

"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.1.0",
"type": "platform"
},
"Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Routing": "1.1.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Configuration.CommandLine": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.EntityFrameworkCore.Design": "1.1.0",
"Microsoft.EntityFrameworkCore": "1.1.0",
"Microsoft.EntityFrameworkCore.Sqlite": "1.1.0",
"Microsoft.EntityFrameworkCore.Sqlite.Design": {
"version": "1.1.0",
"type": "build"
},
"Microsoft.EntityFrameworkCore.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
}
},
view raw project.json hosted with ❤ by GitHub

Here, I’ve added the entirety of the dependencies section of the project.json, and I’ve highlighted the lines that you should add.

We should also add the Entity Framework Core Tools to the tools section:

"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.1.0-preview4-final",
"Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-preview3-final"
},
view raw project.json hosted with ❤ by GitHub

And the last thing you need to do is to change the default namespace prefix. This will help us when we create new classes, as it will set a useful default namespace:

"tooling": {
"defaultNamespace": "webApiTutorial"
},
view raw project.json hosted with ❤ by GitHub

If you used Visual Studio to create the project, this might already be set.

Now that we’ve added our dependencies, we need to restore them after stopping the application (if it’s still running from earlier)

dotnet restore
view raw shell.sh hosted with ❤ by GitHub

Visual Studio should pull the dependencies as soon as you save the project.json, and VS Code might prompt you to. So you might not need to run this command.

Creating and Adding The Book Model

Before we add the Data Context, we need to add our first Data Model class. This is because the Database Context will need to create a DbSet of Book models.

Create a sub-directory (within the src directory) called Models, create a file called Book.cs within it and paste the following code into it:

namespace webApiTutorial.Models
{
public class Book
{
public int BookId { get; set; }
public int BookOrdinal { get; set; }
public string BookName { get; set; }
public string BookIsbn10 { get; set; }
public string BookIsbn13 { get; set; }
public string BookDescription { get; set; }
}
}
view raw book.cs hosted with ❤ by GitHub

That’s about it, really. Pretty simple, eh?

Adding a Database Context

So the first thing we need to do is tell .NET Core what our connection string is. To do that, we’ll need to paste the following code into the appsettings.json file within the src directory:

{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"Data": {
"SqliteConnection":{
"ConnectionString":"Data Source=dwDatabase.db"
}
}
}

Now that we have the connection string, we should go create our DbContext class. Create a directory called ‘DatabaseContexts’ to the src directory, add a file called ‘dwContext.cs’ to that sub-directory, then paste the following code into it:

using Microsoft.EntityFrameworkCore;
using webApiTutorial.Models;
namespace webApiTutorial.DatabaseContexts
{
public class DwContext : DbContext
{
public DwContext(DbContextOptions<DwContext> options) : base(options) { }
public DwContext() { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
public override int SaveChanges()
{
return base.SaveChanges();
}
public DbSet<Book> Books { get; set; }
}
}
view raw dwContext.cs hosted with ❤ by GitHub

The DwContext class will be what we use to communicate with the database. We wont interact with with the context directly from our controllers (we’ll use services for that).

If we need to perform any specific commands when the DbModel is created (for instance, to add Shadow Properties for auditing), we’ll do that in the OnModelCreating method. We’ll also use the SaveChanges method (at a later stage) to apply audit information and persist our seed data to the database.

But we don’t need to worry about those just yet.

Adding a Book Service

As I mentioned in the previous section, our controllers will not interact with the DbContext directly, they’ll use a separate service for each of our models.

We’ll inject our services where they’re needed via .NET Core’s built in support for dependency injection, so we’ll need to start with an Interface.

Create a sub-directory (within the ‘src’ directory) called ‘Services’, add a file called ‘IBookService.cs’ to that sub-directory, and paste the following code into it:

using System.Collections.Generic;
using webApiTutorial.Models;
namespace webApiTutorial.Services
{
public interface IBookService
{
// Search and Get
Book FindByOrdinal (int id);
IEnumerable<Book> Search(string searchKey);
}
}
view raw IBookService.cs hosted with ❤ by GitHub

A quick side note about Interfaces (for those who don’t know), is that they define the public methods that a class which implements it MUST implement.

Now create a file called ‘BookService.cs’ (again, in the Services directory) and paste the following into it:

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using webApiTutorial.DatabaseContexts;
using webApiTutorial.Models;
namespace webApiTutorial.Services
{
public class BookService : IBookService
{
private DwContext _dwContext;
public BookService (DwContext dwContext)
{
_dwContext = dwContext;
}
public Book FindByOrdinal (int id)
{
return BaseQuery()
.FirstOrDefault(book => book.BookOrdinal == id);
}
public IEnumerable<Book> Search(string searchKey)
{
var blankSearchString = string.IsNullOrWhiteSpace(searchKey);
var results = BaseQuery();
if (!blankSearchString)
{
searchKey = searchKey.ToLower();
results = results
.Where(book => book.BookName.ToLower().Contains(searchKey)
|| book.BookDescription.ToLower().Contains(searchKey)
|| book.BookIsbn10.ToLower().Contains(searchKey)
|| book.BookIsbn13.ToLower().Contains(searchKey));
}
return results.OrderBy(book => book.BookOrdinal);
}
private IEnumerable<Book> BaseQuery()
{
return _dwContext.Books.AsNoTracking();
}
}
}
view raw BookService.cs hosted with ❤ by GitHub

There’s an awful lot happening here, so let’s take a deeper look at the file in chunks:

private DwContext _dwContext;
public BookService (DwContext dwContext)
{
_dwContext = dwContext;
}
view raw BookService.cs hosted with ❤ by GitHub

Here we’re making use of .NET Core’s dependency injection (we’ll configure it in a moment), to supply us with an instance of the DwContext object (which contains our DbSets).

private IEnumerable<Book> BaseQuery()
{
return _dwContext.Books.AsNoTracking();
}
view raw BookService.cs hosted with ❤ by GitHub

Here we’re creating a base (read-only) query for all of our other methods to use. This is useful because we don’t have to repeat ourselves when writing our queries.

All entities found using this base query will not have their changes tracked.

We’ll come to the Entity Framework Change Tracker in a later part of this tutorial series

So if we want to update any database entities, we’ll have to use a different query to first get the data, alter it in some way, then save the changes to them.

public Book FindByOrdinal (int id)
{
return BaseQuery()
.FirstOrDefault(book => book.BookOrdinal == id);
}
public IEnumerable<Book> Search(string searchKey)
{
var blankSearchString = string.IsNullOrWhiteSpace(searchKey);
var results = BaseQuery();
if (!blankSearchString)
{
searchKey = searchKey.ToLower();
results = results
.Where(book => book.BookName.ToLower().Contains(searchKey)
|| book.BookDescription.ToLower().Contains(searchKey)
|| book.BookIsbn10.ToLower().Contains(searchKey)
|| book.BookIsbn13.ToLower().Contains(searchKey));
}
view raw BookService.cs hosted with ❤ by GitHub

Here we’re creating our two service methods for finding a book entity by an ordinal and for searching through the book records.

Adding Services and DbContexts via Dependency Injection

The final thing we need to do, in order to use our Service (and by extension the DbContext) in any controllers that we create is to alter the Startup.cs class.

The Startup.cs class contains all of the code required to get Kestrel (.NET Core’s web server) up and running, it’s called from within the Program.cs file at line 22:

.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
view raw program.cs hosted with ❤ by GitHub

Open the Startup.cs class and paste the following changes into it:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using webApiTutorial.DatabaseContexts;
using webApiTutorial.Services;
view raw startup.cs hosted with ❤ by GitHub

Before we can reference our types, we need to reference them. This isn’t too exciting, but the next thing might be:

public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Give ourselves access to the DwContext
services.AddDbContext<DwContext>(options =>
options.UseSqlite(Configuration["Data:SqliteConnection:ConnectionString"]));
// DI our Book service into our controllers
services.AddTransient<IBookService, BookService>();
}
view raw startup.cs hosted with ❤ by GitHub

The first thing we’re doing here is we’re adding a reference to our DwContext class, and telling .NET Core which key within the appsettings.json file contains our connection string.

The next thing we’re doing is mapping the interface IBookService to the concrete type BookService using AddTransient. Using AddTransient rather than any of the other methods for mapping (we’ll go into them in a moment) means that a new instance of each concrete class will be spun up for each object that requires one – this is called the object’s lifetime.

For example: if we had four controllers (actively responding requests) which used the BookService, we would end up with 4 instances of the BookService in memory.

Depending on the lifetime you want for your DI objects, you could use one of the following methods:

Transient – Transient lifetime services are created each time they are requested. This lifetime works best for lightweight, stateless services.
Scoped – Scoped lifetime services are created once per request.
Singleton – Singleton lifetime services are created the first time they are requested (or when ConfigureServices is run if you specify an instance there) and then every subsequent request will use the same instance.

Taken from the .NET Core documentation on Dependency Injection

Using Our Service – An Example

We’ll create our own controllers in the next part of this tutorial series, but we’ll use the Values controller in order to show how to use an instance of the service for now.

You don’t have to follow this part, I’m just going to show off how you might use an injected service in preparation for next time.

Open the Values Controller (found in the ‘Controllers’ sub-directory) and paste the following code blocks into it.

First, the using statement (important, but often over looked):

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using webApiTutorial.Services;

Next, we want to create a constructor, so that we can inject our dependencies into the class, and a private variable for our service instance:

public class ValuesController : Controller
{
private IBookService _bookService;
public ValuesController(IBookService bookService)
{
_bookService = bookService;
}

And finally, we’re going to use the BookService that was injected into our controller:

// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
if (_bookService != null)
{
return new string[] { "Book service is ready" };
}
return new string[] { "value1", "value2" };
}

If you now do a GET request for /api/values you should get the following response:

dwCheckApi - Service is Ready

I think we’ll leave it there for now

Mainly because this article is now 3000 words long, and I can’t imagine how tedious it must be to read it by this point.

Next time, we’ll create the database (using Entity Framework Core), seed some data into the database, get a record from the database and send it back as a response.

Exciting, or what?

If this post has helped you out, please consider   Buy me a coffeeBuying me a coffee
Jamie Taylor
A .NET developer specialising in ASP.NET MVC websites and services, with a background in WinForms and Games Development. When not programming using .NET, he is either learning about .NET Core (and usually building something cross platform with it), speaking Japanese to anyone who'll listen, learning about languages, writing for his non-dev blog, or writing for a blog about video games (which he runs with his brother)