Skip to content

Commit 5811c98

Browse files
Beginning React+Redux "Music Store" sample
1 parent 35e620a commit 5811c98

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+7508
-6
lines changed

samples/react/MusicStore/.babelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["es2015", "react"]
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
music-db.sqlite
2+
/wwwroot/dist/
3+
/node_modules/
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNet.Authorization;
6+
using Microsoft.AspNet.Mvc;
7+
using Microsoft.Data.Entity;
8+
using AutoMapper;
9+
using MusicStore.Models;
10+
using MusicStore.Infrastructure;
11+
12+
namespace MusicStore.Apis
13+
{
14+
[Route("api/albums")]
15+
public class AlbumsApiController : Controller
16+
{
17+
private readonly MusicStoreContext _storeContext;
18+
19+
public AlbumsApiController(MusicStoreContext storeContext)
20+
{
21+
_storeContext = storeContext;
22+
}
23+
24+
[HttpGet]
25+
[NoCache]
26+
public async Task<ActionResult> Paged(int page = 1, int pageSize = 50, string sortBy = null)
27+
{
28+
await _storeContext.Genres.LoadAsync();
29+
await _storeContext.Artists.LoadAsync();
30+
31+
var albums = await _storeContext.Albums
32+
// .Include(a => a.Genre)
33+
// .Include(a => a.Artist)
34+
.ToPagedListAsync(page, pageSize, sortBy,
35+
a => a.Title, // sortExpression
36+
SortDirection.Ascending, // defaultSortDirection
37+
a => Mapper.Map(a, new AlbumResultDto())); // selector
38+
39+
return Json(albums);
40+
}
41+
42+
[HttpGet("all")]
43+
[NoCache]
44+
public async Task<ActionResult> All()
45+
{
46+
var albums = await _storeContext.Albums
47+
//.Include(a => a.Genre)
48+
//.Include(a => a.Artist)
49+
.OrderBy(a => a.Title)
50+
.ToListAsync();
51+
52+
return Json(albums.Select(a => Mapper.Map(a, new AlbumResultDto())));
53+
}
54+
55+
[HttpGet("mostPopular")]
56+
[NoCache]
57+
public async Task<ActionResult> MostPopular(int count = 6)
58+
{
59+
count = count > 0 && count < 20 ? count : 6;
60+
var albums = await _storeContext.Albums
61+
.OrderByDescending(a => a.OrderDetails.Count())
62+
.Take(count)
63+
.ToListAsync();
64+
65+
// TODO: Move the .Select() to end of albums query when EF supports it
66+
return Json(albums.Select(a => Mapper.Map(a, new AlbumResultDto())));
67+
}
68+
69+
[HttpGet("{albumId:int}")]
70+
[NoCache]
71+
public async Task<ActionResult> Details(int albumId)
72+
{
73+
await _storeContext.Genres.LoadAsync();
74+
await _storeContext.Artists.LoadAsync();
75+
76+
var album = await _storeContext.Albums
77+
//.Include(a => a.Artist)
78+
//.Include(a => a.Genre)
79+
.Where(a => a.AlbumId == albumId)
80+
.SingleOrDefaultAsync();
81+
82+
var albumResult = Mapper.Map(album, new AlbumResultDto());
83+
84+
// TODO: Get these from the related entities when EF supports that again, i.e. when .Include() works
85+
//album.Artist.Name = (await _storeContext.Artists.SingleOrDefaultAsync(a => a.ArtistId == album.ArtistId)).Name;
86+
//album.Genre.Name = (await _storeContext.Genres.SingleOrDefaultAsync(g => g.GenreId == album.GenreId)).Name;
87+
88+
// TODO: Add null checking and return 404 in that case
89+
90+
return Json(albumResult);
91+
}
92+
93+
[HttpPost]
94+
[Authorize("app-ManageStore")]
95+
public async Task<ActionResult> CreateAlbum([FromBody]AlbumChangeDto album)
96+
{
97+
if (!ModelState.IsValid)
98+
{
99+
// Return the model errors
100+
return HttpBadRequest(ModelState);
101+
}
102+
103+
// Save the changes to the DB
104+
var dbAlbum = new Album();
105+
_storeContext.Albums.Add(Mapper.Map(album, dbAlbum));
106+
await _storeContext.SaveChangesAsync();
107+
108+
// TODO: Handle missing record, key violations, concurrency issues, etc.
109+
110+
return new ObjectResult(new {
111+
Data = dbAlbum.AlbumId,
112+
Message = "Album created successfully."
113+
});
114+
}
115+
116+
[HttpPut("{albumId:int}/update")]
117+
public async Task<ActionResult> UpdateAlbum(int albumId, [FromBody] AlbumChangeDto album)
118+
{
119+
if (!ModelState.IsValid)
120+
{
121+
// Return the model errors
122+
return HttpBadRequest(ModelState);
123+
}
124+
125+
var dbAlbum = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId);
126+
127+
if (dbAlbum == null)
128+
{
129+
return new ObjectResult(new {
130+
Message = string.Format("The album with ID {0} was not found.", albumId)
131+
}) { StatusCode = 404 };
132+
}
133+
134+
// Save the changes to the DB
135+
Mapper.Map(album, dbAlbum);
136+
await _storeContext.SaveChangesAsync();
137+
138+
// TODO: Handle missing record, key violations, concurrency issues, etc.
139+
140+
return new ObjectResult (new {
141+
Message = "Album updated successfully."
142+
});
143+
}
144+
145+
[HttpDelete("{albumId:int}")]
146+
[Authorize("app-ManageStore")]
147+
public async Task<ActionResult> DeleteAlbum(int albumId)
148+
{
149+
var album = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId);
150+
//var album = _storeContext.Albums.SingleOrDefault(a => a.AlbumId == albumId);
151+
152+
if (album != null)
153+
{
154+
_storeContext.Albums.Remove(album);
155+
156+
// Save the changes to the DB
157+
await _storeContext.SaveChangesAsync();
158+
159+
// TODO: Handle missing record, key violations, concurrency issues, etc.
160+
}
161+
162+
return new ObjectResult (new {
163+
Message = "Album deleted successfully."
164+
});
165+
}
166+
}
167+
168+
[ModelMetadataType(typeof(Album))]
169+
public class AlbumChangeDto
170+
{
171+
public int GenreId { get; set; }
172+
173+
public int ArtistId { get; set; }
174+
175+
public string Title { get; set; }
176+
177+
public decimal Price { get; set; }
178+
179+
public string AlbumArtUrl { get; set; }
180+
}
181+
182+
public class AlbumResultDto : AlbumChangeDto
183+
{
184+
public AlbumResultDto()
185+
{
186+
Artist = new ArtistResultDto();
187+
Genre = new GenreResultDto();
188+
}
189+
190+
public int AlbumId { get; set; }
191+
192+
public ArtistResultDto Artist { get; private set; }
193+
194+
public GenreResultDto Genre { get; private set; }
195+
}
196+
197+
public class ArtistResultDto
198+
{
199+
public string Name { get; set; }
200+
}
201+
202+
public class GenreResultDto
203+
{
204+
public string Name { get; set; }
205+
}
206+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Microsoft.AspNet.Mvc;
5+
using Microsoft.Data.Entity;
6+
using MusicStore.Models;
7+
8+
namespace MusicStore.Apis
9+
{
10+
[Route("api/artists")]
11+
public class ArtistsApiController : Controller
12+
{
13+
private readonly MusicStoreContext _storeContext;
14+
15+
public ArtistsApiController(MusicStoreContext storeContext)
16+
{
17+
_storeContext = storeContext;
18+
}
19+
20+
[HttpGet("lookup")]
21+
public async Task<ActionResult> Lookup()
22+
{
23+
var artists = await _storeContext.Artists
24+
.OrderBy(a => a.Name)
25+
.ToListAsync();
26+
27+
return Json(artists);
28+
}
29+
}
30+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.Linq;
2+
using System.Threading.Tasks;
3+
using Microsoft.AspNet.Mvc;
4+
using Microsoft.Data.Entity;
5+
using MusicStore.Models;
6+
using MusicStore.Infrastructure;
7+
8+
namespace MusicStore.Apis
9+
{
10+
[Route("api/genres")]
11+
public class GenresApiController : Controller
12+
{
13+
private readonly MusicStoreContext _storeContext;
14+
15+
public GenresApiController(MusicStoreContext storeContext)
16+
{
17+
_storeContext = storeContext;
18+
}
19+
20+
[HttpGet]
21+
public async Task<ActionResult> GenreList()
22+
{
23+
var genres = await _storeContext.Genres
24+
//.Include(g => g.Albums)
25+
.OrderBy(g => g.Name)
26+
.ToListAsync();
27+
28+
return Json(genres);
29+
}
30+
31+
[HttpGet("genre-lookup")]
32+
public async Task<ActionResult> Lookup()
33+
{
34+
var genres = await _storeContext.Genres
35+
.Select(g => new { g.GenreId, g.Name })
36+
.ToListAsync();
37+
38+
return Json(genres);
39+
}
40+
41+
[HttpGet("menu")]
42+
public async Task<ActionResult> GenreMenuList(int count = 9)
43+
{
44+
count = count > 0 && count < 20 ? count : 9;
45+
46+
var genres = await _storeContext.Genres
47+
.OrderByDescending(g =>
48+
g.Albums.Sum(a =>
49+
a.OrderDetails.Sum(od => od.Quantity)))
50+
.Take(count)
51+
.ToListAsync();
52+
53+
return Json(genres);
54+
}
55+
56+
[HttpGet("{genreId:int}/albums")]
57+
[NoCache]
58+
public async Task<ActionResult> GenreAlbums(int genreId)
59+
{
60+
var albums = await _storeContext.Albums
61+
.Where(a => a.GenreId == genreId)
62+
//.Include(a => a.Genre)
63+
//.Include(a => a.Artist)
64+
//.OrderBy(a => a.Genre.Name)
65+
.ToListAsync();
66+
67+
return Json(albums);
68+
}
69+
}
70+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace MusicStore.Models
4+
{
5+
public class ExternalLoginConfirmationViewModel
6+
{
7+
[Required]
8+
[Display(Name = "User name")]
9+
public string UserName { get; set; }
10+
}
11+
12+
public class ManageUserViewModel
13+
{
14+
[Required]
15+
[DataType(DataType.Password)]
16+
[Display(Name = "Current password")]
17+
public string OldPassword { get; set; }
18+
19+
[Required]
20+
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
21+
[DataType(DataType.Password)]
22+
[Display(Name = "New password")]
23+
public string NewPassword { get; set; }
24+
25+
[DataType(DataType.Password)]
26+
[Display(Name = "Confirm new password")]
27+
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
28+
public string ConfirmPassword { get; set; }
29+
}
30+
31+
public class LoginViewModel
32+
{
33+
[Required]
34+
[Display(Name = "User name")]
35+
public string UserName { get; set; }
36+
37+
[Required]
38+
[DataType(DataType.Password)]
39+
[Display(Name = "Password")]
40+
public string Password { get; set; }
41+
42+
[Display(Name = "Remember me?")]
43+
public bool RememberMe { get; set; }
44+
}
45+
46+
public class RegisterViewModel
47+
{
48+
[Required]
49+
[Display(Name = "User name")]
50+
public string UserName { get; set; }
51+
52+
[Required]
53+
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
54+
[DataType(DataType.Password)]
55+
[Display(Name = "Password")]
56+
public string Password { get; set; }
57+
58+
[DataType(DataType.Password)]
59+
[Display(Name = "Confirm password")]
60+
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
61+
public string ConfirmPassword { get; set; }
62+
}
63+
}

0 commit comments

Comments
 (0)