Merge branch 'feature/display-marketing-banner-mvc' into dev

This commit is contained in:
Christian Arenas 2017-06-16 12:16:16 +02:00
commit a537e689f4
36 changed files with 561 additions and 38 deletions

View File

@ -61,6 +61,7 @@ services:
- MongoConnectionString=mongodb://nosql.data - MongoConnectionString=mongodb://nosql.data
- MongoDatabase=MarketingDb - MongoDatabase=MarketingDb
- EventBusConnection=rabbitmq - EventBusConnection=rabbitmq
- ExternalCatalogBaseUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110 #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. - 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"
@ -73,6 +74,7 @@ services:
- OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102 - OrderingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5102
- IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105. - IdentityUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103 - BasketUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5103
- MarketingUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5110
- CatalogUrlHC=http://catalog.api/hc - CatalogUrlHC=http://catalog.api/hc
- OrderingUrlHC=http://ordering.api/hc - OrderingUrlHC=http://ordering.api/hc
- IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser. - IdentityUrlHC=http://identity.api/hc #Local: Use ${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}, if using external IP or DNS name from browser.
@ -87,7 +89,8 @@ services:
- CatalogUrl=http://catalog.api - CatalogUrl=http://catalog.api
- OrderingUrl=http://ordering.api - OrderingUrl=http://ordering.api
- BasketUrl=http://basket.api - BasketUrl=http://basket.api
- IdentityUrl=http://10.0.75.1:5105 #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser. - IdentityUrl=http://10.0.75.1:5105
- MarketingUrl=http://marketing.api #Local: Use 10.0.75.1 in a "Docker for Windows" environment, if using "localhost" from browser.
#Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser. #Remote: Use ${ESHOP_EXTERNAL_DNS_NAME_OR_IP} if using external IP or DNS name from browser.
ports: ports:
- "5100:80" - "5100:80"

View File

@ -76,6 +76,7 @@ services:
- ordering.api - ordering.api
- identity.api - identity.api
- basket.api - basket.api
- marketing.api
sql.data: sql.data:
image: microsoft/mssql-server-linux image: microsoft/mssql-server-linux

View File

@ -108,7 +108,8 @@ namespace Identity.API.Configuration
IdentityServerConstants.StandardScopes.OfflineAccess, IdentityServerConstants.StandardScopes.OfflineAccess,
"orders", "orders",
"basket", "basket",
"locations" "locations",
"marketing"
}, },
} }
}; };

View File

@ -11,19 +11,23 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using System; using System;
using System.Linq; using System.Linq;
using Microsoft.Extensions.Options;
[Route("api/v1/[controller]")] [Route("api/v1/[controller]")]
[Authorize] [Authorize]
public class CampaignsController : Controller public class CampaignsController : Controller
{ {
private readonly MarketingContext _context; private readonly MarketingContext _context;
private readonly MarketingSettings _settings;
private readonly IMarketingDataRepository _marketingDataRepository; private readonly IMarketingDataRepository _marketingDataRepository;
public CampaignsController(MarketingContext context, public CampaignsController(MarketingContext context,
IMarketingDataRepository marketingDataRepository) IMarketingDataRepository marketingDataRepository,
IOptionsSnapshot<MarketingSettings> settings)
{ {
_context = context; _context = context;
_marketingDataRepository = marketingDataRepository; _marketingDataRepository = marketingDataRepository;
_settings = settings.Value;
} }
[HttpGet] [HttpGet]
@ -88,10 +92,11 @@
return NotFound(); return NotFound();
} }
campaignToUpdate.Name = campaignDto.Name;
campaignToUpdate.Description = campaignDto.Description; campaignToUpdate.Description = campaignDto.Description;
campaignToUpdate.From = campaignDto.From; campaignToUpdate.From = campaignDto.From;
campaignToUpdate.To = campaignDto.To; campaignToUpdate.To = campaignDto.To;
campaignToUpdate.Url = campaignDto.Url; campaignToUpdate.PictureUri = campaignDto.PictureUri;
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@ -136,7 +141,9 @@
var userCampaignList = await _context.Rules var userCampaignList = await _context.Rules
.OfType<UserLocationRule>() .OfType<UserLocationRule>()
.Include(c => c.Campaign) .Include(c => c.Campaign)
.Where(c => c.LocationId == userLocation.LocationId) .Where(c => c.Campaign.From <= DateTime.Now
&& c.Campaign.To >= DateTime.Now
&& c.LocationId == userLocation.LocationId)
.Select(c => c.Campaign) .Select(c => c.Campaign)
.ToListAsync(); .ToListAsync();
@ -166,10 +173,11 @@
return new CampaignDTO return new CampaignDTO
{ {
Id = campaign.Id, Id = campaign.Id,
Name = campaign.Name,
Description = campaign.Description, Description = campaign.Description,
From = campaign.From, From = campaign.From,
To = campaign.To, To = campaign.To,
Url = campaign.Url, PictureUri = GetUriPlaceholder(campaign.PictureUri)
}; };
} }
@ -178,11 +186,21 @@
return new Campaign return new Campaign
{ {
Id = campaignDto.Id, Id = campaignDto.Id,
Name = campaignDto.Name,
Description = campaignDto.Description, Description = campaignDto.Description,
From = campaignDto.From, From = campaignDto.From,
To = campaignDto.To, To = campaignDto.To,
Url = campaignDto.Url PictureUri = campaignDto.PictureUri
}; };
} }
private string GetUriPlaceholder(string campaignUri)
{
var baseUri = _settings.ExternalCatalogBaseUrl;
campaignUri = campaignUri.Replace("http://externalcatalogbaseurltobereplaced", baseUri);
return campaignUri;
}
} }
} }

View File

@ -0,0 +1,28 @@
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Controllers
{
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using System.IO;
public class PicController : Controller
{
private readonly IHostingEnvironment _env;
public PicController(IHostingEnvironment env)
{
_env = env;
}
[HttpGet]
[Route("api/v1/campaigns/{campaignId:int}/pic")]
public IActionResult GetImage(int campaignId)
{
var webRoot = _env.WebRootPath;
var path = Path.Combine(webRoot, campaignId + ".png");
var buffer = System.IO.File.ReadAllBytes(path);
return File(buffer, "image/png");
}
}
}

View File

@ -6,12 +6,14 @@
{ {
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; } public string Description { get; set; }
public DateTime From { get; set; } public DateTime From { get; set; }
public DateTime To { get; set; } public DateTime To { get; set; }
public string Url { get; set; } public string PictureUri { get; set; }
} }
} }

View File

@ -31,8 +31,8 @@
.ForSqlServerUseSequenceHiLo("campaign_hilo") .ForSqlServerUseSequenceHiLo("campaign_hilo")
.IsRequired(); .IsRequired();
builder.Property(m => m.Description) builder.Property(m => m.Name)
.HasColumnName("Description") .HasColumnName("Name")
.IsRequired(); .IsRequired();
builder.Property(m => m.From) builder.Property(m => m.From)
@ -47,6 +47,10 @@
.HasColumnName("Description") .HasColumnName("Description")
.IsRequired(); .IsRequired();
builder.Property(m => m.PictureUri)
.HasColumnName("PictureUri")
.IsRequired();
builder.HasMany(m => m.Rules) builder.HasMany(m => m.Rules)
.WithOne(r => r.Campaign) .WithOne(r => r.Campaign)
.HasForeignKey(r => r.CampaignId) .HasForeignKey(r => r.CampaignId)

View File

