@ -1,73 +1,121 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Model; | |||||
| |||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers | namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers | ||||
{ | { | ||||
[Route("/")] | |||||
using Microsoft.AspNetCore.Mvc; | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; | |||||
using System; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using ViewModel; | |||||
[Route("api/v1/[controller]")] | |||||
public class CatalogController : ControllerBase | public class CatalogController : ControllerBase | ||||
{ | { | ||||
private CatalogContext _context; | |||||
private readonly CatalogContext _context; | |||||
public CatalogController(CatalogContext context) | public CatalogController(CatalogContext context) | ||||
{ | { | ||||
_context = context; | _context = context; | ||||
} | } | ||||
// GET api/values | |||||
// GET api/v1/[controller]/items/[?pageSize=3&pageIndex=10] | |||||
[HttpGet] | [HttpGet] | ||||
public IEnumerable<CatalogItem> Get() | |||||
[Route("[action]")] | |||||
public async Task<IActionResult> Items(int pageSize = 10, int pageIndex = 0) | |||||
{ | { | ||||
return _context.CatalogItems.ToList(); | |||||
var totalItems = await _context.CatalogItems | |||||
.LongCountAsync(); | |||||
var itemsOnPage = await _context.CatalogItems | |||||
.Skip(pageSize * pageIndex) | |||||
.Take(pageSize) | |||||
.ToListAsync(); | |||||
var model = new PaginatedItemsViewModel<CatalogItem>( | |||||
pageIndex, pageSize, totalItems, itemsOnPage); | |||||
return Ok(model); | |||||
} | } | ||||
// GET api/values/5 | |||||
[HttpGet("{id}")] | |||||
public IActionResult Get(Guid id) | |||||
// GET api/v1/[controller]/items/withname/samplename | |||||
[HttpGet] | |||||
[Route("[action]/withname/{name:minlength(1)}")] | |||||
public async Task<IActionResult> Items(string name, int pageSize = 10, int pageIndex = 0) | |||||
{ | { | ||||
var item = _context.CatalogItems.FirstOrDefault(x=> x.Id == id); | |||||
if(item == null) | |||||
{ | |||||
return NotFound(); | |||||
} | |||||
var totalItems = await _context.CatalogItems | |||||
.Where(c => c.Name.StartsWith(name)) | |||||
.LongCountAsync(); | |||||
return new OkObjectResult(item); | |||||
var itemsOnPage = await _context.CatalogItems | |||||
.Where(c => c.Name.StartsWith(name)) | |||||
.Skip(pageSize * pageIndex) | |||||
.Take(pageSize) | |||||
.ToListAsync(); | |||||
var model = new PaginatedItemsViewModel<CatalogItem>( | |||||
pageIndex, pageSize, totalItems, itemsOnPage); | |||||
return Ok(model); | |||||
} | } | ||||
// POST api/values | |||||
[HttpPost] | |||||
public IActionResult Post([FromBody]CatalogItem item) | |||||
// GET api/v1/[controller]/items/type/1/brand/null | |||||
[HttpGet] | |||||
[Route("[action]/type/{catalogTypeId}/brand/{catalogBrandId}")] | |||||
public async Task<IActionResult> Items(int? catalogTypeId, int? catalogBrandId, int pageSize = 10, int pageIndex = 0) | |||||
{ | { | ||||
try | |||||
var root = (IQueryable<CatalogItem>)_context.CatalogItems; | |||||
if (catalogTypeId.HasValue) | |||||
{ | { | ||||
_context.CatalogItems.Add(item); | |||||
_context.SaveChanges(); | |||||
return Ok(); | |||||
root = root.Where(ci => ci.CatalogTypeId == catalogTypeId); | |||||
} | } | ||||
catch | |||||
if (catalogBrandId.HasValue) | |||||
{ | { | ||||
return StatusCode(500, "Unable to add new catalog item"); | |||||
root = root.Where(ci => ci.CatalogBrandId == catalogBrandId); | |||||
} | } | ||||
var totalItems = await root | |||||
.LongCountAsync(); | |||||
var itemsOnPage = await root | |||||
.Skip(pageSize * pageIndex) | |||||
.Take(pageSize) | |||||
.ToListAsync(); | |||||
var model = new PaginatedItemsViewModel<CatalogItem>( | |||||
pageIndex, pageSize, totalItems, itemsOnPage); | |||||
return Ok(model); | |||||
} | } | ||||
// PUT api/values/5 | |||||
[HttpPut("{id}")] | |||||
public IActionResult Put(int id, [FromBody]CatalogItem item) | |||||
// GET api/v1/[controller]/CatalogTypes | |||||
[HttpGet] | |||||
[Route("[action]")] | |||||
public async Task<IActionResult> CatalogTypes() | |||||
{ | { | ||||
_context.CatalogItems.Update(item); | |||||
_context.SaveChanges(); | |||||
return Ok(); | |||||
var items = await _context.CatalogTypes | |||||
.ToListAsync(); | |||||
return Ok(items); | |||||
} | } | ||||
// DELETE api/values/5 | |||||
[HttpDelete("{id}")] | |||||
public IActionResult Delete(Guid id) | |||||
// GET api/v1/[controller]/CatalogBrands | |||||
[HttpGet] | |||||
[Route("[action]")] | |||||
public async Task<IActionResult> CatalogBrands() | |||||
{ | { | ||||
return Ok(); | |||||
var items = await _context.CatalogBrands | |||||
.ToListAsync(); | |||||
return Ok(items); | |||||
} | } | ||||
} | } | ||||
} | } |
@ -1,4 +1,4 @@ | |||||
FROM microsoft/aspnetcore | |||||
FROM microsoft/aspnetcore:latest | |||||
WORKDIR /app | WORKDIR /app | ||||
EXPOSE 80 | EXPOSE 80 | ||||
ADD . /app | ADD . /app |
@ -0,0 +1,15 @@ | |||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure | |||||
{ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
public class CatalogBrand | |||||
{ | |||||
public int Id { get; set; } | |||||
public string Brand { get; set; } | |||||
} | |||||
} |
@ -0,0 +1,95 @@ | |||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure | |||||
{ | |||||
using EntityFrameworkCore.Metadata.Builders; | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Npgsql.EntityFrameworkCore.PostgreSQL; | |||||
public class CatalogContext : DbContext | |||||
{ | |||||
public CatalogContext(DbContextOptions options) : base(options) | |||||
{ | |||||
} | |||||
public DbSet<CatalogItem> CatalogItems { get; set; } | |||||
public DbSet<CatalogBrand> CatalogBrands { get; set; } | |||||
public DbSet<CatalogType> CatalogTypes { get; set; } | |||||
protected override void OnModelCreating(ModelBuilder builder) | |||||
{ | |||||
builder.HasSequence("idseqcatalog") | |||||
.StartsAt(1) | |||||
.IncrementsBy(1); | |||||
builder.HasSequence("idseqcatalogbrand") | |||||
.StartsAt(1) | |||||
.IncrementsBy(1); | |||||
builder.HasSequence("idseqcatalogtype") | |||||
.StartsAt(1) | |||||
.IncrementsBy(1); | |||||
builder.Entity<CatalogBrand>(ConfigureCatalogBrand); | |||||
builder.Entity<CatalogType>(ConfigureCatalogType); | |||||
builder.Entity<CatalogItem>(ConfigureCatalogItem); | |||||
builder.HasPostgresExtension("uuid-ossp"); | |||||
} | |||||
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder) | |||||
{ | |||||
builder.ForNpgsqlToTable("catalog"); | |||||
builder.Property(ci => ci.Id) | |||||
.HasDefaultValueSql("nextval('idseqcatalog')") | |||||
.IsRequired(); | |||||
builder.Property(ci => ci.Name) | |||||
.IsRequired(true) | |||||
.HasMaxLength(50); | |||||
builder.Property(ci => ci.Price) | |||||
.IsRequired(true); | |||||
builder.Property(ci => ci.PictureUri) | |||||
.IsRequired(false); | |||||
builder.HasOne(ci => ci.CatalogBrand) | |||||
.WithMany() | |||||
.HasForeignKey(ci => ci.CatalogBrandId); | |||||
builder.HasOne(ci => ci.CatalogType) | |||||
.WithMany() | |||||
.HasForeignKey(ci => ci.CatalogTypeId); | |||||
} | |||||
void ConfigureCatalogBrand(EntityTypeBuilder<CatalogBrand> builder) | |||||
{ | |||||
builder.ForNpgsqlToTable("catalogbrand"); | |||||
builder.Property(cb => cb.Id) | |||||
.HasDefaultValueSql("nextval('idseqcatalogbrand')") | |||||
.IsRequired(); | |||||
builder.Property(cb => cb.Brand) | |||||
.IsRequired() | |||||
.HasMaxLength(100); | |||||
} | |||||
void ConfigureCatalogType(EntityTypeBuilder<CatalogType> builder) | |||||
{ | |||||
builder.ForNpgsqlToTable("catalogtype"); | |||||
builder.Property(cb => cb.Id) | |||||
.HasDefaultValueSql("nextval('idseqcatalogtype')") | |||||
.IsRequired(); | |||||
builder.Property(cb => cb.Type) | |||||
.IsRequired() | |||||
.HasMaxLength(100); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,77 @@ | |||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure | |||||
{ | |||||
using Microsoft.AspNetCore.Builder; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
public class CatalogContextSeed | |||||
{ | |||||
public static async Task SeedAsync(IApplicationBuilder applicationBuilder) | |||||
{ | |||||
var context = (CatalogContext)applicationBuilder | |||||
.ApplicationServices.GetService(typeof(CatalogContext)); | |||||
using (context) | |||||
{ | |||||
context.Database.EnsureDeleted(); | |||||
context.Database.EnsureCreated(); | |||||
if (!context.CatalogBrands.Any()) | |||||
{ | |||||
context.CatalogBrands.AddRange( | |||||
GetPreconfiguredCatalogBrands()); | |||||
await context.SaveChangesAsync(); | |||||
} | |||||
if (!context.CatalogTypes.Any()) | |||||
{ | |||||
context.CatalogTypes.AddRange( | |||||
GetPreconfiguredCatalogTypes()); | |||||
await context.SaveChangesAsync(); | |||||
} | |||||
if (!context.CatalogItems.Any()) | |||||
{ | |||||
context.CatalogItems.AddRange( | |||||
GetPreconfiguredItems()); | |||||
await context.SaveChangesAsync(); | |||||
} | |||||
} | |||||
} | |||||
static IEnumerable<CatalogBrand> GetPreconfiguredCatalogBrands() | |||||
{ | |||||
return new List<CatalogBrand>() | |||||
{ | |||||
new CatalogBrand() { Brand="Azure"}, | |||||
new CatalogBrand() { Brand = "Visual Studio" } | |||||
}; | |||||
} | |||||
static IEnumerable<CatalogType> GetPreconfiguredCatalogTypes() | |||||
{ | |||||
return new List<CatalogType>() | |||||
{ | |||||
new CatalogType() { Type="Mug"}, | |||||
new CatalogType() { Type = "T-Shirt" } | |||||
}; | |||||
} | |||||
static IEnumerable<CatalogItem> GetPreconfiguredItems() | |||||
{ | |||||
return new List<CatalogItem>() | |||||
{ | |||||
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = "Roslyn Red T-Shirt", Name = "Roslyn Red T-Shirt", Price = 12, PictureUri = "https://fakeimg.pl/370x240/EEEEEE/000/?text=RoslynRedT-Shirt" }, | |||||
new CatalogItem() { CatalogTypeId=1,CatalogBrandId=2, Description = "Cupt Black & White Mug", Name = "Cupt Black & White Mug", Price= 17, PictureUri = "https://fakeimg.pl/370x240/EEEEEE/000/?text=CuptBlack&WhiteMug" }, | |||||
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=2, Description = "Prism White T-Shirt", Name = "Prism White T-Shirt", Price = 12, PictureUri = "http://fakeimg.pl/370x240/EEEEEE/000/?text=.PrismWhiteT-Shirt" }, | |||||
new CatalogItem() { CatalogTypeId=2,CatalogBrandId=1, Description = ".NET Bot Black Sweatshirt", Name = ".NET Bot Black Sweatshirt", Price = 19.5M, PictureUri = "http://fakeimg.pl/370x240/EEEEEE/000/?text=.NETBotBlack" }, | |||||
}; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,27 @@ | |||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure | |||||
{ | |||||
using System; | |||||
public class CatalogItem | |||||
{ | |||||
public int Id { get; set; } | |||||
public string Name { get; set; } | |||||
public string Description { get; set; } | |||||
public decimal Price { get; set; } | |||||
public string PictureUri { get; set; } | |||||
public int CatalogTypeId { get; set; } | |||||
public CatalogType CatalogType { get; set; } | |||||
public int CatalogBrandId { get; set; } | |||||
public CatalogBrand CatalogBrand { get; set; } | |||||
public CatalogItem() { } | |||||
} | |||||
} |
@ -0,0 +1,14 @@ | |||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure | |||||
{ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
public class CatalogType | |||||
{ | |||||
public int Id { get; set; } | |||||
public string Type { get; set; } | |||||
} | |||||
} |
@ -1,23 +0,0 @@ | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Npgsql.EntityFrameworkCore.PostgreSQL; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Model | |||||
{ | |||||
public class CatalogContext : DbContext | |||||
{ | |||||
public CatalogContext(DbContextOptions options): base(options) | |||||
{ | |||||
} | |||||
public DbSet<CatalogItem> CatalogItems { get; set; } | |||||
protected override void OnModelCreating(ModelBuilder builder) | |||||
{ | |||||
builder.HasPostgresExtension("uuid-ossp"); | |||||
} | |||||
} | |||||
} |
@ -1,20 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Model | |||||
{ | |||||
public class CatalogItem | |||||
{ | |||||
public CatalogItem() | |||||
{ | |||||
} | |||||
public Guid Id { get; set; } | |||||
public string Name { get; set; } | |||||
public string Description { get; set; } | |||||
public decimal Price { get; set; } | |||||
public int ImageCount { get; set; } | |||||
} | |||||
} |
@ -1,48 +1,81 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.Builder; | |||||
using Microsoft.AspNetCore.Hosting; | using Microsoft.AspNetCore.Hosting; | ||||
using Microsoft.AspNetCore.Mvc.ApiExplorer; | |||||
using Microsoft.AspNetCore.Mvc.Formatters; | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.EntityFrameworkCore.Infrastructure; | |||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; | |||||
using Microsoft.Extensions.Configuration; | using Microsoft.Extensions.Configuration; | ||||
using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Model; | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Newtonsoft.Json.Serialization; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API | namespace Microsoft.eShopOnContainers.Services.Catalog.API | ||||
{ | { | ||||
public class Startup | public class Startup | ||||
{ | { | ||||
public IConfigurationRoot Configuration { get; } | |||||
public Startup(IHostingEnvironment env) | public Startup(IHostingEnvironment env) | ||||
{ | { | ||||
var builder = new ConfigurationBuilder() | var builder = new ConfigurationBuilder() | ||||
.SetBasePath(env.ContentRootPath) | .SetBasePath(env.ContentRootPath) | ||||
.AddJsonFile("settings.json") | |||||
.AddJsonFile($"settings.{env.EnvironmentName}.json",optional:false) | |||||
.AddEnvironmentVariables(); | .AddEnvironmentVariables(); | ||||
Configuration = builder.Build(); | 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) | public void ConfigureServices(IServiceCollection services) | ||||
{ | { | ||||
services.AddDbContext<CatalogContext>(c => { | |||||
services.AddSingleton<IConfiguration>(Configuration); | |||||
services.AddDbContext<CatalogContext>(c => | |||||
{ | |||||
c.UseNpgsql(Configuration["ConnectionString"]); | c.UseNpgsql(Configuration["ConnectionString"]); | ||||
c.ConfigureWarnings(wb => | |||||
{ | |||||
wb.Throw(RelationalEventId.QueryClientEvaluationWarning); | |||||
}); | |||||
}); | }); | ||||
// Add framework services. | // Add framework services. | ||||
services.AddCors(); | |||||
services.AddMvcCore() | services.AddMvcCore() | ||||
.AddJsonFormatters(); | |||||
.AddJsonFormatters(settings=> | |||||
{ | |||||
settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); | |||||
}); | |||||
} | } | ||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | // 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) | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) | ||||
{ | { | ||||
//Configure logs | |||||
if(env.IsDevelopment()) | |||||
{ | |||||
app.UseDeveloperExceptionPage(); | |||||
} | |||||
loggerFactory.AddConsole(Configuration.GetSection("Logging")); | loggerFactory.AddConsole(Configuration.GetSection("Logging")); | ||||
loggerFactory.AddDebug(); | loggerFactory.AddDebug(); | ||||
//Seed Data | |||||
CatalogContextSeed.SeedAsync(app) | |||||
.Wait(); | |||||
// Use frameworks | |||||
app.UseCors(policyBuilder=>policyBuilder.AllowAnyOrigin()); | |||||
app.UseMvc(); | app.UseMvc(); | ||||
} | } | ||||
} | } | ||||
@ -0,0 +1,24 @@ | |||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel | |||||
{ | |||||
using System.Collections.Generic; | |||||
public class PaginatedItemsViewModel<TEntity> where TEntity : class | |||||
{ | |||||
public int PageIndex { get; private set; } | |||||
public int PageSize { get; private set; } | |||||
public long Count { get; private set; } | |||||
public IEnumerable<TEntity> Data { get; private set; } | |||||
public PaginatedItemsViewModel(int pageIndex, int pageSize, long count, IEnumerable<TEntity> data) | |||||
{ | |||||
this.PageIndex = pageIndex; | |||||
this.PageSize = pageSize; | |||||
this.Count = count; | |||||
this.Data = data; | |||||
} | |||||
} | |||||
} |
@ -1,5 +1,5 @@ | |||||
{ | { | ||||
"ConnectionString": "Server=127.0.0.1;Port=5432;Database=postgres;username=postgres", | |||||
"ConnectionString": "Server=127.0.0.1;Port=5432;Database=CatalogDB;username=postgres;password=postgres", | |||||
"Logging": { | "Logging": { | ||||
"IncludeScopes": false, | "IncludeScopes": false, | ||||
"LogLevel": { | "LogLevel": { |
@ -0,0 +1,11 @@ | |||||
{ | |||||
"ConnectionString": "Server=127.0.0.1;Port=5432;Database=CatalogDB;username=postgres;password=postgres", | |||||
"Logging": { | |||||
"IncludeScopes": false, | |||||
"LogLevel": { | |||||
"Default": "Debug", | |||||
"System": "Information", | |||||
"Microsoft": "Information" | |||||
} | |||||
} | |||||
} |