Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
* Updates based on documentation

* Getting the build passing

* Getting app functioning

* A few cleanups to confirm it's working as expected

* Fixing functional tests

* Updating dockerfile for 3.0

* Functional Tests now run sequentially

* Updating to latest version of moq

* Adding migration for post 3.0 upgrades

* Removing commented out lines

* Moving address and catalogitemordered configuration in to classes that own them

* Adding admin user

* Adding admin catalog screen

- will also only display menu option if user is logged in as an admin

* WIP - squash this

* Allow user to edit a catalog item

* Adding entry for new service

* Invalidating cache after catalog item update

- also a little bit of cleanup

* Fixing bad merge

* Removing Picture Uri and making Id readonly

* Adjusting style in menu dropdown so all options are shown

* Creating Cache helpers with unit tests
  • Loading branch information
efleming18 authored and ardalis committed Dec 11, 2019
1 parent 539d8c6 commit f3f74a3
Show file tree
Hide file tree
Showing 20 changed files with 360 additions and 45 deletions.
12 changes: 12 additions & 0 deletions src/ApplicationCore/Constants/AuthorizationConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Microsoft.eShopWeb.ApplicationCore.Constants
{
public class AuthorizationConstants
{
public static class Roles
{
public const string ADMINISTRATORS = "Administrators";
}

public const string DEFAULT_PASSWORD = "Pass@word1";
}
}
13 changes: 11 additions & 2 deletions src/Infrastructure/Identity/AppIdentityDbContextSeed.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.eShopWeb.ApplicationCore.Constants;
using System.Threading.Tasks;

namespace Microsoft.eShopWeb.Infrastructure.Identity
{
public class AppIdentityDbContextSeed
{
public static async Task SeedAsync(UserManager<ApplicationUser> userManager)
public static async Task SeedAsync(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
{
await roleManager.CreateAsync(new IdentityRole(AuthorizationConstants.Roles.ADMINISTRATORS));

var defaultUser = new ApplicationUser { UserName = "demouser@microsoft.com", Email = "demouser@microsoft.com" };
await userManager.CreateAsync(defaultUser, "Pass@word1");
await userManager.CreateAsync(defaultUser, AuthorizationConstants.DEFAULT_PASSWORD);

string adminUserName = "admin@microsoft.com";
var adminUser = new ApplicationUser { UserName = adminUserName, Email = adminUserName };
await userManager.CreateAsync(adminUser, AuthorizationConstants.DEFAULT_PASSWORD);
adminUser = await userManager.FindByNameAsync(adminUserName);
await userManager.AddToRoleAsync(adminUser, AuthorizationConstants.Roles.ADMINISTRATORS);
}
}
}
25 changes: 25 additions & 0 deletions src/Web/Extensions/CacheHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace Microsoft.eShopWeb.Web.Extensions
{
public static class CacheHelpers
{
public static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromSeconds(30);
private static readonly string _itemsKeyTemplate = "items-{0}-{1}-{2}-{3}";

public static string GenerateCatalogItemCacheKey(int pageIndex, int itemsPage, int? brandId, int? typeId)
{
return string.Format(_itemsKeyTemplate, pageIndex, itemsPage, brandId, typeId);
}

public static string GenerateBrandsCacheKey()
{
return "brands";
}

public static string GenerateTypesCacheKey()
{
return "types";
}
}
}
10 changes: 10 additions & 0 deletions src/Web/Interfaces/ICatalogItemViewModelService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.eShopWeb.Web.ViewModels;
using System.Threading.Tasks;

namespace Microsoft.eShopWeb.Web.Interfaces
{
public interface ICatalogItemViewModelService
{
Task UpdateCatalogItem(CatalogItemViewModel viewModel);
}
}
38 changes: 38 additions & 0 deletions src/Web/Pages/Admin/EditCatalogItem.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
@page
@{
ViewData["Title"] = "Admin - Edit Catalog";
@model EditCatalogItemModel
}

<div class="container">
<div class="row">
<div class="col-md-8">
<img class="esh-catalog-thumbnail" src="@Model.CatalogModel.PictureUri" />
<form method="post">
<div class="form-group">
<label asp-for="CatalogModel.Name"></label>
<input asp-for="CatalogModel.Name" class="form-control" />
<span asp-validation-for="CatalogModel.Name"></span>
</div>
<div class="form-group">
<label asp-for="CatalogModel.Price"></label>
<input asp-for="CatalogModel.Price" class="form-control" />
<span asp-validation-for="CatalogModel.Price"></span>
</div>
<div class="form-group">
<label asp-for="CatalogModel.Id"></label>
<input asp-for="CatalogModel.Id" readonly class="form-control" />
<span asp-validation-for="CatalogModel.Id"></span>
</div>
<div class="col-md-2">
<input type="submit" value="Save" class="esh-catalog-button" />
</div>
</form>
</div>
</div>
</div>


@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
39 changes: 39 additions & 0 deletions src/Web/Pages/Admin/EditCatalogItem.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.ApplicationCore.Constants;
using Microsoft.eShopWeb.Web.Interfaces;
using Microsoft.eShopWeb.Web.ViewModels;
using System.Threading.Tasks;

namespace Microsoft.eShopWeb.Web.Pages.Admin
{
[Authorize(Roles = AuthorizationConstants.Roles.ADMINISTRATORS)]
public class EditCatalogItemModel : PageModel
{
private readonly ICatalogItemViewModelService _catalogItemViewModelService;

public EditCatalogItemModel(ICatalogItemViewModelService catalogItemViewModelService)
{
_catalogItemViewModelService = catalogItemViewModelService;
}

[BindProperty]
public CatalogItemViewModel CatalogModel { get; set; } = new CatalogItemViewModel();

public async Task OnGet(CatalogItemViewModel catalogModel)
{
CatalogModel = catalogModel;
}

public async Task<IActionResult> OnPostAsync()
{
if (ModelState.IsValid)
{
await _catalogItemViewModelService.UpdateCatalogItem(CatalogModel);
}

return RedirectToPage("/Admin/Index");
}
}
}
45 changes: 45 additions & 0 deletions src/Web/Pages/Admin/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@page
@{
ViewData["Title"] = "Admin - Catalog";
@model IndexModel
}
<section class="esh-catalog-hero">
<div class="container">
<img class="esh-catalog-title" src="~/images/main_banner_text.png" />
</div>
</section>
<section class="esh-catalog-filters">
<div class="container">
<form method="get">
<label class="esh-catalog-label" data-title="brand">
<select asp-for="@Model.CatalogModel.BrandFilterApplied" asp-items="@Model.CatalogModel.Brands" class="esh-catalog-filter"></select>
</label>
<label class="esh-catalog-label" data-title="type">
<select asp-for="@Model.CatalogModel.TypesFilterApplied" asp-items="@Model.CatalogModel.Types" class="esh-catalog-filter"></select>
</label>
<input class="esh-catalog-send" type="image" src="images/arrow-right.svg" />
</form>
</div>
</section>
<div class="container">
@if (Model.CatalogModel.CatalogItems.Any())
{
<partial name="_pagination" for="CatalogModel.PaginationInfo" />

<div class="esh-catalog-items row">
@foreach (var catalogItem in Model.CatalogModel.CatalogItems)
{
<div class="esh-catalog-item col-md-4">
<partial name="_editCatalog" for="@catalogItem" />
</div>
}
</div>
<partial name="_pagination" for="CatalogModel.PaginationInfo" />
}
else
{
<div class="esh-catalog-items row">
THERE ARE NO RESULTS THAT MATCH YOUR SEARCH
</div>
}
</div>
35 changes: 35 additions & 0 deletions src/Web/Pages/Admin/Index.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.ApplicationCore.Constants;
using Microsoft.eShopWeb.Web.Extensions;
using Microsoft.eShopWeb.Web.Services;
using Microsoft.eShopWeb.Web.ViewModels;
using Microsoft.Extensions.Caching.Memory;
using System.Threading.Tasks;

namespace Microsoft.eShopWeb.Web.Pages.Admin
{
[Authorize(Roles = AuthorizationConstants.Roles.ADMINISTRATORS)]
public class IndexModel : PageModel
{
private readonly ICatalogViewModelService _catalogViewModelService;
private readonly IMemoryCache _cache;

public IndexModel(ICatalogViewModelService catalogViewModelService, IMemoryCache cache)
{
_catalogViewModelService = catalogViewModelService;
_cache = cache;
}

public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel();

public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId)
{
var cacheKey = CacheHelpers.GenerateCatalogItemCacheKey(pageId.GetValueOrDefault(), Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied);

_cache.Remove(cacheKey);

CatalogModel = await _catalogViewModelService.GetCatalogItems(pageId.GetValueOrDefault(), Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied);
}
}
}
50 changes: 24 additions & 26 deletions src/Web/Pages/Index.cshtml.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.Web.Services;
using Microsoft.eShopWeb.Web.ViewModels;
using System.Threading.Tasks;

namespace Microsoft.eShopWeb.Web.Pages
{
public class IndexModel : PageModel
{
private readonly ICatalogViewModelService _catalogViewModelService;

public IndexModel(ICatalogViewModelService catalogViewModelService)
{
_catalogViewModelService = catalogViewModelService;
}

public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel();

public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId)
{
CatalogModel = await _catalogViewModelService.GetCatalogItems(pageId ?? 0, Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied);
}


}
}
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.Web.Services;
using Microsoft.eShopWeb.Web.ViewModels;
using System.Threading.Tasks;

namespace Microsoft.eShopWeb.Web.Pages
{
public class IndexModel : PageModel
{
private readonly ICatalogViewModelService _catalogViewModelService;

public IndexModel(ICatalogViewModelService catalogViewModelService)
{
_catalogViewModelService = catalogViewModelService;
}

public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel();

public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId)
{
CatalogModel = await _catalogViewModelService.GetCatalogItems(pageId ?? 0, Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied);
}
}
}
15 changes: 15 additions & 0 deletions src/Web/Pages/Shared/_editCatalog.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@model CatalogItemViewModel

<form asp-page="/Admin/EditCatalogItem" method="get">
<div>
<img class="esh-catalog-thumbnail" src="@Model.PictureUri" />
<div class="esh-catalog-name">
<span>@Model.Name</span>
</div>
<input class="esh-catalog-button" type="submit" value="[ Edit ]" />
<input type="hidden" asp-for="@Model.Id" name="id" />
<input type="hidden" asp-for="@Model.Name" name="name" />
<input type="hidden" asp-for="@Model.PictureUri" name="pictureUri" />
<input type="hidden" asp-for="@Model.Price" name="price" />
</div>
</form>
3 changes: 2 additions & 1 deletion src/Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public async static Task Main(string[] args)
await CatalogContextSeed.SeedAsync(catalogContext, loggerFactory);

var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
await AppIdentityDbContextSeed.SeedAsync(userManager);
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
await AppIdentityDbContextSeed.SeedAsync(userManager, roleManager);
}
catch (Exception ex)
{
Expand Down
19 changes: 8 additions & 11 deletions src/Web/Services/CachedCatalogViewModelService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,14 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.eShopWeb.Web.ViewModels;
using Microsoft.Extensions.Caching.Memory;
using System;
using Microsoft.eShopWeb.Web.Extensions;

namespace Microsoft.eShopWeb.Web.Services
{
public class CachedCatalogViewModelService : ICatalogViewModelService
{
private readonly IMemoryCache _cache;
private readonly CatalogViewModelService _catalogViewModelService;
private static readonly string _brandsKey = "brands";
private static readonly string _typesKey = "types";
private static readonly string _itemsKeyTemplate = "items-{0}-{1}-{2}-{3}";
private static readonly TimeSpan _defaultCacheDuration = TimeSpan.FromSeconds(30);

public CachedCatalogViewModelService(IMemoryCache cache,
CatalogViewModelService catalogViewModelService)
Expand All @@ -25,28 +21,29 @@ public CachedCatalogViewModelService(IMemoryCache cache,

public async Task<IEnumerable<SelectListItem>> GetBrands()
{
return await _cache.GetOrCreateAsync(_brandsKey, async entry =>
return await _cache.GetOrCreateAsync(CacheHelpers.GenerateBrandsCacheKey(), async entry =>
{
entry.SlidingExpiration = _defaultCacheDuration;
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
return await _catalogViewModelService.GetBrands();
});
}

public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId)
{
string cacheKey = String.Format(_itemsKeyTemplate, pageIndex, itemsPage, brandId, typeId);
var cacheKey = CacheHelpers.GenerateCatalogItemCacheKey(pageIndex, Constants.ITEMS_PER_PAGE, brandId, typeId);

return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.SlidingExpiration = _defaultCacheDuration;
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
return await _catalogViewModelService.GetCatalogItems(pageIndex, itemsPage, brandId, typeId);
});
}

public async Task<IEnumerable<SelectListItem>> GetTypes()
{
return await _cache.GetOrCreateAsync(_typesKey, async entry =>
return await _cache.GetOrCreateAsync(CacheHelpers.GenerateTypesCacheKey(), async entry =>
{
entry.SlidingExpiration = _defaultCacheDuration;
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
return await _catalogViewModelService.GetTypes();
});
}
Expand Down
Loading

0 comments on commit f3f74a3

Please sign in to comment.