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:
DamianEdwards 2014-10-06 10:04:56 -07:00
parent e50cb5262a
commit 7055949e7b
12 changed files with 98 additions and 67 deletions

View File

@ -1,5 +1,4 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using MusicStore.Infrastructure;
@ -23,8 +22,7 @@ namespace MusicStore.Apis
var albums = await _storeContext.Albums
//.Include(a => a.Genre)
//.Include(a => a.Artist)
.SortBy(sortBy, a => a.Title)
.ToPagedListAsync(page, pageSize);
.ToPagedListAsync(page, pageSize, sortBy, a => a.Title);
return Json(albums);
}
@ -72,8 +70,7 @@ namespace MusicStore.Apis
}
[HttpPost]
//[Authorize(Roles = "Administrator")]
[Authorize(ClaimTypes.Role, "Administrator")]
[Authorize("ManageStore", "Allowed")]
public async Task<ActionResult> CreateAlbum()
{
var album = new Album();
@ -99,8 +96,7 @@ namespace MusicStore.Apis
}
[HttpPut("{albumId:int}/update")]
//[Authorize(Roles = "Administrator")]
[Authorize(ClaimTypes.Role, "Administrator")]
[Authorize("ManageStore", "Allowed")]
public async Task<ActionResult> UpdateAlbum(int albumId)
{
var album = _storeContext.Albums.SingleOrDefault(a => a.AlbumId == albumId);
@ -133,11 +129,11 @@ namespace MusicStore.Apis
}
[HttpDelete("{albumId:int}")]
//[Authorize(Roles = "Administrator")]
[Authorize(ClaimTypes.Role, "Administrator")]
[Authorize("ManageStore", "Allowed")]
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)
{

View File

@ -7,6 +7,7 @@ using MusicStore.Models;
namespace MusicStore.Controllers
{
[Route("account")]
[Authorize]
public class AccountController : Controller
{
@ -20,20 +21,16 @@ namespace MusicStore.Controllers
public SignInManager<ApplicationUser> SignInManager { get; private set; }
//
// GET: /Account/Login
[AllowAnonymous]
//Bug: https://github.com/aspnet/WebFx/issues/339
[HttpGet]
[HttpGet("login")]
public IActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
//
// POST: /Account/Login
[HttpPost]
[HttpPost("login")]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl)
@ -59,19 +56,15 @@ namespace MusicStore.Controllers
return View(model);
}
//
// GET: /Account/Register
[AllowAnonymous]
//Bug: https://github.com/aspnet/WebFx/issues/339
[HttpGet]
[HttpGet("register")]
public IActionResult Register()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
[HttpPost("register")]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
@ -84,7 +77,7 @@ namespace MusicStore.Controllers
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
return RedirectToAction("Home", "Page");
}
else
{
@ -96,9 +89,7 @@ namespace MusicStore.Controllers
return View(model);
}
//
// GET: /Account/Manage
[HttpGet]
[HttpGet("manage")]
public IActionResult Manage(ManageMessageId? message = null)
{
ViewBag.StatusMessage =
@ -109,9 +100,7 @@ namespace MusicStore.Controllers
return View();
}
//
// POST: /Account/Manage
[HttpPost]
[HttpPost("manage")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Manage(ManageUserViewModel model)
{
@ -135,14 +124,12 @@ namespace MusicStore.Controllers
return View(model);
}
//
// POST: /Account/LogOff
[HttpPost]
[HttpPost("logoff")]
[ValidateAntiForgeryToken]
public IActionResult LogOff()
{
SignInManager.SignOut();
return RedirectToAction("Index", "Home");
return RedirectToAction("Home", "Page");
}
#region Helpers

View File

@ -1,12 +0,0 @@
using Microsoft.AspNet.Mvc;
namespace MusicStore.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
}

View File

@ -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");
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
using Newtonsoft.Json;
@ -40,7 +41,7 @@ namespace Microsoft.AspNet.Mvc
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<ModelError> ModelErrors { get; set; }
public override void ExecuteResult(ActionContext context)
public override Task ExecuteResultAsync(ActionContext context)
{
if (StatusCode.HasValue)
{
@ -48,7 +49,7 @@ namespace Microsoft.AspNet.Mvc
}
var json = new JsonResult(this);
json.ExecuteResult(context);
return json.ExecuteResultAsync(context);
}
public class ModelError

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace MusicStore.Infrastructure
@ -64,7 +65,8 @@ namespace MusicStore.Infrastructure
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)
{
@ -73,8 +75,15 @@ namespace MusicStore.Infrastructure
var pagingConfig = new PagingConfig(page, pageSize);
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)
.Take(pagingConfig.PageSize)
.ToListAsync();
@ -83,12 +92,14 @@ namespace MusicStore.Infrastructure
{
// Requested page has no records, just return the first page
pagingConfig.Page = 1;
data = await query
data = await dataQuery
.Take(pagingConfig.PageSize)
.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)

View File

@ -56,22 +56,22 @@ namespace MusicStore.Models
private static async Task CreateAdminUser(IServiceProvider serviceProvider)
{
var options = serviceProvider.GetService<IOptionsAccessor<IdentityDbContextOptions>>().Options;
//const string adminRole = "Administrator";
const string adminRole = "Administrator";
var userManager = serviceProvider.GetService<UserManager<ApplicationUser>>();
// TODO: Identity SQL does not support roles yet
//var roleManager = serviceProvider.GetService<ApplicationRoleManager>();
//if (!await roleManager.RoleExistsAsync(adminRole))
//{
// await roleManager.CreateAsync(new IdentityRole(adminRole));
//}
var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
if (!await roleManager.RoleExistsAsync(adminRole))
{
await roleManager.CreateAsync(new IdentityRole(adminRole));
}
var user = await userManager.FindByNameAsync(options.DefaultAdminUserName);
if (user == null)
{
user = new ApplicationUser { UserName = options.DefaultAdminUserName };
await userManager.CreateAsync(user, options.DefaultAdminPassword);
//await userManager.AddToRoleAsync(user, adminRole);
await userManager.AddToRoleAsync(user, adminRole);
await userManager.AddClaimAsync(user, new Claim("ManageStore", "Allowed"));
}
}

View File

@ -15,7 +15,7 @@
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>5001</DevelopmentServerPort>
<DevelopmentServerPort>5101</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
<ProjectExtensions>

View File

@ -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>
}

View File

@ -17,11 +17,11 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</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 class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("Home", "Home", "Page")</li>
@RenderSection("NavBarItems", required: false)
@ -35,8 +35,8 @@
@RenderBody()
<hr />
<footer class="navbar navbar-fixed-bottom navbar-default text-center">
<p><a href="http://mvcmusicstore.codeplex.com">mvcmusicstore.codeplex.com</a></p>
<small>@Html.ActionLink("admin", "Index", "StoreManager")</small>
<p><a href="https://github.com/aspnet/musicstore">github.com/aspnet/musicstore</a></p>
<small>@Html.ActionLink("admin", "Admin", "Page")</small>
</footer>
</div>

View File

@ -21,9 +21,9 @@
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*"
},
"commands": {
"WebListener": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5002",
"Kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5004",
"run": "run server.urls=http://localhost:5003"
"WebListener": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5102",
"Kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5104",
"run": "run server.urls=http://localhost:5103"
},
"frameworks": {
"aspnet50": { },