Browse Source

More microservices for tenants

pull/1240/head
espent1004 5 years ago
parent
commit
309e3b23eb
43 changed files with 1098 additions and 179 deletions
  1. +11
    -0
      docker-compose.override.yml
  2. +10
    -0
      docker-compose.yml
  3. +51
    -0
      eShopOnContainers-ServicesAndWebApps.sln
  4. +0
    -10
      src/Services/TenantCustomisations/TenantACustomisations/Controllers/SavedEventsController.cs
  5. +3
    -7
      src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/AutofacModules/ApplicationModule.cs
  6. +1
    -2
      src/Services/TenantCustomisations/TenantACustomisations/Startup.cs
  7. +4
    -0
      src/Services/TenantCustomisations/TenantACustomisations/TenantACustomisations.csproj
  8. +106
    -0
      src/Services/TenantCustomisations/TenantAShippingInformation/Controllers/ShippingInformationsController.cs
  9. +2
    -5
      src/Services/TenantCustomisations/TenantAShippingInformation/Database/DbInitializer.cs
  10. +5
    -15
      src/Services/TenantCustomisations/TenantAShippingInformation/Database/TenantAContext.cs
  11. +2
    -6
      src/Services/TenantCustomisations/TenantAShippingInformation/ExternalServices/IShippingService.cs
  12. +2
    -5
      src/Services/TenantCustomisations/TenantAShippingInformation/ExternalServices/MockedShippingService.cs
  13. +5
    -1
      src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/AutofacModules/ApplicationModule.cs
  14. +1
    -5
      src/Services/TenantCustomisations/TenantAShippingInformation/IntegrationEvents/Events/OrderStatusChangedToSubmittedIntegrationEvent.cs
  15. +1
    -6
      src/Services/TenantCustomisations/TenantAShippingInformation/Models/Fragility.cs
  16. +1
    -6
      src/Services/TenantCustomisations/TenantAShippingInformation/Models/Priority.cs
  17. +1
    -4
      src/Services/TenantCustomisations/TenantAShippingInformation/Models/ShippingInformation.cs
  18. +12
    -8
      src/Services/TenantCustomisations/TenantAShippingInformation/Startup.cs
  19. +1
    -1
      src/Services/TenantCustomisations/TenantAShippingInformation/appsettings.json
  20. +106
    -0
      src/Services/TenantCustomisations/TenantBShippingInformation/Controllers/ShippingInformationsController.cs
  21. +3
    -3
      src/Services/TenantCustomisations/TenantBShippingInformation/Database/DbInitializer.cs
  22. +0
    -28
      src/Services/TenantCustomisations/TenantBShippingInformation/Database/TenantAContext.cs
  23. +28
    -0
      src/Services/TenantCustomisations/TenantBShippingInformation/Database/TenantBContext.cs
  24. +2
    -2
      src/Services/TenantCustomisations/TenantBShippingInformation/Dockerfile
  25. +2
    -2
      src/Services/TenantCustomisations/TenantBShippingInformation/ExternalServices/IShippingService.cs
  26. +2
    -2
      src/Services/TenantCustomisations/TenantBShippingInformation/ExternalServices/MockedShippingService.cs
  27. +7
    -3
      src/Services/TenantCustomisations/TenantBShippingInformation/Infrastructure/AutofacModules/ApplicationModule.cs
  28. +1
    -1
      src/Services/TenantCustomisations/TenantBShippingInformation/Infrastructure/AutofacModules/MediatorModule.cs
  29. +1
    -1
      src/Services/TenantCustomisations/TenantBShippingInformation/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs
  30. +1
    -1
      src/Services/TenantCustomisations/TenantBShippingInformation/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
  31. +17
    -5
      src/Services/TenantCustomisations/TenantBShippingInformation/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs
  32. +1
    -1
      src/Services/TenantCustomisations/TenantBShippingInformation/Models/Fragility.cs
  33. +1
    -1
      src/Services/TenantCustomisations/TenantBShippingInformation/Models/Priority.cs
  34. +1
    -1
      src/Services/TenantCustomisations/TenantBShippingInformation/Models/ShippingInformation.cs
  35. +87
    -0
      src/Services/TenantCustomisations/TenantBShippingInformation/Program.cs
  36. +27
    -0
      src/Services/TenantCustomisations/TenantBShippingInformation/Properties/launchSettings.json
  37. +414
    -0
      src/Services/TenantCustomisations/TenantBShippingInformation/Startup.cs
  38. +56
    -0
      src/Services/TenantCustomisations/TenantBShippingInformation/TenantBShippingInformation.csproj
  39. +9
    -0
      src/Services/TenantCustomisations/TenantBShippingInformation/appsettings.Development.json
  40. +29
    -0
      src/Services/TenantCustomisations/TenantBShippingInformation/appsettings.json
  41. +0
    -3
      src/Services/TenantManager/TenantManager/Database/DbInitializer.cs
  42. +12
    -3
      src/Web/WebMVC/Controllers/OrderController.cs
  43. +72
    -41
      src/Web/WebMVC/Views/Order/Index.cshtml

