From ce25733591b6916604c8535d18bbc5fe0419f58c Mon Sep 17 00:00:00 2001 From: Tim Guenthner Date: Thu, 15 Mar 2018 14:58:44 -0500 Subject: [PATCH] Update example project to fix #141 - Update SQL and Entity Framework models to work with SQLite - Add Route attributes to controllers - Disable input JSON HAL input formatter. Including this formatter is causing all responses to be empty. This may be related to a larger bug. --- WebApi.Hal.Web/Api/BeerController.cs | 36 +++++++++------ WebApi.Hal.Web/Api/BeerDetailController.cs | 45 +++++++++++-------- WebApi.Hal.Web/Api/BeersController.cs | 25 ++++++----- .../Api/BeersFromBreweryController.cs | 5 ++- .../Api/BeersFromStyleController.cs | 11 ++++- WebApi.Hal.Web/Api/BreweriesController.cs | 29 +++++++----- WebApi.Hal.Web/Api/StylesController.cs | 31 +++++++------ WebApi.Hal.Web/Data/BeerDbContext.cs | 5 ++- WebApi.Hal.Web/Data/IBeerDbContext.cs | 2 +- WebApi.Hal.Web/Data/Scripts/Script001.sql | 14 +++--- WebApi.Hal.Web/Data/Scripts/Script003.sql | 7 +-- WebApi.Hal.Web/FormattersMvcOptionsSetup.cs | 16 ++++--- WebApi.Hal.Web/LinkTemplates.cs | 2 +- WebApi.Hal.Web/Models/Beer.cs | 13 +++++- WebApi.Hal.Web/Startup.cs | 18 +++++--- 15 files changed, 161 insertions(+), 98 deletions(-) diff --git a/WebApi.Hal.Web/Api/BeerController.cs b/WebApi.Hal.Web/Api/BeerController.cs index 4f911b3..81b2a7b 100644 --- a/WebApi.Hal.Web/Api/BeerController.cs +++ b/WebApi.Hal.Web/Api/BeerController.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using WebApi.Hal.Web.Api.Resources; @@ -6,6 +7,7 @@ namespace WebApi.Hal.Web.Api { + [Route("[controller]")] public class BeerController : Controller { readonly IBeerDbContext beerDbContext; @@ -15,29 +17,35 @@ public BeerController(IBeerDbContext beerDbContext) this.beerDbContext = beerDbContext; } - // GET beers/5 + [HttpGet("{id}")] + // GET beer/5 public BeerRepresentation Get(int id) { - var beer = beerDbContext.Beers.Include("Brewery").Include("Style").Single(br => br.Id == id); // lazy loading isn't on for this query; force loading + var beer = beerDbContext.Beers + .Include("Brewery") // lazy loading isn't on for this query; force loading + .Include("BeerStyle") + .Single(br => br.Id == id); return new BeerRepresentation - { - Id = beer.Id, - Name = beer.Name, - BreweryId = beer.Brewery == null ? (int?)null : beer.Brewery.Id, - BreweryName = beer.Brewery == null ? null : beer.Brewery.Name, - StyleId = beer.Style == null ? (int?)null : beer.Style.Id, - StyleName = beer.Style == null ? null : beer.Style.Name, - ReviewIds = beerDbContext.Reviews.Where(r => r.Beer_Id == id).Select(r => r.Id).ToList() - }; + { + Id = beer.Id, + Name = beer.Name, + BreweryId = beer.Brewery == null ? (int?) null : beer.Brewery.Id, + BreweryName = beer.Brewery == null ? null : beer.Brewery.Name, + StyleId = beer.Style == null ? (int?) null : beer.Style.Id, + StyleName = beer.Style == null ? null : beer.Style.Name, + ReviewIds = beerDbContext.Reviews.Where(r => r.Beer_Id == id).Select(r => r.Id).ToList() + }; } - // PUT beers/5 + [HttpPut] + // PUT beer?id=1&value=foo public void Put(int id, string value) { } - // DELETE beers/5 + [HttpDelete] + // DELETE beer?id=1 public void Delete(int id) { } diff --git a/WebApi.Hal.Web/Api/BeerDetailController.cs b/WebApi.Hal.Web/Api/BeerDetailController.cs index b72496f..c268547 100644 --- a/WebApi.Hal.Web/Api/BeerDetailController.cs +++ b/WebApi.Hal.Web/Api/BeerDetailController.cs @@ -7,6 +7,7 @@ namespace WebApi.Hal.Web.Api { + [Route("[controller]")] public class BeerDetailController : Controller { readonly IBeerDbContext beerDbContext; @@ -16,38 +17,46 @@ public BeerDetailController(IBeerDbContext beerDbContext) this.beerDbContext = beerDbContext; } + [HttpGet("{id}")] // GET beerdetail/5 public BeerDetailRepresentation Get(int id) { - var beer = beerDbContext.Beers.Include("Brewery").Include("Style").Single(br => br.Id == id); // lazy loading isn't on for this query; force loading + var beer = beerDbContext.Beers + .Include("Brewery") // lazy loading isn't on for this query; force loading + .Include("BeerStyle") + .Single(br => br.Id == id); + var reviews = beerDbContext.Reviews - .Where(r=>r.Beer_Id == id) - .ToList() - .Select(s => new ReviewRepresentation - { - Id = s.Id, - Beer_Id = s.Beer_Id, - Title = s.Title, - Content = s.Content - }) - .ToList(); + .Where(r => r.Beer_Id == id) + .ToList() + .Select(s => new ReviewRepresentation + { + Id = s.Id, + Beer_Id = s.Beer_Id, + Title = s.Title, + Content = s.Content + }) + .ToList(); var detail = new BeerDetailRepresentation - { - Id = beer.Id, - Name = beer.Name, - Style = new BeerStyleRepresentation {Id = beer.Style.Id, Name = beer.Style.Name}, - Brewery = new BreweryRepresentation {Id = beer.Brewery.Id, Name = beer.Brewery.Name} - }; + { + Id = beer.Id, + Name = beer.Name, + Style = new BeerStyleRepresentation {Id = beer.Style.Id, Name = beer.Style.Name}, + Brewery = new BreweryRepresentation {Id = beer.Brewery.Id, Name = beer.Brewery.Name} + }; + if (reviews.Count > 0) { detail.Reviews = new List(); foreach (var review in reviews) detail.Reviews.Add(review); } + return detail; } + [HttpPut("{id}")] // PUT beerdetail/5 public void Put(int id, BeerDetailRepresentation beer) { @@ -56,4 +65,4 @@ public void Put(int id, BeerDetailRepresentation beer) // we'd be better off creating a client to test the full deserializing, but this way is cheap for now } } -} +} \ No newline at end of file diff --git a/WebApi.Hal.Web/Api/BeersController.cs b/WebApi.Hal.Web/Api/BeersController.cs index ded4135..775e4a0 100644 --- a/WebApi.Hal.Web/Api/BeersController.cs +++ b/WebApi.Hal.Web/Api/BeersController.cs @@ -1,6 +1,4 @@ using System.Linq; -using System.Net; -using System.Net.Http; using Microsoft.AspNetCore.Mvc; using WebApi.Hal.Web.Api.Resources; using WebApi.Hal.Web.Data; @@ -9,6 +7,7 @@ namespace WebApi.Hal.Web.Api { + [Route("[controller]")] public class BeersController : Controller { public const int PageSize = 5; @@ -20,6 +19,7 @@ public BeersController(IRepository repository) this.repository = repository; } + [HttpGet] // GET beers public BeerListRepresentation Get(int page = 1) { @@ -30,7 +30,8 @@ public BeerListRepresentation Get(int page = 1) return resourceList; } - [HttpGet] + [HttpGet("Search")] + // GET beers/Search?searchTerm=Roger public BeerListRepresentation Search(string searchTerm, int page = 1) { var beers = repository.Find(new GetBeersQuery(b => b.Name.Contains(searchTerm)), page, PageSize); @@ -39,24 +40,28 @@ public BeerListRepresentation Search(string searchTerm, int page = 1) if (page > beers.TotalPages) page = beers.TotalPages; //var link = LinkTemplates.Beers.SearchBeers.CreateLink(new { searchTerm, page }); - var beersResource = new BeerListRepresentation(beers.ToList(), beers.TotalResults, beers.TotalPages, page, + var beersResource = new BeerListRepresentation(beers.ToList(), + beers.TotalResults, + beers.TotalPages, + page, LinkTemplates.Beers.SearchBeers, - new { searchTerm }) - { - Page = page, - TotalResults = beers.TotalResults - }; + new {searchTerm}) + { + Page = page, + TotalResults = beers.TotalResults + }; return beersResource; } + [HttpPost] // POST beers public IActionResult Post(BeerRepresentation value) { var newBeer = new Beer(value.Name); repository.Add(newBeer); - return Created(LinkTemplates.Beers.Beer.CreateUri(new { id = newBeer.Id }), newBeer); + return Created(LinkTemplates.Beers.Beer.CreateUri(new {id = newBeer.Id}), newBeer); } } } \ No newline at end of file diff --git a/WebApi.Hal.Web/Api/BeersFromBreweryController.cs b/WebApi.Hal.Web/Api/BeersFromBreweryController.cs index 4741b55..6258355 100644 --- a/WebApi.Hal.Web/Api/BeersFromBreweryController.cs +++ b/WebApi.Hal.Web/Api/BeersFromBreweryController.cs @@ -6,6 +6,7 @@ namespace WebApi.Hal.Web.Api { + [Route("[controller]")] public class BeersFromBreweryController : Controller { readonly IRepository repository; @@ -15,10 +16,12 @@ public BeersFromBreweryController(IRepository repository) this.repository = repository; } + [HttpGet("{id}")] + // GET BeersFromBrewery/5 public BeerListRepresentation Get(int id, int page = 1) { var beers = repository.Find(new GetBeersQuery(b => b.Brewery.Id == id), page, BeersController.PageSize); - return new BeerListRepresentation(beers.ToList(), beers.TotalResults, beers.TotalPages, page, LinkTemplates.Breweries.AssociatedBeers, new { id }); + return new BeerListRepresentation(beers.ToList(), beers.TotalResults, beers.TotalPages, page, LinkTemplates.Breweries.AssociatedBeers, new {id}); } } } \ No newline at end of file diff --git a/WebApi.Hal.Web/Api/BeersFromStyleController.cs b/WebApi.Hal.Web/Api/BeersFromStyleController.cs index 2c75363..f8202dd 100644 --- a/WebApi.Hal.Web/Api/BeersFromStyleController.cs +++ b/WebApi.Hal.Web/Api/BeersFromStyleController.cs @@ -6,6 +6,7 @@ namespace WebApi.Hal.Web.Api { + [Route("[controller]")] public class BeersFromStyleController : Controller { readonly IRepository repository; @@ -15,12 +16,18 @@ public BeersFromStyleController(IRepository repository) this.repository = repository; } + [HttpGet("{id}")] + // GET BeersFromStyle/5 public BeerListRepresentation Get(int id, int page = 1) { var beers = repository.Find(new GetBeersQuery(b => b.Style.Id == id), page, BeersController.PageSize); var resourceList = new BeerListRepresentation( - beers.ToList(), beers.TotalResults, beers.TotalPages, page, - LinkTemplates.BeerStyles.AssociatedBeers, new {id}); + beers.ToList(), + beers.TotalResults, + beers.TotalPages, + page, + LinkTemplates.BeerStyles.AssociatedBeers, + new {id}); return resourceList; } } diff --git a/WebApi.Hal.Web/Api/BreweriesController.cs b/WebApi.Hal.Web/Api/BreweriesController.cs index f4631fb..975bff9 100644 --- a/WebApi.Hal.Web/Api/BreweriesController.cs +++ b/WebApi.Hal.Web/Api/BreweriesController.cs @@ -5,6 +5,7 @@ namespace WebApi.Hal.Web.Api { + [Route("[controller]")] public class BreweriesController : Controller { readonly IBeerDbContext beerDbContext; @@ -14,29 +15,33 @@ public BreweriesController(IBeerDbContext beerDbContext) this.beerDbContext = beerDbContext; } + [HttpGet] + // GET breweries public BreweryListRepresentation Get() { - var breweries = beerDbContext.Styles - .ToList() - .Select(s => new BreweryRepresentation - { - Id = s.Id, - Name = s.Name - }) - .ToList(); + var breweries = beerDbContext.BeerStyles + .ToList() + .Select(s => new BreweryRepresentation + { + Id = s.Id, + Name = s.Name + }) + .ToList(); return new BreweryListRepresentation(breweries); } + [HttpGet("{id}")] + // GET breweries/5 public BreweryRepresentation Get(int id) { var brewery = beerDbContext.Breweries.Find(id); return new BreweryRepresentation - { - Id = brewery.Id, - Name = brewery.Name - }; + { + Id = brewery.Id, + Name = brewery.Name + }; } } } \ No newline at end of file diff --git a/WebApi.Hal.Web/Api/StylesController.cs b/WebApi.Hal.Web/Api/StylesController.cs index 6b83b95..42364b4 100644 --- a/WebApi.Hal.Web/Api/StylesController.cs +++ b/WebApi.Hal.Web/Api/StylesController.cs @@ -5,6 +5,7 @@ namespace WebApi.Hal.Web.Api { + [Route("[controller]")] public class StylesController : Controller { readonly IBeerDbContext beerDbContext; @@ -14,31 +15,35 @@ public StylesController(IBeerDbContext beerDbContext) this.beerDbContext = beerDbContext; } + [HttpGet] + // GET styles public BeerStyleListRepresentation Get() { - var beerStyles = beerDbContext.Styles - .ToList() - .Select(s => new BeerStyleRepresentation - { - Id = s.Id, - Name = s.Name - }) - .ToList(); + var beerStyles = beerDbContext.BeerStyles + .ToList() + .Select(s => new BeerStyleRepresentation + { + Id = s.Id, + Name = s.Name + }) + .ToList(); return new BeerStyleListRepresentation(beerStyles); } + [HttpGet("{id}")] + // GET styles/5 public IActionResult Get(int id) { - var beerStyle = beerDbContext.Styles.SingleOrDefault(s => s.Id == id); + var beerStyle = beerDbContext.BeerStyles.SingleOrDefault(s => s.Id == id); if (beerStyle == null) return NotFound(); var beerStyleResource = new BeerStyleRepresentation - { - Id = beerStyle.Id, - Name = beerStyle.Name - }; + { + Id = beerStyle.Id, + Name = beerStyle.Name + }; return Ok(beerStyleResource); } diff --git a/WebApi.Hal.Web/Data/BeerDbContext.cs b/WebApi.Hal.Web/Data/BeerDbContext.cs index a12c22f..174da95 100644 --- a/WebApi.Hal.Web/Data/BeerDbContext.cs +++ b/WebApi.Hal.Web/Data/BeerDbContext.cs @@ -5,11 +5,12 @@ namespace WebApi.Hal.Web.Data { public class BeerDbContext : DbContext, IBeerDbContext { - public BeerDbContext(DbContextOptions options) : base(options) { + public BeerDbContext(DbContextOptions options) : base(options) + { } public DbSet Beers { get; set; } - public DbSet Styles { get; set; } + public DbSet BeerStyles { get; set; } public DbSet Breweries { get; set; } public DbSet Reviews { get; set; } } diff --git a/WebApi.Hal.Web/Data/IBeerDbContext.cs b/WebApi.Hal.Web/Data/IBeerDbContext.cs index 382a9ec..5c636fa 100644 --- a/WebApi.Hal.Web/Data/IBeerDbContext.cs +++ b/WebApi.Hal.Web/Data/IBeerDbContext.cs @@ -6,7 +6,7 @@ namespace WebApi.Hal.Web.Data public interface IBeerDbContext { DbSet Beers { get; } - DbSet Styles { get; } + DbSet BeerStyles { get; } DbSet Breweries { get; set; } DbSet Reviews { get; set; } int SaveChanges(); diff --git a/WebApi.Hal.Web/Data/Scripts/Script001.sql b/WebApi.Hal.Web/Data/Scripts/Script001.sql index c3bf538..f9553e8 100644 --- a/WebApi.Hal.Web/Data/Scripts/Script001.sql +++ b/WebApi.Hal.Web/Data/Scripts/Script001.sql @@ -1,6 +1,6 @@ create table BeerStyles ( - Id int identity PRIMARY KEY, + Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, Name nvarchar(100) NOT NULL, [Description] ntext NULL ) @@ -8,7 +8,7 @@ go create table Breweries ( - Id int identity PRIMARY KEY, + Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, Name nvarchar(100) NOT NULL, [Address] nvarchar(255) NULL, City nvarchar(100) NULL, @@ -22,10 +22,12 @@ go create table Beers ( - Id int identity PRIMARY KEY, + Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, Name nvarchar(100) NOT NULL, - Style_Id int FOREIGN KEY REFERENCES BeerStyles(Id) NULL, - Brewery_Id int FOREIGN KEY REFERENCES Breweries(Id) NULL, - Abv decimal(3,2) NULL + Style_Id int NULL, + Brewery_Id int NULL, + Abv decimal(3,2) NULL, + FOREIGN KEY (Style_Id) REFERENCES BeerStyles(Id), + FOREIGN KEY (Brewery_Id) REFERENCES Breweries(Id) ) go diff --git a/WebApi.Hal.Web/Data/Scripts/Script003.sql b/WebApi.Hal.Web/Data/Scripts/Script003.sql index 09f4805..0ef3331 100644 --- a/WebApi.Hal.Web/Data/Scripts/Script003.sql +++ b/WebApi.Hal.Web/Data/Scripts/Script003.sql @@ -1,9 +1,10 @@ create table Reviews ( - Id int identity PRIMARY KEY, - Beer_Id int FOREIGN KEY REFERENCES Beers(Id) NULL, + Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + Beer_Id int NULL, [Title] ntext NULL, - [Content] ntext NULL + [Content] ntext NULL, + FOREIGN KEY (Beer_Id) REFERENCES Beers(Id) ) go diff --git a/WebApi.Hal.Web/FormattersMvcOptionsSetup.cs b/WebApi.Hal.Web/FormattersMvcOptionsSetup.cs index 55fba4b..63b2cc0 100644 --- a/WebApi.Hal.Web/FormattersMvcOptionsSetup.cs +++ b/WebApi.Hal.Web/FormattersMvcOptionsSetup.cs @@ -55,12 +55,14 @@ public void Configure(MvcOptions options) // Register JsonPatchInputFormatter before JsonInputFormatter, otherwise // JsonInputFormatter would consume "application/json-patch+json" requests // before JsonPatchInputFormatter gets to see them. - var jsonInputPatchLogger = _loggerFactory.CreateLogger(); - options.InputFormatters.Add(new JsonHalMediaTypeInputFormatter( - jsonInputPatchLogger, - _jsonSerializerSettings, - _charPool, - _objectPoolProvider)); + + //TODO Including this formatter causes all returns to be empty + //var jsonInputPatchLogger = _loggerFactory.CreateLogger(); + //options.InputFormatters.Add(new JsonHalMediaTypeInputFormatter( + // jsonInputPatchLogger, + // _jsonSerializerSettings, + // _charPool, + // _objectPoolProvider)); } } -} +} \ No newline at end of file diff --git a/WebApi.Hal.Web/LinkTemplates.cs b/WebApi.Hal.Web/LinkTemplates.cs index 4ac09c8..32b388c 100644 --- a/WebApi.Hal.Web/LinkTemplates.cs +++ b/WebApi.Hal.Web/LinkTemplates.cs @@ -53,7 +53,7 @@ public static class Beers /// /// /beers/{id} /// - public static Link Beer { get { return new Link("beer", "~/beers/{id}"); } } + public static Link Beer { get { return new Link("beer", "~/beer/{id}"); } } } public static class BeerDetails diff --git a/WebApi.Hal.Web/Models/Beer.cs b/WebApi.Hal.Web/Models/Beer.cs index 54c4363..47d5ec4 100644 --- a/WebApi.Hal.Web/Models/Beer.cs +++ b/WebApi.Hal.Web/Models/Beer.cs @@ -1,4 +1,6 @@ -namespace WebApi.Hal.Web.Models +using System.ComponentModel.DataAnnotations.Schema; + +namespace WebApi.Hal.Web.Models { public class Beer { @@ -12,8 +14,17 @@ public Beer(string name) } public int Id { get; protected set; } + public string Name { get; set; } + + [ForeignKey("Style")] + public int Style_Id { get; set; } + public BeerStyle Style { get; set; } + + [ForeignKey("Brewery")] + public int Brewery_Id { get; set; } + public Brewery Brewery { get; set; } } } \ No newline at end of file diff --git a/WebApi.Hal.Web/Startup.cs b/WebApi.Hal.Web/Startup.cs index b9ef28d..56f299d 100644 --- a/WebApi.Hal.Web/Startup.cs +++ b/WebApi.Hal.Web/Startup.cs @@ -19,17 +19,21 @@ public class Startup public void ConfigureServices(IServiceCollection services) { services.AddDbContext((oa) => oa.UseSqlite("Data Source=beer.db")); - + services.AddScoped(); services.AddMvc(); - services.Configure(options => { + services.Configure(options => + { options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore; + options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; }); services.TryAddEnumerable( ServiceDescriptor.Transient, FormattersMvcOptionsSetup>()); + + services.TryAddSingleton(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -49,11 +53,11 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF var context = serviceScope.ServiceProvider.GetService(); DeployChanges.To - .SQLiteDatabase("Data Source=beer.db") - .WithScriptsEmbeddedInAssembly(typeof(Startup).GetTypeInfo().Assembly) - .Build() - .PerformUpgrade(); + .SQLiteDatabase("Data Source=beer.db") + .WithScriptsEmbeddedInAssembly(typeof(Startup).GetTypeInfo().Assembly) + .Build() + .PerformUpgrade(); } } } -} +} \ No newline at end of file