Enabling admin pages in MusicStore.Spa:
- Updated attribute routing so it works now - Created a Pages folder and PageController for serving pages, I don't like views very much - Worked around an EF issue - Fixed ApiResult to use JsonResult.ExecuteResultAsync - Made PagedList take the sort expression so it can be conditionally applied as calling Count on the query passed causes issues if it contains an OrderBy expression - Made web server ports not conflict with non-SPA MusicStore
This commit is contained in:
parent
e50cb5262a
commit
7055949e7b
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
using MusicStore.Infrastructure;
|
using MusicStore.Infrastructure;
|
||||||
|
|
@ -23,8 +22,7 @@ namespace MusicStore.Apis
|
||||||
var albums = await _storeContext.Albums
|
var albums = await _storeContext.Albums
|
||||||
//.Include(a => a.Genre)
|
//.Include(a => a.Genre)
|
||||||
//.Include(a => a.Artist)
|
//.Include(a => a.Artist)
|
||||||
.SortBy(sortBy, a => a.Title)
|
.ToPagedListAsync(page, pageSize, sortBy, a => a.Title);
|
||||||
.ToPagedListAsync(page, pageSize);
|
|
||||||
|
|
||||||
return Json(albums);
|
return Json(albums);
|
||||||
}
|
}
|
||||||
|
|
@ -72,8 +70,7 @@ namespace MusicStore.Apis
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
//[Authorize(Roles = "Administrator")]
|
[Authorize("ManageStore", "Allowed")]
|
||||||
[Authorize(ClaimTypes.Role, "Administrator")]
|
|
||||||
public async Task<ActionResult> CreateAlbum()
|
public async Task<ActionResult> CreateAlbum()
|
||||||
{
|
{
|
||||||
var album = new Album();
|
var album = new Album();
|
||||||
|
|
@ -99,8 +96,7 @@ namespace MusicStore.Apis
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{albumId:int}/update")]
|
[HttpPut("{albumId:int}/update")]
|
||||||
//[Authorize(Roles = "Administrator")]
|
[Authorize("ManageStore", "Allowed")]
|
||||||
[Authorize(ClaimTypes.Role, "Administrator")]
|
|
||||||
public async Task<ActionResult> UpdateAlbum(int albumId)
|
public async Task<ActionResult> UpdateAlbum(int albumId)
|
||||||
{
|
{
|
||||||
var album = _storeContext.Albums.SingleOrDefault(a => a.AlbumId == albumId);
|
var album = _storeContext.Albums.SingleOrDefault(a => a.AlbumId == albumId);
|
||||||
|
|
@ -133,11 +129,11 @@ namespace MusicStore.Apis
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{albumId:int}")]
|
[HttpDelete("{albumId:int}")]
|
||||||
//[Authorize(Roles = "Administrator")]
|
[Authorize("ManageStore", "Allowed")]
|
||||||
[Authorize(ClaimTypes.Role, "Administrator")]
|
|
||||||
public async Task<ActionResult> DeleteAlbum(int albumId)
|
public async Task<ActionResult> DeleteAlbum(int albumId)
|
||||||
{
|
{
|
||||||
var album = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId);
|
//var album = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId);
|
||||||
|
var album = _storeContext.Albums.SingleOrDefault(a => a.AlbumId == albumId);
|
||||||
|
|
||||||
if (album != null)
|
if (album != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using MusicStore.Models;
|
||||||
|
|
||||||
namespace MusicStore.Controllers
|
namespace MusicStore.Controllers
|
||||||
{
|
{
|
||||||
|
[Route("account")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public class AccountController : Controller
|
public class AccountController : Controller
|
||||||
{
|
{
|
||||||
|
|
@ -20,20 +21,16 @@ namespace MusicStore.Controllers
|
||||||
|
|
||||||
public SignInManager<ApplicationUser> SignInManager { get; private set; }
|
public SignInManager<ApplicationUser> SignInManager { get; private set; }
|
||||||
|
|
||||||
//
|
|
||||||
// GET: /Account/Login
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
//Bug: https://github.com/aspnet/WebFx/issues/339
|
//Bug: https://github.com/aspnet/WebFx/issues/339
|
||||||
[HttpGet]
|
[HttpGet("login")]
|
||||||
public IActionResult Login(string returnUrl)
|
public IActionResult Login(string returnUrl)
|
||||||
{
|
{
|
||||||
ViewBag.ReturnUrl = returnUrl;
|
ViewBag.ReturnUrl = returnUrl;
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
[HttpPost("login")]
|
||||||
// POST: /Account/Login
|
|
||||||
[HttpPost]
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl)
|
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl)
|
||||||
|
|
@ -59,19 +56,15 @@ namespace MusicStore.Controllers
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// GET: /Account/Register
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
//Bug: https://github.com/aspnet/WebFx/issues/339
|
//Bug: https://github.com/aspnet/WebFx/issues/339
|
||||||
[HttpGet]
|
[HttpGet("register")]
|
||||||
public IActionResult Register()
|
public IActionResult Register()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
[HttpPost("register")]
|
||||||
// POST: /Account/Register
|
|
||||||
[HttpPost]
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> Register(RegisterViewModel model)
|
public async Task<IActionResult> Register(RegisterViewModel model)
|
||||||
|
|
@ -84,7 +77,7 @@ namespace MusicStore.Controllers
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
await SignInManager.SignInAsync(user, isPersistent: false);
|
await SignInManager.SignInAsync(user, isPersistent: false);
|
||||||
return RedirectToAction("Index", "Home");
|
return RedirectToAction("Home", "Page");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -96,9 +89,7 @@ namespace MusicStore.Controllers
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
[HttpGet("manage")]
|
||||||
// GET: /Account/Manage
|
|
||||||
[HttpGet]
|
|
||||||
public IActionResult Manage(ManageMessageId? message = null)
|
public IActionResult Manage(ManageMessageId? message = null)
|
||||||
{
|
{
|
||||||
ViewBag.StatusMessage =
|
ViewBag.StatusMessage =
|
||||||
|
|
@ -109,9 +100,7 @@ namespace MusicStore.Controllers
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
[HttpPost("manage")]
|
||||||
// POST: /Account/Manage
|
|
||||||
[HttpPost]
|
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> Manage(ManageUserViewModel model)
|
public async Task<IActionResult> Manage(ManageUserViewModel model)
|
||||||
{
|
{
|
||||||
|
|
@ -135,14 +124,12 @@ namespace MusicStore.Controllers
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
[HttpPost("logoff")]
|
||||||
// POST: /Account/LogOff
|
|
||||||
[HttpPost]
|
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public IActionResult LogOff()
|
public IActionResult LogOff()
|
||||||
{
|
{
|
||||||
SignInManager.SignOut();
|
SignInManager.SignOut();
|
||||||
return RedirectToAction("Index", "Home");
|
return RedirectToAction("Home", "Page");
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Helpers
|
#region Helpers
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
using Microsoft.AspNet.Mvc;
|
|
||||||
|
|
||||||
namespace MusicStore.Controllers
|
|
||||||
{
|
|
||||||
public class HomeController : Controller
|
|
||||||
{
|
|
||||||
public IActionResult Index()
|
|
||||||
{
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace MusicStore.Spa.Controllers
|
||||||
|
{
|
||||||
|
[Route("/")]
|
||||||
|
public class PageController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult Home()
|
||||||
|
{
|
||||||
|
return View("/Pages/Home.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("admin")]
|
||||||
|
[Authorize("ManageStore", "Allowed")]
|
||||||
|
public IActionResult Admin()
|
||||||
|
{
|
||||||
|
return View("/Pages/Admin.cshtml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
|
@ -40,7 +41,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public IEnumerable<ModelError> ModelErrors { get; set; }
|
public IEnumerable<ModelError> ModelErrors { get; set; }
|
||||||
|
|
||||||
public override void ExecuteResult(ActionContext context)
|
public override Task ExecuteResultAsync(ActionContext context)
|
||||||
{
|
{
|
||||||
if (StatusCode.HasValue)
|
if (StatusCode.HasValue)
|
||||||
{
|
{
|
||||||
|
|
@ -48,7 +49,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
var json = new JsonResult(this);
|
var json = new JsonResult(this);
|
||||||
json.ExecuteResult(context);
|
return json.ExecuteResultAsync(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ModelError
|
public class ModelError
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MusicStore.Infrastructure
|
namespace MusicStore.Infrastructure
|
||||||
|
|
@ -64,7 +65,8 @@ namespace MusicStore.Infrastructure
|
||||||
return new PagedList<T>(data, pagingConfig.Page, pagingConfig.PageSize, query.Count());
|
return new PagedList<T>(data, pagingConfig.Page, pagingConfig.PageSize, query.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IPagedList<T>> ToPagedListAsync<T>(this IQueryable<T> query, int page, int pageSize)
|
public static async Task<IPagedList<TModel>> ToPagedListAsync<TModel, TProperty>(this IQueryable<TModel> query, int page, int pageSize, string sortExpression, Expression<Func<TModel, TProperty>> defaultSortExpression, SortDirection defaultSortDirection = SortDirection.Ascending)
|
||||||
|
where TModel : class
|
||||||
{
|
{
|
||||||
if (query == null)
|
if (query == null)
|
||||||
{
|
{
|
||||||
|
|
@ -73,8 +75,15 @@ namespace MusicStore.Infrastructure
|
||||||
|
|
||||||
var pagingConfig = new PagingConfig(page, pageSize);
|
var pagingConfig = new PagingConfig(page, pageSize);
|
||||||
var skipCount = ValidatePagePropertiesAndGetSkipCount(pagingConfig);
|
var skipCount = ValidatePagePropertiesAndGetSkipCount(pagingConfig);
|
||||||
|
var dataQuery = query;
|
||||||
|
|
||||||
var data = await query
|
if (defaultSortExpression != null)
|
||||||
|
{
|
||||||
|
dataQuery = dataQuery
|
||||||
|
.SortBy(sortExpression, defaultSortExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = await dataQuery
|
||||||
.Skip(skipCount)
|
.Skip(skipCount)
|
||||||
.Take(pagingConfig.PageSize)
|
.Take(pagingConfig.PageSize)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
@ -83,12 +92,14 @@ namespace MusicStore.Infrastructure
|
||||||
{
|
{
|
||||||
// Requested page has no records, just return the first page
|
// Requested page has no records, just return the first page
|
||||||
pagingConfig.Page = 1;
|
pagingConfig.Page = 1;
|
||||||
data = await query
|
data = await dataQuery
|
||||||
.Take(pagingConfig.PageSize)
|
.Take(pagingConfig.PageSize)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PagedList<T>(data, pagingConfig.Page, pagingConfig.PageSize, await query.CountAsync());
|
var count = await query.CountAsync();
|
||||||
|
|
||||||
|
return new PagedList<TModel>(data, pagingConfig.Page, pagingConfig.PageSize, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ValidatePagePropertiesAndGetSkipCount(PagingConfig pagingConfig)
|
private static int ValidatePagePropertiesAndGetSkipCount(PagingConfig pagingConfig)
|
||||||
|
|
|
||||||
|
|
@ -56,22 +56,22 @@ namespace MusicStore.Models
|
||||||
private static async Task CreateAdminUser(IServiceProvider serviceProvider)
|
private static async Task CreateAdminUser(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
var options = serviceProvider.GetService<IOptionsAccessor<IdentityDbContextOptions>>().Options;
|
var options = serviceProvider.GetService<IOptionsAccessor<IdentityDbContextOptions>>().Options;
|
||||||
//const string adminRole = "Administrator";
|
const string adminRole = "Administrator";
|
||||||
|
|
||||||
var userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
|
var userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
|
||||||
// TODO: Identity SQL does not support roles yet
|
var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
|
||||||
//var roleManager = serviceProvider.GetService<ApplicationRoleManager>();
|
|
||||||
//if (!await roleManager.RoleExistsAsync(adminRole))
|
if (!await roleManager.RoleExistsAsync(adminRole))
|
||||||
//{
|
{
|
||||||
// await roleManager.CreateAsync(new IdentityRole(adminRole));
|
await roleManager.CreateAsync(new IdentityRole(adminRole));
|
||||||
//}
|
}
|
||||||
|
|
||||||
var user = await userManager.FindByNameAsync(options.DefaultAdminUserName);
|
var user = await userManager.FindByNameAsync(options.DefaultAdminUserName);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
user = new ApplicationUser { UserName = options.DefaultAdminUserName };
|
user = new ApplicationUser { UserName = options.DefaultAdminUserName };
|
||||||
await userManager.CreateAsync(user, options.DefaultAdminPassword);
|
await userManager.CreateAsync(user, options.DefaultAdminPassword);
|
||||||
//await userManager.AddToRoleAsync(user, adminRole);
|
await userManager.AddToRoleAsync(user, adminRole);
|
||||||
await userManager.AddClaimAsync(user, new Claim("ManageStore", "Allowed"));
|
await userManager.AddClaimAsync(user, new Claim("ManageStore", "Allowed"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<SchemaVersion>2.0</SchemaVersion>
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
<DevelopmentServerPort>5001</DevelopmentServerPort>
|
<DevelopmentServerPort>5101</DevelopmentServerPort>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
<ProjectExtensions>
|
<ProjectExtensions>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
@model IEnumerable<MusicStore.Models.Album>
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewBag.Title = "Store Manager";
|
||||||
|
ViewBag.ngApp = "MusicStore.Admin";
|
||||||
|
Layout = "/Views/Shared/_Layout.cshtml";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1>Store Manager</h1>
|
||||||
|
|
||||||
|
<div class="ng-view"></div>
|
||||||
|
|
||||||
|
@*@Html.InlineData("Lookup", "ArtistsApi")*@
|
||||||
|
@*@Html.InlineData("Lookup", "GenresApi")*@
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
|
||||||
|
<script src="~/js/angular.js"></script>
|
||||||
|
<script src="~/js/angular-route.js"></script>
|
||||||
|
<script src="~/js/ui-bootstrap.js"></script>
|
||||||
|
<script src="~/js/ui-bootstrap-tpls.js"></script>
|
||||||
|
@* TODO: This is currently all the compiled TypeScript, non-minified. Need to explore options
|
||||||
|
for alternate loading schemes, e.g. AMD loader of individual modules, min vs. non-min, etc. *@
|
||||||
|
<script src="~/js/@(ViewBag.ngApp).js"></script>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -17,11 +17,11 @@
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
@Html.ActionLink("ASP.NET MVC Music Store", "Index", "Home", null, new { @class = "navbar-brand" })
|
@Html.ActionLink("ASP.NET MVC Music Store", "Home", "Page", null, new { @class = "navbar-brand" })
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse navbar-collapse">
|
<div class="collapse navbar-collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li>@Html.ActionLink("Home", "Index", "Home")</li>
|
<li>@Html.ActionLink("Home", "Home", "Page")</li>
|
||||||
|
|
||||||
@RenderSection("NavBarItems", required: false)
|
@RenderSection("NavBarItems", required: false)
|
||||||
|
|
||||||
|
|
@ -35,8 +35,8 @@
|
||||||
@RenderBody()
|
@RenderBody()
|
||||||
<hr />
|
<hr />
|
||||||
<footer class="navbar navbar-fixed-bottom navbar-default text-center">
|
<footer class="navbar navbar-fixed-bottom navbar-default text-center">
|
||||||
<p><a href="http://mvcmusicstore.codeplex.com">mvcmusicstore.codeplex.com</a></p>
|
<p><a href="https://github.com/aspnet/musicstore">github.com/aspnet/musicstore</a></p>
|
||||||
<small>@Html.ActionLink("admin", "Index", "StoreManager")</small>
|
<small>@Html.ActionLink("admin", "Admin", "Page")</small>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@
|
||||||
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*"
|
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*"
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"WebListener": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5002",
|
"WebListener": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5102",
|
||||||
"Kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5004",
|
"Kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5104",
|
||||||
"run": "run server.urls=http://localhost:5003"
|
"run": "run server.urls=http://localhost:5103"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"aspnet50": { },
|
"aspnet50": { },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue