Event handling customisationpull/1240/head
@ -0,0 +1,74 @@ | |||
using System.Threading.Tasks; | |||
using System; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
using System.Net; | |||
using System.IO; | |||
using System.Net.Http; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using Microsoft.Extensions.Logging; | |||
namespace Ordering.API.Application.IntegrationEvents.EventHandling | |||
{ | |||
public abstract class AbstractIntegrationEventHandler<IIntegrationEvent> | |||
{ | |||
private static String url = @"http://tenantmanager/"; | |||
private readonly IEventBus _eventBus; | |||
//private readonly ILogger<AbstractIntegrationEventHandler<IIntegrationEvent>> _logger; | |||
protected AbstractIntegrationEventHandler(IEventBus eventBus) | |||
{ | |||
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); | |||
} | |||
public async Task<bool> CheckIfCustomised(IntegrationEvent @event) | |||
{ | |||
if (!@event.CheckForCustomisation) | |||
{ | |||
return false; | |||
} | |||
Boolean result = Get(@event); | |||
if (result) | |||
{ | |||
CustomisationEvent customisationEvent = new CustomisationEvent(1, @event); | |||
try | |||
{ | |||
//_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppName} - ({@IntegrationEvent})", eventMessage.Id, Program.AppName, eventMessage); | |||
_eventBus.Publish(customisationEvent); | |||
} | |||
catch (Exception ex) | |||
{ | |||
//_logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName}", eventMessage.Id, Program.AppName); | |||
throw; | |||
} | |||
} | |||
return result; | |||
} | |||
private Boolean Get(IntegrationEvent @event) | |||
{ | |||
//TODO return true/false | |||
Console.WriteLine("Making API Call..."); | |||
using (var client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate })) | |||
{ | |||
client.BaseAddress = new Uri(url); | |||
try | |||
{ | |||
HttpResponseMessage response = client.GetAsync("api/tenants").Result; | |||
response.EnsureSuccessStatusCode(); | |||
string result = response.Content.ReadAsStringAsync().Result; | |||
Console.WriteLine("Result: " + result); | |||
} | |||
catch(Exception e) | |||
{ | |||
Console.WriteLine(e); | |||
} | |||
} | |||
return true; | |||
} | |||
} | |||
} |
@ -0,0 +1,20 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events | |||
{ | |||
public class CustomisationEvent : IntegrationEvent | |||
{ | |||
public CustomisationEvent(int tenantId, IntegrationEvent @event) | |||
{ | |||
TenantId = tenantId; | |||
this.@event = @event; | |||
eventType = @event.GetType().Name; | |||
} | |||
public int TenantId { get; set; } | |||
public IntegrationEvent @event { get; set; } | |||
public String eventType { get; set; } | |||
} | |||
} |
@ -0,0 +1,183 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.EntityFrameworkCore; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
using Microsoft.Extensions.Logging; | |||
using Newtonsoft.Json; | |||
using TenantACustomisations.Database; | |||
using TenantACustomisations.IntegrationEvents.Events; | |||
namespace TenantACustomisations.Controllers | |||
{ | |||
[Route("api/[controller]")] | |||
[ApiController] | |||
public class SavedEventsController : ControllerBase | |||
{ | |||
private readonly TenantAContext _context; | |||
private readonly ILogger<SavedEventsController> _logger; | |||
private readonly IEventBus _eventBus; | |||
private List<Type> types = new List<Type>() | |||
{ | |||
typeof(OrderStatusChangedToSubmittedIntegrationEvent), | |||
typeof(OrderStatusChangedToAwaitingValidationIntegrationEvent) | |||
}; | |||
public SavedEventsController(TenantAContext context, ILogger<SavedEventsController> logger, IEventBus eventBus) | |||
{ | |||
_context = context; | |||
_logger = logger; | |||
_eventBus = eventBus; | |||
} | |||
// GET: api/SavedEvents | |||
[HttpGet] | |||
public async Task<ActionResult<IEnumerable<SavedEvent>>> GetSavedEvent(String orderId) | |||
{ | |||
if (String.IsNullOrEmpty(orderId)) | |||
{ | |||
return await _context.SavedEvent.ToListAsync(); | |||
} | |||
//Getting saved events | |||
var savedEvents = await _context.SavedEvent.ToListAsync(); | |||
//Returning if list is empty | |||
if (savedEvents.Count == 0) | |||
{ | |||
return NotFound(); | |||
} | |||
List<IntegrationEvent> events = new List<IntegrationEvent>(); | |||
//Converting events to actual type | |||
savedEvents.ForEach(e => | |||
{ | |||
var integrationEvent =JsonConvert.DeserializeObject(e.Content, GetEventTypeByName(e.EventName)); | |||
IntegrationEvent evt = (IntegrationEvent)integrationEvent; | |||
events.Add(evt); | |||
}); | |||
bool found = false; | |||
//Casting to class to check the orderId | |||
events.ForEach(e => | |||
{ | |||
if(e is OrderStatusChangedToAwaitingValidationIntegrationEvent) | |||
{ | |||
OrderStatusChangedToAwaitingValidationIntegrationEvent evt = (OrderStatusChangedToAwaitingValidationIntegrationEvent)e; | |||
if (evt.OrderId == Int32.Parse(orderId)) | |||
{ | |||
found = true; | |||
} | |||
} | |||
else if(e is OrderStatusChangedToSubmittedIntegrationEvent) | |||
{ | |||
OrderStatusChangedToSubmittedIntegrationEvent evt = (OrderStatusChangedToSubmittedIntegrationEvent)e; | |||
if (evt.OrderId == Int32.Parse(orderId)) | |||
{ | |||
found = true; | |||
} | |||
} | |||
}); | |||
if (!found) | |||
{ | |||
return NotFound(); | |||
} | |||
return savedEvents; | |||
} | |||
// PUT: api/SavedEvents/5 | |||
[HttpPut("{id}")] | |||
public async Task<IActionResult> PutSavedEvent(string id, SavedEvent savedEvent) | |||
{ | |||
if (id != savedEvent.SavedEventId) | |||
{ | |||
return BadRequest(); | |||
} | |||
_context.Entry(savedEvent).State = EntityState.Modified; | |||
try | |||
{ | |||
await _context.SaveChangesAsync(); | |||
} | |||
catch (DbUpdateConcurrencyException) | |||
{ | |||
if (!SavedEventExists(id)) | |||
{ | |||
return NotFound(); | |||
} | |||
else | |||
{ | |||
throw; | |||
} | |||
} | |||
return NoContent(); | |||
} | |||
// POST: api/SavedEvents | |||
[HttpPost] | |||
public async Task<ActionResult<SavedEvent>> PostSavedEvent(SavedEvent savedEvent) | |||
{ | |||
_context.SavedEvent.Add(savedEvent); | |||
await _context.SaveChangesAsync(); | |||
return CreatedAtAction("GetSavedEvent", new {id = savedEvent.SavedEventId}, savedEvent); | |||
} | |||
// DELETE: api/SavedEvents/5 | |||
[HttpDelete("{id}")] | |||
public async Task<ActionResult<SavedEvent>> DeleteSavedEvent(string id) | |||
{ | |||
var savedEvent = await _context.SavedEvent.FindAsync(id); | |||
if (savedEvent == null) | |||
{ | |||
return NotFound(); | |||
} | |||
var integrationEvent = | |||
JsonConvert.DeserializeObject(savedEvent.Content, GetEventTypeByName(savedEvent.EventName)); | |||
IntegrationEvent evt = (IntegrationEvent) integrationEvent; | |||
try | |||
{ | |||
_logger.LogInformation( | |||
"----- Publishing integration event: {IntegrationEventId} from OrderStatusChangedToSubmittedIntegrationEventsController - ({@IntegrationEvent})", | |||
evt.Id, evt); | |||
evt.CheckForCustomisation = false; | |||
_eventBus.Publish(evt); | |||
_context.SavedEvent.Remove(savedEvent); | |||
await _context.SaveChangesAsync(); | |||
return savedEvent; | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogError(ex, | |||
"ERROR Publishing integration event: {IntegrationEventId} from OrderStatusChangedToSubmittedIntegrationEventsController", | |||
evt.Id); | |||
throw; | |||
} | |||
_context.SavedEvent.Remove(savedEvent); | |||
await _context.SaveChangesAsync(); | |||
return savedEvent; | |||
} | |||
private bool SavedEventExists(string id) | |||
{ | |||
return _context.SavedEvent.Any(e => e.SavedEventId == id); | |||
} | |||
private Type GetEventTypeByName(string eventName) => types.SingleOrDefault(t => t.Name == eventName); | |||
} | |||
} |
@ -0,0 +1,106 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.EntityFrameworkCore; | |||
using TenantACustomisations.Database; | |||
using TenantACustomisations.ExternalServices; | |||
namespace TenantACustomisations.Controllers | |||
{ | |||
[Route("api/[controller]")] | |||
[ApiController] | |||
public class ShippingInformationsController : ControllerBase | |||
{ | |||
private readonly TenantAContext _context; | |||
public ShippingInformationsController(TenantAContext context) | |||
{ | |||
_context = context; | |||
} | |||
// GET: api/ShippingInformations | |||
[HttpGet] | |||
public async Task<ActionResult<IEnumerable<ShippingInformation>>> GetShippingInformation() | |||
{ | |||
return await _context.ShippingInformation.ToListAsync(); | |||
} | |||
// GET: api/ShippingInformations/5 | |||
[HttpGet("{id}")] | |||
public async Task<ActionResult<ShippingInformation>> GetShippingInformation(int id) | |||
{ | |||
var shippingInformation = await _context.ShippingInformation.FindAsync(id); | |||
if (shippingInformation == null) | |||
{ | |||
return NotFound(); | |||
} | |||
return shippingInformation; | |||
} | |||
// PUT: api/ShippingInformations/5 | |||
[HttpPut("{id}")] | |||
public async Task<IActionResult> PutShippingInformation(int id, ShippingInformation shippingInformation) | |||
{ | |||
if (id != shippingInformation.ShippingInformationId) | |||
{ | |||
return BadRequest(); | |||
} | |||
_context.Entry(shippingInformation).State = EntityState.Modified; | |||
try | |||
{ | |||
await _context.SaveChangesAsync(); | |||
} | |||
catch (DbUpdateConcurrencyException) | |||
{ | |||
if (!ShippingInformationExists(id)) | |||
{ | |||
return NotFound(); | |||
} | |||
else | |||
{ | |||
throw; | |||
} | |||
} | |||
return NoContent(); | |||
} | |||
// POST: api/ShippingInformations | |||
[HttpPost] | |||
public async Task<ActionResult<ShippingInformation>> PostShippingInformation(ShippingInformation shippingInformation) | |||
{ | |||
_context.ShippingInformation.Add(shippingInformation); | |||
await _context.SaveChangesAsync(); | |||
return CreatedAtAction("GetShippingInformation", new { id = shippingInformation.ShippingInformationId }, shippingInformation); | |||
} | |||
// DELETE: api/ShippingInformations/5 | |||
[HttpDelete("{id}")] | |||
public async Task<ActionResult<ShippingInformation>> DeleteShippingInformation(int id) | |||
{ | |||
var shippingInformation = await _context.ShippingInformation.FindAsync(id); | |||
if (shippingInformation == null) | |||
{ | |||
return NotFound(); | |||
} | |||
_context.ShippingInformation.Remove(shippingInformation); | |||
await _context.SaveChangesAsync(); | |||
return shippingInformation; | |||
} | |||
private bool ShippingInformationExists(int id) | |||
{ | |||
return _context.ShippingInformation.Any(e => e.ShippingInformationId == id); | |||
} | |||
} | |||
} |
@ -0,0 +1,58 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.EntityFrameworkCore; | |||
using TenantACustomisations.Database; | |||
using TenantACustomisations.ExternalServices; | |||
namespace TenantACustomisations.Controllers | |||
{ | |||
[Route("api/[controller]")] | |||
[ApiController] | |||
public class ValuesController : ControllerBase | |||
{ | |||
private readonly TenantAContext _context; | |||
public ValuesController(TenantAContext context) | |||
{ | |||
_context = context ?? throw new ArgumentNullException(nameof(context)); | |||
} | |||
// GET api/values | |||
[HttpGet] | |||
public async Task<ActionResult<IEnumerable<ShippingInformation>>> GetShippingInformation() | |||
{ | |||
return await _context.ShippingInformation.ToListAsync(); | |||
} | |||
// GET api/values/5 | |||
[HttpGet("{id}")] | |||
public ActionResult<string> Get(int id) | |||
{ | |||
return "value"; | |||
} | |||
// POST api/values | |||
[HttpPost] | |||
public void Post([FromBody] string value) | |||
{ | |||
} | |||
// PUT api/values/5 | |||
[HttpPut("{id}")] | |||
public void Put(int id, [FromBody] string value) | |||
{ | |||
} | |||
// DELETE api/values/5 | |||
[HttpDelete("{id}")] | |||
public void Delete(int id) | |||
{ | |||
} | |||
} | |||
} |
@ -0,0 +1,34 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using TenantACustomisations.ExternalServices; | |||
namespace TenantACustomisations.Database | |||
{ | |||
public class DbInitializer | |||
{ | |||
public void Initialize(TenantAContext context) | |||
{ | |||
context.Database.EnsureCreated(); | |||
if (context.ShippingInformation.Any()) | |||
{ | |||
return; | |||
} | |||
ShippingInformation shippingInformation = new ShippingInformation(); | |||
shippingInformation.ShippingTime = DateTime.Today; | |||
shippingInformation.ArrivalTime = DateTime.Today.AddDays(2); | |||
shippingInformation.FragilityLevel = Fragility.Medium; | |||
shippingInformation.PriorityLevel = Priority.High; | |||
shippingInformation.ShippingInformationId = 1; | |||
shippingInformation.OrderNumber = "1"; | |||
context.ShippingInformation.Add(shippingInformation); | |||
context.SaveChanges(); | |||
} | |||
} | |||
} |
@ -0,0 +1,11 @@ | |||
using System; | |||
namespace TenantACustomisations.Database | |||
{ | |||
public class SavedEvent | |||
{ | |||
public string SavedEventId { get; set; } | |||
public string Content { get; set; } | |||
public String EventName { get; set; } | |||
} | |||
} |
@ -0,0 +1,38 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Microsoft.EntityFrameworkCore; | |||
using Microsoft.EntityFrameworkCore.Design; | |||
using TenantACustomisations.ExternalServices; | |||
using TenantACustomisations.IntegrationEvents.Events; | |||
namespace TenantACustomisations.Database | |||
{ | |||
public class TenantAContext : DbContext | |||
{ | |||
public TenantAContext(DbContextOptions<TenantAContext> options) | |||
: base(options) | |||
{ | |||
} | |||
public DbSet<ShippingInformation> ShippingInformation { get; set; } | |||
public DbSet<SavedEvent> SavedEvent | |||
{ | |||
get; | |||
set; | |||
} | |||
} | |||
public class TenantAContextDesignFactory : IDesignTimeDbContextFactory<TenantAContext> | |||
{ | |||
public TenantAContext CreateDbContext(string[] args) | |||
{ | |||
var optionsBuilder = new DbContextOptionsBuilder<TenantAContext>() | |||
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.TenantADb;Integrated Security=true"); | |||
return new TenantAContext(optionsBuilder.Options); | |||
} | |||
} | |||
} |
@ -0,0 +1,59 @@ | |||
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim AS base | |||
WORKDIR /app | |||
EXPOSE 80 | |||
FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build | |||
WORKDIR /src | |||
# Keep the project list and command dotnet restore identical in all Dockfiles to maximize image cache utilization | |||
COPY eShopOnContainers-ServicesAndWebApps.sln . | |||
COPY docker-compose.dcproj /src/ | |||
COPY src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj src/ApiGateways/ApiGw-Base/ | |||
COPY src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj src/ApiGateways/Mobile.Bff.Shopping/aggregator/ | |||
COPY src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj src/ApiGateways/Web.Bff.Shopping/aggregator/ | |||
COPY src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj src/BuildingBlocks/Devspaces.Support/ | |||
COPY src/BuildingBlocks/EventBus/EventBus/EventBus.csproj src/BuildingBlocks/EventBus/EventBus/ | |||
COPY src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj src/BuildingBlocks/EventBus/EventBus.Tests/ | |||
COPY src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj src/BuildingBlocks/EventBus/EventBusRabbitMQ/ | |||
COPY src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj src/BuildingBlocks/EventBus/EventBusServiceBus/ | |||
COPY src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj src/BuildingBlocks/EventBus/IntegrationEventLogEF/ | |||
COPY src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj src/BuildingBlocks/WebHostCustomization/WebHost.Customization/ | |||
COPY src/Services/Basket/Basket.API/Basket.API.csproj src/Services/Basket/Basket.API/ | |||
COPY src/Services/Basket/Basket.FunctionalTests/Basket.FunctionalTests.csproj src/Services/Basket/Basket.FunctionalTests/ | |||
COPY src/Services/Basket/Basket.UnitTests/Basket.UnitTests.csproj src/Services/Basket/Basket.UnitTests/ | |||
COPY src/Services/Catalog/Catalog.API/Catalog.API.csproj src/Services/Catalog/Catalog.API/ | |||
COPY src/Services/Catalog/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj src/Services/Catalog/Catalog.FunctionalTests/ | |||
COPY src/Services/Catalog/Catalog.UnitTests/Catalog.UnitTests.csproj src/Services/Catalog/Catalog.UnitTests/ | |||
COPY src/Services/Identity/Identity.API/Identity.API.csproj src/Services/Identity/Identity.API/ | |||
COPY src/Services/Location/Locations.API/Locations.API.csproj src/Services/Location/Locations.API/ | |||
COPY src/Services/Location/Locations.FunctionalTests/Locations.FunctionalTests.csproj src/Services/Location/Locations.FunctionalTests/ | |||
COPY src/Services/Marketing/Marketing.API/Marketing.API.csproj src/Services/Marketing/Marketing.API/ | |||
COPY src/Services/Marketing/Marketing.FunctionalTests/Marketing.FunctionalTests.csproj src/Services/Marketing/Marketing.FunctionalTests/ | |||
COPY src/Services/Ordering/Ordering.API/Ordering.API.csproj src/Services/Ordering/Ordering.API/ | |||
COPY src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj src/Services/Ordering/Ordering.BackgroundTasks/ | |||
COPY src/Services/Ordering/Ordering.Domain/Ordering.Domain.csproj src/Services/Ordering/Ordering.Domain/ | |||
COPY src/Services/Ordering/Ordering.FunctionalTests/Ordering.FunctionalTests.csproj src/Services/Ordering/Ordering.FunctionalTests/ | |||
COPY src/Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj src/Services/Ordering/Ordering.Infrastructure/ | |||
COPY src/Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj src/Services/Ordering/Ordering.SignalrHub/ | |||
COPY src/Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj src/Services/Ordering/Ordering.UnitTests/ | |||
COPY src/Services/Payment/Payment.API/Payment.API.csproj src/Services/Payment/Payment.API/ | |||
COPY src/Services/Webhooks/Webhooks.API/Webhooks.API.csproj src/Services/Webhooks/Webhooks.API/ | |||
COPY src/Web/WebhookClient/WebhookClient.csproj src/Web/WebhookClient/ | |||
COPY src/Web/WebMVC/WebMVC.csproj src/Web/WebMVC/ | |||
COPY src/Web/WebSPA/WebSPA.csproj src/Web/WebSPA/ | |||
COPY src/Web/WebStatus/WebStatus.csproj src/Web/WebStatus/ | |||
COPY test/ServicesTests/Application.FunctionalTests/Application.FunctionalTests.csproj test/ServicesTests/Application.FunctionalTests/ | |||
COPY test/ServicesTests/LoadTest/LoadTest.csproj test/ServicesTests/LoadTest/ | |||
RUN dotnet restore eShopOnContainers-ServicesAndWebApps.sln | |||
COPY . . | |||
WORKDIR /src/src/Services/TenantCustomisations/TenantACustomisations | |||
RUN dotnet publish --no-restore -c Release -o /app | |||
FROM build AS publish | |||
FROM base AS final | |||
WORKDIR /app | |||
COPY --from=publish /app . | |||
ENTRYPOINT ["dotnet", "TenantACustomisations.dll"] |
@ -0,0 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace TenantACustomisations.ExternalServices | |||
{ | |||
public interface IRFIDService | |||
{ | |||
bool IsOrderRFIDTagged(int orderNumber); | |||
} | |||
} |
@ -0,0 +1,13 @@ | |||
using Ordering.API.Application.Models; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace TenantACustomisations.ExternalServices | |||
{ | |||
public interface IShippingService | |||
{ | |||
ShippingInformation CalculateShippingInformation(int orderId); | |||
} | |||
} |
@ -0,0 +1,23 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Ordering.API.Application.Models; | |||
namespace TenantACustomisations.ExternalServices | |||
{ | |||
public class MockedShippingService : IShippingService | |||
{ | |||
public ShippingInformation CalculateShippingInformation(int orderId) | |||
{ | |||
ShippingInformation shippingInformation = new ShippingInformation(); | |||
shippingInformation.ShippingTime = DateTime.Today; | |||
shippingInformation.ArrivalTime = DateTime.Today.AddDays(2); | |||
shippingInformation.FragilityLevel = Fragility.Medium; | |||
shippingInformation.PriorityLevel = Priority.High; | |||
shippingInformation.OrderNumber = orderId.ToString(); | |||
return shippingInformation; | |||
} | |||
} | |||
} |
@ -0,0 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace TenantACustomisations.ExternalServices | |||
{ | |||
public enum Fragility | |||
{ | |||
Low, Medium, High | |||
} | |||
} |
@ -0,0 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace TenantACustomisations.ExternalServices | |||
{ | |||
public enum Priority | |||
{ | |||
Low, Medium, High | |||
} | |||
} |
@ -0,0 +1,17 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace TenantACustomisations.ExternalServices | |||
{ | |||
public class ShippingInformation | |||
{ | |||
public int ShippingInformationId { get; set; } | |||
public DateTime ArrivalTime { get; set; } | |||
public DateTime ShippingTime { get; set; } | |||
public Priority PriorityLevel {get;set;} | |||
public Fragility FragilityLevel { get; set; } | |||
public String OrderNumber { get; set; } | |||
} | |||
} |
@ -0,0 +1,33 @@ | |||
using Autofac; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using System.Reflection; | |||
using TenantACustomisations.ExternalServices; | |||
using TenantACustomisations.IntegrationEvents.Events; | |||
namespace Microsoft.eShopOnContainers.Services.TenantACustomisations.Infrastructure.AutofacModules | |||
{ | |||
public class ApplicationModule | |||
:Autofac.Module | |||
{ | |||
public string QueriesConnectionString { get; } | |||
public ApplicationModule(string qconstr) | |||
{ | |||
QueriesConnectionString = qconstr; | |||
} | |||
protected override void Load(ContainerBuilder builder) | |||
{ | |||
builder.RegisterAssemblyTypes(typeof(UserCheckoutAcceptedIntegrationEvent).GetTypeInfo().Assembly) | |||
.AsClosedTypesOf(typeof(IIntegrationEventHandler<>)); | |||
builder.RegisterType<MockedShippingService>() | |||
.As<IShippingService>() | |||
.InstancePerLifetimeScope(); | |||
} | |||
} | |||
} |
@ -0,0 +1,12 @@ | |||
using Autofac; | |||
namespace Microsoft.eShopOnContainers.Services.TenantACustomisations.Infrastructure.AutofacModules | |||
{ | |||
public class MediatorModule : Autofac.Module | |||
{ | |||
protected override void Load(ContainerBuilder builder) | |||
{ | |||
//TODO | |||
} | |||
} | |||
} |
@ -0,0 +1,33 @@ | |||
using Microsoft.AspNetCore.Authorization; | |||
using Swashbuckle.AspNetCore.Swagger; | |||
using Swashbuckle.AspNetCore.SwaggerGen; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace TenantACustomisations.Infrastructure.Filters | |||
{ | |||
public class AuthorizeCheckOperationFilter : IOperationFilter | |||
{ | |||
public void Apply(Operation operation, OperationFilterContext context) | |||
{ | |||
// Check for authorize attribute | |||
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() || | |||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any(); | |||
if (!hasAuthorize) return; | |||
operation.Responses.TryAdd("401", new Response { Description = "Unauthorized" }); | |||
operation.Responses.TryAdd("403", new Response { Description = "Forbidden" }); | |||
operation.Security = new List<IDictionary<string, IEnumerable<string>>> | |||
{ | |||
new Dictionary<string, IEnumerable<string>> | |||
{ | |||
{ "oauth2", new [] { "orderingapi" } } | |||
} | |||
}; | |||
} | |||
} | |||
} |
@ -0,0 +1,69 @@ | |||
namespace Microsoft.eShopOnContainers.Services.TenantACustomisations.Infrastructure.Filters | |||
{ | |||
using AspNetCore.Mvc; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.AspNetCore.Mvc.Filters; | |||
using Microsoft.Extensions.Logging; | |||
using System.Net; | |||
public class HttpGlobalExceptionFilter : IExceptionFilter | |||
{ | |||
private readonly IHostingEnvironment env; | |||
private readonly ILogger<HttpGlobalExceptionFilter> logger; | |||
public HttpGlobalExceptionFilter(IHostingEnvironment env, ILogger<HttpGlobalExceptionFilter> logger) | |||
{ | |||
this.env = env; | |||
this.logger = logger; | |||
} | |||
public void OnException(ExceptionContext context) | |||
{ | |||
logger.LogError(new EventId(context.Exception.HResult), | |||
context.Exception, | |||
context.Exception.Message); | |||
if (1==2)//TODO | |||
{ | |||
var problemDetails = new ValidationProblemDetails() | |||
{ | |||
Instance = context.HttpContext.Request.Path, | |||
Status = StatusCodes.Status400BadRequest, | |||
Detail = "Please refer to the errors property for additional details." | |||
}; | |||
problemDetails.Errors.Add("DomainValidations", new string[] { context.Exception.Message.ToString() }); | |||
context.Result = new BadRequestObjectResult(problemDetails); | |||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; | |||
} | |||
else | |||
{ | |||
var json = new JsonErrorResponse | |||
{ | |||
Messages = new[] { "An error occur.Try it again." } | |||
}; | |||
if (env.IsDevelopment()) | |||
{ | |||
json.DeveloperMessage = context.Exception; | |||
} | |||
// Result asigned to a result object but in destiny the response is empty. This is a known bug of .net core 1.1 | |||
// It will be fixed in .net core 1.1.2. See https://github.com/aspnet/Mvc/issues/5594 for more information | |||
//TODO | |||
//context.Result = new InternalServerErrorObjectResult(json); | |||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; | |||
} | |||
context.ExceptionHandled = true; | |||
} | |||
private class JsonErrorResponse | |||
{ | |||
public string[] Messages { get; set; } | |||
public object DeveloperMessage { get; set; } | |||
} | |||
} | |||
} |
@ -0,0 +1,49 @@ | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
using Microsoft.Extensions.Logging; | |||
using Newtonsoft.Json; | |||
using Ordering.API.Application.IntegrationEvents.Events; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using TenantACustomisations.Services; | |||
namespace TenantACustomisations.IntegrationEvents.EventHandling | |||
{ | |||
public class CustomisationEventHandler : IIntegrationEventHandler<CustomisationEvent> | |||
{ | |||
private readonly ILogger<CustomisationEventHandler> _logger; | |||
private readonly IEventBus _eventBus; | |||
private readonly IValidationService validationService; | |||
public CustomisationEventHandler(ILogger<CustomisationEventHandler> logger, IEventBus eventBus) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); | |||
validationService = new ValidationService(); | |||
} | |||
public async Task Handle(CustomisationEvent @event) | |||
{ | |||
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event); | |||
IntegrationEvent integrationEvent = @event.@event; | |||
switch (integrationEvent.GetType().Name) | |||
{ | |||
case "UserCheckoutAcceptedIntegrationEvent": | |||
if (validationService.Validate((UserCheckoutAcceptedIntegrationEvent)integrationEvent)) | |||
{ | |||
integrationEvent.CheckForCustomisation = false; | |||
_eventBus.Publish(integrationEvent); | |||
} | |||
break; | |||
default: | |||
integrationEvent.CheckForCustomisation = false; | |||
_eventBus.Publish(integrationEvent); | |||
break; | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,45 @@ | |||
using Microsoft.AspNetCore.SignalR; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using Microsoft.Extensions.Logging; | |||
using Serilog.Context; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using TenantACustomisations.Database; | |||
using TenantACustomisations.ExternalServices; | |||
using TenantACustomisations.IntegrationEvents.Events; | |||
namespace TenantACustomisations.IntegrationEvents.EventHandling | |||
{ | |||
public class OrderStatusChangedToSubmittedIntegrationEventHandler : | |||
IIntegrationEventHandler<OrderStatusChangedToSubmittedIntegrationEvent> | |||
{ | |||
private readonly ILogger<OrderStatusChangedToSubmittedIntegrationEventHandler> _logger; | |||
private readonly IShippingService _shippingService; | |||
private readonly TenantAContext _context; | |||
public OrderStatusChangedToSubmittedIntegrationEventHandler(ILogger<OrderStatusChangedToSubmittedIntegrationEventHandler> logger, IShippingService shippingService, TenantAContext context) | |||
{ | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_shippingService = shippingService ?? throw new ArgumentNullException(nameof(shippingService)); | |||
_context = context ?? throw new ArgumentNullException(nameof(shippingService)); | |||
} | |||
public async Task Handle(OrderStatusChangedToSubmittedIntegrationEvent @event) | |||
{ | |||
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}- TenantA")) | |||
{ | |||
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at TenantA - ({@IntegrationEvent})", @event.Id, @event); | |||
_logger.LogInformation("Hello"); | |||
//TODO | |||
Debug.WriteLine(@event); | |||
ShippingInformation shippingInformation = _shippingService.CalculateShippingInformation(@event.OrderId); | |||
_context.ShippingInformation.Add(shippingInformation); | |||
_logger.LogInformation("----- Saving shipping information: {IntegrationEventId} at TenantA - ({@IntegrationEvent}) - {@ShippingInformation}", @event.Id, @event, shippingInformation); | |||
_context.SaveChanges(); | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,60 @@ | |||
using MediatR; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Extensions; | |||
using Microsoft.Extensions.Logging; | |||
using Ordering.API.Application.Behaviors; | |||
using Serilog.Context; | |||
using System.Threading.Tasks; | |||
using System; | |||
using System.Diagnostics; | |||
using TenantACustomisations.IntegrationEvents.Events; | |||
using TenantACustomisations.ExternalServices; | |||
using TenantACustomisations.Database; | |||
namespace TenantACustomisations.IntegrationEvents.EventHandling | |||
{ | |||
public class TenantAUserCheckoutAcceptedIntegrationEventHandler : | |||
IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent> | |||
{ | |||
private readonly IMediator _mediator; | |||
private readonly IEventBus _eventBus; | |||
private readonly ILogger<TenantAUserCheckoutAcceptedIntegrationEventHandler> _logger; | |||
//private readonly TenantAContext _context; | |||
//private readonly IShippingService _shippingService; | |||
public TenantAUserCheckoutAcceptedIntegrationEventHandler( | |||
IMediator mediator, | |||
ILogger<TenantAUserCheckoutAcceptedIntegrationEventHandler> logger, | |||
IEventBus eventBus | |||
) | |||
{ | |||
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); | |||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); | |||
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); | |||
} | |||
/// <summary> | |||
/// Integration event handler which starts the create order process | |||
/// </summary> | |||
/// <param name="@event"> | |||
/// Integration event message which is sent by the | |||
/// basket.api once it has successfully process the | |||
/// order items. | |||
/// </param> | |||
/// <returns></returns> | |||
public async Task Handle(UserCheckoutAcceptedIntegrationEvent @event) | |||
{ | |||
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}- TenantA")) | |||
{ | |||
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at TenantA- ({@IntegrationEvent})", @event.Id, @event); | |||
_logger.LogInformation("Hello"); | |||
//TODO | |||
Debug.WriteLine(@event); | |||
//Save shipping info | |||
//Hard code view comp | |||
//Retrieve shipping info and show | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,30 @@ | |||
using System.Collections.Generic; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
namespace TenantACustomisations.IntegrationEvents.Events | |||
{ | |||
public class OrderStatusChangedToAwaitingValidationIntegrationEvent : IntegrationEvent | |||
{ | |||
public int OrderId { get; } | |||
public IEnumerable<OrderStockItem> OrderStockItems { get; } | |||
public OrderStatusChangedToAwaitingValidationIntegrationEvent(int orderId, | |||
IEnumerable<OrderStockItem> orderStockItems) | |||
{ | |||
OrderId = orderId; | |||
OrderStockItems = orderStockItems; | |||
} | |||
} | |||
public class OrderStockItem | |||
{ | |||
public int ProductId { get; } | |||
public int Units { get; } | |||
public OrderStockItem(int productId, int units) | |||
{ | |||
ProductId = productId; | |||
Units = units; | |||
} | |||
} | |||
} |
@ -0,0 +1,22 @@ | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace TenantACustomisations.IntegrationEvents.Events | |||
{ | |||
public class OrderStatusChangedToSubmittedIntegrationEvent : IntegrationEvent | |||
{ | |||
public int OrderId { get; set; } | |||
public string OrderStatus { get; set; } | |||
public string BuyerName { get; set; } | |||
public OrderStatusChangedToSubmittedIntegrationEvent(int orderId, string orderStatus, string buyerName) | |||
{ | |||
OrderId = orderId; | |||
OrderStatus = orderStatus; | |||
BuyerName = buyerName; | |||
} | |||
} | |||
} |
@ -0,0 +1,62 @@ | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
using Ordering.API.Application.Models; | |||
using System; | |||
namespace TenantACustomisations.IntegrationEvents.Events | |||
{ | |||
public class UserCheckoutAcceptedIntegrationEvent : IntegrationEvent | |||
{ | |||
public string UserId { get; } | |||
public string UserName { get; } | |||
public string City { get; set; } | |||
public string Street { get; set; } | |||
public string State { get; set; } | |||
public string Country { get; set; } | |||
public string ZipCode { get; set; } | |||
public string CardNumber { get; set; } | |||
public string CardHolderName { get; set; } | |||
public DateTime CardExpiration { get; set; } | |||
public string CardSecurityNumber { get; set; } | |||
public int CardTypeId { get; set; } | |||
public string Buyer { get; set; } | |||
public Guid RequestId { get; set; } | |||
public CustomerBasket Basket { get; } | |||
public UserCheckoutAcceptedIntegrationEvent(string userId, string userName, string city, string street, | |||
string state, string country, string zipCode, string cardNumber, string cardHolderName, | |||
DateTime cardExpiration, string cardSecurityNumber, int cardTypeId, string buyer, Guid requestId, | |||
CustomerBasket basket) | |||
{ | |||
UserId = userId; | |||
City = city; | |||
Street = street; | |||
State = state; | |||
Country = country; | |||
ZipCode = zipCode; | |||
CardNumber = cardNumber; | |||
CardHolderName = cardHolderName; | |||
CardExpiration = cardExpiration; | |||
CardSecurityNumber = cardSecurityNumber; | |||
CardTypeId = cardTypeId; | |||
Buyer = buyer; | |||
Basket = basket; | |||
RequestId = requestId; | |||
UserName = userName; | |||
} | |||
} | |||
} |
@ -0,0 +1,89 @@ | |||
using Microsoft.AspNetCore; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.eShopOnContainers.Services.TenantACustomisations; | |||
using Microsoft.Extensions.Configuration; | |||
using Serilog; | |||
using System; | |||
using System.IO; | |||
using TenantACustomisations.Database; | |||
namespace Microsoft.eShopOnContainers.Services.TenantACustomisations | |||
{ | |||
public class Program | |||
{ | |||
public static readonly string Namespace = typeof(Program).Namespace; | |||
public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); | |||
public static int Main(string[] args) | |||
{ | |||
var configuration = GetConfiguration(); | |||
Log.Logger = CreateSerilogLogger(configuration); | |||
try | |||
{ | |||
Log.Information("Configuring web host ({ApplicationContext})...", AppName); | |||
var host = BuildWebHost(configuration, args); | |||
Log.Information("Starting web host ({ApplicationContext})...", AppName); | |||
host.Run(); | |||
return 0; | |||
} | |||
catch (Exception ex) | |||
{ | |||
Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", AppName); | |||
return 1; | |||
} | |||
finally | |||
{ | |||
Log.CloseAndFlush(); | |||
} | |||
} | |||
private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => | |||
WebHost.CreateDefaultBuilder(args) | |||
.CaptureStartupErrors(false) | |||
.UseStartup<Startup>() | |||
.UseApplicationInsights() | |||
.UseContentRoot(Directory.GetCurrentDirectory()) | |||
.UseConfiguration(configuration) | |||
.UseSerilog() | |||
.Build(); | |||
private static Serilog.ILogger CreateSerilogLogger(IConfiguration configuration) | |||
{ | |||
var seqServerUrl = configuration["Serilog:SeqServerUrl"]; | |||
var logstashUrl = configuration["Serilog:LogstashgUrl"]; | |||
return new LoggerConfiguration() | |||
.MinimumLevel.Verbose() | |||
.Enrich.WithProperty("ApplicationContext", AppName) | |||
.Enrich.FromLogContext() | |||
.WriteTo.Console() | |||
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl) | |||
.WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl) | |||
.ReadFrom.Configuration(configuration) | |||
.CreateLogger(); | |||
} | |||
private static IConfiguration GetConfiguration() | |||
{ | |||
var builder = new ConfigurationBuilder() | |||
.SetBasePath(Directory.GetCurrentDirectory()) | |||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) | |||
.AddEnvironmentVariables(); | |||
var config = builder.Build(); | |||
if (config.GetValue<bool>("UseVault", false)) | |||
{ | |||
builder.AddAzureKeyVault( | |||
$"https://{config["Vault:Name"]}.vault.azure.net/", | |||
config["Vault:ClientId"], | |||
config["Vault:ClientSecret"]); | |||
} | |||
return builder.Build(); | |||
} | |||
} | |||
} |
@ -0,0 +1,30 @@ | |||
{ | |||
"$schema": "http://json.schemastore.org/launchsettings.json", | |||
"iisSettings": { | |||
"windowsAuthentication": false, | |||
"anonymousAuthentication": true, | |||
"iisExpress": { | |||
"applicationUrl": "http://localhost:57040", | |||
"sslPort": 44328 | |||
} | |||
}, | |||
"profiles": { | |||
"IIS Express": { | |||
"commandName": "IISExpress", | |||
"launchBrowser": true, | |||
"launchUrl": "api/values", | |||
"environmentVariables": { | |||
"ASPNETCORE_ENVIRONMENT": "Development" | |||
} | |||
}, | |||
"TenantACustomisations": { | |||
"commandName": "Project", | |||
"launchBrowser": true, | |||
"launchUrl": "api/values", | |||
"applicationUrl": "https://localhost:5001;http://localhost:5000", | |||
"environmentVariables": { | |||
"ASPNETCORE_ENVIRONMENT": "Development" | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,14 @@ | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
using Ordering.API.Application.IntegrationEvents.Events; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace TenantACustomisations.Services | |||
{ | |||
interface IValidationService | |||
{ | |||
Boolean Validate(UserCheckoutAcceptedIntegrationEvent @event); | |||
} | |||
} |
@ -0,0 +1,16 @@ | |||
using Ordering.API.Application.IntegrationEvents.Events; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace TenantACustomisations.Services | |||
{ | |||
public class ValidationService : IValidationService | |||
{ | |||
public bool Validate(UserCheckoutAcceptedIntegrationEvent @event) | |||
{ | |||
return @event.State == "Oslo"; | |||
} | |||
} | |||
} |
@ -0,0 +1,416 @@ | |||
namespace Microsoft.eShopOnContainers.Services.TenantACustomisations | |||
{ | |||
using AspNetCore.Http; | |||
using Autofac; | |||
using Autofac.Extensions.DependencyInjection; | |||
using Microsoft.ApplicationInsights.Extensibility; | |||
using Microsoft.ApplicationInsights.ServiceFabric; | |||
using Microsoft.AspNetCore.Authentication.JwtBearer; | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.AspNetCore.Mvc; | |||
using Microsoft.Azure.ServiceBus; | |||
using Microsoft.EntityFrameworkCore; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; | |||
using Microsoft.Extensions.Configuration; | |||
using Microsoft.Extensions.DependencyInjection; | |||
using Microsoft.Extensions.Logging; | |||
using RabbitMQ.Client; | |||
using Swashbuckle.AspNetCore.Swagger; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Data.Common; | |||
using System.IdentityModel.Tokens.Jwt; | |||
using System.Reflection; | |||
using HealthChecks.UI.Client; | |||
using Microsoft.AspNetCore.Diagnostics.HealthChecks; | |||
using Microsoft.Extensions.Diagnostics.HealthChecks; | |||
using Infrastructure.AutofacModules; | |||
using Microsoft.eShopOnContainers.Services.TenantACustomisations.Infrastructure.Filters; | |||
using global::TenantACustomisations.Infrastructure.Filters; | |||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||
using global::TenantACustomisations.IntegrationEvents.EventHandling; | |||
using global::TenantACustomisations.IntegrationEvents.Events; | |||
using global::TenantACustomisations.ExternalServices; | |||
using global::TenantACustomisations.Database; | |||
public class Startup | |||
{ | |||
public Startup(IConfiguration configuration) | |||
{ | |||
Configuration = configuration; | |||
} | |||
public IConfiguration Configuration { get; } | |||
public IServiceProvider ConfigureServices(IServiceCollection services) | |||
{ | |||
services.AddApplicationInsights(Configuration) | |||
.AddCustomMvc() | |||
.AddHealthChecks(Configuration) | |||
.AddCustomDbContext(Configuration) | |||
.AddCustomSwagger(Configuration) | |||
.AddCustomIntegrations(Configuration) | |||
.AddCustomConfiguration(Configuration) | |||
.AddEventBus(Configuration) | |||
.AddCustomAuthentication(Configuration); | |||
//configure autofac | |||
var container = new ContainerBuilder(); | |||
container.Populate(services); | |||
container.RegisterModule(new MediatorModule()); | |||
container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"])); | |||
return new AutofacServiceProvider(container.Build()); | |||
} | |||
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) | |||
{ | |||
//loggerFactory.AddAzureWebAppDiagnostics(); | |||
//loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); | |||
var pathBase = Configuration["PATH_BASE"]; | |||
if (!string.IsNullOrEmpty(pathBase)) | |||
{ | |||
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase); | |||
app.UsePathBase(pathBase); | |||
} | |||
app.UseCors("CorsPolicy"); | |||
app.UseHealthChecks("/liveness", new HealthCheckOptions | |||
{ | |||
Predicate = r => r.Name.Contains("self") | |||
}); | |||
app.UseHealthChecks("/hc", new HealthCheckOptions() | |||
{ | |||
Predicate = _ => true, | |||
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse | |||
}); | |||
ConfigureAuth(app); | |||
app.UseMvcWithDefaultRoute(); | |||
app.UseSwagger() | |||
.UseSwaggerUI(c => | |||
{ | |||
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Ordering.API V1"); | |||
c.OAuthClientId("orderingswaggerui"); | |||
c.OAuthAppName("Ordering Swagger UI"); | |||
}); | |||
ConfigureEventBus(app); | |||
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope()) | |||
{ | |||
var context = serviceScope.ServiceProvider.GetRequiredService<TenantAContext>(); | |||
context.Database.EnsureCreated(); | |||
} | |||
} | |||
private void ConfigureEventBus(IApplicationBuilder app) | |||
{ | |||
var eventBus = app.ApplicationServices.GetRequiredService<BuildingBlocks.EventBus.Abstractions.IEventBus>(); | |||
//eventBus.Subscribe<UserCheckoutAcceptedIntegrationEvent, IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>>(); | |||
eventBus.Subscribe<OrderStatusChangedToSubmittedIntegrationEvent, OrderStatusChangedToSubmittedIntegrationEventHandler>(); | |||
} | |||
protected virtual void ConfigureAuth(IApplicationBuilder app) | |||
{ | |||
if (Configuration.GetValue<bool>("UseLoadTest")) | |||
{ | |||
//app.UseMiddleware<ByPassAuthMiddleware>(); | |||
//TODO | |||
} | |||
app.UseAuthentication(); | |||
} | |||
} | |||
static class CustomExtensionsMethods | |||
{ | |||
public static IServiceCollection AddApplicationInsights(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddApplicationInsightsTelemetry(configuration); | |||
var orchestratorType = configuration.GetValue<string>("OrchestratorType"); | |||
if (orchestratorType?.ToUpper() == "K8S") | |||
{ | |||
// Enable K8s telemetry initializer | |||
services.AddApplicationInsightsKubernetesEnricher(); | |||
} | |||
if (orchestratorType?.ToUpper() == "SF") | |||
{ | |||
// Enable SF telemetry initializer | |||
services.AddSingleton<ITelemetryInitializer>((serviceProvider) => | |||
new FabricTelemetryInitializer()); | |||
} | |||
return services; | |||
} | |||
public static IServiceCollection AddCustomMvc(this IServiceCollection services) | |||
{ | |||
// Add framework services. | |||
services.AddMvc(options => | |||
{ | |||
options.Filters.Add(typeof(HttpGlobalExceptionFilter)); | |||
}) | |||
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2) | |||
.AddControllersAsServices(); //Injecting Controllers themselves thru DI | |||
//For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services | |||
services.AddCors(options => | |||
{ | |||
options.AddPolicy("CorsPolicy", | |||
builder => builder | |||
.SetIsOriginAllowed((host) => true) | |||
.AllowAnyMethod() | |||
.AllowAnyHeader() | |||
.AllowCredentials()); | |||
}); | |||
return services; | |||
} | |||
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
var hcBuilder = services.AddHealthChecks(); | |||
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()); | |||
hcBuilder | |||
.AddSqlServer( | |||
configuration["ConnectionString"], | |||
name: "OrderingDB-check", | |||
tags: new string[] { "orderingdb" }); | |||
if (configuration.GetValue<bool>("AzureServiceBusEnabled")) | |||
{ | |||
hcBuilder | |||
.AddAzureServiceBusTopic( | |||
configuration["EventBusConnection"], | |||
topicName: "eshop_event_bus", | |||
name: "ordering-servicebus-check", | |||
tags: new string[] { "servicebus" }); | |||
} | |||
else | |||
{ | |||
hcBuilder | |||
.AddRabbitMQ( | |||
$"amqp://{configuration["EventBusConnection"]}", | |||
name: "ordering-rabbitmqbus-check", | |||
tags: new string[] { "rabbitmqbus" }); | |||
} | |||
return services; | |||
} | |||
public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddDbContext<TenantAContext>(options => | |||
options.UseSqlServer(configuration["ConnectionString"])); | |||
services.AddDbContext<IntegrationEventLogContext>(options => | |||
{ | |||
options.UseSqlServer(configuration["ConnectionString"], | |||
sqlServerOptionsAction: sqlOptions => | |||
{ | |||
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); | |||
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency | |||
sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); | |||
}); | |||
}); | |||
return services; | |||
} | |||
public static IServiceCollection AddCustomSwagger(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddSwaggerGen(options => | |||
{ | |||
options.DescribeAllEnumsAsStrings(); | |||
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info | |||
{ | |||
Title = "Ordering HTTP API", | |||
Version = "v1", | |||
Description = "The Ordering Service HTTP API", | |||
TermsOfService = "Terms Of Service" | |||
}); | |||
options.AddSecurityDefinition("oauth2", new OAuth2Scheme | |||
{ | |||
Type = "oauth2", | |||
Flow = "implicit", | |||
AuthorizationUrl = $"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize", | |||
TokenUrl = $"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token", | |||
Scopes = new Dictionary<string, string>() | |||
{ | |||
{ "orders", "Ordering API" } | |||
} | |||
}); | |||
options.OperationFilter<AuthorizeCheckOperationFilter>(); | |||
}); | |||
return services; | |||
} | |||
public static IServiceCollection AddCustomIntegrations(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | |||
//services.AddTransient<IIdentityService, IdentityService>(); | |||
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>( | |||
sp => (DbConnection c) => new IntegrationEventLogService(c)); | |||
//services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>(); | |||
if (configuration.GetValue<bool>("AzureServiceBusEnabled")) | |||
{ | |||
services.AddSingleton<IServiceBusPersisterConnection>(sp => | |||
{ | |||
var logger = sp.GetRequiredService<ILogger<DefaultServiceBusPersisterConnection>>(); | |||
var serviceBusConnectionString = configuration["EventBusConnection"]; | |||
var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString); | |||
return new DefaultServiceBusPersisterConnection(serviceBusConnection, logger); | |||
}); | |||
} | |||
else | |||
{ | |||
services.AddSingleton<IRabbitMQPersistentConnection>(sp => | |||
{ | |||
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>(); | |||
var factory = new ConnectionFactory() | |||
{ | |||
HostName = configuration["EventBusConnection"], | |||
DispatchConsumersAsync = true | |||
}; | |||
if (!string.IsNullOrEmpty(configuration["EventBusUserName"])) | |||
{ | |||
factory.UserName = configuration["EventBusUserName"]; | |||
} | |||
if (!string.IsNullOrEmpty(configuration["EventBusPassword"])) | |||
{ | |||
factory.Password = configuration["EventBusPassword"]; | |||
} | |||
var retryCount = 5; | |||
if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) | |||
{ | |||
retryCount = int.Parse(configuration["EventBusRetryCount"]); | |||
} | |||
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); | |||
}); | |||
} | |||
return services; | |||
} | |||
public static IServiceCollection AddCustomConfiguration(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
services.AddOptions(); | |||
//services.Configure<OrderingSettings>(configuration); | |||
services.Configure<ApiBehaviorOptions>(options => | |||
{ | |||
options.InvalidModelStateResponseFactory = context => | |||
{ | |||
var problemDetails = new ValidationProblemDetails(context.ModelState) | |||
{ | |||
Instance = context.HttpContext.Request.Path, | |||
Status = StatusCodes.Status400BadRequest, | |||
Detail = "Please refer to the errors property for additional details." | |||
}; | |||
return new BadRequestObjectResult(problemDetails) | |||
{ | |||
ContentTypes = { "application/problem+json", "application/problem+xml" } | |||
}; | |||
}; | |||
}); | |||
return services; | |||
} | |||
public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
var subscriptionClientName = configuration["SubscriptionClientName"]; | |||
if (configuration.GetValue<bool>("AzureServiceBusEnabled")) | |||
{ | |||
services.AddSingleton<IEventBus, EventBusServiceBus>(sp => | |||
{ | |||
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>(); | |||
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>(); | |||
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>(); | |||
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | |||
return new EventBusServiceBus(serviceBusPersisterConnection, logger, | |||
eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope); | |||
}); | |||
} | |||
else | |||
{ | |||
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp => | |||
{ | |||
var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>(); | |||
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>(); | |||
var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>(); | |||
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>(); | |||
var retryCount = 5; | |||
if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) | |||
{ | |||
retryCount = int.Parse(configuration["EventBusRetryCount"]); | |||
} | |||
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount); | |||
}); | |||
} | |||
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>(); | |||
//services.AddTransient<TenantAUserCheckoutAcceptedIntegrationEventHandler>(); | |||
return services; | |||
} | |||
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) | |||
{ | |||
// prevent from mapping "sub" claim to nameidentifier. | |||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); | |||
var identityUrl = configuration.GetValue<string>("IdentityUrl"); | |||
services.AddAuthentication(options => | |||
{ | |||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; | |||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; | |||
}).AddJwtBearer(options => | |||
{ | |||
options.Authority = identityUrl; | |||
options.RequireHttpsMetadata = false; | |||
options.Audience = "orders"; | |||
}); | |||
return services; | |||
} | |||
} | |||
} |
@ -0,0 +1,58 @@ | |||
<Project Sdk="Microsoft.NET.Sdk.Web"> | |||
<PropertyGroup> | |||
<TargetFramework>netcoreapp2.2</TargetFramework> | |||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> | |||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> | |||
<DockerfileContext>..\eShopOnContainersCustomised</DockerfileContext> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="2.2.0" /> | |||
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="2.2.0" /> | |||
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="2.2.0" /> | |||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="2.2.4" /> | |||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.1" /> | |||
<PackageReference Include="Dapper" Version="1.50.7" /> | |||
<PackageReference Include="FluentValidation.AspNetCore" Version="7.5.0" /> | |||
<PackageReference Include="MediatR" Version="5.1.0" /> | |||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="5.1.0" /> | |||
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.11.0" /> | |||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.2.1" /> | |||
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.6.1" /> | |||
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="1.0.2" /> | |||
<PackageReference Include="Microsoft.ApplicationInsights.ServiceFabric" Version="2.2.2" /> | |||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" /> | |||
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" /> | |||
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.2.0" /> | |||
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="2.2.0" /> | |||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.4" /> | |||
<PackageReference Include="Polly" Version="6.0.1" /> | |||
<PackageReference Include="Serilog" Version="2.9.0" /> | |||
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" /> | |||
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.1.3" /> | |||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.0.1" /> | |||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" /> | |||
<PackageReference Include="Serilog.Sinks.Http" Version="4.2.1" /> | |||
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" /> | |||
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" /> | |||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="4.0.1" /> | |||
<PackageReference Include="System.Reflection" Version="4.3.0" /> | |||
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" /> | |||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" /> | |||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" /> | |||
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" /> | |||
<ProjectReference Include="..\..\Ordering\Ordering.API\Ordering.API.csproj" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.0" /> | |||
</ItemGroup> | |||
</Project> |
@ -0,0 +1,9 @@ | |||
{ | |||
"Logging": { | |||
"LogLevel": { | |||
"Default": "Debug", | |||
"System": "Information", | |||
"Microsoft": "Information" | |||
} | |||
} | |||
} |
@ -0,0 +1,29 @@ | |||
{ | |||
"Serilog": { | |||
"SeqServerUrl": null, | |||
"LogstashgUrl": null, | |||
"MinimumLevel": { | |||
"Default": "Information", | |||
"Override": { | |||
"Microsoft": "Warning", | |||
"Microsoft.eShopOnContainers": "Information", | |||
"System": "Warning" | |||
} | |||
} | |||
}, | |||
"IdentityUrl": "http://localhost:5105", | |||
//"ConnectionString": "127.0.0.1", | |||
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.TenantADb;User Id=sa;Password=Pass@word;", | |||
"AzureServiceBusEnabled": false, | |||
"SubscriptionClientName": "TenantACustomisation", | |||
"ApplicationInsights": { | |||
"InstrumentationKey": "" | |||
}, | |||
"EventBusRetryCount": 5, | |||
"UseVault": false, | |||
"Vault": { | |||
"Name": "eshop", | |||
"ClientId": "your-clien-id", | |||
"ClientSecret": "your-client-secret" | |||
} | |||
} |
@ -0,0 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Customisation | |||
{ | |||
public enum Fragility | |||
{ | |||
Low, Medium, High | |||
} | |||
} |
@ -0,0 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Customisation | |||
{ | |||
public enum Priority | |||
{ | |||
Low, Medium, High | |||
} | |||
} |
@ -0,0 +1,17 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Customisation | |||
{ | |||
public class ShippingInformation | |||
{ | |||
public int ShippingInformationId { get; set; } | |||
public DateTime ArrivalTime { get; set; } | |||
public DateTime ShippingTime { get; set; } | |||
public Priority PriorityLevel { get; set; } | |||
public Fragility FragilityLevel { get; set; } | |||
public String OrderNumber { get; set; } | |||
} | |||
} |