Ardalis/upgrade1 (#44)

* Upgrading to netcore 2.0
Updating repository to support async options and refactoring to use it.

* Starting work on tracking customer orders feature.

* Cleaning up some bugs
Working on basket view component implementation

* Fixing up styles, especially for basket in header.
This commit is contained in:
Steve Smith
2017-08-28 12:15:18 -04:00
committed by GitHub
parent 19c10ed0e5
commit ed5d17672d
45 changed files with 1024 additions and 482 deletions

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@@ -0,0 +1,26 @@
using ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System;
using System.Collections.Generic;
using System.Text;
namespace ApplicationCore.Entities.BuyerAggregate
{
public class Buyer : BaseEntity, IAggregateRoot
{
public string IdentityGuid { get; private set; }
private List<PaymentMethod> _paymentMethods = new List<PaymentMethod>();
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();
protected Buyer()
{
}
public Buyer(string identity) : this()
{
IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity));
}
}
}

View File

@@ -0,0 +1,11 @@
using Microsoft.eShopWeb.ApplicationCore.Entities;
namespace ApplicationCore.Entities.BuyerAggregate
{
public class PaymentMethod : BaseEntity
{
public string Alias { get; set; }
public string CardId { get; set; } // actual card data must be stored in a PCI compliant system, like Stripe
public string Last4 { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
namespace ApplicationCore.Entities.OrderAggregate
{
public class Address // ValueObject
{
public String Street { get; private set; }
public String City { get; private set; }
public String State { get; private set; }
public String Country { get; private set; }
public String ZipCode { get; private set; }
private Address() { }
public Address(string street, string city, string state, string country, string zipcode)
{
Street = street;
City = city;
State = state;
Country = country;
ZipCode = zipcode;
}
//protected override IEnumerable<object> GetAtomicValues()
//{
// yield return Street;
// yield return City;
// yield return State;
// yield return Country;
// yield return ZipCode;
//}
}
}

View File

@@ -0,0 +1,13 @@
namespace ApplicationCore.Entities.OrderAggregate
{
/// <summary>
/// Represents the item that was ordered. If catalog item details change, details of
/// the item that was part of a completed order should not change.
/// </summary>
public class CatalogItemOrdered // ValueObject
{
public string CatalogItemId { get; private set; }
public string ProductName { get; private set; }
public string PictureUri { get; private set; }
}
}

View File

@@ -0,0 +1,44 @@
using ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System;
using System.Collections.Generic;
using System.Text;
namespace ApplicationCore.Entities.OrderAggregate
{
public class Order : BaseEntity, IAggregateRoot
{
public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now;
public Address ShipToAddress { get; private set; }
// DDD Patterns comment
// Using a private collection field, better for DDD Aggregate's encapsulation
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
// but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour.
private readonly List<OrderItem> _orderItems;
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
// Using List<>.AsReadOnly()
// This will create a read only wrapper around the private list so is protected against "external updates".
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
}
public class OrderItem : BaseEntity
{
public CatalogItemOrdered ItemOrdered { get; private set; }
public decimal UnitPrice { get; private set; }
public int Units { get; private set; }
protected OrderItem()
{
}
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)
{
ItemOrdered = itemOrdered;
UnitPrice = unitPrice;
Units = units;
}
}
}

View File

@@ -0,0 +1,5 @@
namespace ApplicationCore.Interfaces
{
public interface IAggregateRoot
{ }
}

View File

@@ -0,0 +1,16 @@
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ApplicationCore.Interfaces
{
public interface IAsyncRepository<T> where T : BaseEntity
{
Task<T> GetByIdAsync(int id);
Task<List<T>> ListAllAsync();
Task<List<T>> ListAsync(ISpecification<T> spec);
Task<T> AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
}
}

View File

@@ -1,7 +0,0 @@
namespace ApplicationCore.Interfaces
{
public interface IImageService
{
byte[] GetImageBytesById(int id);
}
}

View File

@@ -1,16 +1,13 @@
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace ApplicationCore.Interfaces
{
public interface IRepository<T> where T : BaseEntity
{
T GetById(int id);
List<T> List();
List<T> List(ISpecification<T> spec);
IEnumerable<T> ListAll();
IEnumerable<T> List(ISpecification<T> spec);
T Add(T entity);
void Update(T entity);
void Delete(T entity);

View File

@@ -10,39 +10,38 @@ namespace Infrastructure.Data
{
public class CatalogContextSeed
{
public static async Task SeedAsync(IApplicationBuilder applicationBuilder, ILoggerFactory loggerFactory, int? retry = 0)
public static async Task SeedAsync(IApplicationBuilder applicationBuilder,
CatalogContext catalogContext,
ILoggerFactory loggerFactory, int? retry = 0)
{
int retryForAvailability = retry.Value;
try
{
var context = (CatalogContext)applicationBuilder
.ApplicationServices.GetService(typeof(CatalogContext));
// TODO: Only run this if using a real database
// context.Database.Migrate();
if (!context.CatalogBrands.Any())
if (!catalogContext.CatalogBrands.Any())
{
context.CatalogBrands.AddRange(
catalogContext.CatalogBrands.AddRange(
GetPreconfiguredCatalogBrands());
await context.SaveChangesAsync();
await catalogContext.SaveChangesAsync();
}
if (!context.CatalogTypes.Any())
if (!catalogContext.CatalogTypes.Any())
{
context.CatalogTypes.AddRange(
catalogContext.CatalogTypes.AddRange(
GetPreconfiguredCatalogTypes());
await context.SaveChangesAsync();
await catalogContext.SaveChangesAsync();
}
if (!context.CatalogItems.Any())
if (!catalogContext.CatalogItems.Any())
{
context.CatalogItems.AddRange(
catalogContext.CatalogItems.AddRange(
GetPreconfiguredItems());
await context.SaveChangesAsync();
await catalogContext.SaveChangesAsync();
}
}
catch (Exception ex)
@@ -50,9 +49,9 @@ namespace Infrastructure.Data
if (retryForAvailability < 10)
{
retryForAvailability++;
var log = loggerFactory.CreateLogger("catalog seed");
var log = loggerFactory.CreateLogger<CatalogContextSeed>();
log.LogError(ex.Message);
await SeedAsync(applicationBuilder, loggerFactory, retryForAvailability);
await SeedAsync(applicationBuilder, catalogContext, loggerFactory, retryForAvailability);
}
}
}

View File

@@ -3,10 +3,16 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Infrastructure.Data
{
public class EfRepository<T> : IRepository<T> where T : BaseEntity
/// <summary>
/// "There's some repetition here - couldn't we have some the sync methods call the async?"
/// https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i-expose-synchronous-wrappers-for-asynchronous-methods/
/// </summary>
/// <typeparam name="T"></typeparam>
public class EfRepository<T> : IRepository<T>, IAsyncRepository<T> where T : BaseEntity
{
private readonly CatalogContext _dbContext;
@@ -17,22 +23,41 @@ namespace Infrastructure.Data
public T GetById(int id)
{
return _dbContext.Set<T>().SingleOrDefault(e => e.Id == id);
return _dbContext.Set<T>().Find(id);
}
public List<T> List()
public async Task<T> GetByIdAsync(int id)
{
return _dbContext.Set<T>().ToList();
return await _dbContext.Set<T>().FindAsync(id);
}
public List<T> List(ISpecification<T> spec)
public IEnumerable<T> ListAll()
{
return _dbContext.Set<T>().AsEnumerable();
}
public async Task<List<T>> ListAllAsync()
{
return await _dbContext.Set<T>().ToListAsync();
}
public IEnumerable<T> List(ISpecification<T> spec)
{
var queryableResultWithIncludes = spec.Includes
.Aggregate(_dbContext.Set<T>().AsQueryable(),
.Aggregate(_dbContext.Set<T>().AsQueryable(),
(current, include) => current.Include(include));
return queryableResultWithIncludes
.Where(spec.Criteria)
.ToList();
.AsEnumerable();
}
public async Task<List<T>> ListAsync(ISpecification<T> spec)
{
var queryableResultWithIncludes = spec.Includes
.Aggregate(_dbContext.Set<T>().AsQueryable(),
(current, include) => current.Include(include));
return await queryableResultWithIncludes
.Where(spec.Criteria)
.ToListAsync();
}
public T Add(T entity)
@@ -43,10 +68,12 @@ namespace Infrastructure.Data
return entity;
}
public void Delete(T entity)
public async Task<T> AddAsync(T entity)
{
_dbContext.Set<T>().Remove(entity);
_dbContext.SaveChanges();
_dbContext.Set<T>().Add(entity);
await _dbContext.SaveChangesAsync();
return entity;
}
public void Update(T entity)
@@ -54,6 +81,21 @@ namespace Infrastructure.Data
_dbContext.Entry(entity).State = EntityState.Modified;
_dbContext.SaveChanges();
}
public async Task UpdateAsync(T entity)
{
_dbContext.Entry(entity).State = EntityState.Modified;
await _dbContext.SaveChangesAsync();
}
public void Delete(T entity)
{
_dbContext.Set<T>().Remove(entity);
_dbContext.SaveChanges();
}
public async Task DeleteAsync(T entity)
{
_dbContext.Set<T>().Remove(entity);
await _dbContext.SaveChangesAsync();
}
}
}

View File

@@ -1,30 +0,0 @@
using ApplicationCore.Exceptions;
using ApplicationCore.Interfaces;
using Microsoft.AspNetCore.Hosting;
using System.IO;
namespace Infrastructure.FileSystem
{
public class LocalFileImageService : IImageService
{
private readonly IHostingEnvironment _env;
public LocalFileImageService(IHostingEnvironment env)
{
_env = env;
}
public byte[] GetImageBytesById(int id)
{
try
{
var contentRoot = _env.ContentRootPath + "//Pics";
var path = Path.Combine(contentRoot, id + ".png");
return File.ReadAllBytes(path);
}
catch (FileNotFoundException ex)
{
throw new CatalogImageMissingException(ex);
}
}
}
}

View File

@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
namespace Infrastructure.Identity

View File

@@ -1,26 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
<PackageReference Include="StructureMap.Microsoft.DependencyInjection" Version="1.3.0" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ApplicationCore\ApplicationCore.csproj" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.1" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Data\Migrations\" />

View File

@@ -3,12 +3,11 @@ using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Infrastructure.Identity;
using System;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using ApplicationCore.Interfaces;
using Web;
using Microsoft.AspNetCore.Authentication;
namespace Microsoft.eShopWeb.Controllers
{
@@ -17,18 +16,15 @@ namespace Microsoft.eShopWeb.Controllers
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly string _externalCookieScheme;
private readonly IBasketService _basketService;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IOptions<IdentityCookieOptions> identityCookieOptions,
IBasketService basketService)
{
_userManager = userManager;
_signInManager = signInManager;
_externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
_basketService = basketService;
}
@@ -37,7 +33,7 @@ namespace Microsoft.eShopWeb.Controllers
[AllowAnonymous]
public async Task<IActionResult> SignIn(string returnUrl = null)
{
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ViewData["ReturnUrl"] = returnUrl;
return View();
@@ -61,7 +57,7 @@ namespace Microsoft.eShopWeb.Controllers
string anonymousBasketId = Request.Cookies[Constants.BASKET_COOKIENAME];
if (!String.IsNullOrEmpty(anonymousBasketId))
{
_basketService.TransferBasket(anonymousBasketId, model.Email);
await _basketService.TransferBasketAsync(anonymousBasketId, model.Email);
Response.Cookies.Delete(Constants.BASKET_COOKIENAME);
}
return RedirectToLocal(returnUrl);
@@ -74,7 +70,6 @@ namespace Microsoft.eShopWeb.Controllers
[ValidateAntiForgeryToken]
public async Task<ActionResult> SignOut()
{
HttpContext.Session.Clear();
await _signInManager.SignOutAsync();
return RedirectToAction(nameof(CatalogController.Index), "Catalog");

View File

@@ -6,7 +6,7 @@ namespace ApplicationCore.Interfaces
public interface IBasketService
{
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
Task TransferBasket(string anonymousId, string userName);
Task TransferBasketAsync(string anonymousId, string userName);
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity);
Task Checkout(int basketId);
}

View File

@@ -1,28 +1,20 @@
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore;
namespace Microsoft.eShopWeb
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://0.0.0.0:5106")
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureLogging(factory =>
{
factory.AddConsole(LogLevel.Warning);
factory.AddDebug();
})
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();
host.Run();
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://0.0.0.0:5106")
.UseStartup<Startup>()
.Build();
}
}

View File

@@ -1,7 +1,6 @@
using ApplicationCore.Interfaces;
using System.Threading.Tasks;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System;
using System.Linq;
using Microsoft.eShopWeb.ViewModels;
using System.Collections.Generic;
@@ -11,11 +10,11 @@ namespace Web.Services
{
public class BasketService : IBasketService
{
private readonly IRepository<Basket> _basketRepository;
private readonly IAsyncRepository<Basket> _basketRepository;
private readonly IUriComposer _uriComposer;
private readonly IRepository<CatalogItem> _itemRepository;
public BasketService(IRepository<Basket> basketRepository,
public BasketService(IAsyncRepository<Basket> basketRepository,
IRepository<CatalogItem> itemRepository,
IUriComposer uriComposer)
{
@@ -27,7 +26,7 @@ namespace Web.Services
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
{
var basketSpec = new BasketWithItemsSpecification(userName);
var basket = _basketRepository.List(basketSpec).FirstOrDefault();
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
if(basket == null)
{
@@ -63,7 +62,7 @@ namespace Web.Services
public async Task<BasketViewModel> CreateBasketForUser(string userId)
{
var basket = new Basket() { BuyerId = userId };
_basketRepository.Add(basket);
await _basketRepository.AddAsync(basket);
return new BasketViewModel()
{
@@ -75,30 +74,29 @@ namespace Web.Services
public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity)
{
var basket = _basketRepository.GetById(basketId);
var basket = await _basketRepository.GetByIdAsync(basketId);
basket.AddItem(catalogItemId, price, quantity);
_basketRepository.Update(basket);
await _basketRepository.UpdateAsync(basket);
}
public async Task Checkout(int basketId)
{
var basket = _basketRepository.GetById(basketId);
var basket = await _basketRepository.GetByIdAsync(basketId);
// TODO: Actually Process the order
_basketRepository.Delete(basket);
await _basketRepository.DeleteAsync(basket);
}
public Task TransferBasket(string anonymousId, string userName)
public async Task TransferBasketAsync(string anonymousId, string userName)
{
var basketSpec = new BasketWithItemsSpecification(anonymousId);
var basket = _basketRepository.List(basketSpec).FirstOrDefault();
if (basket == null) return Task.CompletedTask;
var basket = (await _basketRepository.ListAsync(basketSpec)).FirstOrDefault();
if (basket == null) return;
basket.BuyerId = userName;
_basketRepository.Update(basket);
return Task.CompletedTask;
await _basketRepository.UpdateAsync(basket);
}
}
}

View File

@@ -15,15 +15,15 @@ namespace Microsoft.eShopWeb.Services
{
private readonly ILogger<CatalogService> _logger;
private readonly IRepository<CatalogItem> _itemRepository;
private readonly IRepository<CatalogBrand> _brandRepository;
private readonly IRepository<CatalogType> _typeRepository;
private readonly IAsyncRepository<CatalogBrand> _brandRepository;
private readonly IAsyncRepository<CatalogType> _typeRepository;
private readonly IUriComposer _uriComposer;
public CatalogService(
ILoggerFactory loggerFactory,
IRepository<CatalogItem> itemRepository,
IRepository<CatalogBrand> brandRepository,
IRepository<CatalogType> typeRepository,
IAsyncRepository<CatalogBrand> brandRepository,
IAsyncRepository<CatalogType> typeRepository,
IUriComposer uriComposer)
{
_logger = loggerFactory.CreateLogger<CatalogService>();
@@ -83,7 +83,7 @@ namespace Microsoft.eShopWeb.Services
public async Task<IEnumerable<SelectListItem>> GetBrands()
{
_logger.LogInformation("GetBrands called.");
var brands = _brandRepository.List();
var brands = await _brandRepository.ListAllAsync();
var items = new List<SelectListItem>
{
@@ -100,7 +100,7 @@ namespace Microsoft.eShopWeb.Services
public async Task<IEnumerable<SelectListItem>> GetTypes()
{
_logger.LogInformation("GetTypes called.");
var types = _typeRepository.List();
var types = await _typeRepository.ListAllAsync();
var items = new List<SelectListItem>
{
new SelectListItem() { Value = null, Text = "All", Selected = true }

View File

@@ -1,41 +1,32 @@
using Microsoft.eShopWeb.Services;
using ApplicationCore.Interfaces;
using ApplicationCore.Services;
using Infrastructure.Data;
using Infrastructure.Identity;
using Infrastructure.Logging;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.eShopWeb.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Infrastructure.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using System.Text;
using Microsoft.AspNetCore.Http;
using ApplicationCore.Interfaces;
using Infrastructure.FileSystem;
using Infrastructure.Logging;
using Microsoft.AspNetCore.Identity;
using Web.Services;
using ApplicationCore.Services;
using Infrastructure.Data;
namespace Microsoft.eShopWeb
{
public class Startup
{
private IServiceCollection _services;
public Startup(IHostingEnvironment env)
public Startup(IConfiguration configuration)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
Configuration = configuration;
}
public IConfigurationRoot Configuration { get; }
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Requires LocalDB which can be installed with SQL Server Express 2016
@@ -46,11 +37,6 @@ namespace Microsoft.eShopWeb
{
c.UseInMemoryDatabase("Catalog");
//c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection"));
c.ConfigureWarnings(wb =>
{
//By default, in this application, we don't want to have client evaluations
wb.Log(RelationalEventId.QueryClientEvaluationWarning);
});
}
catch (System.Exception ex )
{
@@ -68,6 +54,7 @@ namespace Microsoft.eShopWeb
.AddDefaultTokenProviders();
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
services.AddMemoryCache();
services.AddScoped<ICatalogService, CachedCatalogService>();
@@ -76,12 +63,8 @@ namespace Microsoft.eShopWeb
services.Configure<CatalogSettings>(Configuration);
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));
// TODO: Remove
services.AddSingleton<IImageService, LocalFileImageService>();
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
// Add memory cache services
services.AddMemoryCache();
@@ -98,25 +81,8 @@ namespace Microsoft.eShopWeb
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.Map("/allservices", builder => builder.Run(async context =>
{
var sb = new StringBuilder();
sb.Append("<h1>All Services</h1>");
sb.Append("<table><thead>");
sb.Append("<tr><th>Type</th><th>Lifetime</th><th>Instance</th></tr>");
sb.Append("</thead><tbody>");
foreach (var svc in _services)
{
sb.Append("<tr>");
sb.Append($"<td>{svc.ServiceType.FullName}</td>");
sb.Append($"<td>{svc.Lifetime}</td>");
sb.Append($"<td>{svc.ImplementationType?.FullName}</td>");
sb.Append("</tr>");
}
sb.Append("</tbody></table>");
await context.Response.WriteAsync(sb.ToString());
}));
ListAllRegisteredServices(app);
app.UseDatabaseErrorPage();
}
else
{
@@ -124,20 +90,43 @@ namespace Microsoft.eShopWeb
}
app.UseStaticFiles();
app.UseIdentity();
app.UseAuthentication();
app.UseMvc();
}
private void ListAllRegisteredServices(IApplicationBuilder app)
{
app.Map("/allservices", builder => builder.Run(async context =>
{
var sb = new StringBuilder();
sb.Append("<h1>All Services</h1>");
sb.Append("<table><thead>");
sb.Append("<tr><th>Type</th><th>Lifetime</th><th>Instance</th></tr>");
sb.Append("</thead><tbody>");
foreach (var svc in _services)
{
sb.Append("<tr>");
sb.Append($"<td>{svc.ServiceType.FullName}</td>");
sb.Append($"<td>{svc.Lifetime}</td>");
sb.Append($"<td>{svc.ImplementationType?.FullName}</td>");
sb.Append("</tr>");
}
sb.Append("</tbody></table>");
await context.Response.WriteAsync(sb.ToString());
}));
}
public void ConfigureDevelopment(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
UserManager<ApplicationUser> userManager)
UserManager<ApplicationUser> userManager,
CatalogContext catalogContext)
{
Configure(app, env);
//Seed Data
CatalogContextSeed.SeedAsync(app, loggerFactory)
CatalogContextSeed.SeedAsync(app, catalogContext, loggerFactory)
.Wait();
var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" };
@@ -155,17 +144,17 @@ namespace Microsoft.eShopWeb
public void ConfigureProduction(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
UserManager<ApplicationUser> userManager)
UserManager<ApplicationUser> userManager,
CatalogContext catalogContext)
{
Configure(app, env);
//Seed Data
CatalogContextSeed.SeedAsync(app, loggerFactory)
CatalogContextSeed.SeedAsync(app, catalogContext, loggerFactory)
.Wait();
var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" };
userManager.CreateAsync(defaultUser, "Pass@word1").Wait();
}
}
}

View File

@@ -0,0 +1,27 @@
using ApplicationCore.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.ViewModels;
using System.Threading.Tasks;
namespace Web.ViewComponents
{
public class Basket : ViewComponent
{
private readonly IBasketService _cartSvc;
public Basket(IBasketService cartSvc) => _cartSvc = cartSvc;
public async Task<IViewComponentResult> InvokeAsync(string userName)
{
var vm = new BasketComponentViewModel();
var itemsInCart = await ItemsInBasketAsync(userName);
vm.ItemsCount = itemsInCart;
return View(vm);
}
private async Task<int> ItemsInBasketAsync(string userName)
{
var basket = await _cartSvc.GetOrCreateBasketForUser(userName);
return basket.Items.Count;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Microsoft.eShopWeb.ViewModels
{
public class BasketComponentViewModel
{
public int ItemsCount { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
@model BasketComponentViewModel
@{
ViewData["Title"] = "My Basket";
}
<a class="esh-basketstatus "
asp-area=""
asp-controller="Cart"
asp-action="Index">
<div class="esh-basketstatus-image">
<img src="~/images/cart.png" />
</div>
<div class="esh-basketstatus-badge">
@Model.ItemsCount
</div>
</a>

View File

@@ -7,7 +7,19 @@
<environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/app.css" />
<link rel="stylesheet" href="~/css/app.css" />
<link rel="stylesheet" href="~/css/app.component.css" />
@*<link rel="stylesheet" href="~/css/shared/components/header/header.css" />
<link rel="stylesheet" href="~/css/shared/components/identity/identity.css" />
<link rel="stylesheet" href="~/css/campaigns/campaigns.component.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-status/basket-status.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-detail/orders-detail.component.css" />
<link rel="stylesheet" href="~/css/orders/orders-new/orders-new.component.css" />
<link rel="stylesheet" href="~/css/override.css" type="text/css" />*@
</environment>
<environment names="Staging,Production">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.5/css/bootstrap.min.css"
@@ -15,8 +27,8 @@
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
<link rel="stylesheet" href="~/css/app.min.css" asp-append-version="true" />
</environment>
<link rel="stylesheet" href="~/css/catalog/pager.css" />
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
<link rel="stylesheet" href="~/css/catalog/pager.css" />
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
</head>
<body>
<header class="navbar navbar-light navbar-static-top">

View File

@@ -1,5 +1,4 @@
@using Microsoft.AspNetCore.Identity
@*@inject IIdentityParser<ApplicationUser> UserManager*@
@if (Context.User.Identity.IsAuthenticated)
{
@@ -14,13 +13,13 @@
<section class="esh-identity-drop">
@*<a class="esh-identity-item"
<a class="esh-identity-item"
asp-controller="Order"
asp-action="Index">
<div class="esh-identity-name esh-identity-name--upper">My orders</div>
<img class="esh-identity-image" src="~/images/my_orders.png">
</a>*@
</a>
<a class="esh-identity-item"
href="javascript:document.getElementById('logoutForm').submit()">
@@ -33,9 +32,9 @@
</div>
</section>
@*<section class="col-lg-1 col-xs-12">
@await Component.InvokeAsync("Basket", new { user = UserManager.Parse(User) })
</section>*@
<section class="col-lg-1 col-xs-12">
@await Component.InvokeAsync("Basket", User.Identity.Name)
</section>
}
else
@@ -53,5 +52,6 @@ else
</div>
</section>
@*<section class="col-lg-1 col-xs-12"></section>*@
<section class="col-lg-1 col-xs-12">@await Component.InvokeAsync("Basket")
</section>
}

View File

@@ -1,41 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
<RuntimeFrameworkVersion>1.1.0</RuntimeFrameworkVersion>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Pics\**" />
<Content Remove="Pics\**" />
<EmbeddedResource Remove="Pics\**" />
<None Remove="Pics\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.1" />
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Session" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="1.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="1.1.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<Folder Include="Views\Catalog\" />
<Folder Include="wwwroot\css\catalog\" />
<Folder Include="wwwroot\fonts\" />
</ItemGroup>
<ItemGroup>
@@ -43,9 +23,9 @@
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.1" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.1" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<None Include="wwwroot\images\products\1.png" />

View File

@@ -0,0 +1,42 @@
[
//{
// "outputFile": "wwwroot/css/orders/orders.component.css",
// "inputFile": "wwwroot/css/orders/orders.component.scss"
//},
//{
// "outputFile": "wwwroot/css/orders/orders-new/orders-new.component.css",
// "inputFile": "wwwroot/css/orders/orders-new/orders-new.component.scss"
//},
//{
// "outputFile": "wwwroot/css/orders/orders-detail/orders-detail.component.css",
// "inputFile": "wwwroot/css/orders/orders-detail/orders-detail.component.scss"
//},
{
"outputFile": "wwwroot/css/catalog/catalog.component.css",
"inputFile": "wwwroot/css/catalog/catalog.component.scss"
},
{
"outputFile": "wwwroot/css/basket/basket.component.css",
"inputFile": "wwwroot/css/basket/basket.component.scss"
},
{
"outputFile": "wwwroot/css/basket/basket-status/basket-status.component.css",
"inputFile": "wwwroot/css/basket/basket-status/basket-status.component.scss"
},
//{
// "outputFile": "wwwroot/css/shared/components/header/header.css",
// "inputFile": "wwwroot/css/shared/components/header/header.scss"
//},
//{
// "outputFile": "wwwroot/css/shared/components/identity/identity.css",
// "inputFile": "wwwroot/css/shared/components/identity/identity.scss"
//},
//{
// "outputFile": "wwwroot/css/shared/components/pager/pager.css",
// "inputFile": "wwwroot/css/shared/components/pager/pager.scss"
//},
{
"outputFile": "wwwroot/css/app.component.css",
"inputFile": "wwwroot/css/app.component.scss"
}
]

View File

@@ -0,0 +1,65 @@
// Colors
$color-brand: #00A69C;
$color-brand-dark: darken($color-brand, 10%);
$color-brand-darker: darken($color-brand, 20%);
$color-brand-bright: lighten($color-brand, 10%);
$color-brand-brighter: lighten($color-brand, 20%);
$color-secondary: #83D01B;
$color-secondary-dark: darken($color-secondary, 5%);
$color-secondary-darker: darken($color-secondary, 20%);
$color-secondary-bright: lighten($color-secondary, 10%);
$color-secondary-brighter: lighten($color-secondary, 20%);
$color-warning: #ff0000;
$color-warning-dark: darken($color-warning, 5%);
$color-warning-darker: darken($color-warning, 20%);
$color-warning-bright: lighten($color-warning, 10%);
$color-warning-brighter: lighten($color-warning, 20%);
$color-background-dark: #333333;
$color-background-darker: #000000;
$color-background-bright: #EEEEFF;
$color-background-brighter: #FFFFFF;
$color-foreground-dark: #333333;
$color-foreground-darker: #000000;
$color-foreground-bright: #EEEEEE;
$color-foreground-brighter: #FFFFFF;
// Animations
$animation-speed-default: .35s;
$animation-speed-slow: .5s;
$animation-speed-fast: .15s;
// Fonts
$font-weight-light: 200;
$font-weight-semilight: 300;
$font-weight-normal: 400;
$font-weight-semibold: 600;
$font-weight-bold: 700;
$font-size-xs: .65rem; // 10.4px
$font-size-s: .85rem; // 13.6px
$font-size-m: 1rem; // 16px
$font-size-l: 1.25rem; // 20px
$font-size-xl: 1.5rem; // 24px
// Medias
$media-screen-xxs: 360px;
$media-screen-xs: 640px;
$media-screen-s: 768px;
$media-screen-m: 1024px;
$media-screen-l: 1280px;
$media-screen-xl: 1440px;
$media-screen-xxl: 1680px;
$media-screen-xxxl: 1920px;
// Borders
$border-light: 1px;
// Images
$image_path: '../../images/';
$image-main_banner: '#{$image_path}main_banner.png';
$image-arrow_down: '#{$image_path}arrow-down.png';

View File

@@ -0,0 +1,11 @@
.esh-app-footer {
background-color: #000000;
border-top: 1px solid #EEEEEE;
margin-top: 2.5rem;
padding-bottom: 2.5rem;
padding-top: 2.5rem;
width: 100%; }
.esh-app-footer-brand {
height: 50px;
width: 230px; }

View File

@@ -0,0 +1 @@
.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%}.esh-app-footer-brand{height:50px;width:230px}

View File

@@ -0,0 +1,23 @@
@import './variables';
.esh-app {
&-footer {
$margin: 2.5rem;
$padding: 2.5rem;
background-color: $color-background-darker;
border-top: $border-light solid $color-foreground-bright;
margin-top: $margin;
padding-bottom: $padding;
padding-top: $padding;
width: 100%;
$height: 50px;
&-brand {
height: $height;
width: 230px;
}
}
}

View File

@@ -0,0 +1,43 @@
.esh-basketstatus {
cursor: pointer;
display: inline-block;
float: right;
position: relative;
transition: all 0.35s; }
.esh-basketstatus.is-disabled {
opacity: .5;
pointer-events: none; }
.esh-basketstatus-image {
height: 36px;
margin-top: .5rem; }
.esh-basketstatus-badge {
background-color: #83D01B;
border-radius: 50%;
color: #FFFFFF;
display: block;
height: 1.5rem;
left: 50%;
position: absolute;
text-align: center;
top: 0;
transform: translateX(-38%);
transition: all 0.35s;
width: 1.5rem; }
.esh-basketstatus-badge-inoperative {
background-color: #ff0000;
border-radius: 50%;
color: #FFFFFF;
display: block;
height: 1.5rem;
left: 50%;
position: absolute;
text-align: center;
top: 0;
transform: translateX(-38%);
transition: all 0.35s;
width: 1.5rem; }
.esh-basketstatus:hover .esh-basketstatus-badge {
background-color: transparent;
color: #75b918;
transition: all 0.35s; }

View File

@@ -0,0 +1 @@
.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus-badge-inoperative{background-color:#f00;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s}

View File

@@ -0,0 +1,57 @@
@import '../../variables';
.esh-basketstatus {
cursor: pointer;
display: inline-block;
float: right;
position: relative;
transition: all $animation-speed-default;
&.is-disabled {
opacity: .5;
pointer-events: none;
}
&-image {
height: 36px;
margin-top: .5rem;
}
&-badge {
$size: 1.5rem;
background-color: $color-secondary;
border-radius: 50%;
color: $color-foreground-brighter;
display: block;
height: $size;
left: 50%;
position: absolute;
text-align: center;
top: 0;
transform: translateX(-38%);
transition: all $animation-speed-default;
width: $size;
}
&-badge-inoperative {
$size: 1.5rem;
background-color: $color-warning;
border-radius: 50%;
color: $color-foreground-brighter;
display: block;
height: $size;
left: 50%;
position: absolute;
text-align: center;
top: 0;
transform: translateX(-38%);
transition: all $animation-speed-default;
width: $size;
}
&:hover &-badge {
background-color: transparent;
color: $color-secondary-dark;
transition: all $animation-speed-default;
}
}

View File

@@ -0,0 +1,49 @@
.esh-basket {
min-height: 80vh; }
.esh-basket-titles {
padding-bottom: 1rem;
padding-top: 2rem; }
.esh-basket-titles--clean {
padding-bottom: 0;
padding-top: 0; }
.esh-basket-title {
text-transform: uppercase; }
.esh-basket-items--border {
border-bottom: 1px solid #EEEEEE;
padding: .5rem 0; }
.esh-basket-items--border:last-of-type {
border-color: transparent; }
.esh-basket-items-margin-left1 {
margin-left: 1px; }
.esh-basket-item {
font-size: 1rem;
font-weight: 300; }
.esh-basket-item--middle {
line-height: 8rem; }
@media screen and (max-width: 1024px) {
.esh-basket-item--middle {
line-height: 1rem; } }
.esh-basket-item--mark {
color: #00A69C; }
.esh-basket-image {
height: 8rem; }
.esh-basket-input {
line-height: 1rem;
width: 100%; }
.esh-basket-checkout {
background-color: #83D01B;
border: 0;
border-radius: 0;
color: #FFFFFF;
display: inline-block;
font-size: 1rem;
font-weight: 400;
margin-top: 1rem;
padding: 1rem 1.5rem;
text-align: center;
text-transform: uppercase;
transition: all 0.35s; }
.esh-basket-checkout:hover {
background-color: #4a760f;
transition: all 0.35s; }

View File

@@ -0,0 +1 @@
.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-items-margin-left1{margin-left:1px}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}

View File

@@ -0,0 +1,89 @@
@import '../variables';
@mixin margin-left($distance) {
margin-left: $distance;
}
.esh-basket {
min-height: 80vh;
&-titles {
padding-bottom: 1rem;
padding-top: 2rem;
&--clean {
padding-bottom: 0;
padding-top: 0;
}
}
&-title {
text-transform: uppercase;
}
&-items {
&--border {
border-bottom: $border-light solid $color-foreground-bright;
padding: .5rem 0;
&:last-of-type {
border-color: transparent;
}
}
&-margin-left1 {
@include margin-left(1px);
}
}
$item-height: 8rem;
&-item {
font-size: $font-size-m;
font-weight: $font-weight-semilight;
&--middle {
line-height: $item-height;
@media screen and (max-width: $media-screen-m) {
line-height: $font-size-m;
}
}
&--mark {
color: $color-brand;
}
}
&-image {
height: $item-height;
}
&-input {
line-height: 1rem;
width: 100%;
}
&-checkout {
background-color: $color-secondary;
border: 0;
border-radius: 0;
color: $color-foreground-brighter;
display: inline-block;
font-size: 1rem;
font-weight: $font-weight-normal;
margin-top: 1rem;
padding: 1rem 1.5rem;
text-align: center;
text-transform: uppercase;
transition: all $animation-speed-default;
&:hover {
background-color: $color-secondary-darker;
transition: all $animation-speed-default;
}
}
}

View File

@@ -1,228 +1,117 @@
.esh-catalog-hero {
background-image: url("../../images/main_banner.png");
background-size: cover;
height: 260px;
width: 100%;
}
.esh-catalog-hero {
background-image: url("../../images/main_banner.png");
background-size: cover;
height: 260px;
width: 100%; }
.esh-catalog-title {
position: relative;
top: 74.28571px;
}
position: relative;
top: 74.28571px; }
.esh-catalog-filters {
background-color: #00A69C;
height: 65px;
}
background-color: #00A69C;
height: 65px; }
.esh-catalog-filter {
background-color: transparent;
border-color: #00d9cc;
color: #FFFFFF;
cursor: pointer;
margin-right: 1rem;
margin-top: .5rem;
outline-color: #83D01B;
padding-bottom: 0;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 1.5rem;
min-width: 140px;
-webkit-appearance: none;
}
.esh-catalog-filter option {
background-color: #00A69C;
}
-webkit-appearance: none;
background-color: transparent;
border-color: #00d9cc;
color: #FFFFFF;
cursor: pointer;
margin-right: 1rem;
margin-top: .5rem;
min-width: 140px;
outline-color: #83D01B;
padding-bottom: 0;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 1.5rem; }
.esh-catalog-filter option {
background-color: #00A69C; }
.esh-catalog-label {
display: inline-block;
position: relative;
z-index: 0;
}
.esh-catalog-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-catalog-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;
}
display: inline-block;
position: relative;
z-index: 0; }
.esh-catalog-label::before {
color: rgba(255, 255, 255, 0.5);
content: attr(data-title);
font-size: 0.65rem;
margin-left: 0.5rem;
margin-top: 0.65rem;
position: absolute;
text-transform: uppercase;
z-index: 1; }
.esh-catalog-label::after {
background-image: url("../../images/arrow-down.png");
content: '';
height: 7px;
position: absolute;
right: 1.5rem;
top: 2.5rem;
width: 10px;
z-index: 1; }
.esh-catalog-send {
background-color: #83D01B;
color: #FFFFFF;
cursor: pointer;
font-size: 1rem;
transform: translateY(.5rem);
padding: 0.5rem;
transition: all 0.35s;
}
.esh-catalog-send:hover {
background-color: #4a760f;
transition: all 0.35s;
}
background-color: #83D01B;
color: #FFFFFF;
cursor: pointer;
font-size: 1rem;
margin-top: -1.5rem;
padding: 0.5rem;
transition: all 0.35s; }
.esh-catalog-send:hover {
background-color: #4a760f;
transition: all 0.35s; }
.esh-catalog-items {
margin-top: 1rem;
}
margin-top: 1rem; }
.esh-catalog-item {
text-align: center;
margin-bottom: 1.5rem;
width: 33%;
display: inline-block;
float: none !important;
}
@media screen and (max-width: 1024px) {
margin-bottom: 1.5rem;
text-align: center;
width: 33%;
display: inline-block;
float: none !important; }
@media screen and (max-width: 1024px) {
.esh-catalog-item {
width: 50%;
}
}
@media screen and (max-width: 768px) {
width: 50%; } }
@media screen and (max-width: 768px) {
.esh-catalog-item {
width: 100%;
}
}
width: 100%; } }
.esh-catalog-thumbnail {
max-width: 370px;
width: 100%;
}
max-width: 370px;
width: 100%; }
.esh-catalog-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-catalog-button.is-disabled {
opacity: .5;
pointer-events: none;
}
.esh-catalog-button:hover {
background-color: #4a760f;
transition: all 0.35s;
}
background-color: #83D01B;
border: 0;
color: #FFFFFF;
cursor: pointer;
font-size: 1rem;
height: 3rem;
margin-top: 1rem;
transition: all 0.35s;
width: 80%; }
.esh-catalog-button.is-disabled {
opacity: .5;
pointer-events: none; }
.esh-catalog-button:hover {
background-color: #4a760f;
transition: all 0.35s; }
.esh-catalog-name {
font-size: 1rem;
font-weight: 300;
margin-top: .5rem;
text-align: center;
text-transform: uppercase;
}
font-size: 1rem;
font-weight: 300;
margin-top: .5rem;
text-align: center;
text-transform: uppercase; }
.esh-catalog-price {
text-align: center;
font-weight: 900;
font-size: 28px;
}
font-size: 28px;
font-weight: 900;
text-align: center; }
.esh-catalog-price::before {
content: '$'; }
.esh-catalog-price::before {
content: '$';
}
.esh-basket {
min-height: 80vh;
}
.esh-basket-titles {
padding-bottom: 1rem;
padding-top: 2rem;
}
.esh-basket-titles--clean {
padding-bottom: 0;
padding-top: 0;
}
.esh-basket-title {
text-transform: uppercase;
}
.esh-basket-items--border {
border-bottom: 1px solid #EEEEEE;
padding: .5rem 0;
}
.esh-basket-items--border:last-of-type {
border-color: transparent;
}
.esh-basket-items-margin-left1 {
margin-left: 1px;
}
.esh-basket-item {
font-size: 1rem;
font-weight: 300;
}
.esh-basket-item--middle {
line-height: 8rem;
}
@media screen and (max-width: 1024px) {
.esh-basket-item--middle {
line-height: 1rem;
}
}
.esh-basket-item--mark {
color: #00A69C;
}
.esh-basket-image {
height: 8rem;
}
.esh-basket-input {
line-height: 1rem;
width: 100%;
}
.esh-basket-checkout {
background-color: #83D01B;
border: 0;
border-radius: 0;
color: #FFFFFF;
display: inline-block;
font-size: 1rem;
font-weight: 400;
margin-top: 1rem;
padding: 1rem 1.5rem;
text-align: center;
text-transform: uppercase;
transition: all 0.35s;
}
.esh-basket-checkout:hover {
background-color: #4a760f;
transition: all 0.35s;
}

View File

@@ -0,0 +1 @@
.esh-catalog-hero{background-image:url("../../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{-webkit-appearance:none;background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;min-width:140px;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-left:.5rem;margin-top:.65rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../../images/arrow-down.png");content:'';height:7px;position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;margin-top:-1.5rem;padding:.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{margin-bottom:1.5rem;text-align:center;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:0;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{font-size:28px;font-weight:900;text-align:center}.esh-catalog-price::before{content:'$'}

View File

@@ -0,0 +1,154 @@
@import '../variables';
.esh-catalog {
$banner-height: 260px;
&-hero {
background-image: url($image-main_banner);
background-size: cover;
height: $banner-height;
width: 100%;
}
&-title {
position: relative;
top: $banner-height / 3.5;
}
$filter-height: 65px;
&-filters {
background-color: $color-brand;
height: $filter-height;
}
$filter-padding: .5rem;
&-filter {
-webkit-appearance: none;
background-color: transparent;
border-color: $color-brand-bright;
color: $color-foreground-brighter;
cursor: pointer;
margin-right: 1rem;
margin-top: .5rem;
min-width: 140px;
outline-color: $color-secondary;
padding-bottom: 0;
padding-left: $filter-padding;
padding-right: $filter-padding;
padding-top: $filter-padding * 3;
option {
background-color: $color-brand;
}
}
&-label {
display: inline-block;
position: relative;
z-index: 0;
&::before {
color: rgba($color-foreground-brighter, .5);
content: attr(data-title);
font-size: $font-size-xs;
margin-left: $filter-padding;
margin-top: $font-size-xs;
position: absolute;
text-transform: uppercase;
z-index: 1;
}
&::after {
background-image: url($image-arrow_down);
content: '';
height: 7px; //png height
position: absolute;
right: $filter-padding * 3;
top: $filter-padding * 5;
width: 10px; //png width
z-index: 1;
}
}
&-send {
background-color: $color-secondary;
color: $color-foreground-brighter;
cursor: pointer;
font-size: $font-size-m;
margin-top: -$filter-padding * 3;
padding: $filter-padding;
transition: all $animation-speed-default;
&:hover {
background-color: $color-secondary-darker;
transition: all $animation-speed-default;
}
}
&-items {
margin-top: 1rem;
}
&-item {
margin-bottom: 1.5rem;
text-align: center;
width: 33%;
display: inline-block;
float: none !important;
@media screen and (max-width: $media-screen-m) {
width: 50%;
}
@media screen and (max-width: $media-screen-s) {
width: 100%;
}
}
&-thumbnail {
max-width: 370px;
width: 100%;
}
&-button {
background-color: $color-secondary;
border: 0;
color: $color-foreground-brighter;
cursor: pointer;
font-size: $font-size-m;
height: 3rem;
margin-top: 1rem;
transition: all $animation-speed-default;
width: 80%;
&.is-disabled {
opacity: .5;
pointer-events: none;
}
&:hover {
background-color: $color-secondary-darker;
transition: all $animation-speed-default;
}
}
&-name {
font-size: $font-size-m;
font-weight: $font-weight-semilight;
margin-top: .5rem;
text-align: center;
text-transform: uppercase;
}
&-price {
font-size: 28px;
font-weight: 900;
text-align: center;
&::before {
content: '$';
}
}
}

View File

@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.0" />
</ItemGroup>

View File

@@ -1,47 +0,0 @@
using Infrastructure.FileSystem;
using Microsoft.AspNetCore.Hosting;
using System.IO;
using Xunit;
using Moq;
namespace IntegrationTests.Infrastructure.File
{
public class LocalFileImageServiceGetImageBytesById
{
private byte[] _testBytes = new byte[] { 0x01, 0x02, 0x03 };
private readonly Mock<IHostingEnvironment> _mockEnvironment = new Mock<IHostingEnvironment>();
private int _testImageId = 123;
private string _testFileName = "123.png";
public LocalFileImageServiceGetImageBytesById()
{
// create folder if necessary
Directory.CreateDirectory(Path.Combine(GetFileDirectory(), "Pics"));
string filePath = GetFilePath(_testFileName);
System.IO.File.WriteAllBytes(filePath, _testBytes);
_mockEnvironment.SetupGet<string>(m => m.ContentRootPath).Returns(GetFileDirectory());
}
private string GetFilePath(string fileName)
{
return Path.Combine(GetFileDirectory(), "Pics", fileName);
}
private string GetFileDirectory()
{
var location = System.Reflection.Assembly.GetEntryAssembly().Location;
return Path.GetDirectoryName(location);
}
[Fact]
public void ReturnsFileContentResultGivenValidId()
{
var fileService = new LocalFileImageService(_mockEnvironment.Object);
var result = fileService.GetImageBytesById(_testImageId);
Assert.Equal(_testBytes, result);
}
}
}

View File

@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="Moq" Version="4.7.49" />
<PackageReference Include="Moq" Version="4.7.99" />
</ItemGroup>

View File

@@ -1,15 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="Moq" Version="4.7.49" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.console" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />