[WebApi Tutorial] Library Check – Extra Controllers and POCO View Models

WebApi Tutorial – Extra Controllers and POCO View Models

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

Stop!

Hammer time?

This is the third in a series of blog posts related to building a WebApi application.

If you haven’t read the previous parts, I’d recommend starting there.

Let’s Go

If you remember, last time we built a basic WebApi application, added Entity Framework Core, added a DbContext, and a service. In this post we’ll build on that application.

What We’ll Be Doing

In this post we’ll:

  • Create the Character class
  • Create the Character service
  • Create the Controller to handle GET requests for Characters
  • Do a little refactoring
  • Create the static POCO to transform Character data models into Character view models
  • Create the static POCO to transform Book data models into Book view models

As with the previous parts in this series, I’ve already uploaded all of the code 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.

Also, as with the previous part in the series, I’m not going to be including Visual Studio specific steps for this tutorial.

You can read about why, here.

Running the Service

The first thing I would recommend that you do is make sure that the code from last time still runs. So cd into the src directory and issue the run command:

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

Then head over to ‘http://localhost:5000/Books/Get/1’ in your browser:

webApiTutorial response success
The JSON response for our Book record

If something doesn’t go quite right, then take a quick look at the the previous part of this series, or clone the code from the GitHub repository for this part of the series.

Creating The Character Data Model

Cast your mind back to the first part in this tutorial series and you might remember our entity relationship diagram:

Domain Model
Our domain modal (generated with DbVisualiser)

Ignoring the Books and BookCharacters tables, our Character table looks really quite simple: just a primary key (which is an integer) and a string.

As long as you ignore the audit fields (Created and Modified) for now, too. We’ll get to them next time.

Over in the Models directory, you’ll need to add a file called “Character.cs” and paste the following code into it:

namespace webApiTutorial.Models
{
public class Character
{
public int CharacterId { get; set; }
public string CharacterName { get; set; }
}
}
view raw Character.cs hosted with ❤ by GitHub

We’ve seen all of this before (in the form of our Book model), so let’s go build the Service.

Creating The Character Service

The Character service will be quite similar to the Book service. Just like the Book service, we’ll need an interface (ICharacterService) so that we can consume it via Dependency Injection and an implementation of it (CharacterService).

Head over to the Services directory, create a file called “ICharacterService.cs” and paste the following code into it:

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

Then create a file called “CharacterService.cs” and paste the following code into it:

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using webApiTutorial.DatabaseContexts;
using webApiTutorial.Models;
namespace webApiTutorial.Services
{
public class CharacterService : ICharacterService
{
private DwContext _dwContext;
public CharacterService (DwContext dwContext)
{
_dwContext = dwContext;
}
public Character FindById (int id)
{
return BaseQuery()
.FirstOrDefault(character => character.CharacterId == id);
}
public IEnumerable<Character> Search(string searchKey)
{
var blankSearchString = string.IsNullOrWhiteSpace(searchKey);
var results = BaseQuery();
if (!blankSearchString)
{
searchKey = searchKey.ToLower();
results = results
.Where(charatcer => charatcer.CharacterName.ToLower().Contains(searchKey));
}
return results;
}
private IEnumerable<Character> BaseQuery()
{
return _dwContext.Characters.AsNoTracking();
}
}
}
view raw CharacterService.cs hosted with ❤ by GitHub

Almost all of this service is similar to the Book service, except for one key difference: the Book model has an ordinal, whereas our Character model does not.

Because of this key difference, we’re not ordering the results returned from the search method.

Not that it would make much of a difference if we did. Although, we could order them by name if we wanted.

Transforming Our Data Models to View Models

Before we go ahead and create our Character controller, we’re going to do a little refactoring and create some extension methods to create instance of Character View Models from our Character Data Models.

In a real world application, especially where your Data Model has navigation properties,

In Entity Framework, navigation properties represent the foreign key relationships between tables.

you won’t want to pull all of the data related to a record and display it on the view.

Looking at our ERD again for a moment

Domain Model
Our domain modal (generated with DbVisualiser)

We have a foreign key from the Books table to the BookCharacters table (this will be our join table, when we cover those), and one from the Characters table to the BookCharacters join table.

We haven’t covered this yet, but we will in the next tutorial.

So, if we wrote a service to get all of the BookCharacter entries, Entity Framework would pull all of the Character and Book records for each entry in the BookCharacters table. This isn’t so bad in our example, because the data set will be quite small.

But imagine that you have a Data Model which represents Students, Parents, Exams and Grades:

Example Student ERD
DB Diagram created using SQL Server Management Studio

If we wanted to create a page in the system where we listed every student by name, we could write a service that would return all of the student records. But Entity Framework (by default) would give us all of that student exam and parent data, too. That could end up being a massive amount of data to send to the view, if we didn’t strip those navigation properties out.

That’s one of the reasons why we have separate view and data models.

There are other reasons, but for the purposes of this example we’ll focus on that one reason.

So you can (hopefully) agree that it’s good practise to send only the data that we want to display, over to the view. Traditionally, you might achieve this with a library like AutoMapper or any of it’s alternatives. But our data models are quite small and we can do it much quicker by hand writing the conversion methods ourselves rather than relying on AutoMapper, reflection or boxing.

The Character and Book View Models

Our View Models aren’t going to be that dissimilar to the Data Models (but they will change a little once we’ve added the join table), but it’s good practise to separate them out. So let’s do that.

Create a sub-directory within your src directory called “ViewModels”

webApiTutorial - ViewModels Directory
Here is my ViewModels directory, with the files we’re about to create already in it

Within here create a new file called “BaseViewModel.cs”, this will be an abstract class that we’ll use when we refactor our controllers.

But more on that in a moment.

Paste the following code into the BaseViewModel.cs file:

namespace webApiTutorial.ViewModels
{
public abstract class BaseViewModel
{
}
}
view raw BaseViewModel.cs hosted with ❤ by GitHub

Now create a file (in the same ViewModels directory) called “BookViewModel.cs”, and paste the following code into it:

namespace webApiTutorial.ViewModels
{
public class BookViewModel : BaseViewModel
{
public BookViewModel(){}
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; }
public byte[] BookCoverImage { get; set; }
public string BookCoverImageUrl { get; set; }
}
}
view raw BookViewModel.cs hosted with ❤ by GitHub

And finally, do the same for a file called “CharacterViewModel.cs”, pasting the following code into it:

namespace webApiTutorial.ViewModels
{
public class CharacterViewModel : BaseViewModel
{
public CharacterViewModel(){}
public string CharacterName { get; set; }
}
}

Looking at the View Models and comparing them to the Data Models, you should notice that the only differences between them is the lack of the primary key in the View Model. We really don’t need to display this to the user, so we won’t include it.

Instantiating the Character and Book View Models from DB Models

To instantiate our View Models from the Data Models, we’ll use some helper classes.

We could do the instantiation, inline, within the controllers but we’ll extract the logic to Helper methods. That way, if anything changes

Like, for instance, once we’ve added the join table and navigation properties.

then we only have to change the methods that do the instantiation, rather than the controller methods.

For an interesting discussion on how much Business Logic should happen in controllers, see this Stack Exchange question.

To that end we’ll need a new sub-directory within the src directory, this time called “Helpers”.

webApiTutorial - Helpers Directory
Here is my Helpers directory, with the files we’re about to create already in it

Within this directory, create a file called “BookViewModelHelper.cs” and paste the following code into it:

using webApiTutorial.Models;
using webApiTutorial.ViewModels;
using System.Collections.Generic;
using System.Linq;
namespace webApiTutorial.Helpers
{
public static class BookViewModelHelpers
{
public static BookViewModel ConvertToViewModel (Book dbModel)
{
return new BookViewModel
{
BookOrdinal = dbModel.BookOrdinal,
BookName = dbModel.BookName,
BookIsbn10 = dbModel.BookIsbn10,
BookIsbn13 = dbModel.BookIsbn13,
BookDescription = dbModel.BookDescription,
};
}
public static List<BookViewModel> ConvertToViewModels(List<Book> dbModel)
{
return dbModel.Select(book => ConvertToViewModel(book)).ToList();
}
}
}

This static class has two methods: “ConvertToViewModel” and “ConvertToViewModels”.

public static BookViewModel ConvertToViewModel (Book dbModel)
{
return new BookViewModel
{
BookOrdinal = dbModel.BookOrdinal,
BookName = dbModel.BookName,
BookIsbn10 = dbModel.BookIsbn10,
BookIsbn13 = dbModel.BookIsbn13,
BookDescription = dbModel.BookDescription,
};
}

Here we’re creating a single Book View Model instance from a Book Data Model instance. The conversion is pretty simple, we take the Book Data Model and populate the fields on a new Book View Model instance with the data from the Data Model.

I feel like I should write Data Model one more time, here.

public static List<BookViewModel> ConvertToViewModels(List<Book> dbModel)
{
return dbModel.Select(book => ConvertToViewModel(book)).ToList();
}

Here we’re Creating a List of Book View Models from a list of Book Data Models. We do this by using a Lambda expression to iterate over the List of Data Models, passing each into the other method in this class, and populating a List of Book View Models.

If you’re not particularly familiar with Lambda expressions in C#, I’d recommend that you read this DotNetPerls article to get up to speed.

Now do the same for a “CharacterViewModelHelper.cs” file, but paste the following code into it:

using webApiTutorial.Models;
using webApiTutorial.ViewModels;
using System.Collections.Generic;
using System.Linq;
namespace webApiTutorial.Helpers
{
public static class CharacterViewModelHelpers
{
public static CharacterViewModel ConvertToviewModel (Character dbModel)
{
return new CharacterViewModel
{
CharacterName = dbModel.CharacterName
};
}
public static List<CharacterViewModel> ConvertToViewModels(List<Character> dbModels)
{
return dbModels.Select(ch => ConvertToviewModel(ch)).ToList();
}
}
}

This helper class works in much the same way as the BookViewModelHelper class, but by creating new instances of Character View Models from Character Data Models.

The Character Controller

Before we add the Character controller, we need to think about possible duplication of code. We already have a Book controller and that contains the following method:

protected JsonResult ErrorResponse(string message = "Not Found")
{
return Json (new {
Success = false,
Result = message
});
}

But we might want this method available to our Character controller.

Spoiler: we definitely want this method in our Character controller.

So how can we do this? Why not use inheritance and boxing?

Even though we’ll take a slight performance hit due to the nature of boxing.

Base Controller

We can create base controller (which inherits from the MVC Controller class) and we can inherit from this base controller in both the Book and Character controllers.

And any others that we create, going forward.

In the Controllers directory, create a file called “BaseController.cs” file and paste the following code into it:

using webApiTutorial.ViewModels;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace webApiTutorial.Controllers
{
public class BaseController : Controller
{
protected JsonResult ErrorResponse(string message = "Not Found")
{
return Json (new {
Success = false,
Result = message
});
}
protected JsonResult SingleResult(BaseViewModel singleResult)
{
return Json(new {
Success = true,
Result = singleResult
});
}
protected JsonResult MultipleResults(IEnumerable<BaseViewModel> multipleResults)
{
return Json (new {
Success = true,
Result = multipleResults
});
}
}
}
view raw BaseController.cs hosted with ❤ by GitHub

We’ll cover the SingleResult and MultipleResults methods in a moment.

Now we need to tell the Book controller to inherit from our BaseController:

using webApiTutorial.Helpers;
using webApiTutorial.Services;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace webApiTutorial.Controllers
{
[Route("/[controller]")]
public class BooksController : BaseController
{
view raw BookController.cs hosted with ❤ by GitHub

We’ll now need to remove the ErrorResult method from the bottom of the Book controller too, as we’re already declaring it in the BaseController.

SingleResult and MultipleResults

Let’s take a look at the other two methods in the BaseController (SingleResult and MultipleResults)

protected JsonResult SingleResult(BaseViewModel singleResult)
{
return Json(new {
Success = true,
Result = singleResult
});
}
protected JsonResult MultipleResults(IEnumerable<BaseViewModel> multipleResults)
{
return Json (new {
Success = true,
Result = multipleResults
});
}
view raw BaseController.cs hosted with ❤ by GitHub

Each method deals with instances of the BaseViewModel.

Here’s the slightly canny thing about the BaseViewModel: it’s an abstract class that both the BookViewModel and CharacterViewModel inherit from. This means that we can pass in an instance of either the BookViewModel or the CharacterViewModel and, through the magic of inheritance and boxing, the method will just work.

By the way: “Canny” can mean shrewd, wise or intelligent – in case you didn’t know.

Even though (as I’ve mentioned earlier) we’ll take a hit on performance due to the nature of boxing and unboxing. But the data here is relatively small, so we should be ok.

Let’s use it in our BooksController (before creating the CharacterController). You’ll need to alter your GetByOrdinal and Search methods within the BooksController to match the following:

[HttpGet("Get/{id}")]
public JsonResult GetByOrdinal(int id)
{
var book = _bookService.FindByOrdinal(id);
if (book == null)
{
return ErrorResponse("Not found");
}
return SingleResult(BookViewModelHelpers.ConvertToViewModel(book));
}
[HttpGet("Search")]
public JsonResult Search(string searchString)
{
if (string.IsNullOrWhiteSpace(searchString))
{
return ErrorResponse("Search string cannot be empty");
}
var books = _bookService.Search(searchString);
if (!books.Any())
{
return ErrorResponse("Cannot find any books with the provided search string");
}
return MultipleResults(BookViewModelHelpers.ConvertToViewModels(books.ToList()));
}
view raw BookController.cs hosted with ❤ by GitHub

Character Controller

Create a new file called “CharactersController.cs” in the Controllers directory, and paste the following code into it:

using webApiTutorial.Helpers;
using webApiTutorial.Services;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace webApiTutorial.Controllers
{
[Route("/[controller]")]
public class CharactersController : BaseController
{
private ICharacterService _characterService;
public CharactersController(ICharacterService characterService)
{
_characterService = characterService;
}
// Get/5
[HttpGet("Get/{id}")]
public JsonResult Get(int id)
{
var character = _characterService.FindById(id);
if (character == null)
{
return ErrorResponse("Not found");
}
return SingleResult(CharacterViewModelHelpers.ConvertToviewModel(character));
}
[HttpGet("Search")]
public JsonResult Search(string searchString)
{
if (string.IsNullOrWhiteSpace(searchString))
{
return ErrorResponse("Search string cannot be empty");
}
var characters = _characterService.Search(searchString);
if(!characters.Any())
{
return ErrorResponse("Cannot find any characters with the provided search string");
}
return MultipleResults(CharacterViewModelHelpers.ConvertToViewModels(characters.ToList()));
}
}
}

Pretty much everything we’re now doing in the CharactersController matches what we’re doing in the BooksController. The only differences being the services we’re calling (and the models we’re dealing with) and that the fact that the CharactersController doesn’t get by Ordinal.

Injecting the Character Service

In our CharacterController, we’ve relying on dependency injection to get an instance of the ICharacterService. We’d better make sure that .NET Core has access to the ICharacterService. So head over to your Startup.cs file and add it:

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>();
services.AddTransient<ICharacterService, CharacterService>();
}
view raw Startup.cs hosted with ❤ by GitHub

I’ve highlighted the line you need to add.

Adding Another Migration

Because we’ve changed the model (we’ve added the Character class), we need to add a new migration. Before we do that, we’d better make sure that there aren’t any build errors.

In the terminal, cd into the “src” directory and issue the following command:

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

Assuming that there are no build errors,

And if there are, take another look through the above steps. I’m writing the code as we go along here, so everything should work if you’ve pasted the code in as it is.

issue the following command to add our migration:

dotnet ef migrations add AddedCharacterModel
view raw shell.sh hosted with ❤ by GitHub

This will add a new migration to your Migrations directory which should look like this:

using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace webApiTutorial.Migrations
{
public partial class AddedCharacterModel : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Characters",
columns: table => new
{
CharacterId = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
CharacterName = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Characters", x => x.CharacterId);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Characters");
}
}
}

Now that we’ve added our migration, we need to tell Entity Framework Core to apply the migration, so run the following command:

dotnet ef database update
view raw shell.sh hosted with ❤ by GitHub

This will do the magic of converting the Migration into SQLite statements, which will be run against our database.

Seeding the New Data

The last thing we need to do before we run the application is to ensure that we seed our Characters table. If you take a look at our “dwExtensions.cs” file, you should see a method called EnsureSeedData. We’re going to edit that method to seed our character data.

Remember this method is called whenever we start the application, to ensure that we have some data.

You’ll need to paste the following into the EnsureSeedData method:

public static void EnsureSeedData(this DwContext context)
{
if (context.AllMigrationsApplied())
{
if(!context.Books.Any())
{
context.Books.AddRange(GenerateAllBookEntiies());
}
if (!context.Characters.Any())
{
context.Characters.AddRange(GenerateAllCharacterEntities());
}
context.SaveChanges();
}
}
view raw dwExtensions.cs hosted with ❤ by GitHub

I’ve highlighted the lines that you need to change.

The last thing we need to do is create the GenerateAllCharacterEntities method (which the EnsureSeedData method will now call). This method exists at the bottom of the dwExtensions class (but you can paste it anywhere within that class) and looks like this:

private static List<Character> GenerateAllCharacterEntities()
{
return new List<Character>() {
new Character {
CharacterName = "Rincewind",
CharacterOrdinal = 1
}, new Character{
CharacterName = "Two-flower",
CharacterOrdinal = 2
}, new Character {
CharacterName = "Death",
CharacterOrdinal = 3
}
};
}
view raw dwExtensions.cs hosted with ❤ by GitHub

As with the Books table, we’re only going to seed a subset of the data for now.

Once you’ve added these all that’s left to do is run the application and check out our hard work:

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

This should start the application, and you should see a bunch of SQLite commands go by (which will be seeding our Characters table). Once the application has started, point your browser at “localhost:5000/Characters/Get/1”

Swapping out “localhost:5000” for the host and port that .NET Core give you (the default is localhost:5000)

And you should get a response that looks like this:

webApiTutorial - Character Get resposnse
You should get a response similar to this one. Although EF Core doesn’t guarantee the order in which the data is persisted, so the actual Character Name might be one of the the other Seeded entries.

Taking a look at the database itself, you should be able to see the new table and the seed data:

webApiTutorial - View of Characters Table with Seed data
DB Browser showing off our Character table with the seed data

Again, Entity Framework doesn’t guarantee that the data we’ve added to the DbSet (in the GenerateAllCharacterEntries method) will be persisted to the database in the same order that we added them to the DbSet. So yours might be in a slightly different order.

Conclusion

We’ve added a new service, Data Model type, Controller and View Models. We’ve added Helper methods to create instances of the relevant view models from a given data model. We’ve refactored our controllers to include a base controller and we’ve seeded our database with more data.

Not necessarily all in that order, though.

Next time, we’ll take a look at adding Shadow Properties, how EF Core handles Many to Many relationships and a better way to seed the database.

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 Retro Gaming (which he runs with his brother)