diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj
index 5af8b11..e0b5a21 100644
--- a/src/Infrastructure/Infrastructure.csproj
+++ b/src/Infrastructure/Infrastructure.csproj
@@ -9,9 +9,9 @@
-
-
-
+
+
+
diff --git a/src/Web/API/BaseRequest.cs b/src/Web/API/BaseRequest.cs
new file mode 100644
index 0000000..06eb1c6
--- /dev/null
+++ b/src/Web/API/BaseRequest.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Microsoft.eShopWeb.Web.API
+{
+ ///
+ /// Base class used by API requests
+ ///
+ public abstract class BaseMessage
+ {
+ ///
+ /// Unique Identifier used by logging
+ ///
+ protected Guid _correlationId = Guid.NewGuid();
+ public Guid CorrelationId() => _correlationId;
+ }
+
+ public abstract class BaseRequest : BaseMessage
+ {
+ }
+}
diff --git a/src/Web/API/BaseResponse.cs b/src/Web/API/BaseResponse.cs
new file mode 100644
index 0000000..d2c8535
--- /dev/null
+++ b/src/Web/API/BaseResponse.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Microsoft.eShopWeb.Web.API
+{
+ ///
+ /// Base class used by API responses
+ ///
+ public abstract class BaseResponse : BaseMessage
+ {
+ public BaseResponse(Guid correlationId) : base()
+ {
+ base._correlationId = correlationId;
+ }
+
+ public BaseResponse()
+ {
+ }
+ }
+}
diff --git a/src/Web/API/CatalogItemEndpoints/CatalogItemDto.cs b/src/Web/API/CatalogItemEndpoints/CatalogItemDto.cs
new file mode 100644
index 0000000..9a3a197
--- /dev/null
+++ b/src/Web/API/CatalogItemEndpoints/CatalogItemDto.cs
@@ -0,0 +1,13 @@
+namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints
+{
+ public class CatalogItemDto
+ {
+ 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 int CatalogBrandId { get; set; }
+ }
+}
diff --git a/src/Web/API/CatalogItemEndpoints/Create.CreateCatalogItemRequest.cs b/src/Web/API/CatalogItemEndpoints/Create.CreateCatalogItemRequest.cs
new file mode 100644
index 0000000..f5e168c
--- /dev/null
+++ b/src/Web/API/CatalogItemEndpoints/Create.CreateCatalogItemRequest.cs
@@ -0,0 +1,12 @@
+namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints
+{
+ public class CreateCatalogItemRequest : BaseRequest
+ {
+ public int CatalogBrandId { get; set; }
+ public int CatalogTypeId { get; set; }
+ public string Description { get; set; }
+ public string Name { get; set; }
+ public string PictureUri { get; set; }
+ public decimal Price { get; set; }
+ }
+}
diff --git a/src/Web/API/CatalogItemEndpoints/Create.CreateCatalogItemResponse.cs b/src/Web/API/CatalogItemEndpoints/Create.CreateCatalogItemResponse.cs
new file mode 100644
index 0000000..9b22c63
--- /dev/null
+++ b/src/Web/API/CatalogItemEndpoints/Create.CreateCatalogItemResponse.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints
+{
+ public class CreateCatalogItemResponse : BaseResponse
+ {
+ public CreateCatalogItemResponse(Guid correlationId) : base(correlationId)
+ {
+ }
+
+ public CatalogItemDto CatalogItem { get; set; }
+ }
+}
diff --git a/src/Web/API/CatalogItemEndpoints/Create.cs b/src/Web/API/CatalogItemEndpoints/Create.cs
new file mode 100644
index 0000000..133690b
--- /dev/null
+++ b/src/Web/API/CatalogItemEndpoints/Create.cs
@@ -0,0 +1,48 @@
+using Ardalis.ApiEndpoints;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.eShopWeb.ApplicationCore.Entities;
+using Microsoft.eShopWeb.ApplicationCore.Interfaces;
+using Swashbuckle.AspNetCore.Annotations;
+using System.Threading.Tasks;
+
+namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints
+{
+ public class Create : BaseAsyncEndpoint
+ {
+ private readonly IAsyncRepository _itemRepository;
+
+ public Create(IAsyncRepository itemRepository)
+ {
+ _itemRepository = itemRepository;
+ }
+
+ [HttpPost("api/catalog-items")]
+ [SwaggerOperation(
+ Summary = "Creates a new Catalog Item",
+ Description = "Creates a new Catalog Item",
+ OperationId = "catalog-items.create",
+ Tags = new[] { "CatalogItemEndpoints" })
+ ]
+ public override async Task> HandleAsync(CreateCatalogItemRequest request)
+ {
+ var response = new CreateCatalogItemResponse(request.CorrelationId());
+
+ CatalogItem newItem = new CatalogItem(request.CatalogTypeId, request.CatalogBrandId, request.Description, request.Name, request.Price, request.PictureUri);
+
+ newItem = await _itemRepository.AddAsync(newItem);
+
+ var dto = new CatalogItemDto
+ {
+ Id = newItem.Id,
+ CatalogBrandId = newItem.CatalogBrandId,
+ CatalogTypeId = newItem.CatalogTypeId,
+ Description = newItem.Description,
+ Name = newItem.Name,
+ PictureUri = newItem.PictureUri,
+ Price = newItem.Price
+ };
+ response.CatalogItem = dto;
+ return response;
+ }
+ }
+}
diff --git a/src/Web/API/CatalogItemEndpoints/Delete.DeleteCatalogItemRequest.cs b/src/Web/API/CatalogItemEndpoints/Delete.DeleteCatalogItemRequest.cs
new file mode 100644
index 0000000..7e59de7
--- /dev/null
+++ b/src/Web/API/CatalogItemEndpoints/Delete.DeleteCatalogItemRequest.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints
+{
+ public class DeleteCatalogItemRequest : BaseRequest
+ {
+ //[FromRoute]
+ public int CatalogItemId { get; set; }
+ }
+}
diff --git a/src/Web/API/CatalogItemEndpoints/Delete.DeleteCatalogItemResponse.cs b/src/Web/API/CatalogItemEndpoints/Delete.DeleteCatalogItemResponse.cs
new file mode 100644
index 0000000..40bed9a
--- /dev/null
+++ b/src/Web/API/CatalogItemEndpoints/Delete.DeleteCatalogItemResponse.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints
+{
+ public class DeleteCatalogItemResponse : BaseResponse
+ {
+ public DeleteCatalogItemResponse(Guid correlationId) : base(correlationId)
+ {
+ }
+
+ public string Status { get; set; } = "Deleted";
+ }
+}
diff --git a/src/Web/API/CatalogItemEndpoints/Delete.cs b/src/Web/API/CatalogItemEndpoints/Delete.cs
new file mode 100644
index 0000000..323bea5
--- /dev/null
+++ b/src/Web/API/CatalogItemEndpoints/Delete.cs
@@ -0,0 +1,38 @@
+using Ardalis.ApiEndpoints;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.eShopWeb.ApplicationCore.Entities;
+using Microsoft.eShopWeb.ApplicationCore.Interfaces;
+using Swashbuckle.AspNetCore.Annotations;
+using System.Threading.Tasks;
+
+namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints
+{
+ public class Delete : BaseAsyncEndpoint
+ {
+ private readonly IAsyncRepository _itemRepository;
+
+ public Delete(IAsyncRepository itemRepository)
+ {
+ _itemRepository = itemRepository;
+ }
+
+ [HttpDelete("api/catalog-items/{CatalogItemId}")]
+ [SwaggerOperation(
+ Summary = "Deletes a Catalog Item",
+ Description = "Deletes a Catalog Item",
+ OperationId = "catalog-items.Delete",
+ Tags = new[] { "CatalogItemEndpoints" })
+ ]
+ public override async Task> HandleAsync([FromRoute]DeleteCatalogItemRequest request)
+ {
+ var response = new DeleteCatalogItemResponse(request.CorrelationId());
+
+ var itemToDelete = await _itemRepository.GetByIdAsync(request.CatalogItemId);
+ if (itemToDelete is null) return NotFound();
+
+ await _itemRepository.DeleteAsync(itemToDelete);
+
+ return Ok(response);
+ }
+ }
+}
diff --git a/src/Web/API/CatalogItemEndpoints/GetById.GetByIdCatalogItemRequest.cs b/src/Web/API/CatalogItemEndpoints/GetById.GetByIdCatalogItemRequest.cs
new file mode 100644
index 0000000..b935c97
--- /dev/null
+++ b/src/Web/API/CatalogItemEndpoints/GetById.GetByIdCatalogItemRequest.cs
@@ -0,0 +1,7 @@
+namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints
+{
+ public class GetByIdCatalogItemRequest : BaseRequest
+ {
+ public int CatalogItemId { get; set; }
+ }
+}
diff --git a/src/Web/API/CatalogItemEndpoints/GetById.GetByIdCatalogItemResponse.cs b/src/Web/API/CatalogItemEndpoints/GetById.GetByIdCatalogItemResponse.cs
new file mode 100644
index 0000000..ebdd3e0
--- /dev/null
+++ b/src/Web/API/CatalogItemEndpoints/GetById.GetByIdCatalogItemResponse.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints
+{
+ public class GetByIdCatalogItemResponse : BaseResponse
+ {
+ public GetByIdCatalogItemResponse(Guid correlationId) : base(correlationId)
+ {
+ }
+
+ public CatalogItemDto CatalogItem { get; set; }
+ }
+}
diff --git a/src/Web/API/CatalogItemEndpoints/GetById.cs b/src/Web/API/CatalogItemEndpoints/GetById.cs
new file mode 100644
index 0000000..ff28eaf
--- /dev/null
+++ b/src/Web/API/CatalogItemEndpoints/GetById.cs
@@ -0,0 +1,46 @@
+using Ardalis.ApiEndpoints;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.eShopWeb.ApplicationCore.Entities;
+using Microsoft.eShopWeb.ApplicationCore.Interfaces;
+using Swashbuckle.AspNetCore.Annotations;
+using System.Threading.Tasks;
+
+namespace Microsoft.eShopWeb.Web.API.CatalogItemEndpoints
+{
+ public class GetById : BaseAsyncEndpoint
+ {
+ private readonly IAsyncRepository _itemRepository;
+
+ public GetById(IAsyncRepository itemRepository)
+ {
+ _itemRepository = itemRepository;
+ }
+
+ [HttpGet("api/catalog-items/{CatalogItemId}")]
+ [SwaggerOperation(
+ Summary = "Get a Catalog Item by Id",
+ Description = "Gets a Catalog Item by Id",
+ OperationId = "catalog-items.GetById",
+ Tags = new[] { "CatalogItemEndpoints" })
+ ]
+ public override async Task> HandleAsync([FromRoute]GetByIdCatalogItemRequest request)
+ {
+ var response = new GetByIdCatalogItemResponse(request.CorrelationId());
+
+ var item = await _itemRepository.GetByIdAsync(request.CatalogItemId);
+ if (item is null) return NotFound();
+
+ response.CatalogItem = new CatalogItemDto
+ {
+ Id = item.Id,
+ CatalogBrandId = item.CatalogBrandId,
+ CatalogTypeId = item.CatalogTypeId,
+ Description = item.Description,
+ Name = item.Name,
+ PictureUri = item.PictureUri,
+ Price = item.Price
+ };
+ return Ok(response);
+ }
+ }
+}
diff --git a/src/Web/API/CustomSchemaFilters.cs b/src/Web/API/CustomSchemaFilters.cs
new file mode 100644
index 0000000..490bb6c
--- /dev/null
+++ b/src/Web/API/CustomSchemaFilters.cs
@@ -0,0 +1,17 @@
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace Microsoft.eShopWeb.Web.API
+{
+ public class CustomSchemaFilters : ISchemaFilter
+ {
+ public void Apply(OpenApiSchema schema, SchemaFilterContext context)
+ {
+ var excludeProperties = new[] { "CorrelationId" };
+
+ foreach (var prop in excludeProperties)
+ if (schema.Properties.ContainsKey(prop))
+ schema.Properties.Remove(prop);
+ }
+ }
+}
diff --git a/src/Web/API/README.md b/src/Web/API/README.md
new file mode 100644
index 0000000..91d3f77
--- /dev/null
+++ b/src/Web/API/README.md
@@ -0,0 +1,4 @@
+# API Endpoints
+
+This folder demonstrates how to configure API endpoints as individual classes. You can compare it to the traditional controller-based approach found in /Controllers/Api.
+
diff --git a/src/Web/Startup.cs b/src/Web/Startup.cs
index e7591c5..c6a78fd 100644
--- a/src/Web/Startup.cs
+++ b/src/Web/Startup.cs
@@ -13,6 +13,7 @@ using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.eShopWeb.Infrastructure.Logging;
using Microsoft.eShopWeb.Infrastructure.Services;
+using Microsoft.eShopWeb.Web.API;
using Microsoft.eShopWeb.Web.Interfaces;
using Microsoft.eShopWeb.Web.Services;
using Microsoft.Extensions.Configuration;
@@ -24,6 +25,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mime;
+using static Microsoft.eShopWeb.Web.API.BaseRequest;
namespace Microsoft.eShopWeb.Web
{
@@ -129,7 +131,12 @@ namespace Microsoft.eShopWeb.Web
services.AddHttpContextAccessor();
- services.AddSwaggerGen(c => c.SwaggerDoc("v1", new OpenApi.Models.OpenApiInfo { Title = "My API", Version = "v1" }));
+ services.AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("v1", new OpenApi.Models.OpenApiInfo { Title = "My API", Version = "v1" });
+ c.EnableAnnotations();
+ c.SchemaFilter();
+ });
services.AddHealthChecks();
diff --git a/src/Web/Web.csproj b/src/Web/Web.csproj
index f3ca000..7aa917f 100644
--- a/src/Web/Web.csproj
+++ b/src/Web/Web.csproj
@@ -13,23 +13,24 @@
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/FunctionalTests/FunctionalTests.csproj b/tests/FunctionalTests/FunctionalTests.csproj
index 3253ce9..ef7ae1f 100644
--- a/tests/FunctionalTests/FunctionalTests.csproj
+++ b/tests/FunctionalTests/FunctionalTests.csproj
@@ -13,14 +13,14 @@
-
+
-
+
all
runtime; build; native; contentfiles; analyzers
-
+
@@ -33,4 +33,8 @@
+
+
+
+
diff --git a/tests/IntegrationTests/IntegrationTests.csproj b/tests/IntegrationTests/IntegrationTests.csproj
index dd0510c..4317f2f 100644
--- a/tests/IntegrationTests/IntegrationTests.csproj
+++ b/tests/IntegrationTests/IntegrationTests.csproj
@@ -7,11 +7,11 @@
-
+
-
+
-
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/tests/UnitTests/UnitTests.csproj b/tests/UnitTests/UnitTests.csproj
index f0b97b8..2ea972b 100644
--- a/tests/UnitTests/UnitTests.csproj
+++ b/tests/UnitTests/UnitTests.csproj
@@ -9,9 +9,9 @@
-
+
-
+
all
runtime; build; native; contentfiles; analyzers