@ -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 | |||
{ | |||
[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 | |||
{ | |||
private CatalogContext _context; | |||
private readonly CatalogContext _context; | |||
public CatalogController(CatalogContext context) | |||
{ | |||
_context = context; | |||
} | |||
// GET api/values | |||
// GET api/v1/[controller]/items/[?pageSize=3&pageIndex=10] | |||
[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 | |||
EXPOSE 80 | |||
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.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.DependencyInjection; | |||
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 | |||
{ | |||
public class Startup | |||
{ | |||
public IConfigurationRoot Configuration { get; } | |||
public Startup(IHostingEnvironment env) | |||
{ | |||
var builder = new ConfigurationBuilder() | |||
.SetBasePath(env.ContentRootPath) | |||
.AddJsonFile("settings.json") | |||
.AddJsonFile($"settings.{env.EnvironmentName}.json",optional:false) | |||
.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) | |||
{ | |||
services.AddDbContext<CatalogContext>(c => { | |||
services.AddSingleton<IConfiguration>(Configuration); | |||
services.AddDbContext<CatalogContext>(c => | |||
{ | |||
c.UseNpgsql(Configuration["ConnectionString"]); | |||
c.ConfigureWarnings(wb => | |||
{ | |||
wb.Throw(RelationalEventId.QueryClientEvaluationWarning); | |||
}); | |||
}); | |||
// Add framework services. | |||
services.AddCors(); | |||
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. | |||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) | |||
{ | |||
//Configure logs | |||
if(env.IsDevelopment()) | |||
{ | |||
app.UseDeveloperExceptionPage(); | |||
} | |||
loggerFactory.AddConsole(Configuration.GetSection("Logging")); | |||
loggerFactory.AddDebug(); | |||
//Seed Data | |||
CatalogContextSeed.SeedAsync(app) | |||
.Wait(); | |||
// Use frameworks | |||
app.UseCors(policyBuilder=>policyBuilder.AllowAnyOrigin()); | |||
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": { | |||
"IncludeScopes": false, | |||
"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" | |||
} | |||
} | |||
} |