+ 11
- 0
docker-compose.override.yml View File

@ -78,9 +78,20 @@ services:
- EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME}
- EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD}
- AzureServiceBusEnabled=False
- ConnectionString=${ESHOP_AZURE_CATALOG_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.TenantAShippingInformationDb;User Id=sa;Password=Pass@word}
ports:
- "5117:80"
tenantbshippinginformation:
environment:
- EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq}
- EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME}
- EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD}
- AzureServiceBusEnabled=False
- ConnectionString=${ESHOP_AZURE_CATALOG_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.TenantBShippingInformationDb;User Id=sa;Password=Pass@word}
ports:
- "5118:80"
basket.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development


+ 10
- 0
docker-compose.yml View File

@ -56,6 +56,16 @@ services:
build:
context: .
dockerfile: src/Services/TenantCustomisations/TenantAShippingInformation/Dockerfile
depends_on:
- sql.data
tenantbshippinginformation:
image: ${REGISTRY:-eshop}/tenantbshippinginformation:${PLATFORM:-linux}-${TAG:-latest}
build:
context: .
dockerfile: src/Services/TenantCustomisations/TenantBShippingInformation/Dockerfile
depends_on:
- sql.data
catalog.api:
image: ${REGISTRY:-eshop}/catalog.api:${PLATFORM:-linux}-${TAG:-latest}


+ 51
- 0
eShopOnContainers-ServicesAndWebApps.sln View File

@ -160,6 +160,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TenantACustomisations", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TenantAShippingInformation", "src\Services\TenantCustomisations\TenantAShippingInformation\TenantAShippingInformation.csproj", "{6228E4B8-4672-4253-9F9F-2F75BD40623B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TenantBShippingInformation", "src\Services\TenantCustomisations\TenantBShippingInformation\TenantBShippingInformation.csproj", "{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -1950,6 +1952,54 @@ Global
{6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|x64.Build.0 = Release|Any CPU
{6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|x86.ActiveCfg = Release|Any CPU
{6228E4B8-4672-4253-9F9F-2F75BD40623B}.Release|x86.Build.0 = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|ARM.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|iPhone.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|x64.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|x64.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|x86.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.AppStore|x86.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|ARM.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|ARM.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|iPhone.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|x64.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|x64.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|x86.ActiveCfg = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Debug|x86.Build.0 = Debug|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|Any CPU.Build.0 = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|ARM.ActiveCfg = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|ARM.Build.0 = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|iPhone.ActiveCfg = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|iPhone.Build.0 = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|x64.ActiveCfg = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|x64.Build.0 = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|x86.ActiveCfg = Release|Any CPU
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2021,6 +2071,7 @@ Global
{773A0C2A-CA6F-4D4A-860B-C518EFA6FACB} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{76651DAE-FF27-44A4-AF84-34689E2FFDF2} = {773A0C2A-CA6F-4D4A-860B-C518EFA6FACB}
{6228E4B8-4672-4253-9F9F-2F75BD40623B} = {773A0C2A-CA6F-4D4A-860B-C518EFA6FACB}
{B18DE120-E00E-4456-AAA3-ECFDD01FAEAA} = {773A0C2A-CA6F-4D4A-860B-C518EFA6FACB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9}


+ 0
- 10
src/Services/TenantCustomisations/TenantACustomisations/Controllers/SavedEventsController.cs View File

@ -24,7 +24,6 @@ namespace TenantACustomisations.Controllers
private List<Type> types = new List<Type>()
{
typeof(OrderStatusChangedToSubmittedIntegrationEvent),
typeof(OrderStatusChangedToAwaitingValidationIntegrationEvent)
};
@ -74,15 +73,6 @@ namespace TenantACustomisations.Controllers
found = true;
}
}
else if(e is OrderStatusChangedToSubmittedIntegrationEvent)
{
OrderStatusChangedToSubmittedIntegrationEvent evt = (OrderStatusChangedToSubmittedIntegrationEvent)e;
if (evt.OrderId == Int32.Parse(orderId))
{
found = true;
}
}
});
if (!found)


+ 3
- 7
src/Services/TenantCustomisations/TenantACustomisations/Infrastructure/AutofacModules/ApplicationModule.cs View File

@ -21,13 +21,9 @@ namespace Microsoft.eShopOnContainers.Services.TenantACustomisations.Infrastruct
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(UserCheckoutAcceptedIntegrationEvent).GetTypeInfo().Assembly)
.AsClosedTypesOf(typeof(IIntegrationEventHandler<>));
builder.RegisterType<MockedShippingService>()
.As<IShippingService>()
.InstancePerLifetimeScope();
/*builder.RegisterAssemblyTypes(typeof(UserCheckoutAcceptedIntegrationEvent).GetTypeInfo().Assembly)
.AsClosedTypesOf(typeof(IIntegrationEventHandler<>));*/
}
}
}

+ 1
- 2
src/Services/TenantCustomisations/TenantACustomisations/Startup.cs View File

@ -34,7 +34,6 @@
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;
@ -123,7 +122,7 @@
var eventBus = app.ApplicationServices.GetRequiredService<BuildingBlocks.EventBus.Abstractions.IEventBus>();
//eventBus.Subscribe<UserCheckoutAcceptedIntegrationEvent, IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>>();
eventBus.Subscribe<OrderStatusChangedToSubmittedIntegrationEvent, OrderStatusChangedToSubmittedIntegrationEventHandler>();
//eventBus.Subscribe<OrderStatusChangedToSubmittedIntegrationEvent, OrderStatusChangedToSubmittedIntegrationEventHandler>();
}


+ 4
- 0
src/Services/TenantCustomisations/TenantACustomisations/TenantACustomisations.csproj View File

@ -56,4 +56,8 @@
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="IntegrationEvents\EventHandling" />
</ItemGroup>
</Project>

+ 106
- 0
src/Services/TenantCustomisations/TenantAShippingInformation/Controllers/ShippingInformationsController.cs View File

@ -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 TenantAShippingInformation.Database;
using TenantAShippingInformation.Models;
namespace TenantAShippingInformation.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);
}
}
}

+ 2
- 5
src/Services/TenantCustomisations/TenantAShippingInformation/Database/DbInitializer.cs View File

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TenantACustomisations.ExternalServices;
using TenantAShippingInformation.Models;
namespace TenantACustomisations.Database
namespace TenantAShippingInformation.Database
{
public class DbInitializer
{
@ -28,7 +26,6 @@ namespace TenantACustomisations.Database
context.SaveChanges();
}
}
}

+ 5
- 15
src/Services/TenantCustomisations/TenantAShippingInformation/Database/TenantAContext.cs View File

@ -1,13 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using TenantACustomisations.ExternalServices;
using TenantACustomisations.IntegrationEvents.Events;
using TenantAShippingInformation.Models;
namespace TenantACustomisations.Database
namespace TenantAShippingInformation.Database
{
public class TenantAContext : DbContext
{
@ -17,12 +12,7 @@ namespace TenantACustomisations.Database
}
public DbSet<ShippingInformation> ShippingInformation { get; set; }
public DbSet<SavedEvent> SavedEvent
{
get;
set;
}
}
public class TenantAContextDesignFactory : IDesignTimeDbContextFactory<TenantAContext>
@ -30,7 +20,7 @@ namespace TenantACustomisations.Database
public TenantAContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<TenantAContext>()
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.TenantADb;Integrated Security=true");
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.TenantAShippingInformationDb;Integrated Security=true");
return new TenantAContext(optionsBuilder.Options);
}


+ 2
- 6
src/Services/TenantCustomisations/TenantAShippingInformation/ExternalServices/IShippingService.cs View File

@ -1,10 +1,6 @@
using Ordering.API.Application.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TenantAShippingInformation.Models;
namespace TenantACustomisations.ExternalServices
namespace TenantAShippingInformation.ExternalServices
{
public interface IShippingService
{


+ 2
- 5
src/Services/TenantCustomisations/TenantAShippingInformation/ExternalServices/MockedShippingService.cs View File

@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ordering.API.Application.Models;
using TenantAShippingInformation.Models;
namespace TenantACustomisations.ExternalServices
namespace TenantAShippingInformation.ExternalServices
{
public class MockedShippingService : IShippingService
{


+ 5
- 1
src/Services/TenantCustomisations/TenantAShippingInformation/Infrastructure/AutofacModules/ApplicationModule.cs View File

@ -1,6 +1,7 @@
using Autofac;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System.Reflection;
using TenantAShippingInformation.ExternalServices;
using TenantAShippingInformation.IntegrationEvents.EventHandling;
namespace TenantAShippingInformation.Infrastructure.AutofacModules
@ -20,9 +21,12 @@ namespace TenantAShippingInformation.Infrastructure.AutofacModules
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(OrderStatusChangedToAwaitingValidationIntegrationEventHandler).GetTypeInfo().Assembly)
builder.RegisterAssemblyTypes(typeof(OrderStatusChangedToSubmittedIntegrationEventHandler).GetTypeInfo().Assembly)
.AsClosedTypesOf(typeof(IIntegrationEventHandler<>));
builder.RegisterType<MockedShippingService>()
.As<IShippingService>()
.InstancePerLifetimeScope();
}
}
}

+ 1
- 5
src/Services/TenantCustomisations/TenantAShippingInformation/IntegrationEvents/Events/OrderStatusChangedToSubmittedIntegrationEvent.cs View File