@ -9,7 +9,7 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
public class MarketingContextSeed public static class MarketingContextSeed
{ {
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0) public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
{ {
@ -33,31 +33,33 @@
{ {
new Campaign new Campaign
{ {
Description = "Campaign1", Name = ".NET Bot Black Hoodie 50% OFF",
Description = "Campaign Description 1",
From = DateTime.Now, From = DateTime.Now,
To = DateTime.Now.AddDays(7), To = DateTime.Now.AddDays(7),
Url = "http://CampaignUrl.test/12f09ed3cef54187123f500ad", PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/campaigns/1/pic",
Rules = new List<Rule> Rules = new List<Rule>
{ {
new UserLocationRule new UserLocationRule
{ {
Description = "UserLocationRule1", Description = "Campaign is only for United States users.",
LocationId = 1 LocationId = 1
} }
} }
}, },
new Campaign new Campaign
{ {
Description = "Campaign2", Name = "Roslyn Red T-Shirt 3x2",
From = DateTime.Now.AddDays(7), Description = "Campaign Description 2",
From = DateTime.Now.AddDays(-7),
To = DateTime.Now.AddDays(14), To = DateTime.Now.AddDays(14),
Url = "http://CampaignUrl.test/02a59eda65f241871239000ff", PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/campaigns/2/pic",
Rules = new List<Rule> Rules = new List<Rule>
{ {
new UserLocationRule new UserLocationRule
{ {
Description = "UserLocationRule2", Description = "Campaign is only for Seattle users.",
LocationId = 6 LocationId = 3
} }
} }
} }

View File

@ -8,8 +8,8 @@ using Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure;
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
{ {
[DbContext(typeof(MarketingContext))] [DbContext(typeof(MarketingContext))]
[Migration("20170609104915_Initial")] [Migration("20170615163431_Init")]
partial class Initial partial class Init
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
@ -33,11 +33,17 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Mark
b.Property<DateTime>("From") b.Property<DateTime>("From")
.HasColumnName("From"); .HasColumnName("From");
b.Property<string>("Name")
.IsRequired()
.HasColumnName("Name");
b.Property<string>("PictureUri")
.IsRequired()
.HasColumnName("PictureUri");
b.Property<DateTime>("To") b.Property<DateTime>("To")
.HasColumnName("To"); .HasColumnName("To");
b.Property<string>("Url");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("Campaign"); b.ToTable("Campaign");

View File

@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.MarketingMigrations
{ {
public partial class Initial : Migration public partial class Init : Migration
{ {
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
@ -23,8 +23,9 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Mark
Id = table.Column<int>(nullable: false), Id = table.Column<int>(nullable: false),
Description = table.Column<string>(nullable: false), Description = table.Column<string>(nullable: false),
From = table.Column<DateTime>(nullable: false), From = table.Column<DateTime>(nullable: false),
To = table.Column<DateTime>(nullable: false), Name = table.Column<string>(nullable: false),
Url = table.Column<string>(nullable: true) PictureUri = table.Column<string>(nullable: false),
To = table.Column<DateTime>(nullable: false)
}, },
constraints: table => constraints: table =>
{ {

View File

@ -32,11 +32,17 @@ namespace Microsoft.eShopOnContainers.Services.Marketing.API.Infrastructure.Mark
b.Property<DateTime>("From") b.Property<DateTime>("From")
.HasColumnName("From"); .HasColumnName("From");
b.Property<string>("Name")
.IsRequired()
.HasColumnName("Name");
b.Property<string>("PictureUri")
.IsRequired()
.HasColumnName("PictureUri");
b.Property<DateTime>("To") b.Property<DateTime>("To")
.HasColumnName("To"); .HasColumnName("To");
b.Property<string>("Url");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("Campaign"); b.ToTable("Campaign");

View File

@ -12,6 +12,10 @@
<ItemGroup> <ItemGroup>
<Folder Include="Infrastructure\MarketingMigrations\" /> <Folder Include="Infrastructure\MarketingMigrations\" />
<Content Include="Pics\**\*;">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Folder Include="Infrastructure\MarketingMigrations\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" /> <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.1.0" />
@ -53,4 +57,13 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" /> <ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="Dockerfile">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Pics\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>

View File

@ -5,5 +5,6 @@
public string ConnectionString { get; set; } public string ConnectionString { get; set; }
public string MongoConnectionString { get; set; } public string MongoConnectionString { get; set; }
public string MongoDatabase { get; set; } public string MongoDatabase { get; set; }
public string ExternalCatalogBaseUrl { get; set; }
} }
} }

View File

@ -7,13 +7,15 @@
{ {
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; } public string Description { get; set; }
public DateTime From { get; set; } public DateTime From { get; set; }
public DateTime To { get; set; } public DateTime To { get; set; }
public string Url { get; set; } public string PictureUri { get; set; }
public List<Rule> Rules { get; set; } public List<Rule> Rules { get; set; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

View File

@ -12,6 +12,7 @@
.UseKestrel() .UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>() .UseStartup<Startup>()
.UseWebRoot("Pics")
.Build(); .Build();
host.Run(); host.Run();

View File

@ -20,6 +20,9 @@
using Infrastructure.Repositories; using Infrastructure.Repositories;
using Autofac; using Autofac;
using Autofac.Extensions.DependencyInjection; using Autofac.Extensions.DependencyInjection;
using Polly;
using System.Threading.Tasks;
using System.Data.SqlClient;
public class Startup public class Startup
{ {
@ -133,10 +136,12 @@
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
}); });
ConfigureEventBus(app); var context = (MarketingContext)app
.ApplicationServices.GetService(typeof(MarketingContext));
MarketingContextSeed.SeedAsync(app, loggerFactory) WaitForSqlAvailabilityAsync(context, loggerFactory, app).Wait();
.Wait();
ConfigureEventBus(app);
} }
protected virtual void ConfigureAuth(IApplicationBuilder app) protected virtual void ConfigureAuth(IApplicationBuilder app)
@ -166,5 +171,28 @@
eventBus.Subscribe<UserLocationUpdatedIntegrationEvent, eventBus.Subscribe<UserLocationUpdatedIntegrationEvent,
IIntegrationEventHandler<UserLocationUpdatedIntegrationEvent>>(); IIntegrationEventHandler<UserLocationUpdatedIntegrationEvent>>();
} }
private async Task WaitForSqlAvailabilityAsync(MarketingContext ctx, ILoggerFactory loggerFactory, IApplicationBuilder app, int retries = 0)
{
var logger = loggerFactory.CreateLogger(nameof(Startup));
var policy = CreatePolicy(retries, logger, nameof(WaitForSqlAvailabilityAsync));
await policy.ExecuteAsync(async () =>
{
await MarketingContextSeed.SeedAsync(app, loggerFactory);
});
}
private Policy CreatePolicy(int retries, ILogger logger, string prefix)
{
return Policy.Handle<SqlException>().
WaitAndRetryAsync(
retryCount: retries,
sleepDurationProvider: retry => TimeSpan.FromSeconds(5),
onRetry: (exception, timeSpan, retry, ctx) =>
{
logger.LogTrace($"[{prefix}] Exception {exception.GetType().Name} with message ${exception.Message} detected on attempt {retry} of {retries}");
}
);
}
} }
} }

View File

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

View File

@ -11,6 +11,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
public string CatalogUrl { get; set; } public string CatalogUrl { get; set; }
public string OrderingUrl { get; set; } public string OrderingUrl { get; set; }
public string BasketUrl { get; set; } public string BasketUrl { get; set; }
public string MarketingUrl { get; set; }
public Logging Logging { get; set; } public Logging Logging { get; set; }
} }

View File

@ -0,0 +1,80 @@
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using System.Collections.Generic;
using System.Threading.Tasks;
[Authorize]
public class CampaignsController : Controller
{
private ICampaignService _campaignService;
public CampaignsController(ICampaignService campaignService) =>
_campaignService = campaignService;
public async Task<IActionResult> Index()
{
var campaignDtoList = await _campaignService.GetCampaigns();
if(campaignDtoList is null)
{
return View();
}
var campaignList = MapCampaignModelListToDtoList(campaignDtoList);
return View(campaignList);
}
public async Task<IActionResult> Details(int id)
{
var campaignDto = await _campaignService.GetCampaignById(id);
if (campaignDto is null)
{
return NotFound();
}
var campaign = new Campaign
{
Id = campaignDto.Id,
Name = campaignDto.Name,
Description = campaignDto.Description,
From = campaignDto.From,
To = campaignDto.To,
PictureUri = campaignDto.PictureUri
};
return View(campaign);
}
private List<Campaign> MapCampaignModelListToDtoList(IEnumerable<CampaignDTO> campaignDtoList)
{
var campaignList = new List<Campaign>();
foreach(var campaignDto in campaignDtoList)
{
campaignList.Add(MapCampaignDtoToModel(campaignDto));
}
return campaignList;
}
private Campaign MapCampaignDtoToModel(CampaignDTO campaign)
{
return new Campaign
{
Id = campaign.Id,
Name = campaign.Name,
Description = campaign.Description,
From = campaign.From,
To = campaign.To,
PictureUri = campaign.PictureUri
};
}
}
}

View File

@ -1,4 +1,6 @@
namespace WebMVC.Infrastructure using System;
namespace WebMVC.Infrastructure
{ {
public static class API public static class API
{ {
@ -79,5 +81,18 @@
return $"{baseUri}catalogTypes"; return $"{baseUri}catalogTypes";
} }
} }
public static class Marketing
{
public static string GetAllCampaigns(string baseUri, Guid userId)
{
return $"{baseUri}user/{userId}";
}
public static string GetAllCampaignById(string baseUri, int id)
{
return $"{baseUri}{id}";
}
}
} }
} }

View File

@ -0,0 +1,19 @@
namespace Microsoft.eShopOnContainers.WebMVC.Models
{
using System;
public class CampaignDTO
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string PictureUri { get; set; }
}
}

View File

@ -0,0 +1,71 @@
namespace Microsoft.eShopOnContainers.WebMVC.Services
{
using global::WebMVC.Infrastructure;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public class CampaignService : ICampaignService
{
private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IHttpClient _apiClient;
private readonly ILogger<CampaignService> _logger;
private readonly string _remoteServiceBaseUrl;
private readonly IHttpContextAccessor _httpContextAccesor;
public CampaignService(IOptionsSnapshot<AppSettings> settings, IHttpClient httpClient,
ILogger<CampaignService> logger, IHttpContextAccessor httpContextAccesor)
{
_settings = settings;
_apiClient = httpClient;
_logger = logger;
_remoteServiceBaseUrl = $"{_settings.Value.MarketingUrl}/api/v1/campaigns/";
_httpContextAccesor = httpContextAccesor ?? throw new ArgumentNullException(nameof(httpContextAccesor));
}
public async Task<IEnumerable<CampaignDTO>> GetCampaigns()
{
var userId = GetUserIdentity();
var allCampaignItemsUri = API.Marketing.GetAllCampaigns(_remoteServiceBaseUrl, Guid.Parse(userId));
var authorizationToken = await GetUserTokenAsync();
var dataString = await _apiClient.GetStringAsync(allCampaignItemsUri, authorizationToken);
var response = JsonConvert.DeserializeObject<IEnumerable<CampaignDTO>>(dataString);
return response;
}
public async Task<CampaignDTO> GetCampaignById(int id)
{
var userId = GetUserIdentity();
var campaignByIdItemUri = API.Marketing.GetAllCampaignById(_remoteServiceBaseUrl, id);
var authorizationToken = await GetUserTokenAsync();
var dataString = await _apiClient.GetStringAsync(campaignByIdItemUri, authorizationToken);
var response = JsonConvert.DeserializeObject<CampaignDTO>(dataString);
return response;
}
private string GetUserIdentity()
{
return _httpContextAccesor.HttpContext.User.FindFirst("sub").Value;
}
private async Task<string> GetUserTokenAsync()
{
var context = _httpContextAccesor.HttpContext;
return await context.Authentication.GetTokenAsync("access_token");
}
}
}

View File

@ -0,0 +1,13 @@
namespace Microsoft.eShopOnContainers.WebMVC.Services
{
using Microsoft.eShopOnContainers.WebMVC.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
public interface ICampaignService
{
Task<IEnumerable<CampaignDTO>> GetCampaigns();
Task<CampaignDTO> GetCampaignById(int id);
}
}

View File

@ -70,6 +70,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
services.AddTransient<ICatalogService, CatalogService>(); services.AddTransient<ICatalogService, CatalogService>();
services.AddTransient<IOrderingService, OrderingService>(); services.AddTransient<IOrderingService, OrderingService>();
services.AddTransient<IBasketService, BasketService>(); services.AddTransient<IBasketService, BasketService>();
services.AddTransient<ICampaignService, CampaignService>();
services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>(); services.AddTransient<IIdentityParser<ApplicationUser>, IdentityParser>();
if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString) if (Configuration.GetValue<string>("UseResilientHttp") == bool.TrueString)
@ -125,7 +126,7 @@ namespace Microsoft.eShopOnContainers.WebMVC
SaveTokens = true, SaveTokens = true,
GetClaimsFromUserInfoEndpoint = true, GetClaimsFromUserInfoEndpoint = true,
RequireHttpsMetadata = false, RequireHttpsMetadata = false,
Scope = { "openid", "profile", "orders", "basket" } Scope = { "openid", "profile", "orders", "basket", "marketing" }
}; };
//Wait untill identity service is ready on compose. //Wait untill identity service is ready on compose.

View File

@ -0,0 +1,19 @@
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
using System;
public class Campaign
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
public string PictureUri { get; set; }
}
}

View File

@ -0,0 +1,28 @@
@{
ViewData["Title"] = "Campaign details";
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Campaign
}
<section class="esh-campaigns-hero">
<div class="container">
<img class="esh-campaigns-title" src="~/images/main_banner_text.png" />
</div>
</section>
@Html.Partial("_Header", new List<Header>() {
new Header() { Controller = "Catalog", Text = "Back to catalog" },
new Header() { Controller = "Campaigns", Text = "Back to Campaigns" } })
<div class="container">
<div class="card esh-campaigns-items">
<img class="card-img-top" src="@Model.PictureUri" alt="Card image cap">
<div class="card-block">
<h4 class="card-title">@Model.Name</h4>
<p class="card-text">@Model.Description</p>
<p class="card-text">
<small class="text-muted">
From @Model.From.ToString("MMMM dd, yyyy") until @Model.To.ToString("MMMM dd, yyyy")
</small>
</p>
</div>
</div>
</div>

View File

@ -0,0 +1,36 @@
@{
ViewData["Title"] = "Campaigns";
@model IEnumerable<Campaign>
}
<section class="esh-campaigns-hero">
<div class="container">
<img class="esh-campaigns-title" src="~/images/main_banner_text.png" />
</div>
</section>
@Html.Partial("_Header", new List<Header>() {
new Header() { Controller = "Catalog", Text = "Back to catalog" } })
<div class="container">
<div class="card-group esh-campaigns-items row">
@foreach (var campaign in Model ?? Enumerable.Empty<Campaign>())
{
<div class="esh-campaigns-item col-md-4">
<form asp-controller="Campaigns" asp-action="Details" asp-route-id="@campaign.Id">
<div class="card-block">
<h4 class="card-title esh-campaigns-name">@campaign.Name</h4>
<img class="card-img-top esh-campaigns-thumbnail" src="@campaign.PictureUri" alt="@campaign.Name">
<input class="esh-campaigns-button" type="submit" value="More details">
</div>
<div class="card-footer">
<small class="text-muted">
From @campaign.From.ToString("MMMM dd, yyyy") until @campaign.To.ToString("MMMM dd, yyyy")
</small>
</div>
</form>
</div>
}
</div>
</div>

View File

@ -13,6 +13,7 @@
<link rel="stylesheet" href="~/css/shared/components/identity/identity.css" /> <link rel="stylesheet" href="~/css/shared/components/identity/identity.css" />
<link rel="stylesheet" href="~/css/shared/components/pager/pager.css" /> <link rel="stylesheet" href="~/css/shared/components/pager/pager.css" />
<link rel="stylesheet" href="~/css/basket/basket.component.css" /> <link rel="stylesheet" href="~/css/basket/basket.component.css" />
<link rel="stylesheet" href="~/css/campaigns/campaigns.component.css" />
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" /> <link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" /> <link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
<link rel="stylesheet" href="~/css/orders/orders.component.css" /> <link rel="stylesheet" href="~/css/orders/orders.component.css" />

View File

@ -26,6 +26,14 @@
<img class="esh-identity-image" src="~/images/my_orders.png"> <img class="esh-identity-image" src="~/images/my_orders.png">
</a> </a>
<a class="esh-identity-item"
asp-controller="Campaigns"
asp-action="Index">
<div class="esh-identity-name esh-identity-name--upper">Campaigns</div>
<img class="esh-identity-image" src="~/images/my_orders.png">
</a>
<a class="esh-identity-item" <a class="esh-identity-item"
href="javascript:document.getElementById('logoutForm').submit()"> href="javascript:document.getElementById('logoutForm').submit()">

View File

@ -9,6 +9,18 @@
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath> <DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Content Remove="wwwroot/css\campaigns\catalog.component.css" />
</ItemGroup>
<ItemGroup>
<Content Include="wwwroot\css\campaigns\campaigns.component.css" />
<Content Include="wwwroot\css\campaigns\orders.component.css" />
<Content Include="wwwroot\css\catalog\orders.component.css">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<!--<ItemGroup> <!--<ItemGroup>
<Compile Remove="wwwroot\lib\bootstrap\**" /> <Compile Remove="wwwroot\lib\bootstrap\**" />
<Content Remove="wwwroot\lib\bootstrap\**" /> <Content Remove="wwwroot\lib\bootstrap\**" />
@ -67,6 +79,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Views\Campaigns\" />
<Folder Include="wwwroot\lib\" /> <Folder Include="wwwroot\lib\" />
</ItemGroup> </ItemGroup>

View File

@ -2,6 +2,7 @@
"CatalogUrl": "http://localhost:5101", "CatalogUrl": "http://localhost:5101",
"OrderingUrl": "http://localhost:5102", "OrderingUrl": "http://localhost:5102",
"BasketUrl": "http://localhost:5103", "BasketUrl": "http://localhost:5103",
"MarketingUrl": "http://localhost:5110",
"IdentityUrl": "http://localhost:5105", "IdentityUrl": "http://localhost:5105",
"CallBackUrl": "http://localhost:5100/", "CallBackUrl": "http://localhost:5100/",
"IsClusterEnv": "False", "IsClusterEnv": "False",

View File

@ -0,0 +1,98 @@
.esh-campaigns-hero {
background-image: url("../../images/main_banner.png");
background-size: cover;
height: 260px;
width: 100%;
}
.esh-campaigns-title {
position: relative;
top: 74.28571px;
}
.esh-campaigns-label::before {
color: rgba(255, 255, 255, 0.5);
content: attr(data-title);
font-size: 0.65rem;
margin-top: 0.65rem;
margin-left: 0.5rem;
position: absolute;
text-transform: uppercase;
z-index: 1;
}
.esh-campaigns-label::after {
background-image: url("../../images/arrow-down.png");
height: 7px;
content: '';
position: absolute;
right: 1.5rem;
top: 2.5rem;
width: 10px;
z-index: 1;
}
.esh-campaigns-items {
margin-top: 1rem;
}
.esh-campaigns-item {
text-align: center;
margin-bottom: 1.5rem;
width: 33%;
display: inline-block;
float: none !important;
}
@media screen and (max-width: 1024px) {
.esh-campaigns-item {
width: 50%;
}
}
@media screen and (max-width: 768px) {
.esh-campaigns-item {
width: 100%;
}
}
.esh-campaigns-thumbnail {
max-width: 370px;
width: 100%;
}
.esh-campaigns-button {
background-color: #83D01B;
border: none;
color: #FFFFFF;
cursor: pointer;
font-size: 1rem;
height: 3rem;
margin-top: 1rem;
transition: all 0.35s;
width: 80%;
}
.esh-campaigns-button.is-disabled {
opacity: .5;
pointer-events: none;
}
.esh-campaigns-button:hover {
background-color: #4a760f;
transition: all 0.35s;
}
.esh-campaigns-name {
font-size: 1rem;
font-weight: 300;
margin-top: .5rem;
text-align: center;
text-transform: uppercase;
}
.esh-campaigns-description {
text-align: center;
font-weight: 300;
font-size: 14px;
}

View File

@ -29,7 +29,7 @@
.esh-identity-drop { .esh-identity-drop {
background: #FFFFFF; background: #FFFFFF;
height: 0; height: 0rem;
min-width: 14rem; min-width: 14rem;
right: 0; right: 0;
overflow: hidden; overflow: hidden;
@ -41,7 +41,7 @@
.esh-identity:hover .esh-identity-drop { .esh-identity:hover .esh-identity-drop {
border: 1px solid #EEEEEE; border: 1px solid #EEEEEE;
height: 7rem; height: 9.5rem;
transition: height 0.35s; transition: height 0.35s;
} }

View File

@ -117,10 +117,11 @@
{ {
return new CampaignDTO() return new CampaignDTO()
{ {
Name = "FakeCampaignName",
Description = "FakeCampaignDescription", Description = "FakeCampaignDescription",
From = DateTime.Now, From = DateTime.Now,
To = DateTime.Now.AddDays(7), To = DateTime.Now.AddDays(7),
Url = "http://CampaignUrl.test/fdaf91ad0cef5419719f50198", PictureUri = "http://externalcatalogbaseurltobereplaced/api/v1/campaigns/0/pic"
}; };
} }
} }