Skip to content

Instantly share code, notes, and snippets.

@GaProgMan
Last active February 16, 2017 20:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GaProgMan/a8e8f8caccefe8c6e19d29a493843886 to your computer and use it in GitHub Desktop.
Save GaProgMan/a8e8f8caccefe8c6e19d29a493843886 to your computer and use it in GitHub Desktop.
Code listings for the blog post /2017/02/16/webapi-tutorial-library-check-extra-controllers-and-poco-view-models
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");
}
}
}
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
});
}
}
}
namespace webApiTutorial.ViewModels
{
public abstract class BaseViewModel
{
}
}
using webApiTutorial.Helpers;
using webApiTutorial.Services;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
namespace webApiTutorial.Controllers
{
[Route("/[controller]")]
public class BooksController : BaseController
{
private IBookService _bookService;
public BooksController(IBookService bookService)
{
_bookService = bookService;
}
// Get/5
[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()));
}
}
}
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; }
}
}
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();
}
}
}
namespace webApiTutorial.Models
{
public class Character
{
public int CharacterId { get; set; }
public string CharacterName { get; set; }
}
}
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()));
}
}
}
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();
}
}
}
namespace webApiTutorial.ViewModels
{
public class CharacterViewModel : BaseViewModel
{
public CharacterViewModel(){}
public string CharacterName { get; set; }
}
}
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();
}
}
}
using webApiTutorial.Services;
using Microsoft.AspNetCore.Mvc;
namespace webApiTutorial.Controllers
{
[Route("/[controller]")]
public class BooksController : Controller
{
private IBookService _bookService;
public BooksController(IBookService bookService)
{
_bookService = bookService;
}
// Get/5
[HttpGet("Get/{id}")]
public JsonResult GetByOrdinal(int id)
{
var book = _bookService.FindByOrdinal(id);
if (book == null)
{
return ErrorResponse("Not found");
}
return Json(new {
Success = false,
Result = new {
id = book.BookId,
ordinal = book.BookOrdinal,
name = book.BookName,
isbn10 = book.BookIsbn10,
isbn13 = book.BookIsbn13,
description = book.BookDescription
}
});
}
protected JsonResult ErrorResponse(string message = "Not Found")
{
return Json (new {
Success = false,
Result = message
});
}
}
}
using webApiTutorial.Models;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace webApiTutorial.DatabaseContexts
{
public static class DatabaseContextExtentsions
{
public static bool AllMigrationsApplied(this DwContext context)
{
var applied = context.GetService<IHistoryRepository>()
.GetAppliedMigrations()
.Select(m => m.MigrationId);
var total = context.GetService<IMigrationsAssembly>()
.Migrations
.Select(m => m.Key);
return !total.Except(applied).Any();
}
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();
}
}
private static List<Book> GenerateAllBookEntiies()
{
return new List<Book>() {
new Book {
BookName = "The Colour of Magic",
BookOrdinal = 1,
BookIsbn10 = "086140324X",
BookIsbn13 = "9780552138932",
BookDescription = "On a world supported on the back of a giant turtle (sex unknown), a gleeful, explosive, wickedly eccentric expedition sets out. There's an avaricious but inept wizard, a naive tourist whose luggage moves on hundreds of dear little legs, dragons who only exist if you believe in them, and of course THE EDGE of the planet ...",
}
};
}
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
}
};
}
}
}
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);
}
}
dotnet build
dotnet ef migrations add AddedCharacterModel
dotnet ef database update
dotnet run
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;
namespace webApiTutorial
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
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>();
}
// 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();
app.UseMvc();
// seed the database using an extension method
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<DwContext>();
context.Database.Migrate();
context.EnsureSeedData();
}
}
}
}
// Run the project to ensure that it works
// Open the database in DB browser for SQLite to see current data model
// Add Character Class to Models directory (don't forget ordinal)
// Add Character DbSet to DwContext.cs
// Create GenerateAllCharatcers method in DwExtensions.cs
// Call GenerateAllCharatcers method in Seed method of DwExtensions.cs, in this way:
if (context.AllMigrationsApplied())
{
if(!context.Books.Any())
{
context.Books.AddRange(GenerateAllBookEntiies());
}
if (!context.Characters.Any())
{
context.Characters.AddRange(GenerateAllCharacterEntities());
}
context.SaveChanges();
}
// If we run at this point (without creating a migration), we'll get a runtime error at "context.Database.Migrate();" because we have pending migrations
// Create migration to add Characters table (dotnet ef migrations add AddedCharatcerModel)
// Apply migration (dotnet ef database update)
// Check database in DB Browser for SQLite to show new Character table
// Run site to populate the model
// Check database in DB Browser to SQLite to show new Character data
// Add Character Service
// Add Character Service as transient to startup.cs
// Add Character Controller
// Add Character and Book view models
// Add Helpers to convert from DB models to View models
// Create Base Controller (which Book and Character controllers implement)
// Update return methods of Gets to call SingleResult (on BaseController)
// with the result of calling the appropriate extension method)
// Add search methods to controllers - seems silly to have them in the services if we're not using them
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment