Merge branch 'feature/marketing-read-model-data' into marketingcampaign

# Conflicts:
#	src/Services/Location/Locations.API/Infrastructure/Services/LocationsService.cs
#	src/Services/Location/Locations.API/Model/UserLocation.cs
This commit is contained in:
Christian Arenas 2017-06-13 17:38:07 +02:00
commit be5cfe7399
29 changed files with 426 additions and 40 deletions

View File

@ -95,4 +95,16 @@ services:
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- EventBusConnection=rabbitmq - EventBusConnection=rabbitmq
ports: ports:
- "5109:80" - "5109:80"
marketing.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
- EventBusConnection=rabbitmq
- MongoConnectionString=mongodb://nosql.data
- MongoDatabase=MarketingDb
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports:
- "5110:80"

View File

@ -75,7 +75,19 @@ services:
- BasketUrl=http://basket.api - BasketUrl=http://basket.api
ports: ports:
- "5100:80" - "5100:80"
marketing.api:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
- EventBusConnection=rabbitmq
- MongoConnectionString=mongodb://nosql.data
- MongoDatabase=MarketingDb
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports:
- "5110:80"
sql.data: sql.data:
environment: environment:
- SA_PASSWORD=Pass@word - SA_PASSWORD=Pass@word

View File

@ -61,7 +61,19 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- nosql.data - nosql.data
- rabbitmq
marketing.api:
image: eshop/marketing.api
build:
context: ./src/Services/Marketing/Marketing.API
dockerfile: Dockerfile
depends_on:
- sql.data
- nosql.data
- identity.api
- rabbitmq
sql.data: sql.data:
image: microsoft/mssql-server-windows image: microsoft/mssql-server-windows

View File

@ -58,6 +58,9 @@ services:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80 - ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
- MongoConnectionString=mongodb://nosql.data
- MongoDatabase=MarketingDb
- EventBusConnection=rabbitmq
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports: ports:
- "5110:80" - "5110:80"

View File

@ -59,6 +59,9 @@ services:
- ASPNETCORE_ENVIRONMENT=Production - ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://0.0.0.0:80 - ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word - ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=Pass@word
- MongoConnectionString=mongodb://nosql.data
- MongoDatabase=MarketingDb
- EventBusConnection=rabbitmq
- identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - identityUrl=http://identity.api #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports: ports:
- "5110:80" - "5110:80"

View File

@ -53,7 +53,9 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- sql.data - sql.data
- nosql.data
- identity.api - identity.api
- rabbitmq
webspa: webspa:
image: eshop/webspa image: eshop/webspa
@ -112,3 +114,4 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on: depends_on:
- nosql.data - nosql.data
- rabbitmq

View File

@ -54,6 +54,7 @@ namespace Locations.API.Controllers
{ {
var userId = _identityService.GetUserIdentity(); var userId = _identityService.GetUserIdentity();
var result = await _locationsService.AddOrUpdateUserLocation(userId, newLocReq); var result = await _locationsService.AddOrUpdateUserLocation(userId, newLocReq);
return result ? return result ?
(IActionResult)Ok() : (IActionResult)Ok() :
(IActionResult)BadRequest(); (IActionResult)BadRequest();

View File

@ -8,14 +8,18 @@
using System.Linq; using System.Linq;
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions; using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Exceptions;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Locations.API.IntegrationEvents.Events;
public class LocationsService : ILocationsService public class LocationsService : ILocationsService
{ {
private ILocationsRepository _locationsRepository; private readonly ILocationsRepository _locationsRepository;
private readonly IEventBus _eventBus;
public LocationsService(ILocationsRepository locationsRepository) public LocationsService(ILocationsRepository locationsRepository, IEventBus eventBus)
{ {
_locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository)); _locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
} }
public async Task<Locations> GetLocation(string locationId) public async Task<Locations> GetLocation(string locationId)
@ -23,14 +27,9 @@
return await _locationsRepository.GetAsync(locationId); return await _locationsRepository.GetAsync(locationId);
} }
public async Task<UserLocation> GetUserLocation(string id) public async Task<UserLocation> GetUserLocation(string userId)
{ {
if (!Guid.TryParse(id, out Guid userId)) return await _locationsRepository.GetUserLocationAsync(id);
{
throw new ArgumentException("Not valid userId");
}
return await _locationsRepository.GetUserLocationAsync(userId.ToString());
} }
public async Task<List<Locations>> GetAllLocation() public async Task<List<Locations>> GetAllLocation()
@ -38,13 +37,8 @@
return await _locationsRepository.GetLocationListAsync(); return await _locationsRepository.GetLocationListAsync();
} }
public async Task<bool> AddOrUpdateUserLocation(string id, LocationRequest currentPosition) public async Task<bool> AddOrUpdateUserLocation(string userId, LocationRequest currentPosition)
{ {
if (!Guid.TryParse(id, out Guid userId))
{
throw new ArgumentException("Not valid userId");
}
// Get the list of ordered regions the user currently is within // Get the list of ordered regions the user currently is within
var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition); var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition);
@ -55,14 +49,40 @@
// If current area found, then update user location // If current area found, then update user location
var locationAncestors = new List<string>(); var locationAncestors = new List<string>();
var userLocation = await _locationsRepository.GetUserLocationAsync(userId.ToString()); var userLocation = await _locationsRepository.GetUserLocationAsync(userId);
userLocation = userLocation ?? new UserLocation(); userLocation = userLocation ?? new UserLocation();
userLocation.UserId = userId; userLocation.UserId = userId;
userLocation.LocationId = currentUserAreaLocationList[0].Id; userLocation.LocationId = currentUserAreaLocationList[0].Id;
userLocation.UpdateDate = DateTime.UtcNow; userLocation.UpdateDate = DateTime.UtcNow;
await _locationsRepository.UpdateUserLocationAsync(userLocation); await _locationsRepository.UpdateUserLocationAsync(userLocation);
// Publish integration event to update marketing read data model
// with the new locations updated
PublishNewUserLocationPositionIntegrationEvent(userId, currentUserAreaLocationList);
return true; return true;
} }
private void PublishNewUserLocationPositionIntegrationEvent(string userId, List<Locations> newLocations)
{
var newUserLocations = MapUserLocationDetails(newLocations);
var @event = new UserLocationUpdatedIntegrationEvent(userId, newUserLocations);
_eventBus.Publish(@event);
}
private List<UserLocationDetails> MapUserLocationDetails(List<Locations> newLocations)
{
var result = new List<UserLocationDetails>();
newLocations.ForEach(location => {
result.Add(new UserLocationDetails()
{
LocationId = location.Id,
Code = location.Code,
Description = location.Description
});
});
return result;
}
} }
} }

View File

@ -0,0 +1,18 @@
namespace Microsoft.eShopOnContainers.Services.Locations.API.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.Services.Locations.API.Model;
using System.Collections.Generic;
public class UserLocationUpdatedIntegrationEvent : IntegrationEvent
{
public string UserId { get; private set; }
public List<UserLocationDetails> LocationList { get; private set; }
public UserLocationUpdatedIntegrationEvent(string userId, List<UserLocationDetails> locationList)
{
UserId = userId;
LocationList = locationList;
}
}
}

View File

@ -10,6 +10,7 @@
<Folder Include="wwwroot\" /> <Folder Include="wwwroot\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" /> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" /> <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
@ -37,5 +38,9 @@
<ItemGroup> <ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -9,7 +9,7 @@
[BsonIgnoreIfDefault] [BsonIgnoreIfDefault]
[BsonRepresentation(BsonType.ObjectId)] [BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; } public string Id { get; set; }
public Guid UserId { get; set; } public string UserId { get; set; }
[BsonRepresentation(BsonType.ObjectId)] [BsonRepresentation(BsonType.ObjectId)]
public string LocationId { get; set; } public string LocationId { get; set; }
public DateTime UpdateDate { get; set; } public DateTime UpdateDate { get; set; }

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Locations.API.Model
{
public class UserLocationDetails
{
public string LocationId { get; set; }
public string Code { get; set; }
public string Description { get; set; }
}
}

View File

@ -1,17 +1,21 @@
using Microsoft.AspNetCore.Builder; using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure; using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure;
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Filters;
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories;
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using RabbitMQ.Client;
using System.Reflection; using System.Reflection;
using System; using System;
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Services;
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.Services.Locations.API.Infrastructure.Filters;
namespace Microsoft.eShopOnContainers.Services.Locations.API namespace Microsoft.eShopOnContainers.Services.Locations.API
{ {
@ -37,7 +41,7 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
} }
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public IServiceProvider ConfigureServices(IServiceCollection services)
{ {
// Add framework services. // Add framework services.
services.AddMvc(options => services.AddMvc(options =>
@ -46,7 +50,21 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
}).AddControllersAsServices(); }).AddControllersAsServices();
services.Configure<LocationSettings>(Configuration); services.Configure<LocationSettings>(Configuration);
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = Configuration["EventBusConnection"]
};
return new DefaultRabbitMQPersistentConnection(factory, logger);
});
RegisterServiceBus(services);
// Add framework services. // Add framework services.
services.AddSwaggerGen(options => services.AddSwaggerGen(options =>
{ {
@ -72,7 +90,13 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IIdentityService, IdentityService>(); services.AddTransient<IIdentityService, IdentityService>();
services.AddTransient<ILocationsService, LocationsService>(); services.AddTransient<ILocationsService, LocationsService>();
services.AddTransient<ILocationsRepository, LocationsRepository>(); services.AddTransient<ILocationsRepository, LocationsRepository>();
//configure autofac
var container = new ContainerBuilder();
container.Populate(services);
return new AutofacServiceProvider(container.Build());
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -109,5 +133,11 @@ namespace Microsoft.eShopOnContainers.Services.Locations.API
RequireHttpsMetadata = false RequireHttpsMetadata = false
}); });
} }
private void RegisterServiceBus(IServiceCollection services)
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
}
} }
} }

View File

@ -0,0 +1,26 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure
{
using Microsoft.eShopOnContainers.Services.Marketing.API.Model;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
public class MarketingReadDataContext
{
private readonly IMongoDatabase _database = null;
public MarketingReadDataContext(IOptions<MarketingSettings> settings)
{
var client = new MongoClient(settings.Value.MongoConnectionString);
if (client != null)
_database = client.GetDatabase(settings.Value.MongoDatabase);
}
public IMongoCollection<MarketingData> MarketingData
{
get
{
return _database.GetCollection<MarketingData>("MarketingReadDataModel");
}
}
}
}

View File

@ -0,0 +1,11 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Repositories
{
using Model;
using System.Threading.Tasks;
public interface IMarketingDataRepository
{
Task<MarketingData> GetAsync(string userId);
Task UpdateLocationAsync(MarketingData marketingData);
}
}

View File

@ -0,0 +1,41 @@
using Microsoft.eShopOnContainers.Services.Marketing.API.Model;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Repositories
{
public class MarketingDataRepository
: IMarketingDataRepository
{
private readonly MarketingReadDataContext _context;
public MarketingDataRepository(IOptions<MarketingSettings> settings)
{
_context = new MarketingReadDataContext(settings);
}
public async Task<MarketingData> GetAsync(string userId)
{
var filter = Builders<MarketingData>.Filter.Eq("UserId", userId);
return await _context.MarketingData
.Find(filter)
.FirstOrDefaultAsync();
}
public async Task UpdateLocationAsync(MarketingData marketingData)
{
var filter = Builders<MarketingData>.Filter.Eq("UserId", marketingData.UserId);
var update = Builders<MarketingData>.Update
.Set("Locations", marketingData.Locations)
.CurrentDate("UpdateDate");
await _context.MarketingData
.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true });
}
}
}

View File

@ -0,0 +1,18 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.IntegrationEvents.Events
{
using Model;
using System.Collections.Generic;
using BuildingBlocks.EventBus.Events;
public class UserLocationUpdatedIntegrationEvent : IntegrationEvent
{
public string UserId { get; private set; }
public List<UserLocationDetails> LocationList { get; private set; }
public UserLocationUpdatedIntegrationEvent(string userId, List<UserLocationDetails> locationList)
{
UserId = userId;
LocationList = locationList;
}
}
}

View File

@ -0,0 +1,46 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.IntegrationEvents.Handlers
{
using BuildingBlocks.EventBus.Abstractions;
using System.Threading.Tasks;
using Events;
using System;
using Infrastructure.Repositories;
using Model;
using System.Collections.Generic;
public class UserLocationUpdatedIntegrationEventHandler
: IIntegrationEventHandler<UserLocationUpdatedIntegrationEvent>
{
private readonly IMarketingDataRepository _marketingDataRepository;
public UserLocationUpdatedIntegrationEventHandler(IMarketingDataRepository repository)
{
_marketingDataRepository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public async Task Handle(UserLocationUpdatedIntegrationEvent @event)
{
var userMarketingData = await _marketingDataRepository.GetAsync(@event.UserId);
userMarketingData = userMarketingData ??
new MarketingData() { UserId = @event.UserId };
userMarketingData.Locations = MapUpdatedUserLocations(@event.LocationList);
await _marketingDataRepository.UpdateLocationAsync(userMarketingData);
}
private List<Location> MapUpdatedUserLocations(List<UserLocationDetails> newUserLocations)
{
var result = new List<Location>();
newUserLocations.ForEach(location => {
result.Add(new Location()
{
LocationId = location.LocationId,
Code = location.Code,
Description = location.Description
});
});
return result;
}
}
}

View File

@ -12,10 +12,9 @@
<ItemGroup> <ItemGroup>
<Folder Include="Infrastructure\MarketingMigrations\" /> <Folder Include="Infrastructure\MarketingMigrations\" />
<Folder Include="IntegrationEvents\EventHandling\" />
<Folder Include="IntegrationEvents\Events\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" /> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
@ -39,6 +38,10 @@
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" /> <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.1" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.1" />
<PackageReference Include="mongocsharpdriver" Version="2.4.3" />
<PackageReference Include="MongoDB.Bson" Version="2.4.3" />
<PackageReference Include="MongoDB.Driver" Version="2.4.3" />
<PackageReference Include="MongoDB.Driver.Core" Version="2.4.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -47,4 +50,7 @@
<ItemGroup> <ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -3,5 +3,7 @@
public class MarketingSettings public class MarketingSettings
{ {
public string ConnectionString { get; set; } public string ConnectionString { get; set; }
public string MongoConnectionString { get; set; }
public string MongoDatabase { get; set; }
} }
} }

View File

@ -0,0 +1,17 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model
{
public class Location
{
[BsonRepresentation(BsonType.ObjectId)]
public string LocationId { get; set; }
public string Code { get; set; }
public string Description { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model
{
public class MarketingData
{
[BsonIgnoreIfDefault]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string UserId { get; set; }
public List<Location> Locations { get; set; }
public DateTime UpdateDate { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Model
{
public class UserLocationDetails
{
public string LocationId { get; set; }
public string Code { get; set; }
public string Description { get; set; }
}
}

View File

@ -11,6 +11,15 @@
using System.Reflection; using System.Reflection;
using System; using System;
using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Filters; using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Filters;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using RabbitMQ.Client;
using BuildingBlocks.EventBus.Abstractions;
using BuildingBlocks.EventBus;
using IntegrationEvents.Events;
using IntegrationEvents.Handlers;
using Infrastructure.Repositories;
using Autofac;
using Autofac.Extensions.DependencyInjection;
public class Startup public class Startup
{ {
@ -35,7 +44,7 @@
public IConfigurationRoot Configuration { get; } public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public IServiceProvider ConfigureServices(IServiceCollection services)
{ {
// Add framework services. // Add framework services.
services.AddMvc(options => services.AddMvc(options =>
@ -43,6 +52,8 @@
options.Filters.Add(typeof(HttpGlobalExceptionFilter)); options.Filters.Add(typeof(HttpGlobalExceptionFilter));
}).AddControllersAsServices(); //Injecting Controllers themselves thru DIFor further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services }).AddControllersAsServices(); //Injecting Controllers themselves thru DIFor further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services
services.Configure<MarketingSettings>(Configuration);
services.AddDbContext<MarketingContext>(options => services.AddDbContext<MarketingContext>(options =>
{ {
options.UseSqlServer(Configuration["ConnectionString"], options.UseSqlServer(Configuration["ConnectionString"],
@ -59,6 +70,20 @@
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval //Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
}); });
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = Configuration["EventBusConnection"]
};
return new DefaultRabbitMQPersistentConnection(factory, logger);
});
RegisterServiceBus(services);
// Add framework services. // Add framework services.
services.AddSwaggerGen(options => services.AddSwaggerGen(options =>
{ {
@ -80,6 +105,14 @@
.AllowAnyHeader() .AllowAnyHeader()
.AllowCredentials()); .AllowCredentials());
}); });
services.AddTransient<IMarketingDataRepository, MarketingDataRepository>();
//configure autofac
var container = new ContainerBuilder();
container.Populate(services);
return new AutofacServiceProvider(container.Build());
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -100,11 +133,12 @@
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
}); });
ConfigureEventBus(app);
MarketingContextSeed.SeedAsync(app, loggerFactory) MarketingContextSeed.SeedAsync(app, loggerFactory)
.Wait(); .Wait();
} }
protected virtual void ConfigureAuth(IApplicationBuilder app) protected virtual void ConfigureAuth(IApplicationBuilder app)
{ {
var identityUrl = Configuration.GetValue<string>("IdentityUrl"); var identityUrl = Configuration.GetValue<string>("IdentityUrl");
@ -115,5 +149,22 @@
RequireHttpsMetadata = false RequireHttpsMetadata = false
}); });
} }
private void RegisterServiceBus(IServiceCollection services)
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddSingleton<IEventBusSubscriptionsManager,
InMemoryEventBusSubscriptionsManager>();
services.AddTransient<IIntegrationEventHandler<UserLocationUpdatedIntegrationEvent>,
UserLocationUpdatedIntegrationEventHandler>();
}
private void ConfigureEventBus(IApplicationBuilder app)
{
var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();
eventBus.Subscribe<UserLocationUpdatedIntegrationEvent,
IIntegrationEventHandler<UserLocationUpdatedIntegrationEvent>>();
}
} }
} }

View File

@ -6,5 +6,7 @@
} }
}, },
"ConnectionString": "127.0.0.1", "ConnectionString": "127.0.0.1",
"MongoConnectionString": "mongodb://nosql.data",
"MongoDatabase": "MarketingDb",
"IdentityUrl": "http://localhost:5105" "IdentityUrl": "http://localhost:5105"
} }

View File

@ -25,6 +25,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\Services\Basket\Basket.API\Basket.API.csproj" /> <ProjectReference Include="..\..\..\src\Services\Basket\Basket.API\Basket.API.csproj" />
<ProjectReference Include="..\..\..\src\Services\Catalog\Catalog.API\Catalog.API.csproj" /> <ProjectReference Include="..\..\..\src\Services\Catalog\Catalog.API\Catalog.API.csproj" />
<ProjectReference Include="..\..\..\src\Services\Location\Locations.API\Locations.API.csproj" />
<ProjectReference Include="..\..\..\src\Services\Marketing\Marketing.API\Marketing.API.csproj" />
<ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" /> <ProjectReference Include="..\..\..\src\Services\Ordering\Ordering.API\Ordering.API.csproj" />
<ProjectReference Include="..\..\..\src\Web\WebMVC\WebMVC.csproj" /> <ProjectReference Include="..\..\..\src\Web\WebMVC\WebMVC.csproj" />
</ItemGroup> </ItemGroup>
@ -33,6 +35,12 @@
<None Update="appsettings.json"> <None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="Services\Locations\appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Services\Marketing\appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Services\Ordering\settings.json"> <None Update="Services\Ordering\settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

View File

@ -18,7 +18,7 @@ namespace FunctionalTests.Middleware
public async Task Invoke(HttpContext httpContext) public async Task Invoke(HttpContext httpContext)
{ {
var identity = new ClaimsIdentity("cookies"); var identity = new ClaimsIdentity("cookies");
identity.AddClaim(new Claim("sub", "1234")); identity.AddClaim(new Claim("sub", "9e3163b9-1ae6-4652-9dc6-7898ab7b7a00"));
httpContext.User.AddIdentity(identity); httpContext.User.AddIdentity(identity);
await _next.Invoke(httpContext); await _next.Invoke(httpContext);
} }

View File

@ -88,7 +88,7 @@ namespace FunctionalTests.Services.Ordering
string BuildBasket() string BuildBasket()
{ {
var order = new CustomerBasket("1234"); var order = new CustomerBasket("9e3163b9-1ae6-4652-9dc6-7898ab7b7a00");
order.Items = new List<Microsoft.eShopOnContainers.Services.Basket.API.Model.BasketItem>() order.Items = new List<Microsoft.eShopOnContainers.Services.Basket.API.Model.BasketItem>()
{ {
new Microsoft.eShopOnContainers.Services.Basket.API.Model.BasketItem() new Microsoft.eShopOnContainers.Services.Basket.API.Model.BasketItem()

View File

@ -1,9 +1,6 @@
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.TestHost;
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
namespace IntegrationTests.Services.Locations namespace IntegrationTests.Services.Locations
{ {