@ -1,10 +1,6 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TenantACustomisations.IntegrationEvents.Events
namespace TenantAShippingInformation.IntegrationEvents.Events
{
public class OrderStatusChangedToSubmittedIntegrationEvent : IntegrationEvent
{


+ 1
- 6
src/Services/TenantCustomisations/TenantAShippingInformation/Models/Fragility.cs View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TenantACustomisations.ExternalServices
namespace TenantAShippingInformation.Models
{
public enum Fragility
{


+ 1
- 6
src/Services/TenantCustomisations/TenantAShippingInformation/Models/Priority.cs View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TenantACustomisations.ExternalServices
namespace TenantAShippingInformation.Models
{
public enum Priority
{


+ 1
- 4
src/Services/TenantCustomisations/TenantAShippingInformation/Models/ShippingInformation.cs View File

@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TenantACustomisations.ExternalServices
namespace TenantAShippingInformation.Models
{
public class ShippingInformation
{


+ 12
- 8
src/Services/TenantCustomisations/TenantAShippingInformation/Startup.cs View File

@ -27,8 +27,12 @@ using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using RabbitMQ.Client;
using Swashbuckle.AspNetCore.Swagger;
using TenantAShippingInformation.Database;
using TenantAShippingInformation.Infrastructure.AutofacModules;
using TenantAShippingInformation;
using TenantAShippingInformation.Infrastructure.Filters;
using TenantAShippingInformation.IntegrationEvents.EventHandling;
using TenantAShippingInformation.IntegrationEvents.Events;
namespace TenantAShippingInformation
{
@ -103,11 +107,11 @@ namespace TenantAShippingInformation
});
ConfigureEventBus(app);
//using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
//{
// var context = serviceScope.ServiceProvider.GetRequiredService<TenantAContext>();
// context.Database.EnsureCreated();
//}
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService<TenantAContext>();
context.Database.EnsureCreated();
}
}
@ -116,7 +120,7 @@ namespace TenantAShippingInformation
var eventBus = app.ApplicationServices.GetRequiredService<Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions.IEventBus>();
//eventBus.Subscribe<UserCheckoutAcceptedIntegrationEvent, IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>>();
//eventBus.Subscribe<OrderStatusChangedToSubmittedIntegrationEvent, OrderStatusChangedToSubmittedIntegrationEventHandler>();
eventBus.Subscribe<OrderStatusChangedToSubmittedIntegrationEvent, OrderStatusChangedToSubmittedIntegrationEventHandler>();
}
@ -213,8 +217,8 @@ namespace TenantAShippingInformation
public static IServiceCollection AddCustomDbContext(this IServiceCollection services, IConfiguration configuration)
{
//services.AddDbContext<TenantAContext>(options =>
// options.UseSqlServer(configuration["ConnectionString"]));
services.AddDbContext<TenantAContext>(options =>
options.UseSqlServer(configuration["ConnectionString"]));
services.AddDbContext<IntegrationEventLogContext>(options =>
{


+ 1
- 1
src/Services/TenantCustomisations/TenantAShippingInformation/appsettings.json View File

@ -13,7 +13,7 @@
},
"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;",
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.TenantAShippingInformationDb;User Id=sa;Password=Pass@word;",
"AzureServiceBusEnabled": false,
"SubscriptionClientName": "TenantAShippingInformation",
"ApplicationInsights": {


+ 106
- 0
src/Services/TenantCustomisations/TenantBShippingInformation/Controllers/ShippingInformationsController.cs View File

@ -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 TenantBShippingInformation.Database;
using TenantBShippingInformation.Models;
namespace TenantBShippingInformation.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ShippingInformationsController : ControllerBase
{
private readonly TenantBContext _context;
public ShippingInformationsController(TenantBContext 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);
}
}
}

+ 3
- 3
src/Services/TenantCustomisations/TenantBShippingInformation/Database/DbInitializer.cs View File

@ -1,12 +1,12 @@
using System;
using System.Linq;
using TenantAShippingInformation.Models;
using TenantBShippingInformation.Models;
namespace TenantAShippingInformation.Database
namespace TenantBShippingInformation.Database
{
public class DbInitializer
{
public void Initialize(TenantAContext context)
public void Initialize(TenantBContext context)
{
context.Database.EnsureCreated();


+ 0
- 28
src/Services/TenantCustomisations/TenantBShippingInformation/Database/TenantAContext.cs View File

@ -1,28 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using TenantAShippingInformation.Models;
namespace TenantAShippingInformation.Database
{
public class TenantAContext : DbContext
{
public TenantAContext(DbContextOptions<TenantAContext> options)
: base(options)
{
}
public DbSet<ShippingInformation> ShippingInformation { get; set; }
}
public class TenantAContextDesignFactory : IDesignTimeDbContextFactory<TenantAContext>
{
public TenantAContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<TenantAContext>()
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.TenantAShippingInformationDb;Integrated Security=true");
return new TenantAContext(optionsBuilder.Options);
}
}
}

+ 28
- 0
src/Services/TenantCustomisations/TenantBShippingInformation/Database/TenantBContext.cs View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using TenantBShippingInformation.Models;
namespace TenantBShippingInformation.Database
{
public class TenantBContext : DbContext
{
public TenantBContext(DbContextOptions<TenantBContext> options)
: base(options)
{
}
public DbSet<ShippingInformation> ShippingInformation { get; set; }
}
public class TenantBContextDesignFactory : IDesignTimeDbContextFactory<TenantBContext>
{
public TenantBContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<TenantBContext>()
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.TenantBShippingInformationDb;Integrated Security=true");
return new TenantBContext(optionsBuilder.Options);
}
}
}

+ 2
- 2
src/Services/TenantCustomisations/TenantBShippingInformation/Dockerfile View File

@ -48,7 +48,7 @@ COPY test/ServicesTests/LoadTest/LoadTest.csproj test/ServicesTests/LoadTest/
RUN dotnet restore eShopOnContainers-ServicesAndWebApps.sln
COPY . .
WORKDIR /src/src/Services/TenantCustomisations/TenantAShippingInformation
WORKDIR /src/src/Services/TenantCustomisations/TenantBShippingInformation
RUN dotnet publish --no-restore -c Release -o /app
FROM build AS publish
@ -56,4 +56,4 @@ FROM build AS publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "TenantAShippingInformation.dll"]
ENTRYPOINT ["dotnet", "TenantBShippingInformation.dll"]

+ 2
- 2
src/Services/TenantCustomisations/TenantBShippingInformation/ExternalServices/IShippingService.cs View File

@ -1,6 +1,6 @@
using TenantAShippingInformation.Models;
using TenantBShippingInformation.Models;
namespace TenantAShippingInformation.ExternalServices
namespace TenantBShippingInformation.ExternalServices
{
public interface IShippingService
{


+ 2
- 2
src/Services/TenantCustomisations/TenantBShippingInformation/ExternalServices/MockedShippingService.cs View File

@ -1,7 +1,7 @@
using System;
using TenantAShippingInformation.Models;
using TenantBShippingInformation.Models;
namespace TenantAShippingInformation.ExternalServices
namespace TenantBShippingInformation.ExternalServices
{
public class MockedShippingService : IShippingService
{


+ 7
- 3
src/Services/TenantCustomisations/TenantBShippingInformation/Infrastructure/AutofacModules/ApplicationModule.cs View File

@ -1,9 +1,10 @@
using Autofac;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System.Reflection;
using TenantAShippingInformation.IntegrationEvents.EventHandling;
using TenantBShippingInformation.ExternalServices;
using TenantBShippingInformation.IntegrationEvents.EventHandling;
namespace TenantAShippingInformation.Infrastructure.AutofacModules
namespace TenantBShippingInformation.Infrastructure.AutofacModules
{
public class ApplicationModule
@ -20,9 +21,12 @@ namespace TenantAShippingInformation.Infrastructure.AutofacModules
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(OrderStatusChangedToAwaitingValidationIntegrationEventHandler).GetTypeInfo().Assembly)
builder.RegisterAssemblyTypes(typeof(OrderStatusChangedToSubmittedIntegrationEventHandler).GetTypeInfo().Assembly)
.AsClosedTypesOf(typeof(IIntegrationEventHandler<>));
builder.RegisterType<MockedShippingService>()
.As<IShippingService>()
.InstancePerLifetimeScope();
}
}
}

+ 1
- 1
src/Services/TenantCustomisations/TenantBShippingInformation/Infrastructure/AutofacModules/MediatorModule.cs View File

@ -1,6 +1,6 @@
using Autofac;
namespace TenantAShippingInformation.Infrastructure.AutofacModules
namespace TenantBShippingInformation.Infrastructure.AutofacModules
{
public class MediatorModule : Autofac.Module
{


+ 1
- 1
src/Services/TenantCustomisations/TenantBShippingInformation/Infrastructure/Filters/AuthorizeCheckOperationFilter.cs View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TenantAShippingInformation.Infrastructure.Filters
namespace TenantBShippingInformation.Infrastructure.Filters
{
public class AuthorizeCheckOperationFilter : IOperationFilter
{


+ 1
- 1
src/Services/TenantCustomisations/TenantBShippingInformation/Infrastructure/Filters/HttpGlobalExceptionFilter.cs View File

@ -1,4 +1,4 @@
namespace TenantAShippingInformation.Infrastructure.Filters
namespace TenantBShippingInformation.Infrastructure.Filters
{
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;


+ 17
- 5
src/Services/TenantCustomisations/TenantBShippingInformation/IntegrationEvents/EventHandling/OrderStatusChangedToSubmittedIntegrationEventHandler.cs View File

@ -7,7 +7,10 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using TenantBShippingInformation.Database;
using TenantBShippingInformation.ExternalServices;
using TenantBShippingInformation.IntegrationEvents.Events;
using TenantBShippingInformation.Models;
namespace TenantBShippingInformation.IntegrationEvents.EventHandling
{
@ -15,19 +18,28 @@ namespace TenantBShippingInformation.IntegrationEvents.EventHandling
IIntegrationEventHandler<OrderStatusChangedToSubmittedIntegrationEvent>
{
private readonly ILogger<OrderStatusChangedToSubmittedIntegrationEventHandler> _logger;
private readonly IShippingService _shippingService;
private readonly TenantBContext _context;
public OrderStatusChangedToSubmittedIntegrationEventHandler(ILogger<OrderStatusChangedToSubmittedIntegrationEventHandler> logger)
public OrderStatusChangedToSubmittedIntegrationEventHandler(ILogger<OrderStatusChangedToSubmittedIntegrationEventHandler> logger, IShippingService shippingService, TenantBContext 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"))
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}- TenantB"))
{
//TODO
Debug.WriteLine(@event);
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}- TenantB"))
{
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at TenantB - ({@IntegrationEvent})", @event.Id, @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();
}
}
}
}

+ 1
- 1
src/Services/TenantCustomisations/TenantBShippingInformation/Models/Fragility.cs View File

@ -1,4 +1,4 @@
namespace TenantAShippingInformation.Models
namespace TenantBShippingInformation.Models
{
public enum Fragility
{


+ 1
- 1
src/Services/TenantCustomisations/TenantBShippingInformation/Models/Priority.cs View File

@ -1,4 +1,4 @@
namespace TenantAShippingInformation.Models
namespace TenantBShippingInformation.Models
{
public enum Priority
{


+ 1
- 1
src/Services/TenantCustomisations/TenantBShippingInformation/Models/ShippingInformation.cs View File

@ -1,6 +1,6 @@
using System;
namespace TenantAShippingInformation.Models
namespace TenantBShippingInformation.Models
{
public class ShippingInformation
{


+ 87
- 0
src/Services/TenantCustomisations/TenantBShippingInformation/Program.cs View File

@ -0,0 +1,87 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Serilog;
using System;
using System.IO;
namespace TenantBShippingInformation
{
public class Program
{
public static readonly string Namespace = typeof(Program).Namespace;
public static readonly string AppName = Namespace;
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();
}
}
}

+ 27
- 0
src/Services/TenantCustomisations/TenantBShippingInformation/Properties/launchSettings.json View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50373",
"sslPort": 44351
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"TenantBShippingInformation": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

+ 414
- 0
src/Services/TenantCustomisations/TenantBShippingInformation/Startup.cs View File

@ -0,0 +1,414 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using HealthChecks.UI.Client;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.ServiceFabric;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http;
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.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using RabbitMQ.Client;
using Swashbuckle.AspNetCore.Swagger;
using TenantBShippingInformation.Infrastructure.AutofacModules;
using TenantBShippingInformation.Infrastructure.Filters;
using TenantBShippingInformation.Database;
using TenantBShippingInformation.IntegrationEvents.Events;
using TenantBShippingInformation.IntegrationEvents.EventHandling;
namespace TenantBShippingInformation
{
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<TenantBContext>();
context.Database.EnsureCreated();
}
}
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<Microsoft.eShopOnContainers.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<TenantBContext>(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"];
}
factory.VirtualHost = "TenantB";
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, "TenantB", 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;
}
}
}

+ 56
- 0
src/Services/TenantCustomisations/TenantBShippingInformation/TenantBShippingInformation.csproj View File

@ -0,0 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</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\Devspaces.Support\Devspaces.Support.csproj" />
<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" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" Version="2.2.0" />
</ItemGroup>
</Project>

+ 9
- 0
src/Services/TenantCustomisations/TenantBShippingInformation/appsettings.Development.json View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

+ 29
- 0
src/Services/TenantCustomisations/TenantBShippingInformation/appsettings.json View File

@ -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.TenantBShippingInformationDb;User Id=sa;Password=Pass@word;",
"AzureServiceBusEnabled": false,
"SubscriptionClientName": "TenantBShippingInformation",
"ApplicationInsights": {
"InstrumentationKey": ""
},
"EventBusRetryCount": 5,
"UseVault": false,
"Vault": {
"Name": "eshop",
"ClientId": "your-clien-id",
"ClientSecret": "your-client-secret"
}
}

+ 0
- 3
src/Services/TenantManager/TenantManager/Database/DbInitializer.cs View File

@ -17,12 +17,10 @@ namespace TenantManager.Database
var tenant1 = new Tenant { TenantName = "Tekna" };
context.Tenant.Add(tenant1);
var method1 = new Method { MethodName = "OrderStatusChangedToSubmittedIntegrationEvent" };
var method2 = new Method { MethodName = "OrderStatusChangedToAwaitingValidationIntegrationEvent" };
var methods = new[]
{
method1,
method2
};
@ -33,7 +31,6 @@ namespace TenantManager.Database
var customisations = new[]
{
new Customisation {Tenant=tenant1, Method=method1, CustomisationUrl = @"http://tenantacustomisation/api/savedevents"},
new Customisation {Tenant=tenant1, Method=method2, CustomisationUrl = @"http://tenantacustomisation/api/savedevents"}
};


+ 12
- 3
src/Web/WebMVC/Controllers/OrderController.cs View File

@ -23,6 +23,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
private IBasketService _basketSvc;
private readonly IIdentityParser<ApplicationUser> _appUserParser;
private static String tenantACustomisationsUrl = @"http://tenantacustomisation/";
private static String tenantAShippingInformationUrl = @"http://tenantashippinginformation/";
private static String tenantBShippingInformationUrl = @"http://tenantbshippinginformation/";
private readonly ILogger<OrderController> _logger;
@ -99,11 +101,18 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
if (user.TenantId == 1)
{
List<ShippingInformation> shippingInformation = GetShippingInfo(vm);
List<ShippingInformation> shippingInformation = GetShippingInfo(vm, tenantAShippingInformationUrl);
_logger.LogInformation("----- Shipping info{@ShippingInformation}", shippingInformation);
ViewData["ShippingInfo"] = shippingInformation;
}
else if (user.TenantId == 2)
{
List<ShippingInformation> shippingInformation = GetShippingInfo(vm, tenantBShippingInformationUrl);
_logger.LogInformation("----- Shipping info{@ShippingInformation}", shippingInformation);
ViewData["ShippingInfo"] = shippingInformation;
}
ViewData["TenantID"] = user.TenantId;
return View(vm);
}
@ -130,13 +139,13 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
}
}
private List<ShippingInformation> GetShippingInfo(List<Order> orders)
private List<ShippingInformation> GetShippingInfo(List<Order> orders, String url)
{
List<ShippingInformation> shippingInformation = new List<ShippingInformation>();
using (var client = new HttpClient(new HttpClientHandler
{AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate}))
{
client.BaseAddress = new Uri(tenantACustomisationsUrl);
client.BaseAddress = new Uri(url);
try
{
HttpResponseMessage response = client.GetAsync("api/shippinginformations").Result;


+ 72
- 41
src/Web/WebMVC/Views/Order/Index.cshtml View File

@ -12,6 +12,7 @@
new Header() {Controller = "OrderManagement", Text = "Orders Management"}
};
var shippingInfo = ViewData["ShippingInfo"] as List<ShippingInformation>;
var tenantId = (int)ViewData["TenantId"];
}
<div class="esh-orders">
@ -23,11 +24,16 @@
<section class="esh-orders-title col-2">Date</section>
<section class="esh-orders-title col-1">Total</section>
<section class="esh-orders-title col-1">Status</section>
@if (shippingInfo != null)
@if (shippingInfo != null && tenantId == 1)
{
<section class="esh-orders-title col-2">Shipping date</section>
<section class="esh-orders-title col-2">Estimated arrival date</section>
}
@if (shippingInfo != null && tenantId == 2)
{
<section class="esh-orders-title col-2">Priority</section>
<section class="esh-orders-title col-2">Fragility</section>
}
<section class="esh-orders-title col-2"></section>
</article>
@ -35,48 +41,73 @@
{
foreach (var item in Model)
{
<article class="esh-orders-items row">
<section class="esh-orders-item col-1">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
<section class="esh-orders-item col-2">@item.Date.ToShortDateString()</section>
<section class="esh-orders-item col-1">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-1">@Html.DisplayFor(modelItem => item.Status)</section>
@if (shippingInfo != null)
<article class="esh-orders-items row">
<section class="esh-orders-item col-1">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
<section class="esh-orders-item col-2">@item.Date.ToShortDateString()</section>
<section class="esh-orders-item col-1">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-1">@Html.DisplayFor(modelItem => item.Status)</section>
@if (shippingInfo != null && tenantId == 1)
{
<section class="esh-orders-item col-2">
@for (var i = 0; i < shippingInfo.Count(); i++)
{
var si = shippingInfo[i];
if (si.OrderNumber.Equals(item.OrderNumber))
{
@si.ShippingTime.ToShortDateString()
;
break;
}
}
</section>
<section class="esh-orders-item col-2">
@for (var i = 0; i < shippingInfo.Count(); i++)
{
var si = shippingInfo[i];
if (si.OrderNumber.Equals(item.OrderNumber))
{
@si.ArrivalTime.ToShortDateString()
;
break;
}
}
</section>
}
@if (shippingInfo != null && tenantId == 2)
{
<section class="esh-orders-item col-2">
@for (var i = 0; i < shippingInfo.Count(); i++)
{
var si = shippingInfo[i];
if (si.OrderNumber.Equals(item.OrderNumber))
{
@si.PriorityLevel.ToString();
break;
}
}
</section>
<section class="esh-orders-item col-2">
@for (var i = 0; i < shippingInfo.Count(); i++)
{
var si = shippingInfo[i];
if (si.OrderNumber.Equals(item.OrderNumber))
{
<section class="esh-orders-item col-2">
@for (var i = 0; i < shippingInfo.Count(); i++)
{
var si = shippingInfo[i];
if (si.OrderNumber.Equals(item.OrderNumber))
{
@si.ShippingTime.ToShortDateString()
;
break;
}
}
</section>
<section class="esh-orders-item col-2">
@for (var i = 0; i < shippingInfo.Count(); i++)
{
var si = shippingInfo[i];
if (si.OrderNumber.Equals(item.OrderNumber))
{
@si.ArrivalTime.ToShortDateString()
;
break;
}
}
</section>
@si.FragilityLevel.ToString();
break;
}
<section class="esh-orders-item col-1">
<a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
</section>
<section class="esh-orders-item col-1">
@if (item.Status.ToLower() == "submitted")
{
<a class="esh-orders-link" asp-controller="Order" asp-action="cancel" asp-route-orderId="@item.OrderNumber">Cancel</a>
}
</section>
</article>
}
</section>
}
<section class="esh-orders-item col-1">
<a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
</section>
<section class="esh-orders-item col-1">
@if (item.Status.ToLower() == "submitted")
{
<a class="esh-orders-link" asp-controller="Order" asp-action="cancel" asp-route-orderId="@item.OrderNumber">Cancel</a>
}
</section>
</article>
}
}
</div>

Loading…
Cancel
Save