From 5811c98230f0ae06874c6daf8c255a83ed6c4d85 Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Fri, 5 Feb 2016 23:28:13 +0000 Subject: [PATCH] Beginning React+Redux "Music Store" sample --- samples/react/MusicStore/.babelrc | 3 + samples/react/MusicStore/.gitignore | 3 + .../MusicStore/Apis/AlbumsApiController.cs | 206 ++ .../MusicStore/Apis/ArtistsApiController.cs | 30 + .../MusicStore/Apis/GenresApiController.cs | 70 + .../Apis/Models/AccountViewModels.cs | 63 + samples/react/MusicStore/Apis/Models/Album.cs | 40 + .../react/MusicStore/Apis/Models/Artist.cs | 12 + .../react/MusicStore/Apis/Models/CartItem.cs | 21 + samples/react/MusicStore/Apis/Models/Genre.cs | 24 + .../Apis/Models/MusicStoreContext.cs | 34 + samples/react/MusicStore/Apis/Models/Order.cs | 73 + .../MusicStore/Apis/Models/OrderDetail.cs | 14 + .../MusicStore/Apis/Models/SampleData.cs | 915 +++++++ .../MusicStore/Apis/Models/ShoppingCart.cs | 207 ++ .../MusicStore/Controllers/HomeController.cs | 16 + .../Infrastructure/NoCacheAttribute.cs | 19 + .../MusicStore/Infrastructure/PagedList.cs | 150 ++ .../Infrastructure/SortDirection.cs | 13 + .../Infrastructure/SortExpression.cs | 87 + .../react/MusicStore/ReactApp/TypedRedux.ts | 62 + samples/react/MusicStore/ReactApp/boot.tsx | 19 + .../MusicStore/ReactApp/components/App.tsx | 37 + .../ReactApp/components/NavMenu.tsx | 49 + .../components/public/AlbumDetails.tsx | 60 + .../ReactApp/components/public/AlbumTile.tsx | 17 + .../components/public/GenreDetails.tsx | 48 + .../ReactApp/components/public/Genres.tsx | 41 + .../ReactApp/components/public/Home.tsx | 37 + .../MusicStore/ReactApp/configureStore.ts | 39 + .../MusicStore/ReactApp/isomorphic-fetch.d.ts | 3 + .../MusicStore/ReactApp/store/AlbumDetails.ts | 72 + .../ReactApp/store/FeaturedAlbums.ts | 58 + .../MusicStore/ReactApp/store/GenreDetails.ts | 59 + .../MusicStore/ReactApp/store/GenreList.ts | 51 + .../react/MusicStore/ReactApp/store/index.ts | 27 + .../MusicStore/ReactApp/styles/styles.css | 3 + samples/react/MusicStore/SiteSettings.cs | 13 + samples/react/MusicStore/Startup.cs | 122 + .../react/MusicStore/Views/Home/Index.cshtml | 10 + .../MusicStore/Views/Shared/Error.cshtml | 6 + .../MusicStore/Views/Shared/_Layout.cshtml | 11 + .../MusicStore/Views/_ViewImports.cshtml | 2 + .../react/MusicStore/Views/_ViewStart.cshtml | 3 + samples/react/MusicStore/appsettings.json | 11 + samples/react/MusicStore/package.json | 34 + samples/react/MusicStore/project.json | 53 + samples/react/MusicStore/tsconfig.json | 12 + samples/react/MusicStore/tsd.json | 42 + .../react-bootstrap/react-bootstrap.d.ts | 976 +++++++ .../typings/react-redux/react-redux.d.ts | 69 + .../react-router-bootstrap.d.ts | 46 + .../react-router-redux.d.ts | 48 + .../typings/react-router/history.d.ts | 192 ++ .../typings/react-router/react-router.d.ts | 474 ++++ .../MusicStore/typings/react/react-dom.d.ts | 66 + .../react/MusicStore/typings/react/react.d.ts | 2274 +++++++++++++++++ .../typings/redux-thunk/redux-thunk.d.ts | 18 + .../react/MusicStore/typings/redux/redux.d.ts | 52 + samples/react/MusicStore/typings/tsd.d.ts | 11 + .../typings/webpack/webpack-env.d.ts | 232 ++ samples/react/MusicStore/webpack.config.js | 30 + samples/react/MusicStore/wwwroot/favicon.ico | Bin 0 -> 32038 bytes .../wwwroot/images/home-showcase.png | Bin 0 -> 254130 bytes .../react/MusicStore/wwwroot/images/logo.png | Bin 0 -> 2963 bytes .../MusicStore/wwwroot/images/placeholder.png | Bin 0 -> 1221 bytes samples/react/MusicStore/wwwroot/web.config | 9 + .../Content/Node/webpack-dev-middleware.js | 3 +- .../WebpackDevMiddleware.cs | 13 +- 69 files changed, 7508 insertions(+), 6 deletions(-) create mode 100644 samples/react/MusicStore/.babelrc create mode 100644 samples/react/MusicStore/.gitignore create mode 100644 samples/react/MusicStore/Apis/AlbumsApiController.cs create mode 100644 samples/react/MusicStore/Apis/ArtistsApiController.cs create mode 100644 samples/react/MusicStore/Apis/GenresApiController.cs create mode 100644 samples/react/MusicStore/Apis/Models/AccountViewModels.cs create mode 100644 samples/react/MusicStore/Apis/Models/Album.cs create mode 100644 samples/react/MusicStore/Apis/Models/Artist.cs create mode 100644 samples/react/MusicStore/Apis/Models/CartItem.cs create mode 100644 samples/react/MusicStore/Apis/Models/Genre.cs create mode 100644 samples/react/MusicStore/Apis/Models/MusicStoreContext.cs create mode 100644 samples/react/MusicStore/Apis/Models/Order.cs create mode 100644 samples/react/MusicStore/Apis/Models/OrderDetail.cs create mode 100644 samples/react/MusicStore/Apis/Models/SampleData.cs create mode 100644 samples/react/MusicStore/Apis/Models/ShoppingCart.cs create mode 100755 samples/react/MusicStore/Controllers/HomeController.cs create mode 100644 samples/react/MusicStore/Infrastructure/NoCacheAttribute.cs create mode 100644 samples/react/MusicStore/Infrastructure/PagedList.cs create mode 100644 samples/react/MusicStore/Infrastructure/SortDirection.cs create mode 100644 samples/react/MusicStore/Infrastructure/SortExpression.cs create mode 100644 samples/react/MusicStore/ReactApp/TypedRedux.ts create mode 100644 samples/react/MusicStore/ReactApp/boot.tsx create mode 100644 samples/react/MusicStore/ReactApp/components/App.tsx create mode 100644 samples/react/MusicStore/ReactApp/components/NavMenu.tsx create mode 100644 samples/react/MusicStore/ReactApp/components/public/AlbumDetails.tsx create mode 100644 samples/react/MusicStore/ReactApp/components/public/AlbumTile.tsx create mode 100644 samples/react/MusicStore/ReactApp/components/public/GenreDetails.tsx create mode 100644 samples/react/MusicStore/ReactApp/components/public/Genres.tsx create mode 100644 samples/react/MusicStore/ReactApp/components/public/Home.tsx create mode 100644 samples/react/MusicStore/ReactApp/configureStore.ts create mode 100644 samples/react/MusicStore/ReactApp/isomorphic-fetch.d.ts create mode 100644 samples/react/MusicStore/ReactApp/store/AlbumDetails.ts create mode 100644 samples/react/MusicStore/ReactApp/store/FeaturedAlbums.ts create mode 100644 samples/react/MusicStore/ReactApp/store/GenreDetails.ts create mode 100644 samples/react/MusicStore/ReactApp/store/GenreList.ts create mode 100644 samples/react/MusicStore/ReactApp/store/index.ts create mode 100644 samples/react/MusicStore/ReactApp/styles/styles.css create mode 100644 samples/react/MusicStore/SiteSettings.cs create mode 100755 samples/react/MusicStore/Startup.cs create mode 100755 samples/react/MusicStore/Views/Home/Index.cshtml create mode 100755 samples/react/MusicStore/Views/Shared/Error.cshtml create mode 100755 samples/react/MusicStore/Views/Shared/_Layout.cshtml create mode 100755 samples/react/MusicStore/Views/_ViewImports.cshtml create mode 100755 samples/react/MusicStore/Views/_ViewStart.cshtml create mode 100755 samples/react/MusicStore/appsettings.json create mode 100644 samples/react/MusicStore/package.json create mode 100755 samples/react/MusicStore/project.json create mode 100644 samples/react/MusicStore/tsconfig.json create mode 100644 samples/react/MusicStore/tsd.json create mode 100644 samples/react/MusicStore/typings/react-bootstrap/react-bootstrap.d.ts create mode 100644 samples/react/MusicStore/typings/react-redux/react-redux.d.ts create mode 100644 samples/react/MusicStore/typings/react-router-bootstrap/react-router-bootstrap.d.ts create mode 100644 samples/react/MusicStore/typings/react-router-redux/react-router-redux.d.ts create mode 100644 samples/react/MusicStore/typings/react-router/history.d.ts create mode 100644 samples/react/MusicStore/typings/react-router/react-router.d.ts create mode 100644 samples/react/MusicStore/typings/react/react-dom.d.ts create mode 100644 samples/react/MusicStore/typings/react/react.d.ts create mode 100644 samples/react/MusicStore/typings/redux-thunk/redux-thunk.d.ts create mode 100644 samples/react/MusicStore/typings/redux/redux.d.ts create mode 100644 samples/react/MusicStore/typings/tsd.d.ts create mode 100644 samples/react/MusicStore/typings/webpack/webpack-env.d.ts create mode 100644 samples/react/MusicStore/webpack.config.js create mode 100755 samples/react/MusicStore/wwwroot/favicon.ico create mode 100644 samples/react/MusicStore/wwwroot/images/home-showcase.png create mode 100644 samples/react/MusicStore/wwwroot/images/logo.png create mode 100644 samples/react/MusicStore/wwwroot/images/placeholder.png create mode 100644 samples/react/MusicStore/wwwroot/web.config diff --git a/samples/react/MusicStore/.babelrc b/samples/react/MusicStore/.babelrc new file mode 100644 index 0000000..86c445f --- /dev/null +++ b/samples/react/MusicStore/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "react"] +} diff --git a/samples/react/MusicStore/.gitignore b/samples/react/MusicStore/.gitignore new file mode 100644 index 0000000..bca785d --- /dev/null +++ b/samples/react/MusicStore/.gitignore @@ -0,0 +1,3 @@ +music-db.sqlite +/wwwroot/dist/ +/node_modules/ diff --git a/samples/react/MusicStore/Apis/AlbumsApiController.cs b/samples/react/MusicStore/Apis/AlbumsApiController.cs new file mode 100644 index 0000000..4171a91 --- /dev/null +++ b/samples/react/MusicStore/Apis/AlbumsApiController.cs @@ -0,0 +1,206 @@ +using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Authorization; +using Microsoft.AspNet.Mvc; +using Microsoft.Data.Entity; +using AutoMapper; +using MusicStore.Models; +using MusicStore.Infrastructure; + +namespace MusicStore.Apis +{ + [Route("api/albums")] + public class AlbumsApiController : Controller + { + private readonly MusicStoreContext _storeContext; + + public AlbumsApiController(MusicStoreContext storeContext) + { + _storeContext = storeContext; + } + + [HttpGet] + [NoCache] + public async Task Paged(int page = 1, int pageSize = 50, string sortBy = null) + { + await _storeContext.Genres.LoadAsync(); + await _storeContext.Artists.LoadAsync(); + + var albums = await _storeContext.Albums + // .Include(a => a.Genre) + // .Include(a => a.Artist) + .ToPagedListAsync(page, pageSize, sortBy, + a => a.Title, // sortExpression + SortDirection.Ascending, // defaultSortDirection + a => Mapper.Map(a, new AlbumResultDto())); // selector + + return Json(albums); + } + + [HttpGet("all")] + [NoCache] + public async Task All() + { + var albums = await _storeContext.Albums + //.Include(a => a.Genre) + //.Include(a => a.Artist) + .OrderBy(a => a.Title) + .ToListAsync(); + + return Json(albums.Select(a => Mapper.Map(a, new AlbumResultDto()))); + } + + [HttpGet("mostPopular")] + [NoCache] + public async Task MostPopular(int count = 6) + { + count = count > 0 && count < 20 ? count : 6; + var albums = await _storeContext.Albums + .OrderByDescending(a => a.OrderDetails.Count()) + .Take(count) + .ToListAsync(); + + // TODO: Move the .Select() to end of albums query when EF supports it + return Json(albums.Select(a => Mapper.Map(a, new AlbumResultDto()))); + } + + [HttpGet("{albumId:int}")] + [NoCache] + public async Task Details(int albumId) + { + await _storeContext.Genres.LoadAsync(); + await _storeContext.Artists.LoadAsync(); + + var album = await _storeContext.Albums + //.Include(a => a.Artist) + //.Include(a => a.Genre) + .Where(a => a.AlbumId == albumId) + .SingleOrDefaultAsync(); + + var albumResult = Mapper.Map(album, new AlbumResultDto()); + + // TODO: Get these from the related entities when EF supports that again, i.e. when .Include() works + //album.Artist.Name = (await _storeContext.Artists.SingleOrDefaultAsync(a => a.ArtistId == album.ArtistId)).Name; + //album.Genre.Name = (await _storeContext.Genres.SingleOrDefaultAsync(g => g.GenreId == album.GenreId)).Name; + + // TODO: Add null checking and return 404 in that case + + return Json(albumResult); + } + + [HttpPost] + [Authorize("app-ManageStore")] + public async Task CreateAlbum([FromBody]AlbumChangeDto album) + { + if (!ModelState.IsValid) + { + // Return the model errors + return HttpBadRequest(ModelState); + } + + // Save the changes to the DB + var dbAlbum = new Album(); + _storeContext.Albums.Add(Mapper.Map(album, dbAlbum)); + await _storeContext.SaveChangesAsync(); + + // TODO: Handle missing record, key violations, concurrency issues, etc. + + return new ObjectResult(new { + Data = dbAlbum.AlbumId, + Message = "Album created successfully." + }); + } + + [HttpPut("{albumId:int}/update")] + public async Task UpdateAlbum(int albumId, [FromBody] AlbumChangeDto album) + { + if (!ModelState.IsValid) + { + // Return the model errors + return HttpBadRequest(ModelState); + } + + var dbAlbum = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId); + + if (dbAlbum == null) + { + return new ObjectResult(new { + Message = string.Format("The album with ID {0} was not found.", albumId) + }) { StatusCode = 404 }; + } + + // Save the changes to the DB + Mapper.Map(album, dbAlbum); + await _storeContext.SaveChangesAsync(); + + // TODO: Handle missing record, key violations, concurrency issues, etc. + + return new ObjectResult (new { + Message = "Album updated successfully." + }); + } + + [HttpDelete("{albumId:int}")] + [Authorize("app-ManageStore")] + public async Task DeleteAlbum(int albumId) + { + var album = await _storeContext.Albums.SingleOrDefaultAsync(a => a.AlbumId == albumId); + //var album = _storeContext.Albums.SingleOrDefault(a => a.AlbumId == albumId); + + if (album != null) + { + _storeContext.Albums.Remove(album); + + // Save the changes to the DB + await _storeContext.SaveChangesAsync(); + + // TODO: Handle missing record, key violations, concurrency issues, etc. + } + + return new ObjectResult (new { + Message = "Album deleted successfully." + }); + } + } + + [ModelMetadataType(typeof(Album))] + public class AlbumChangeDto + { + public int GenreId { get; set; } + + public int ArtistId { get; set; } + + public string Title { get; set; } + + public decimal Price { get; set; } + + public string AlbumArtUrl { get; set; } + } + + public class AlbumResultDto : AlbumChangeDto + { + public AlbumResultDto() + { + Artist = new ArtistResultDto(); + Genre = new GenreResultDto(); + } + + public int AlbumId { get; set; } + + public ArtistResultDto Artist { get; private set; } + + public GenreResultDto Genre { get; private set; } + } + + public class ArtistResultDto + { + public string Name { get; set; } + } + + public class GenreResultDto + { + public string Name { get; set; } + } +} diff --git a/samples/react/MusicStore/Apis/ArtistsApiController.cs b/samples/react/MusicStore/Apis/ArtistsApiController.cs new file mode 100644 index 0000000..0efe110 --- /dev/null +++ b/samples/react/MusicStore/Apis/ArtistsApiController.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc; +using Microsoft.Data.Entity; +using MusicStore.Models; + +namespace MusicStore.Apis +{ + [Route("api/artists")] + public class ArtistsApiController : Controller + { + private readonly MusicStoreContext _storeContext; + + public ArtistsApiController(MusicStoreContext storeContext) + { + _storeContext = storeContext; + } + + [HttpGet("lookup")] + public async Task Lookup() + { + var artists = await _storeContext.Artists + .OrderBy(a => a.Name) + .ToListAsync(); + + return Json(artists); + } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/GenresApiController.cs b/samples/react/MusicStore/Apis/GenresApiController.cs new file mode 100644 index 0000000..e5d6ba1 --- /dev/null +++ b/samples/react/MusicStore/Apis/GenresApiController.cs @@ -0,0 +1,70 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc; +using Microsoft.Data.Entity; +using MusicStore.Models; +using MusicStore.Infrastructure; + +namespace MusicStore.Apis +{ + [Route("api/genres")] + public class GenresApiController : Controller + { + private readonly MusicStoreContext _storeContext; + + public GenresApiController(MusicStoreContext storeContext) + { + _storeContext = storeContext; + } + + [HttpGet] + public async Task GenreList() + { + var genres = await _storeContext.Genres + //.Include(g => g.Albums) + .OrderBy(g => g.Name) + .ToListAsync(); + + return Json(genres); + } + + [HttpGet("genre-lookup")] + public async Task Lookup() + { + var genres = await _storeContext.Genres + .Select(g => new { g.GenreId, g.Name }) + .ToListAsync(); + + return Json(genres); + } + + [HttpGet("menu")] + public async Task GenreMenuList(int count = 9) + { + count = count > 0 && count < 20 ? count : 9; + + var genres = await _storeContext.Genres + .OrderByDescending(g => + g.Albums.Sum(a => + a.OrderDetails.Sum(od => od.Quantity))) + .Take(count) + .ToListAsync(); + + return Json(genres); + } + + [HttpGet("{genreId:int}/albums")] + [NoCache] + public async Task GenreAlbums(int genreId) + { + var albums = await _storeContext.Albums + .Where(a => a.GenreId == genreId) + //.Include(a => a.Genre) + //.Include(a => a.Artist) + //.OrderBy(a => a.Genre.Name) + .ToListAsync(); + + return Json(albums); + } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/Models/AccountViewModels.cs b/samples/react/MusicStore/Apis/Models/AccountViewModels.cs new file mode 100644 index 0000000..657a4f0 --- /dev/null +++ b/samples/react/MusicStore/Apis/Models/AccountViewModels.cs @@ -0,0 +1,63 @@ +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class ExternalLoginConfirmationViewModel + { + [Required] + [Display(Name = "User name")] + public string UserName { get; set; } + } + + public class ManageUserViewModel + { + [Required] + [DataType(DataType.Password)] + [Display(Name = "Current password")] + public string OldPassword { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } + + public class LoginViewModel + { + [Required] + [Display(Name = "User name")] + public string UserName { get; set; } + + [Required] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } + + public class RegisterViewModel + { + [Required] + [Display(Name = "User name")] + public string UserName { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/Models/Album.cs b/samples/react/MusicStore/Apis/Models/Album.cs new file mode 100644 index 0000000..919bf1b --- /dev/null +++ b/samples/react/MusicStore/Apis/Models/Album.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class Album + { + public Album() + { + // TODO: Temporary hack to populate the orderdetails until EF does this automatically. + OrderDetails = new List(); + } + + [ScaffoldColumn(false)] + public int AlbumId { get; set; } + + public int GenreId { get; set; } + + public int ArtistId { get; set; } + + [Required] + [StringLength(160, MinimumLength = 2)] + public string Title { get; set; } + + [Required] + [RangeAttribute(typeof(double), "0.01", "100")] // Long-form constructor to work around https://github.com/dotnet/coreclr/issues/2172 + [DataType(DataType.Currency)] + public decimal Price { get; set; } + + [Display(Name = "Album Art URL")] + [StringLength(1024)] + public string AlbumArtUrl { get; set; } + + public virtual Genre Genre { get; set; } + + public virtual Artist Artist { get; set; } + + public virtual ICollection OrderDetails { get; set; } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/Models/Artist.cs b/samples/react/MusicStore/Apis/Models/Artist.cs new file mode 100644 index 0000000..43d677c --- /dev/null +++ b/samples/react/MusicStore/Apis/Models/Artist.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class Artist + { + public int ArtistId { get; set; } + + [Required] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/Models/CartItem.cs b/samples/react/MusicStore/Apis/Models/CartItem.cs new file mode 100644 index 0000000..64550ab --- /dev/null +++ b/samples/react/MusicStore/Apis/Models/CartItem.cs @@ -0,0 +1,21 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class CartItem + { + [Key] + public int CartItemId { get; set; } + + [Required] + public string CartId { get; set; } + public int AlbumId { get; set; } + public int Count { get; set; } + + [DataType(DataType.DateTime)] + public DateTime DateCreated { get; set; } + + public virtual Album Album { get; set; } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/Models/Genre.cs b/samples/react/MusicStore/Apis/Models/Genre.cs new file mode 100644 index 0000000..a7fdb34 --- /dev/null +++ b/samples/react/MusicStore/Apis/Models/Genre.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace MusicStore.Models +{ + public class Genre + { + public Genre() + { + Albums = new List(); + } + + public int GenreId { get; set; } + + [Required] + public string Name { get; set; } + + public string Description { get; set; } + + [JsonIgnore] + public virtual ICollection Albums { get; set; } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/Models/MusicStoreContext.cs b/samples/react/MusicStore/Apis/Models/MusicStoreContext.cs new file mode 100644 index 0000000..ffecfc5 --- /dev/null +++ b/samples/react/MusicStore/Apis/Models/MusicStoreContext.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNet.Identity.EntityFramework; +using Microsoft.Data.Entity; + +namespace MusicStore.Models +{ + public class ApplicationUser : IdentityUser { } + + public class MusicStoreContext : IdentityDbContext + { + public MusicStoreContext() + { + } + + public DbSet Albums { get; set; } + public DbSet Artists { get; set; } + public DbSet Orders { get; set; } + public DbSet Genres { get; set; } + public DbSet CartItems { get; set; } + public DbSet OrderDetails { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + // Configure pluralization + builder.Entity().ToTable("Albums"); + builder.Entity().ToTable("Artists"); + builder.Entity().ToTable("Orders"); + builder.Entity().ToTable("Genres"); + builder.Entity().ToTable("CartItems"); + builder.Entity().ToTable("OrderDetails"); + + base.OnModelCreating(builder); + } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/Models/Order.cs b/samples/react/MusicStore/Apis/Models/Order.cs new file mode 100644 index 0000000..0461dbc --- /dev/null +++ b/samples/react/MusicStore/Apis/Models/Order.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + //[Bind(Include = "FirstName,LastName,Address,City,State,PostalCode,Country,Phone,Email")] + public class Order + { + public Order() + { + OrderDetails = new List(); + } + + [ScaffoldColumn(false)] + public int OrderId { get; set; } + + [ScaffoldColumn(false)] + public DateTime OrderDate { get; set; } + + [Required] + [ScaffoldColumn(false)] + public string Username { get; set; } + + [Required] + [Display(Name = "First Name")] + [StringLength(160)] + public string FirstName { get; set; } + + [Required] + [Display(Name = "Last Name")] + [StringLength(160)] + public string LastName { get; set; } + + [Required] + [StringLength(70, MinimumLength = 3)] + public string Address { get; set; } + + [Required] + [StringLength(40)] + public string City { get; set; } + + [Required] + [StringLength(40)] + public string State { get; set; } + + [Required] + [Display(Name = "Postal Code")] + [StringLength(10, MinimumLength = 5)] + public string PostalCode { get; set; } + + [Required] + [StringLength(40)] + public string Country { get; set; } + + [Required] + [StringLength(24)] + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } + + [Required] + [Display(Name = "Email Address")] + [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", + ErrorMessage = "Email is not valid.")] + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [ScaffoldColumn(false)] + public decimal Total { get; set; } + + public ICollection OrderDetails { get; set; } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/Models/OrderDetail.cs b/samples/react/MusicStore/Apis/Models/OrderDetail.cs new file mode 100644 index 0000000..29f8798 --- /dev/null +++ b/samples/react/MusicStore/Apis/Models/OrderDetail.cs @@ -0,0 +1,14 @@ +namespace MusicStore.Models +{ + public class OrderDetail + { + public int OrderDetailId { get; set; } + public int OrderId { get; set; } + public int AlbumId { get; set; } + public int Quantity { get; set; } + public decimal UnitPrice { get; set; } + + public virtual Album Album { get; set; } + public virtual Order Order { get; set; } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/Models/SampleData.cs b/samples/react/MusicStore/Apis/Models/SampleData.cs new file mode 100644 index 0000000..95ed0b3 --- /dev/null +++ b/samples/react/MusicStore/Apis/Models/SampleData.cs @@ -0,0 +1,915 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNet.Identity; +using Microsoft.AspNet.Identity.EntityFramework; +using Microsoft.Data.Entity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.OptionsModel; + +namespace MusicStore.Models +{ + public static class SampleData + { + const string imgUrl = "/images/placeholder.png"; + + public static async Task InitializeMusicStoreDatabaseAsync(IServiceProvider serviceProvider) + { + using (var db = serviceProvider.GetService()) + { + if (await db.Database.EnsureCreatedAsync()) + { + await InsertTestData(serviceProvider); + } + } + } + + private static async Task InsertTestData(IServiceProvider serviceProvider) + { + var albums = GetAlbums(imgUrl, Genres, Artists); + await AddOrUpdateAsync(serviceProvider, g => g.GenreId, Genres.Select(genre => genre.Value)); + await AddOrUpdateAsync(serviceProvider, a => a.ArtistId, Artists.Select(artist => artist.Value)); + await AddOrUpdateAsync(serviceProvider, a => a.AlbumId, albums); + } + + // TODO [EF] This may be replaced by a first class mechanism in EF + private static async Task AddOrUpdateAsync( + IServiceProvider serviceProvider, + Func propertyToMatch, IEnumerable entities) + where TEntity : class + { + // Query in a separate context so that we can attach existing entities as modified + List existingData; + + using (var scope = serviceProvider.GetRequiredService().CreateScope()) + using (var db = scope.ServiceProvider.GetService()) + { + existingData = db.Set().ToList(); + } + + using (var scope = serviceProvider.GetRequiredService().CreateScope()) + using (var db = scope.ServiceProvider.GetService()) + { + foreach (var item in entities) + { + db.Entry(item).State = existingData.Any(g => propertyToMatch(g).Equals(propertyToMatch(item))) + ? EntityState.Modified + : EntityState.Added; + } + + await db.SaveChangesAsync(); + } + } + + private static Album[] GetAlbums(string imgUrl, Dictionary genres, Dictionary artists) + { + var albums = new Album[] + { + new Album { Title = "The Best Of The Men At Work", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Men At Work"], AlbumArtUrl = imgUrl }, + new Album { Title = "...And Justice For All", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "עד גבול האור", Genre = genres["World"], Price = 8.99M, Artist = artists["אריק אינשטיין"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Light Syndrome", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Terry Bozzio, Tony Levin & Steve Stevens"], AlbumArtUrl = imgUrl }, + new Album { Title = "10,000 Days", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "11i", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Supreme Beings of Leisure"], AlbumArtUrl = imgUrl }, + new Album { Title = "1960", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Soul-Junk"], AlbumArtUrl = imgUrl }, + new Album { Title = "4x4=12 ", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["deadmau5"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Copland Celebration, Vol. I", Genre = genres["Classical"], Price = 8.99M, Artist = artists["London Symphony Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Lively Mind", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Paul Oakenfold"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Matter of Life and Death", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Real Dead One", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Real Live One", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Rush of Blood to the Head", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Coldplay"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Soprano Inspired", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Britten Sinfonia, Ivor Bolton & Lesley Garrett"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Winter Symphony", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Abbey Road", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Beatles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ace Of Spades", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Motörhead"], AlbumArtUrl = imgUrl }, + new Album { Title = "Achtung Baby", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Acústico MTV", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Os Paralamas Do Sucesso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Adams, John: The Chairman Dances", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Edo de Waart & San Francisco Symphony"], AlbumArtUrl = imgUrl }, + new Album { Title = "Adrenaline", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deftones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ænima", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "Afrociberdelia", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Chico Science & Nação Zumbi"], AlbumArtUrl = imgUrl }, + new Album { Title = "After the Goldrush", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Neil Young"], AlbumArtUrl = imgUrl }, + new Album { Title = "Airdrawn Dagger", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Sasha"], AlbumArtUrl = imgUrl }, + new Album { Title = "Album Title Goes Here", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["deadmau5"], AlbumArtUrl = imgUrl }, + new Album { Title = "Alcohol Fueled Brewtality Live! [Disc 1]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Label Society"], AlbumArtUrl = imgUrl }, + new Album { Title = "Alcohol Fueled Brewtality Live! [Disc 2]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Label Society"], AlbumArtUrl = imgUrl }, + new Album { Title = "Alive 2007", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Daft Punk"], AlbumArtUrl = imgUrl }, + new Album { Title = "All I Ask of You", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Amen (So Be It)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Paddy Casey"], AlbumArtUrl = imgUrl }, + new Album { Title = "Animal Vehicle", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Axis of Awesome"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ao Vivo [IMPORT]", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Zeca Pagodinho"], AlbumArtUrl = imgUrl }, + new Album { Title = "Apocalyptic Love", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Slash"], AlbumArtUrl = imgUrl }, + new Album { Title = "Appetite for Destruction", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Are You Experienced?", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Jimi Hendrix"], AlbumArtUrl = imgUrl }, + new Album { Title = "Arquivo II", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Os Paralamas Do Sucesso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Arquivo Os Paralamas Do Sucesso", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Os Paralamas Do Sucesso"], AlbumArtUrl = imgUrl }, + new Album { Title = "A-Sides", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Soundgarden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Audioslave", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Audioslave"], AlbumArtUrl = imgUrl }, + new Album { Title = "Automatic for the People", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["R.E.M."], AlbumArtUrl = imgUrl }, + new Album { Title = "Axé Bahia 2001", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Various Artists"], AlbumArtUrl = imgUrl }, + new Album { Title = "Babel", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Mumford & Sons"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: Goldberg Variations", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Wilhelm Kempff"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: The Brandenburg Concertos", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Orchestra of The Age of Enlightenment"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: The Cello Suites", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Yo-Yo Ma"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: Toccata & Fugue in D Minor", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Ton Koopman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bad Motorfinger", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Soundgarden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Balls to the Wall", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Accept"], AlbumArtUrl = imgUrl }, + new Album { Title = "Banadeek Ta'ala", Genre = genres["World"], Price = 8.99M, Artist = artists["Amr Diab"], AlbumArtUrl = imgUrl }, + new Album { Title = "Barbie Girl", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Aqua"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bark at the Moon (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bartok: Violin & Viola Concertos", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Yehudi Menuhin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Barulhinho Bom", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Marisa Monte"], AlbumArtUrl = imgUrl }, + new Album { Title = "BBC Sessions [Disc 1] [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "BBC Sessions [Disc 2] [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Be Here Now", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Oasis"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bedrock 11 Compiled & Mixed", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["John Digweed"], AlbumArtUrl = imgUrl }, + new Album { Title = "Berlioz: Symphonie Fantastique", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Michael Tilson Thomas"], AlbumArtUrl = imgUrl }, + new Album { Title = "Beyond Good And Evil", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Cult"], AlbumArtUrl = imgUrl }, + new Album { Title = "Big Bad Wolf ", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Armand Van Helden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Big Ones", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Aerosmith"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Album", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Sabbath Vol. 4 (Remaster)", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Sabbath"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Sabbath", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Sabbath"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blackwater Park", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Opeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blizzard of Ozz", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blood", Genre = genres["Rock"], Price = 8.99M, Artist = artists["In This Moment"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blue Moods", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Incognito"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blue", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Weezer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bongo Fury", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Frank Zappa & Captain Beefheart"], AlbumArtUrl = imgUrl }, + new Album { Title = "Boys & Girls", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alabama Shakes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Brave New World", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "B-Sides 1980-1990", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bunkka", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Paul Oakenfold"], AlbumArtUrl = imgUrl }, + new Album { Title = "By The Way", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Red Hot Chili Peppers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cake: B-Sides and Rarities", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Cake"], AlbumArtUrl = imgUrl }, + new Album { Title = "Californication", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Red Hot Chili Peppers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Carmina Burana", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Boston Symphony Orchestra & Seiji Ozawa"], AlbumArtUrl = imgUrl }, + new Album { Title = "Carried to Dust (Bonus Track Version)", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Calexico"], AlbumArtUrl = imgUrl }, + new Album { Title = "Carry On", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Chris Cornell"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cássia Eller - Sem Limite [Disc 1]", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Cássia Eller"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chemical Wedding", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Bruce Dickinson"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chill: Brazil (Disc 1)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Marcos Valle"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chill: Brazil (Disc 2)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Antônio Carlos Jobim"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chocolate Starfish And The Hot Dog Flavored Water", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Limp Bizkit"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chronicle, Vol. 1", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Creedence Clearwater Revival"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chronicle, Vol. 2", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Creedence Clearwater Revival"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ciao, Baby", Genre = genres["Rock"], Price = 8.99M, Artist = artists["TheStart"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cidade Negra - Hits", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Cidade Negra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Classic Munkle: Turbo Edition", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Munkle"], AlbumArtUrl = imgUrl }, + new Album { Title = "Classics: The Best of Sarah Brightman", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Coda", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Come Away With Me", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Norah Jones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Come Taste The Band", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Comfort Eagle", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Cake"], AlbumArtUrl = imgUrl }, + new Album { Title = "Common Reaction", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Uh Huh Her "], AlbumArtUrl = imgUrl }, + new Album { Title = "Compositores", Genre = genres["Rock"], Price = 8.99M, Artist = artists["O Terço"], AlbumArtUrl = imgUrl }, + new Album { Title = "Contraband", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Velvet Revolver"], AlbumArtUrl = imgUrl }, + new Album { Title = "Core", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Stone Temple Pilots"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cornerstone", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Styx"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cosmicolor", Genre = genres["Rap"], Price = 8.99M, Artist = artists["M-Flo"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cross", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Justice"], AlbumArtUrl = imgUrl }, + new Album { Title = "Culture of Fear", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Thievery Corporation"], AlbumArtUrl = imgUrl }, + new Album { Title = "Da Lama Ao Caos", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Chico Science & Nação Zumbi"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dakshina", Genre = genres["World"], Price = 8.99M, Artist = artists["Deva Premal"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dark Side of the Moon", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "Death Magnetic", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deep End of Down", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Above the Fold"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deep Purple In Rock", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deixa Entrar", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Falamansa"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deja Vu", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Crosby, Stills, Nash, and Young"], AlbumArtUrl = imgUrl }, + new Album { Title = "Di Korpu Ku Alma", Genre = genres["World"], Price = 8.99M, Artist = artists["Lura"], AlbumArtUrl = imgUrl }, + new Album { Title = "Diary of a Madman (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Diary of a Madman", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dirt", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alice in Chains"], AlbumArtUrl = imgUrl }, + new Album { Title = "Diver Down", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Djavan Ao Vivo - Vol. 02", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Djavan"], AlbumArtUrl = imgUrl }, + new Album { Title = "Djavan Ao Vivo - Vol. 1", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Djavan"], AlbumArtUrl = imgUrl }, + new Album { Title = "Drum'n'bass for Papa", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Plug"], AlbumArtUrl = imgUrl }, + new Album { Title = "Duluth", Genre = genres["Country"], Price = 8.99M, Artist = artists["Trampled By Turtles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dummy", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Portishead"], AlbumArtUrl = imgUrl }, + new Album { Title = "Duos II", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Luciana Souza/Romero Lubambo"], AlbumArtUrl = imgUrl }, + new Album { Title = "Earl Scruggs and Friends", Genre = genres["Country"], Price = 8.99M, Artist = artists["Earl Scruggs"], AlbumArtUrl = imgUrl }, + new Album { Title = "Eden", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "El Camino", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Black Keys"], AlbumArtUrl = imgUrl }, + new Album { Title = "Elegant Gypsy", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Al di Meola"], AlbumArtUrl = imgUrl }, + new Album { Title = "Elements Of Life", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Tiësto"], AlbumArtUrl = imgUrl }, + new Album { Title = "Elis Regina-Minha História", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Elis Regina"], AlbumArtUrl = imgUrl }, + new Album { Title = "Emergency On Planet Earth", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Jamiroquai"], AlbumArtUrl = imgUrl }, + new Album { Title = "Emotion", Genre = genres["World"], Price = 8.99M, Artist = artists["Papa Wemba"], AlbumArtUrl = imgUrl }, + new Album { Title = "English Renaissance", Genre = genres["Classical"], Price = 8.99M, Artist = artists["The King's Singers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Every Kind of Light", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Posies"], AlbumArtUrl = imgUrl }, + new Album { Title = "Faceless", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Godsmack"], AlbumArtUrl = imgUrl }, + new Album { Title = "Facelift", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alice in Chains"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fair Warning", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fear of a Black Planet", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Public Enemy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fear Of The Dark", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Feels Like Home", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Norah Jones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fireball", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fly", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "For Those About To Rock We Salute You", Genre = genres["Rock"], Price = 8.99M, Artist = artists["AC/DC"], AlbumArtUrl = imgUrl }, + new Album { Title = "Four", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Blues Traveler"], AlbumArtUrl = imgUrl }, + new Album { Title = "Frank", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Amy Winehouse"], AlbumArtUrl = imgUrl }, + new Album { Title = "Further Down the Spiral", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Nine Inch Nails"], AlbumArtUrl = imgUrl }, + new Album { Title = "Garage Inc. (Disc 1)", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Garage Inc. (Disc 2)", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Garbage", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Garbage"], AlbumArtUrl = imgUrl }, + new Album { Title = "Good News For People Who Love Bad News", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Modest Mouse"], AlbumArtUrl = imgUrl }, + new Album { Title = "Gordon", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Barenaked Ladies"], AlbumArtUrl = imgUrl }, + new Album { Title = "Górecki: Symphony No. 3", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Adrian Leaper & Doreen de Feis"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits II", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Duck Sauce"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Lenny Kravitz"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Lenny Kravitz"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Kiss", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Kiss"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greetings from Michigan", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Sufjan Stevens"], AlbumArtUrl = imgUrl }, + new Album { Title = "Group Therapy", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Above & Beyond"], AlbumArtUrl = imgUrl }, + new Album { Title = "Handel: The Messiah (Highlights)", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Scholars Baroque Ensemble"], AlbumArtUrl = imgUrl }, + new Album { Title = "Haydn: Symphonies 99 - 104", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Royal Philharmonic Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Heart of the Night", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Spyro Gyra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Heart On", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Eagles of Death Metal"], AlbumArtUrl = imgUrl }, + new Album { Title = "Holy Diver", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Dio"], AlbumArtUrl = imgUrl }, + new Album { Title = "Homework", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Daft Punk"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hot Rocks, 1964-1971 (Disc 1)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Rolling Stones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Houses Of The Holy", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "How To Dismantle An Atomic Bomb", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Human", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Projected"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hunky Dory", Genre = genres["Rock"], Price = 8.99M, Artist = artists["David Bowie"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hymns", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Projected"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hysteria", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Def Leppard"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Absentia", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Porcupine Tree"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Between", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Paul Van Dyk"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Rainbows", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Radiohead"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Step", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Stevie Ray Vaughan & Double Trouble"], AlbumArtUrl = imgUrl }, + new Album { Title = "In the court of the Crimson King", Genre = genres["Rock"], Price = 8.99M, Artist = artists["King Crimson"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Through The Out Door", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Your Honor [Disc 1]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Foo Fighters"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Your Honor [Disc 2]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Foo Fighters"], AlbumArtUrl = imgUrl }, + new Album { Title = "Indestructible", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Rancid"], AlbumArtUrl = imgUrl }, + new Album { Title = "Infinity", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Journey"], AlbumArtUrl = imgUrl }, + new Album { Title = "Into The Light", Genre = genres["Rock"], Price = 8.99M, Artist = artists["David Coverdale"], AlbumArtUrl = imgUrl }, + new Album { Title = "Introspective", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Pet Shop Boys"], AlbumArtUrl = imgUrl }, + new Album { Title = "Iron Maiden", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "ISAM", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Amon Tobin"], AlbumArtUrl = imgUrl }, + new Album { Title = "IV", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jagged Little Pill", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Alanis Morissette"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jagged Little Pill", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alanis Morissette"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jorge Ben Jor 25 Anos", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Jorge Ben"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jota Quest-1995", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Jota Quest"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kick", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["INXS"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kill 'Em All", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kind of Blue", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "King For A Day Fool For A Lifetime", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Faith No More"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kiss", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Carly Rae Jepsen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Last Call", Genre = genres["Country"], Price = 8.99M, Artist = artists["Cayouche"], AlbumArtUrl = imgUrl }, + new Album { Title = "Le Freak", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Chic"], AlbumArtUrl = imgUrl }, + new Album { Title = "Le Tigre", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Le Tigre"], AlbumArtUrl = imgUrl }, + new Album { Title = "Led Zeppelin I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Led Zeppelin II", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Led Zeppelin III", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Let There Be Rock", Genre = genres["Rock"], Price = 8.99M, Artist = artists["AC/DC"], AlbumArtUrl = imgUrl }, + new Album { Title = "Little Earthquakes", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Tori Amos"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live [Disc 1]", Genre = genres["Blues"], Price = 8.99M, Artist = artists["The Black Crowes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live [Disc 2]", Genre = genres["Blues"], Price = 8.99M, Artist = artists["The Black Crowes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live After Death", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live At Donington 1992 (Disc 1)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live At Donington 1992 (Disc 2)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live on Earth", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["The Cat Empire"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live On Two Legs [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Living After Midnight", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Judas Priest"], AlbumArtUrl = imgUrl }, + new Album { Title = "Living", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Paddy Casey"], AlbumArtUrl = imgUrl }, + new Album { Title = "Load", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Love Changes Everything", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "MacArthur Park Suite", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Donna Summer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Machine Head", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Magical Mystery Tour", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Beatles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mais Do Mesmo", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Legião Urbana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Maquinarama", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Skank"], AlbumArtUrl = imgUrl }, + new Album { Title = "Marasim", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Jagjit Singh"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mascagni: Cavalleria Rusticana", Genre = genres["Classical"], Price = 8.99M, Artist = artists["James Levine"], AlbumArtUrl = imgUrl }, + new Album { Title = "Master of Puppets", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mechanics & Mathematics", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Venus Hum"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mental Jewelry", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Live"], AlbumArtUrl = imgUrl }, + new Album { Title = "Metallics", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "meteora", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Linkin Park"], AlbumArtUrl = imgUrl }, + new Album { Title = "Meus Momentos", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Gonzaguinha"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mezmerize", Genre = genres["Metal"], Price = 8.99M, Artist = artists["System Of A Down"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mezzanine", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Massive Attack"], AlbumArtUrl = imgUrl }, + new Album { Title = "Miles Ahead", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "Milton Nascimento Ao Vivo", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Milton Nascimento"], AlbumArtUrl = imgUrl }, + new Album { Title = "Minas", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Milton Nascimento"], AlbumArtUrl = imgUrl }, + new Album { Title = "Minha Historia", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Chico Buarque"], AlbumArtUrl = imgUrl }, + new Album { Title = "Misplaced Childhood", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Marillion"], AlbumArtUrl = imgUrl }, + new Album { Title = "MK III The Final Concerts [Disc 1]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Morning Dance", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Spyro Gyra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Motley Crue Greatest Hits", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Mötley Crüe"], AlbumArtUrl = imgUrl }, + new Album { Title = "Moving Pictures", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Rush"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mozart: Chamber Music", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Nash Ensemble"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mozart: Symphonies Nos. 40 & 41", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Berliner Philharmoniker"], AlbumArtUrl = imgUrl }, + new Album { Title = "Murder Ballads", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Nick Cave and the Bad Seeds"], AlbumArtUrl = imgUrl }, + new Album { Title = "Music For The Jilted Generation", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["The Prodigy"], AlbumArtUrl = imgUrl }, + new Album { Title = "My Generation - The Very Best Of The Who", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Who"], AlbumArtUrl = imgUrl }, + new Album { Title = "My Name is Skrillex", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Skrillex"], AlbumArtUrl = imgUrl }, + new Album { Title = "Na Pista", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Cláudio Zoli"], AlbumArtUrl = imgUrl }, + new Album { Title = "Nevermind", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Nirvana"], AlbumArtUrl = imgUrl }, + new Album { Title = "New Adventures In Hi-Fi", Genre = genres["Rock"], Price = 8.99M, Artist = artists["R.E.M."], AlbumArtUrl = imgUrl }, + new Album { Title = "New Divide", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Linkin Park"], AlbumArtUrl = imgUrl }, + new Album { Title = "New York Dolls", Genre = genres["Punk"], Price = 8.99M, Artist = artists["New York Dolls"], AlbumArtUrl = imgUrl }, + new Album { Title = "News Of The World", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Nielsen: The Six Symphonies", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Göteborgs Symfoniker & Neeme Järvi"], AlbumArtUrl = imgUrl }, + new Album { Title = "Night At The Opera", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Night Castle", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Trans-Siberian Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Nkolo", Genre = genres["World"], Price = 8.99M, Artist = artists["Lokua Kanza"], AlbumArtUrl = imgUrl }, + new Album { Title = "No More Tears (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "No Prayer For The Dying", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "No Security", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Rolling Stones"], AlbumArtUrl = imgUrl }, + new Album { Title = "O Brother, Where Art Thou?", Genre = genres["Country"], Price = 8.99M, Artist = artists["Alison Krauss"], AlbumArtUrl = imgUrl }, + new Album { Title = "O Samba Poconé", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Skank"], AlbumArtUrl = imgUrl }, + new Album { Title = "O(+>", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Prince"], AlbumArtUrl = imgUrl }, + new Album { Title = "Oceania", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Smashing Pumpkins"], AlbumArtUrl = imgUrl }, + new Album { Title = "Off the Deep End", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Weird Al"], AlbumArtUrl = imgUrl }, + new Album { Title = "OK Computer", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Radiohead"], AlbumArtUrl = imgUrl }, + new Album { Title = "Olodum", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Olodum"], AlbumArtUrl = imgUrl }, + new Album { Title = "One Love", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["David Guetta"], AlbumArtUrl = imgUrl }, + new Album { Title = "Operation: Mindcrime", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Queensrÿche"], AlbumArtUrl = imgUrl }, + new Album { Title = "Opiate", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "Outbreak", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Dennis Chambers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pachelbel: Canon & Gigue", Genre = genres["Classical"], Price = 8.99M, Artist = artists["English Concert & Trevor Pinnock"], AlbumArtUrl = imgUrl }, + new Album { Title = "Paid in Full", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Eric B. and Rakim"], AlbumArtUrl = imgUrl }, + new Album { Title = "Para Siempre", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Vicente Fernandez"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pause", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Four Tet"], AlbumArtUrl = imgUrl }, + new Album { Title = "Peace Sells... but Who's Buying", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Megadeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Physical Graffiti [Disc 1]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Physical Graffiti [Disc 2]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Physical Graffiti", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Piece Of Mind", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pinkerton", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Weezer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Plays Metallica By Four Cellos", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Apocalyptica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pop", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Powerslave", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prenda Minha", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Caetano Veloso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Presence", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pretty Hate Machine", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Nine Inch Nails"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prisoner", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Jezabels"], AlbumArtUrl = imgUrl }, + new Album { Title = "Privateering", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Mark Knopfler"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prokofiev: Romeo & Juliet", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Michael Tilson Thomas"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prokofiev: Symphony No.1", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sergei Prokofiev & Yuri Temirkanov"], AlbumArtUrl = imgUrl }, + new Album { Title = "PSY's Best 6th Part 1", Genre = genres["Pop"], Price = 8.99M, Artist = artists["PSY"], AlbumArtUrl = imgUrl }, + new Album { Title = "Purcell: The Fairy Queen", Genre = genres["Classical"], Price = 8.99M, Artist = artists["London Classical Players"], AlbumArtUrl = imgUrl }, + new Album { Title = "Purpendicular", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Purple", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Stone Temple Pilots"], AlbumArtUrl = imgUrl }, + new Album { Title = "Quanta Gente Veio Ver (Live)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Gilberto Gil"], AlbumArtUrl = imgUrl }, + new Album { Title = "Quanta Gente Veio ver--Bônus De Carnaval", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Gilberto Gil"], AlbumArtUrl = imgUrl }, + new Album { Title = "Quiet Songs", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Aisha Duo"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raices", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Los Tigres del Norte"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raising Hell", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Run DMC"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raoul and the Kings of Spain ", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tears For Fears"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rattle And Hum", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raul Seixas", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Raul Seixas"], AlbumArtUrl = imgUrl }, + new Album { Title = "Recovery [Explicit]", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Eminem"], AlbumArtUrl = imgUrl }, + new Album { Title = "Reign In Blood", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Slayer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Relayed", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Yes"], AlbumArtUrl = imgUrl }, + new Album { Title = "ReLoad", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Respighi:Pines of Rome", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Eugene Ormandy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Restless and Wild", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Accept"], AlbumArtUrl = imgUrl }, + new Album { Title = "Retrospective I (1974-1980)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Rush"], AlbumArtUrl = imgUrl }, + new Album { Title = "Revelations", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Audioslave"], AlbumArtUrl = imgUrl }, + new Album { Title = "Revolver", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Beatles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ride the Lighting ", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ride The Lightning", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ring My Bell", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Anita Ward"], AlbumArtUrl = imgUrl }, + new Album { Title = "Riot Act", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rise of the Phoenix", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Before the Dawn"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rock In Rio [CD1]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rock In Rio [CD2]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rock In Rio [CD2]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Roda De Funk", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Funk Como Le Gusta"], AlbumArtUrl = imgUrl }, + new Album { Title = "Room for Squares", Genre = genres["Pop"], Price = 8.99M, Artist = artists["John Mayer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Root Down", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Jimmy Smith"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rounds", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Four Tet"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rubber Factory", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Black Keys"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rust in Peace", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Megadeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sambas De Enredo 2001", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Various Artists"], AlbumArtUrl = imgUrl }, + new Album { Title = "Santana - As Years Go By", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Santana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Santana Live", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Santana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Saturday Night Fever", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Bee Gees"], AlbumArtUrl = imgUrl }, + new Album { Title = "Scary Monsters and Nice Sprites", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Skrillex"], AlbumArtUrl = imgUrl }, + new Album { Title = "Scheherazade", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Chicago Symphony Orchestra & Fritz Reiner"], AlbumArtUrl = imgUrl }, + new Album { Title = "SCRIABIN: Vers la flamme", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Christopher O'Riley"], AlbumArtUrl = imgUrl }, + new Album { Title = "Second Coming", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Stone Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Serie Sem Limite (Disc 1)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Tim Maia"], AlbumArtUrl = imgUrl }, + new Album { Title = "Serie Sem Limite (Disc 2)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Tim Maia"], AlbumArtUrl = imgUrl }, + new Album { Title = "Serious About Men", Genre = genres["Rap"], Price = 8.99M, Artist = artists["The Rubberbandits"], AlbumArtUrl = imgUrl }, + new Album { Title = "Seventh Son of a Seventh Son", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Short Bus", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Filter"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sibelius: Finlandia", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Berliner Philharmoniker"], AlbumArtUrl = imgUrl }, + new Album { Title = "Singles Collection", Genre = genres["Rock"], Price = 8.99M, Artist = artists["David Bowie"], AlbumArtUrl = imgUrl }, + new Album { Title = "Six Degrees of Inner Turbulence", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Dream Theater"], AlbumArtUrl = imgUrl }, + new Album { Title = "Slave To The Empire", Genre = genres["Metal"], Price = 8.99M, Artist = artists["T&N"], AlbumArtUrl = imgUrl }, + new Album { Title = "Slaves And Masters", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Slouching Towards Bethlehem", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Robert James"], AlbumArtUrl = imgUrl }, + new Album { Title = "Smash", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Offspring"], AlbumArtUrl = imgUrl }, + new Album { Title = "Something Special", Genre = genres["Country"], Price = 8.99M, Artist = artists["Dolly Parton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Somewhere in Time", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Song(s) You Know By Heart", Genre = genres["Country"], Price = 8.99M, Artist = artists["Jimmy Buffett"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sound of Music", Genre = genres["Punk"], Price = 8.99M, Artist = artists["Adicts"], AlbumArtUrl = imgUrl }, + new Album { Title = "South American Getaway", Genre = genres["Classical"], Price = 8.99M, Artist = artists["The 12 Cellists of The Berlin Philharmonic"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sozinho Remix Ao Vivo", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Caetano Veloso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Speak of the Devil", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Spiritual State", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Nujabes"], AlbumArtUrl = imgUrl }, + new Album { Title = "St. Anger", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Still Life", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Opeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Stop Making Sense", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Talking Heads"], AlbumArtUrl = imgUrl }, + new Album { Title = "Stormbringer", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Stranger than Fiction", Genre = genres["Punk"], Price = 8.99M, Artist = artists["Bad Religion"], AlbumArtUrl = imgUrl }, + new Album { Title = "Strauss: Waltzes", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Eugene Ormandy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Supermodified", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Amon Tobin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Supernatural", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Santana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Surfing with the Alien (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Joe Satriani"], AlbumArtUrl = imgUrl }, + new Album { Title = "Switched-On Bach", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Wendy Carlos"], AlbumArtUrl = imgUrl }, + new Album { Title = "Symphony", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Szymanowski: Piano Works, Vol. 1", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Martin Roscoe"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tchaikovsky: The Nutcracker", Genre = genres["Classical"], Price = 8.99M, Artist = artists["London Symphony Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ted Nugent", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ted Nugent"], AlbumArtUrl = imgUrl }, + new Album { Title = "Teflon Don", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Rick Ross"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tell Another Joke at the Ol' Choppin' Block", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Danielson Famile"], AlbumArtUrl = imgUrl }, + new Album { Title = "Temple of the Dog", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Temple of the Dog"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ten", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Texas Flood", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Stevie Ray Vaughan"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Battle Rages On", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Beast Live", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Paul D'Ianno"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best Of 1980-1990", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best of 1990–2000", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best of Beethoven", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Nicolaus Esterhazy Sinfonia"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best Of Billy Cobham", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Billy Cobham"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best of Ed Motta", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Ed Motta"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best Of Van Halen, Vol. I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Bridge", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Melanie Fiona"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Cage", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tygers of Pan Tang"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Chicago Transit Authority", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Chicago "], AlbumArtUrl = imgUrl }, + new Album { Title = "The Chronic", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Dr. Dre"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Colour And The Shape", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Foo Fighters"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Crane Wife", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["The Decemberists"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Cream Of Clapton", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Eric Clapton"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Cure", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Cure"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Dark Side Of The Moon", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Divine Conspiracy", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Epica"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Doors", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Doors"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Dream of the Blue Turtles", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Sting"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Essential Miles Davis [Disc 1]", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Essential Miles Davis [Disc 2]", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Final Concerts (Disc 2)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Final Frontier", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Head and the Heart", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Head and the Heart"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Joshua Tree", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Last Night of the Proms", Genre = genres["Classical"], Price = 8.99M, Artist = artists["BBC Concert Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Lumineers", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Lumineers"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Number of The Beast", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Number of The Beast", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Police Greatest Hits", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Police"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Song Remains The Same (Disc 1)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Song Remains The Same (Disc 2)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Southern Harmony and Musical Companion", Genre = genres["Blues"], Price = 8.99M, Artist = artists["The Black Crowes"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Spade", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Butch Walker & The Black Widows"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Stone Roses", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Stone Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Suburbs", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Arcade Fire"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Three Tenors Disc1/Disc2", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Carreras, Pavarotti, Domingo"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Trees They Grow So High", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Wall", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "The X Factor", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Them Crooked Vultures", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Them Crooked Vultures"], AlbumArtUrl = imgUrl }, + new Album { Title = "This Is Happening", Genre = genres["Rock"], Price = 8.99M, Artist = artists["LCD Soundsystem"], AlbumArtUrl = imgUrl }, + new Album { Title = "Thunder, Lightning, Strike", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Go! Team"], AlbumArtUrl = imgUrl }, + new Album { Title = "Time to Say Goodbye", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Time, Love & Tenderness", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Michael Bolton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tomorrow Starts Today", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Mobile"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tribute", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tuesday Night Music Club", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Sheryl Crow"], AlbumArtUrl = imgUrl }, + new Album { Title = "Umoja", Genre = genres["Rock"], Price = 8.99M, Artist = artists["BLØF"], AlbumArtUrl = imgUrl }, + new Album { Title = "Under the Pink", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Tori Amos"], AlbumArtUrl = imgUrl }, + new Album { Title = "Undertow", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "Un-Led-Ed", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Dread Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Unplugged [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Kiss"], AlbumArtUrl = imgUrl }, + new Album { Title = "Unplugged", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Eric Clapton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Unplugged", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Eric Clapton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Untrue", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Burial"], AlbumArtUrl = imgUrl }, + new Album { Title = "Use Your Illusion I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Use Your Illusion II", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Use Your Illusion II", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Van Halen III", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Van Halen", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Version 2.0", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Garbage"], AlbumArtUrl = imgUrl }, + new Album { Title = "Vinicius De Moraes", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Vinícius De Moraes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Virtual XI", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Voodoo Lounge", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Rolling Stones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Vozes do MPB", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Various Artists"], AlbumArtUrl = imgUrl }, + new Album { Title = "Vs.", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wagner: Favourite Overtures", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sir Georg Solti & Wiener Philharmoniker"], AlbumArtUrl = imgUrl }, + new Album { Title = "Walking Into Clarksdale", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Page & Plant"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wapi Yo", Genre = genres["World"], Price = 8.99M, Artist = artists["Lokua Kanza"], AlbumArtUrl = imgUrl }, + new Album { Title = "War", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Warner 25 Anos", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Antônio Carlos Jobim"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wasteland R&Btheque", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Raunchy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Watermark", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Enya"], AlbumArtUrl = imgUrl }, + new Album { Title = "We Were Exploding Anyway", Genre = genres["Rock"], Price = 8.99M, Artist = artists["65daysofstatic"], AlbumArtUrl = imgUrl }, + new Album { Title = "Weill: The Seven Deadly Sins", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Orchestre de l'Opéra de Lyon"], AlbumArtUrl = imgUrl }, + new Album { Title = "White Pony", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deftones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Who's Next", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Who"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wish You Were Here", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "With Oden on Our Side", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Amon Amarth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Worlds", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Aaron Goldberg"], AlbumArtUrl = imgUrl }, + new Album { Title = "Worship Music", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Anthrax"], AlbumArtUrl = imgUrl }, + new Album { Title = "X&Y", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Coldplay"], AlbumArtUrl = imgUrl }, + new Album { Title = "Xinti", Genre = genres["World"], Price = 8.99M, Artist = artists["Sara Tavares"], AlbumArtUrl = imgUrl }, + new Album { Title = "Yano", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Yano"], AlbumArtUrl = imgUrl }, + new Album { Title = "Yesterday Once More Disc 1/Disc 2", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Carpenters"], AlbumArtUrl = imgUrl }, + new Album { Title = "Zooropa", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Zoso", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + }; + + foreach (var album in albums) + { + album.ArtistId = album.Artist.ArtistId; + album.GenreId = album.Genre.GenreId; + } + + return albums; + } + + private static Dictionary artists; + public static Dictionary Artists + { + get + { + if (artists == null) + { + var artistsList = new Artist[] + { + new Artist { Name = "65daysofstatic" }, + new Artist { Name = "Aaron Goldberg" }, + new Artist { Name = "Above & Beyond" }, + new Artist { Name = "Above the Fold" }, + new Artist { Name = "AC/DC" }, + new Artist { Name = "Accept" }, + new Artist { Name = "Adicts" }, + new Artist { Name = "Adrian Leaper & Doreen de Feis" }, + new Artist { Name = "Aerosmith" }, + new Artist { Name = "Aisha Duo" }, + new Artist { Name = "Al di Meola" }, + new Artist { Name = "Alabama Shakes" }, + new Artist { Name = "Alanis Morissette" }, + new Artist { Name = "Alberto Turco & Nova Schola Gregoriana" }, + new Artist { Name = "Alice in Chains" }, + new Artist { Name = "Alison Krauss" }, + new Artist { Name = "Amon Amarth" }, + new Artist { Name = "Amon Tobin" }, + new Artist { Name = "Amr Diab" }, + new Artist { Name = "Amy Winehouse" }, + new Artist { Name = "Anita Ward" }, + new Artist { Name = "Anthrax" }, + new Artist { Name = "Antônio Carlos Jobim" }, + new Artist { Name = "Apocalyptica" }, + new Artist { Name = "Aqua" }, + new Artist { Name = "Armand Van Helden" }, + new Artist { Name = "Arcade Fire" }, + new Artist { Name = "Audioslave" }, + new Artist { Name = "Bad Religion" }, + new Artist { Name = "Barenaked Ladies" }, + new Artist { Name = "BBC Concert Orchestra" }, + new Artist { Name = "Bee Gees" }, + new Artist { Name = "Before the Dawn" }, + new Artist { Name = "Berliner Philharmoniker" }, + new Artist { Name = "Billy Cobham" }, + new Artist { Name = "Black Label Society" }, + new Artist { Name = "Black Sabbath" }, + new Artist { Name = "BLØF" }, + new Artist { Name = "Blues Traveler" }, + new Artist { Name = "Boston Symphony Orchestra & Seiji Ozawa" }, + new Artist { Name = "Britten Sinfonia, Ivor Bolton & Lesley Garrett" }, + new Artist { Name = "Bruce Dickinson" }, + new Artist { Name = "Buddy Guy" }, + new Artist { Name = "Burial" }, + new Artist { Name = "Butch Walker & The Black Widows" }, + new Artist { Name = "Caetano Veloso" }, + new Artist { Name = "Cake" }, + new Artist { Name = "Calexico" }, + new Artist { Name = "Carly Rae Jepsen" }, + new Artist { Name = "Carreras, Pavarotti, Domingo" }, + new Artist { Name = "Cássia Eller" }, + new Artist { Name = "Cayouche" }, + new Artist { Name = "Chic" }, + new Artist { Name = "Chicago " }, + new Artist { Name = "Chicago Symphony Orchestra & Fritz Reiner" }, + new Artist { Name = "Chico Buarque" }, + new Artist { Name = "Chico Science & Nação Zumbi" }, + new Artist { Name = "Choir Of Westminster Abbey & Simon Preston" }, + new Artist { Name = "Chris Cornell" }, + new Artist { Name = "Christopher O'Riley" }, + new Artist { Name = "Cidade Negra" }, + new Artist { Name = "Cláudio Zoli" }, + new Artist { Name = "Coldplay" }, + new Artist { Name = "Creedence Clearwater Revival" }, + new Artist { Name = "Crosby, Stills, Nash, and Young" }, + new Artist { Name = "Daft Punk" }, + new Artist { Name = "Danielson Famile" }, + new Artist { Name = "David Bowie" }, + new Artist { Name = "David Coverdale" }, + new Artist { Name = "David Guetta" }, + new Artist { Name = "deadmau5" }, + new Artist { Name = "Deep Purple" }, + new Artist { Name = "Def Leppard" }, + new Artist { Name = "Deftones" }, + new Artist { Name = "Dennis Chambers" }, + new Artist { Name = "Deva Premal" }, + new Artist { Name = "Dio" }, + new Artist { Name = "Djavan" }, + new Artist { Name = "Dolly Parton" }, + new Artist { Name = "Donna Summer" }, + new Artist { Name = "Dr. Dre" }, + new Artist { Name = "Dread Zeppelin" }, + new Artist { Name = "Dream Theater" }, + new Artist { Name = "Duck Sauce" }, + new Artist { Name = "Earl Scruggs" }, + new Artist { Name = "Ed Motta" }, + new Artist { Name = "Edo de Waart & San Francisco Symphony" }, + new Artist { Name = "Elis Regina" }, + new Artist { Name = "Eminem" }, + new Artist { Name = "English Concert & Trevor Pinnock" }, + new Artist { Name = "Enya" }, + new Artist { Name = "Epica" }, + new Artist { Name = "Eric B. and Rakim" }, + new Artist { Name = "Eric Clapton" }, + new Artist { Name = "Eugene Ormandy" }, + new Artist { Name = "Faith No More" }, + new Artist { Name = "Falamansa" }, + new Artist { Name = "Filter" }, + new Artist { Name = "Foo Fighters" }, + new Artist { Name = "Four Tet" }, + new Artist { Name = "Frank Zappa & Captain Beefheart" }, + new Artist { Name = "Fretwork" }, + new Artist { Name = "Funk Como Le Gusta" }, + new Artist { Name = "Garbage" }, + new Artist { Name = "Gerald Moore" }, + new Artist { Name = "Gilberto Gil" }, + new Artist { Name = "Godsmack" }, + new Artist { Name = "Gonzaguinha" }, + new Artist { Name = "Göteborgs Symfoniker & Neeme Järvi" }, + new Artist { Name = "Guns N' Roses" }, + new Artist { Name = "Gustav Mahler" }, + new Artist { Name = "In This Moment" }, + new Artist { Name = "Incognito" }, + new Artist { Name = "INXS" }, + new Artist { Name = "Iron Maiden" }, + new Artist { Name = "Jagjit Singh" }, + new Artist { Name = "James Levine" }, + new Artist { Name = "Jamiroquai" }, + new Artist { Name = "Jimi Hendrix" }, + new Artist { Name = "Jimmy Buffett" }, + new Artist { Name = "Jimmy Smith" }, + new Artist { Name = "Joe Satriani" }, + new Artist { Name = "John Digweed" }, + new Artist { Name = "John Mayer" }, + new Artist { Name = "Jorge Ben" }, + new Artist { Name = "Jota Quest" }, + new Artist { Name = "Journey" }, + new Artist { Name = "Judas Priest" }, + new Artist { Name = "Julian Bream" }, + new Artist { Name = "Justice" }, + new Artist { Name = "Orchestre de l'Opéra de Lyon" }, + new Artist { Name = "King Crimson" }, + new Artist { Name = "Kiss" }, + new Artist { Name = "LCD Soundsystem" }, + new Artist { Name = "Le Tigre" }, + new Artist { Name = "Led Zeppelin" }, + new Artist { Name = "Legião Urbana" }, + new Artist { Name = "Lenny Kravitz" }, + new Artist { Name = "Les Arts Florissants & William Christie" }, + new Artist { Name = "Limp Bizkit" }, + new Artist { Name = "Linkin Park" }, + new Artist { Name = "Live" }, + new Artist { Name = "Lokua Kanza" }, + new Artist { Name = "London Symphony Orchestra" }, + new Artist { Name = "Los Tigres del Norte" }, + new Artist { Name = "Luciana Souza/Romero Lubambo" }, + new Artist { Name = "Lulu Santos" }, + new Artist { Name = "Lura" }, + new Artist { Name = "Marcos Valle" }, + new Artist { Name = "Marillion" }, + new Artist { Name = "Marisa Monte" }, + new Artist { Name = "Mark Knopfler" }, + new Artist { Name = "Martin Roscoe" }, + new Artist { Name = "Massive Attack" }, + new Artist { Name = "Maurizio Pollini" }, + new Artist { Name = "Megadeth" }, + new Artist { Name = "Mela Tenenbaum, Pro Musica Prague & Richard Kapp" }, + new Artist { Name = "Melanie Fiona" }, + new Artist { Name = "Men At Work" }, + new Artist { Name = "Metallica" }, + new Artist { Name = "M-Flo" }, + new Artist { Name = "Michael Bolton" }, + new Artist { Name = "Michael Tilson Thomas" }, + new Artist { Name = "Miles Davis" }, + new Artist { Name = "Milton Nascimento" }, + new Artist { Name = "Mobile" }, + new Artist { Name = "Modest Mouse" }, + new Artist { Name = "Mötley Crüe" }, + new Artist { Name = "Motörhead" }, + new Artist { Name = "Mumford & Sons" }, + new Artist { Name = "Munkle" }, + new Artist { Name = "Nash Ensemble" }, + new Artist { Name = "Neil Young" }, + new Artist { Name = "New York Dolls" }, + new Artist { Name = "Nick Cave and the Bad Seeds" }, + new Artist { Name = "Nicolaus Esterhazy Sinfonia" }, + new Artist { Name = "Nine Inch Nails" }, + new Artist { Name = "Nirvana" }, + new Artist { Name = "Norah Jones" }, + new Artist { Name = "Nujabes" }, + new Artist { Name = "O Terço" }, + new Artist { Name = "Oasis" }, + new Artist { Name = "Olodum" }, + new Artist { Name = "Opeth" }, + new Artist { Name = "Orchestra of The Age of Enlightenment" }, + new Artist { Name = "Os Paralamas Do Sucesso" }, + new Artist { Name = "Ozzy Osbourne" }, + new Artist { Name = "Paddy Casey" }, + new Artist { Name = "Page & Plant" }, + new Artist { Name = "Papa Wemba" }, + new Artist { Name = "Paul D'Ianno" }, + new Artist { Name = "Paul Oakenfold" }, + new Artist { Name = "Paul Van Dyk" }, + new Artist { Name = "Pearl Jam" }, + new Artist { Name = "Pet Shop Boys" }, + new Artist { Name = "Pink Floyd" }, + new Artist { Name = "Plug" }, + new Artist { Name = "Porcupine Tree" }, + new Artist { Name = "Portishead" }, + new Artist { Name = "Prince" }, + new Artist { Name = "Projected" }, + new Artist { Name = "PSY" }, + new Artist { Name = "Public Enemy" }, + new Artist { Name = "Queen" }, + new Artist { Name = "Queensrÿche" }, + new Artist { Name = "R.E.M." }, + new Artist { Name = "Radiohead" }, + new Artist { Name = "Rancid" }, + new Artist { Name = "Raul Seixas" }, + new Artist { Name = "Raunchy" }, + new Artist { Name = "Red Hot Chili Peppers" }, + new Artist { Name = "Rick Ross" }, + new Artist { Name = "Robert James" }, + new Artist { Name = "London Classical Players" }, + new Artist { Name = "Royal Philharmonic Orchestra" }, + new Artist { Name = "Run DMC" }, + new Artist { Name = "Rush" }, + new Artist { Name = "Santana" }, + new Artist { Name = "Sara Tavares" }, + new Artist { Name = "Sarah Brightman" }, + new Artist { Name = "Sasha" }, + new Artist { Name = "Scholars Baroque Ensemble" }, + new Artist { Name = "Scorpions" }, + new Artist { Name = "Sergei Prokofiev & Yuri Temirkanov" }, + new Artist { Name = "Sheryl Crow" }, + new Artist { Name = "Sir Georg Solti & Wiener Philharmoniker" }, + new Artist { Name = "Skank" }, + new Artist { Name = "Skrillex" }, + new Artist { Name = "Slash" }, + new Artist { Name = "Slayer" }, + new Artist { Name = "Soul-Junk" }, + new Artist { Name = "Soundgarden" }, + new Artist { Name = "Spyro Gyra" }, + new Artist { Name = "Stevie Ray Vaughan & Double Trouble" }, + new Artist { Name = "Stevie Ray Vaughan" }, + new Artist { Name = "Sting" }, + new Artist { Name = "Stone Temple Pilots" }, + new Artist { Name = "Styx" }, + new Artist { Name = "Sufjan Stevens" }, + new Artist { Name = "Supreme Beings of Leisure" }, + new Artist { Name = "System Of A Down" }, + new Artist { Name = "T&N" }, + new Artist { Name = "Talking Heads" }, + new Artist { Name = "Tears For Fears" }, + new Artist { Name = "Ted Nugent" }, + new Artist { Name = "Temple of the Dog" }, + new Artist { Name = "Terry Bozzio, Tony Levin & Steve Stevens" }, + new Artist { Name = "The 12 Cellists of The Berlin Philharmonic" }, + new Artist { Name = "The Axis of Awesome" }, + new Artist { Name = "The Beatles" }, + new Artist { Name = "The Black Crowes" }, + new Artist { Name = "The Black Keys" }, + new Artist { Name = "The Carpenters" }, + new Artist { Name = "The Cat Empire" }, + new Artist { Name = "The Cult" }, + new Artist { Name = "The Cure" }, + new Artist { Name = "The Decemberists" }, + new Artist { Name = "The Doors" }, + new Artist { Name = "The Eagles of Death Metal" }, + new Artist { Name = "The Go! Team" }, + new Artist { Name = "The Head and the Heart" }, + new Artist { Name = "The Jezabels" }, + new Artist { Name = "The King's Singers" }, + new Artist { Name = "The Lumineers" }, + new Artist { Name = "The Offspring" }, + new Artist { Name = "The Police" }, + new Artist { Name = "The Posies" }, + new Artist { Name = "The Prodigy" }, + new Artist { Name = "The Rolling Stones" }, + new Artist { Name = "The Rubberbandits" }, + new Artist { Name = "The Smashing Pumpkins" }, + new Artist { Name = "The Stone Roses" }, + new Artist { Name = "The Who" }, + new Artist { Name = "Them Crooked Vultures" }, + new Artist { Name = "TheStart" }, + new Artist { Name = "Thievery Corporation" }, + new Artist { Name = "Tiësto" }, + new Artist { Name = "Tim Maia" }, + new Artist { Name = "Ton Koopman" }, + new Artist { Name = "Tool" }, + new Artist { Name = "Tori Amos" }, + new Artist { Name = "Trampled By Turtles" }, + new Artist { Name = "Trans-Siberian Orchestra" }, + new Artist { Name = "Tygers of Pan Tang" }, + new Artist { Name = "U2" }, + new Artist { Name = "UB40" }, + new Artist { Name = "Uh Huh Her " }, + new Artist { Name = "Van Halen" }, + new Artist { Name = "Various Artists" }, + new Artist { Name = "Velvet Revolver" }, + new Artist { Name = "Venus Hum" }, + new Artist { Name = "Vicente Fernandez" }, + new Artist { Name = "Vinícius De Moraes" }, + new Artist { Name = "Weezer" }, + new Artist { Name = "Weird Al" }, + new Artist { Name = "Wendy Carlos" }, + new Artist { Name = "Wilhelm Kempff" }, + new Artist { Name = "Yano" }, + new Artist { Name = "Yehudi Menuhin" }, + new Artist { Name = "Yes" }, + new Artist { Name = "Yo-Yo Ma" }, + new Artist { Name = "Zeca Pagodinho" }, + new Artist { Name = "אריק אינשטיין"} + }; + + // TODO [EF] Swap to store generated keys when available + int artistId = 1; + artists = new Dictionary(); + foreach (Artist artist in artistsList) + { + artist.ArtistId = artistId++; + artists.Add(artist.Name, artist); + } + } + + return artists; + } + } + + private static Dictionary genres; + public static Dictionary Genres + { + get + { + if (genres == null) + { + var genresList = new Genre[] + { + new Genre { Name = "Pop" }, + new Genre { Name = "Rock" }, + new Genre { Name = "Jazz" }, + new Genre { Name = "Metal" }, + new Genre { Name = "Electronic" }, + new Genre { Name = "Blues" }, + new Genre { Name = "Latin" }, + new Genre { Name = "Rap" }, + new Genre { Name = "Classical" }, + new Genre { Name = "Alternative" }, + new Genre { Name = "Country" }, + new Genre { Name = "R&B" }, + new Genre { Name = "Indie" }, + new Genre { Name = "Punk" }, + new Genre { Name = "World" } + }; + + genres = new Dictionary(); + // TODO [EF] Swap to store generated keys when available + int genreId = 1; + foreach (Genre genre in genresList) + { + genre.GenreId = genreId++; + + // TODO [EF] Remove when null values are supported by update pipeline + genre.Description = genre.Name + " is great music (if you like it)."; + + genres.Add(genre.Name, genre); + } + } + + return genres; + } + } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Apis/Models/ShoppingCart.cs b/samples/react/MusicStore/Apis/Models/ShoppingCart.cs new file mode 100644 index 0000000..41b3277 --- /dev/null +++ b/samples/react/MusicStore/Apis/Models/ShoppingCart.cs @@ -0,0 +1,207 @@ +using Microsoft.AspNet.Http; +using Microsoft.Data.Entity; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MusicStore.Models +{ + public partial class ShoppingCart + { + MusicStoreContext _db; + string ShoppingCartId { get; set; } + + public ShoppingCart(MusicStoreContext db) + { + _db = db; + } + + public static ShoppingCart GetCart(MusicStoreContext db, HttpContext context) + { + var cart = new ShoppingCart(db); + cart.ShoppingCartId = cart.GetCartId(context); + return cart; + } + + public void AddToCart(Album album) + { + // Get the matching cart and album instances + var cartItem = _db.CartItems.SingleOrDefault( + c => c.CartId == ShoppingCartId + && c.AlbumId == album.AlbumId); + + if (cartItem == null) + { + // TODO [EF] Swap to store generated key once we support identity pattern + var nextCartItemId = _db.CartItems.Any() + ? _db.CartItems.Max(c => c.CartItemId) + 1 + : 1; + + // Create a new cart item if no cart item exists + cartItem = new CartItem + { + CartItemId = nextCartItemId, + AlbumId = album.AlbumId, + CartId = ShoppingCartId, + Count = 1, + DateCreated = DateTime.Now + }; + + _db.CartItems.Add(cartItem); + } + else + { + // If the item does exist in the cart, then add one to the quantity + cartItem.Count++; + + // TODO [EF] Remove this line once change detection is available + _db.Update(cartItem); + } + } + + public int RemoveFromCart(int id) + { + // Get the cart + var cartItem = _db.CartItems.Single( + cart => cart.CartId == ShoppingCartId + && cart.CartItemId == id); + + int itemCount = 0; + + if (cartItem != null) + { + if (cartItem.Count > 1) + { + cartItem.Count--; + + // TODO [EF] Remove this line once change detection is available + _db.Update(cartItem); + + itemCount = cartItem.Count; + } + else + { + _db.CartItems.Remove(cartItem); + } + } + + return itemCount; + } + + public void EmptyCart() + { + var cartItems = _db.CartItems.Where(cart => cart.CartId == ShoppingCartId); + + foreach (var cartItem in cartItems) + { + _db.Remove(cartItem); + } + } + + public List GetCartItems() + { + var cartItems = _db.CartItems.Where(cart => cart.CartId == ShoppingCartId).ToList(); + //TODO: Auto population of the related album data not available until EF feature is lighted up. + foreach (var cartItem in cartItems) + { + cartItem.Album = _db.Albums.Single(a => a.AlbumId == cartItem.AlbumId); + } + + return cartItems; + } + + public int GetCount() + { + // Get the count of each item in the cart and sum them up + int? count = (from cartItems in _db.CartItems + where cartItems.CartId == ShoppingCartId + select (int?)cartItems.Count).Sum(); + + // Return 0 if all entries are null + return count ?? 0; + } + + public decimal GetTotal() + { + // Multiply album price by count of that album to get + // the current price for each of those albums in the cart + // sum all album price totals to get the cart total + + // TODO Collapse to a single query once EF supports querying related data + decimal total = 0; + foreach (var item in _db.CartItems.Where(c => c.CartId == ShoppingCartId)) + { + var album = _db.Albums.Single(a => a.AlbumId == item.AlbumId); + total += item.Count * album.Price; + } + + return total; + } + + public int CreateOrder(Order order) + { + decimal orderTotal = 0; + + var cartItems = GetCartItems(); + + // TODO [EF] Swap to store generated identity key when supported + var nextId = _db.OrderDetails.Any() + ? _db.OrderDetails.Max(o => o.OrderDetailId) + 1 + : 1; + + // Iterate over the items in the cart, adding the order details for each + foreach (var item in cartItems) + { + //var album = _db.Albums.Find(item.AlbumId); + var album = _db.Albums.Single(a => a.AlbumId == item.AlbumId); + + var orderDetail = new OrderDetail + { + OrderDetailId = nextId, + AlbumId = item.AlbumId, + OrderId = order.OrderId, + UnitPrice = album.Price, + Quantity = item.Count, + }; + + // Set the order total of the shopping cart + orderTotal += (item.Count * album.Price); + + _db.OrderDetails.Add(orderDetail); + + nextId++; + } + + // Set the order's total to the orderTotal count + order.Total = orderTotal; + + // Empty the shopping cart + EmptyCart(); + + // Return the OrderId as the confirmation number + return order.OrderId; + } + + // We're using HttpContextBase to allow access to cookies. + public string GetCartId(HttpContext context) + { + var sessionCookie = context.Request.Cookies["Session"]; + string cartId = null; + + if (string.IsNullOrWhiteSpace(sessionCookie)) + { + //A GUID to hold the cartId. + cartId = Guid.NewGuid().ToString(); + + // Send cart Id as a cookie to the client. + context.Response.Cookies.Append("Session", cartId); + } + else + { + cartId = sessionCookie; + } + + return cartId; + } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Controllers/HomeController.cs b/samples/react/MusicStore/Controllers/HomeController.cs new file mode 100755 index 0000000..d6a55c6 --- /dev/null +++ b/samples/react/MusicStore/Controllers/HomeController.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc; + +namespace MusicStore.Controllers +{ + public class HomeController : Controller + { + public IActionResult Index() + { + return View(); + } + } +} diff --git a/samples/react/MusicStore/Infrastructure/NoCacheAttribute.cs b/samples/react/MusicStore/Infrastructure/NoCacheAttribute.cs new file mode 100644 index 0000000..7c0a9cd --- /dev/null +++ b/samples/react/MusicStore/Infrastructure/NoCacheAttribute.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNet.Mvc; +using System; +using Microsoft.AspNet.Mvc.Filters; + +namespace MusicStore.Infrastructure +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class NoCacheAttribute : ActionFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext context) + { + context.HttpContext.Response.Headers["Cache-Control"] = "no-cache, no-store, max-age=0"; + context.HttpContext.Response.Headers["Pragma"] = "no-cache"; + context.HttpContext.Response.Headers["Expires"] = "-1"; + + base.OnResultExecuting(context); + } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Infrastructure/PagedList.cs b/samples/react/MusicStore/Infrastructure/PagedList.cs new file mode 100644 index 0000000..98c5b13 --- /dev/null +++ b/samples/react/MusicStore/Infrastructure/PagedList.cs @@ -0,0 +1,150 @@ +using Microsoft.Data.Entity; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace MusicStore.Infrastructure +{ + public interface IPagedList + { + IEnumerable Data { get; } + + int Page { get; } + + int PageSize { get; } + + int TotalCount { get; } + } + + internal class PagedList : IPagedList + { + public PagedList(IEnumerable data, int page, int pageSize, int totalCount) + { + Data = data; + Page = page; + PageSize = pageSize; + TotalCount = totalCount; + } + + public IEnumerable Data { get; private set; } + + public int Page { get; private set; } + + public int PageSize { get; private set; } + + public int TotalCount { get; private set; } + } + + public static class PagedListExtensions + { + public static IPagedList ToPagedList(this IQueryable query, int page, int pageSize) + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + var pagingConfig = new PagingConfig(page, pageSize); + var skipCount = ValidatePagePropertiesAndGetSkipCount(pagingConfig); + + var data = query + .Skip(skipCount) + .Take(pagingConfig.PageSize) + .ToList(); + + if (skipCount > 0 && data.Count == 0) + { + // Requested page has no records, just return the first page + pagingConfig.Page = 1; + data = query + .Take(pagingConfig.PageSize) + .ToList(); + } + + return new PagedList(data, pagingConfig.Page, pagingConfig.PageSize, query.Count()); + } + + public static Task> ToPagedListAsync(this IQueryable query, int page, int pageSize, string sortExpression, Expression> defaultSortExpression, SortDirection defaultSortDirection = SortDirection.Ascending) + where TModel : class + { + return ToPagedListAsync(query, page, pageSize, sortExpression, defaultSortExpression, defaultSortDirection, null); + } + + public static async Task> ToPagedListAsync(this IQueryable query, int page, int pageSize, string sortExpression, Expression> defaultSortExpression, SortDirection defaultSortDirection, Func selector) + where TModel : class + where TResult : class + { + if (query == null) + { + throw new ArgumentNullException("query"); + } + + var pagingConfig = new PagingConfig(page, pageSize); + var skipCount = ValidatePagePropertiesAndGetSkipCount(pagingConfig); + var dataQuery = query; + + if (defaultSortExpression != null) + { + dataQuery = dataQuery + .SortBy(sortExpression, defaultSortExpression); + } + + var data = await dataQuery + .Skip(skipCount) + .Take(pagingConfig.PageSize) + .ToListAsync(); + + if (skipCount > 0 && data.Count == 0) + { + // Requested page has no records, just return the first page + pagingConfig.Page = 1; + data = await dataQuery + .Take(pagingConfig.PageSize) + .ToListAsync(); + } + + var count = await query.CountAsync(); + + var resultData = selector != null + ? data.Select(selector) + : data.Cast(); + + return new PagedList(resultData, pagingConfig.Page, pagingConfig.PageSize, count); + } + + private static int ValidatePagePropertiesAndGetSkipCount(PagingConfig pagingConfig) + { + if (pagingConfig.Page < 1) + { + pagingConfig.Page = 1; + } + + if (pagingConfig.PageSize < 10) + { + pagingConfig.PageSize = 10; + } + + if (pagingConfig.PageSize > 100) + { + pagingConfig.PageSize = 100; + } + + return pagingConfig.PageSize * (pagingConfig.Page - 1); + } + + internal class PagingConfig + { + public PagingConfig(int page, int pageSize) + { + Page = page; + PageSize = pageSize; + } + + public int Page { get; set; } + + public int PageSize { get; set; } + } + } +} \ No newline at end of file diff --git a/samples/react/MusicStore/Infrastructure/SortDirection.cs b/samples/react/MusicStore/Infrastructure/SortDirection.cs new file mode 100644 index 0000000..28f7e86 --- /dev/null +++ b/samples/react/MusicStore/Infrastructure/SortDirection.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MusicStore.Infrastructure +{ + public enum SortDirection + { + Ascending, + Descending + } +} diff --git a/samples/react/MusicStore/Infrastructure/SortExpression.cs b/samples/react/MusicStore/Infrastructure/SortExpression.cs new file mode 100644 index 0000000..279efb7 --- /dev/null +++ b/samples/react/MusicStore/Infrastructure/SortExpression.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.ViewFeatures; + +namespace MusicStore.Infrastructure +{ + public static class SortExpression + { + private const string SORT_DIRECTION_DESC = " DESC"; + + public static IQueryable SortBy(this IQueryable query, string sortExpression, Expression> defaultSortExpression, SortDirection defaultSortDirection = SortDirection.Ascending) where TModel : class + { + return SortBy(query, sortExpression ?? Create(defaultSortExpression, defaultSortDirection)); + } + + public static string Create(Expression> expression, SortDirection sortDirection = SortDirection.Ascending) where TModel : class + { + var expressionText = ExpressionHelper.GetExpressionText(expression); + // TODO: Validate the expression depth, etc. + + var sortExpression = expressionText; + + if (sortDirection == SortDirection.Descending) + { + sortExpression += SORT_DIRECTION_DESC; + } + + return sortExpression; + } + + public static IQueryable SortBy(this IQueryable source, string sortExpression) where T : class + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + + if (String.IsNullOrWhiteSpace(sortExpression)) + { + return source; + } + + sortExpression = sortExpression.Trim(); + var isDescending = false; + + // DataSource control passes the sort parameter with a direction + // if the direction is descending + if (sortExpression.EndsWith(SORT_DIRECTION_DESC, StringComparison.OrdinalIgnoreCase)) + { + isDescending = true; + var descIndex = sortExpression.Length - SORT_DIRECTION_DESC.Length; + sortExpression = sortExpression.Substring(0, descIndex).Trim(); + } + + if (string.IsNullOrEmpty(sortExpression)) + { + return source; + } + + ParameterExpression parameter = Expression.Parameter(source.ElementType, String.Empty); + + // Build up the property expression, e.g.: (m => m.Foo.Bar) + var sortExpressionParts = sortExpression.Split('.'); + Expression propertyExpression = parameter; + foreach (var property in sortExpressionParts) + { + propertyExpression = Expression.Property(propertyExpression, property); + } + + LambdaExpression lambda = Expression.Lambda(propertyExpression, parameter); + + var methodName = (isDescending) ? "OrderByDescending" : "OrderBy"; + + Expression methodCallExpression = Expression.Call( + typeof(Queryable), + methodName, + new[] { source.ElementType, propertyExpression.Type }, + source.Expression, + Expression.Quote(lambda)); + + return (IQueryable)source.Provider.CreateQuery(methodCallExpression); + } + } +} diff --git a/samples/react/MusicStore/ReactApp/TypedRedux.ts b/samples/react/MusicStore/ReactApp/TypedRedux.ts new file mode 100644 index 0000000..8da9487 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/TypedRedux.ts @@ -0,0 +1,62 @@ +// Credits for the type detection trick: http://www.bluewire-technologies.com/2015/redux-actions-for-typescript/ +import * as React from 'react'; +import { Dispatch } from 'redux'; +import { connect as nativeConnect, ElementClass } from 'react-redux'; + +interface ActionClass { + prototype: T; +} + +export function typeName(name: string) { + return function(actionClass: ActionClass) { + // Although we could determine the type name using actionClass.prototype.constructor.name, + // it's dangerous to do that because minifiers may interfere with it, and then serialized state + // might not have the expected meaning after a recompile. So we explicitly ask for a name string. + actionClass.prototype.type = name; + } +} + +export function isActionType(action: Action, actionClass: ActionClass): action is T { + return action.type == actionClass.prototype.type; +} + +export abstract class Action { + type: string; + constructor() { + // Make it an own-property (not a prototype property) so that it's included when JSON-serializing + this.type = this.type; + } +} + +export interface Reducer extends Function { + (state: TState, action: Action): TState; +} + +export interface ActionCreatorGeneric extends Function { + (dispatch: Dispatch, getState: () => TState): any; +} + +interface ClassDecoratorWithProps extends Function { + (component: T): T; + props: TProps; +} + +type ReactComponentClass = new(props: T) => React.Component; +class ComponentBuilder { + constructor(private stateToProps: (appState: any) => TOwnProps, private actionCreators: TActions) { + } + + public withExternalProps() { + return this as any as ComponentBuilder; + } + + public get allProps(): TOwnProps & TActions & TExternalProps { return null; } + + public connect(componentClass: ReactComponentClass): ReactComponentClass { + return nativeConnect(this.stateToProps, this.actionCreators as any)(componentClass); + } +} + +export function provide(stateToProps: (appState: any) => TOwnProps, actionCreators: TActions) { + return new ComponentBuilder(stateToProps, actionCreators); +} diff --git a/samples/react/MusicStore/ReactApp/boot.tsx b/samples/react/MusicStore/ReactApp/boot.tsx new file mode 100644 index 0000000..049a807 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/boot.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { browserHistory } from 'react-router'; +import { Provider } from 'react-redux'; +React; // Need this reference otherwise TypeScript doesn't think we're using it and ignores the import + +import './styles/styles.css'; +import 'bootstrap/dist/css/bootstrap.css'; +import configureStore from './configureStore'; +import { App } from './components/App'; + +const store = configureStore(browserHistory); + +ReactDOM.render( + + + , + document.getElementById('react-app') +); diff --git a/samples/react/MusicStore/ReactApp/components/App.tsx b/samples/react/MusicStore/ReactApp/components/App.tsx new file mode 100644 index 0000000..6a69415 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/components/App.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { Router, Route, HistoryBase } from 'react-router'; +import NavMenu from './NavMenu'; +import Home from './public/Home'; +import Genres from './public/Genres'; +import GenreDetails from './public/GenreDetails'; +import AlbumDetails from './public/AlbumDetails'; + +export interface AppProps { + history: HistoryBase; +} + +export class App extends React.Component { + public render() { + return ( + + + + + + + + + ); + } +} + +class Layout extends React.Component<{ body: React.ReactElement }, void> { + public render() { + return
+ +
+ { this.props.body } +
+
; + } +} diff --git a/samples/react/MusicStore/ReactApp/components/NavMenu.tsx b/samples/react/MusicStore/ReactApp/components/NavMenu.tsx new file mode 100644 index 0000000..372677b --- /dev/null +++ b/samples/react/MusicStore/ReactApp/components/NavMenu.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap'; +import { Link } from 'react-router'; +import { LinkContainer } from 'react-router-bootstrap'; +import { provide } from '../TypedRedux'; +import { ApplicationState } from '../store'; +import * as GenreList from '../store/GenreList'; + +class NavMenu extends React.Component { + componentWillMount() { + this.props.requestGenresList(); + } + + public render() { + var genres = this.props.genres.slice(0, 5); + return ( + + + Music Store + + + + + + + ); + } +} + +// Selects which part of global state maps to this component, and defines a type for the resulting props +const provider = provide( + (state: ApplicationState) => state.genreList, + GenreList.actionCreators +); +type NavMenuProps = typeof provider.allProps; +export default provider.connect(NavMenu); diff --git a/samples/react/MusicStore/ReactApp/components/public/AlbumDetails.tsx b/samples/react/MusicStore/ReactApp/components/public/AlbumDetails.tsx new file mode 100644 index 0000000..16c7a33 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/components/public/AlbumDetails.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { Link } from 'react-router'; +import { provide } from '../../TypedRedux'; +import { ApplicationState } from '../../store'; +import * as AlbumDetailsState from '../../store/AlbumDetails'; + +interface RouteParams { + albumId: number; +} + +class AlbumDetails extends React.Component { + componentWillMount() { + this.props.requestAlbumDetails(this.props.params.albumId); + } + + componentWillReceiveProps(nextProps: AlbumDetailsProps) { + if (nextProps.params.albumId !== this.props.params.albumId) { + nextProps.requestAlbumDetails(nextProps.params.albumId); + } + } + + public render() { + if (this.props.isLoaded) { + const albumData = this.props.album; + return
+

{ albumData.Title }

+ +

{

+ +
+

+ Genre: + { albumData.Genre.Name } +

+

+ Artist: + { albumData.Artist.Name } +

+

+ Price: + ${ albumData.Price.toFixed(2) } +

+

+ Add to cart +

+
+
; + } else { + return

Loading...

; + } + } +} + +// Selects which part of global state maps to this component, and defines a type for the resulting props +const provider = provide( + (state: ApplicationState) => state.albumDetails, + AlbumDetailsState.actionCreators +).withExternalProps<{ params: RouteParams }>(); +type AlbumDetailsProps = typeof provider.allProps; +export default provider.connect(AlbumDetails); diff --git a/samples/react/MusicStore/ReactApp/components/public/AlbumTile.tsx b/samples/react/MusicStore/ReactApp/components/public/AlbumTile.tsx new file mode 100644 index 0000000..d93af5d --- /dev/null +++ b/samples/react/MusicStore/ReactApp/components/public/AlbumTile.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { Link } from 'react-router'; +import { Album } from '../../store/FeaturedAlbums'; + +export class AlbumTile extends React.Component<{ album: Album, key?: any }, void> { + public render() { + const { album } = this.props; + return ( +
  • + + { +

    { album.Title }

    + +
  • + ); + } +} diff --git a/samples/react/MusicStore/ReactApp/components/public/GenreDetails.tsx b/samples/react/MusicStore/ReactApp/components/public/GenreDetails.tsx new file mode 100644 index 0000000..4fb83e2 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/components/public/GenreDetails.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import { Link } from 'react-router'; +import { provide } from '../../TypedRedux'; +import { ApplicationState } from '../../store'; +import * as GenreDetailsStore from '../../store/GenreDetails'; +import { AlbumTile } from './AlbumTile'; + +interface RouteParams { + genreId: number +} + +class GenreDetails extends React.Component { + componentWillMount() { + this.props.requestGenreDetails(this.props.params.genreId); + } + + componentWillReceiveProps(nextProps: GenreDetailsProps) { + if (nextProps.params.genreId !== this.props.params.genreId) { + nextProps.requestGenreDetails(nextProps.params.genreId); + } + } + + public render() { + if (this.props.isLoaded) { + let albums = this.props.albums; + return
    +

    Albums

    + +
      + {albums.map(album => + + )} +
    +
    ; + } else { + return

    Loading...

    ; + } + } +} + +// Selects which part of global state maps to this component, and defines a type for the resulting props +const provider = provide( + (state: ApplicationState) => state.genreDetails, + GenreDetailsStore.actionCreators +).withExternalProps<{ params: RouteParams }>(); + +type GenreDetailsProps = typeof provider.allProps; +export default provider.connect(GenreDetails); diff --git a/samples/react/MusicStore/ReactApp/components/public/Genres.tsx b/samples/react/MusicStore/ReactApp/components/public/Genres.tsx new file mode 100644 index 0000000..f8b47c3 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/components/public/Genres.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { Link } from 'react-router'; +import { provide } from '../../TypedRedux'; +import { ApplicationState } from '../../store'; +import * as GenreList from '../../store/GenreList'; + +class Genres extends React.Component { + componentWillMount() { + if (!this.props.genres.length) { + this.props.requestGenresList(); + } + } + + public render() { + let { genres } = this.props; + + return
    +

    Browse Genres

    + +

    Select from { genres.length || '...' } genres:

    + +
      + {genres.map(genre => +
    • + + { genre.Name } + +
    • + )} +
    +
    ; + } +} + +// Selects which part of global state maps to this component, and defines a type for the resulting props +const provider = provide( + (state: ApplicationState) => state.genreList, + GenreList.actionCreators +); +type GenresProps = typeof provider.allProps; +export default provider.connect(Genres); diff --git a/samples/react/MusicStore/ReactApp/components/public/Home.tsx b/samples/react/MusicStore/ReactApp/components/public/Home.tsx new file mode 100644 index 0000000..9f8d1eb --- /dev/null +++ b/samples/react/MusicStore/ReactApp/components/public/Home.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { Link } from 'react-router'; +import { provide } from '../../TypedRedux'; +import { ApplicationState } from '../../store'; +import { actionCreators } from '../../store/FeaturedAlbums'; +import { AlbumTile } from './AlbumTile'; + +class Home extends React.Component { + componentWillMount() { + if (!this.props.albums.length) { + this.props.requestFeaturedAlbums(); + } + } + + public render() { + let { albums } = this.props; + return
    +
    +

    MVC Music Store

    + +
    +
      + {albums.map(album => + + )} +
    +
    ; + } +} + +// Selects which part of global state maps to this component, and defines a type for the resulting props +const provider = provide( + (state: ApplicationState) => state.featuredAlbums, + actionCreators +); +type HomeProps = typeof provider.allProps; +export default provider.connect(Home); diff --git a/samples/react/MusicStore/ReactApp/configureStore.ts b/samples/react/MusicStore/ReactApp/configureStore.ts new file mode 100644 index 0000000..5c99212 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/configureStore.ts @@ -0,0 +1,39 @@ +import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; +import * as thunkModule from 'redux-thunk'; +import { syncHistory, routeReducer } from 'react-router-redux'; +import * as Store from './store'; + +export default function configureStore(history: HistoryModule.History, initialState?: Store.ApplicationState) { + // Build middleware + const thunk = (thunkModule as any).default; // Workaround for TypeScript not importing thunk module as expected + const reduxRouterMiddleware = syncHistory(history); + const middlewares = [thunk, reduxRouterMiddleware]; + const devToolsExtension = (window as any).devToolsExtension; // If devTools is installed, connect to it + + const finalCreateStore = compose( + applyMiddleware(...middlewares), + devToolsExtension ? devToolsExtension() : f => f + )(createStore) + + // Combine all reducers + const allReducers = buildRootReducer(Store.reducers); + + const store = finalCreateStore(allReducers, initialState) as Redux.Store; + + // Required for replaying actions from devtools to work + reduxRouterMiddleware.listenForReplays(store); + + // Enable Webpack hot module replacement for reducers + if (module.hot) { + module.hot.accept('./store', () => { + const nextRootReducer = require('./store'); + store.replaceReducer(buildRootReducer(nextRootReducer.reducers)); + }); + } + + return store; +} + +function buildRootReducer(allReducers) { + return combineReducers(Object.assign({}, allReducers, { routing: routeReducer })) as Redux.Reducer; +} diff --git a/samples/react/MusicStore/ReactApp/isomorphic-fetch.d.ts b/samples/react/MusicStore/ReactApp/isomorphic-fetch.d.ts new file mode 100644 index 0000000..e980a14 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/isomorphic-fetch.d.ts @@ -0,0 +1,3 @@ +declare module 'isomorphic-fetch' { + export default function fetch(url: string): Promise; +} diff --git a/samples/react/MusicStore/ReactApp/store/AlbumDetails.ts b/samples/react/MusicStore/ReactApp/store/AlbumDetails.ts new file mode 100644 index 0000000..49358ca --- /dev/null +++ b/samples/react/MusicStore/ReactApp/store/AlbumDetails.ts @@ -0,0 +1,72 @@ +import fetch from 'isomorphic-fetch'; +import { typeName, isActionType, Action, Reducer } from '../TypedRedux'; +import { ActionCreator } from './'; +import { Genre } from './GenreList'; + +// ----------------- +// STATE - This defines the type of data maintained in the Redux store. + +export interface AlbumDetailsState { + isLoaded: boolean; + album: AlbumDetails; +} + +export interface AlbumDetails { + AlbumId: string; + Title: string; + AlbumArtUrl: string; + Genre: Genre; + Artist: Artist; + Price: number; +} + +interface Artist { + Name: string; +} + +// ----------------- +// ACTIONS - These are serializable (hence replayable) descriptions of state transitions. +// They do not themselves have any side-effects; they just describe something that is going to happen. +// Use @typeName and isActionType for type detection that works even after serialization/deserialization. + +@typeName("REQUEST_ALBUM_DETAILS") +class RequestAlbumDetails extends Action { + constructor(public albumId: number) { + super(); + } +} + +@typeName("RECEIVE_ALBUM_DETAILS") +class ReceiveAlbumDetails extends Action { + constructor(public album: AlbumDetails) { + super(); + } +} + +// ---------------- +// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition. +// They don't directly mutate state, but they can have external side-effects (such as loading data). + +export const actionCreators = { + requestAlbumDetails: (albumId: number): ActionCreator => (dispatch, getState) => { + fetch(`/api/albums/${ albumId }`) + .then(results => results.json()) + .then(album => dispatch(new ReceiveAlbumDetails(album))); + + dispatch(new RequestAlbumDetails(albumId)); + } +}; + +// ---------------- +// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state. +// For unrecognized actions, must return the existing state (or default initial state if none was supplied). +const unloadedState: AlbumDetailsState = { isLoaded: false, album: null }; +export const reducer: Reducer = (state, action) => { + if (isActionType(action, RequestAlbumDetails)) { + return unloadedState; + } else if (isActionType(action, ReceiveAlbumDetails)) { + return { isLoaded: true, album: action.album }; + } else { + return state || unloadedState; + } +}; diff --git a/samples/react/MusicStore/ReactApp/store/FeaturedAlbums.ts b/samples/react/MusicStore/ReactApp/store/FeaturedAlbums.ts new file mode 100644 index 0000000..775aeb2 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/store/FeaturedAlbums.ts @@ -0,0 +1,58 @@ +import fetch from 'isomorphic-fetch'; +import { typeName, isActionType, Action, Reducer } from '../TypedRedux'; +import { ActionCreator } from './'; + +// ----------------- +// STATE - This defines the type of data maintained in the Redux store. + +export interface FeaturedAlbumsState { + albums: Album[]; +} + +export interface Album { + AlbumId: number; + Title: string; + AlbumArtUrl: string; +} + +// ----------------- +// ACTIONS - These are serializable (hence replayable) descriptions of state transitions. +// They do not themselves have any side-effects; they just describe something that is going to happen. +// Use @typeName and isActionType for type detection that works even after serialization/deserialization. + +@typeName("REQUEST_FEATURED_ALBUMS") +class RequestFeaturedAlbums extends Action { +} + +@typeName("RECEIVE_FEATURED_ALBUMS") +class ReceiveFeaturedAlbums extends Action { + constructor(public albums: Album[]) { + super(); + } +} + +// ---------------- +// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition. +// They don't directly mutate state, but they can have external side-effects (such as loading data). + +export const actionCreators = { + requestFeaturedAlbums: (): ActionCreator => (dispatch, getState) => { + fetch('/api/albums/mostPopular') + .then(results => results.json()) + .then(albums => dispatch(new ReceiveFeaturedAlbums(albums))); + + return dispatch(new RequestFeaturedAlbums()); + } +}; + +// ---------------- +// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state. +// For unrecognized actions, must return the existing state (or default initial state if none was supplied). + +export const reducer: Reducer = (state, action) => { + if (isActionType(action, ReceiveFeaturedAlbums)) { + return { albums: action.albums }; + } else { + return state || { albums: [] }; + } +}; diff --git a/samples/react/MusicStore/ReactApp/store/GenreDetails.ts b/samples/react/MusicStore/ReactApp/store/GenreDetails.ts new file mode 100644 index 0000000..f3f411b --- /dev/null +++ b/samples/react/MusicStore/ReactApp/store/GenreDetails.ts @@ -0,0 +1,59 @@ +import fetch from 'isomorphic-fetch'; +import { typeName, isActionType, Action, Reducer } from '../TypedRedux'; +import { ActionCreator } from './'; +import { Album } from './FeaturedAlbums'; + +// ----------------- +// STATE - This defines the type of data maintained in the Redux store. + +export interface GenreDetailsState { + isLoaded: boolean; + albums: Album[]; +} + +// ----------------- +// ACTIONS - These are serializable (hence replayable) descriptions of state transitions. +// They do not themselves have any side-effects; they just describe something that is going to happen. +// Use @typeName and isActionType for type detection that works even after serialization/deserialization. + +@typeName("REQUEST_GENRE_DETAILS") +class RequestGenreDetails extends Action { + constructor(public genreId: number) { + super(); + } +} + +@typeName("RECEIVE_GENRE_DETAILS") +class ReceiveGenreDetails extends Action { + constructor(public albums: Album[]) { + super(); + } +} + +// ---------------- +// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition. +// They don't directly mutate state, but they can have external side-effects (such as loading data). + +export const actionCreators = { + requestGenreDetails: (genreId: number): ActionCreator => (dispatch, getState) => { + fetch(`/api/genres/${ genreId }/albums`) + .then(results => results.json()) + .then(albums => dispatch(new ReceiveGenreDetails(albums))); + + dispatch(new RequestGenreDetails(genreId)); + } +}; + +// ---------------- +// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state. +// For unrecognized actions, must return the existing state (or default initial state if none was supplied). +const unloadedState: GenreDetailsState = { isLoaded: false, albums: [] }; +export const reducer: Reducer = (state, action) => { + if (isActionType(action, RequestGenreDetails)) { + return unloadedState; + } else if (isActionType(action, ReceiveGenreDetails)) { + return { isLoaded: true, albums: action.albums }; + } else { + return state || unloadedState; + } +}; diff --git a/samples/react/MusicStore/ReactApp/store/GenreList.ts b/samples/react/MusicStore/ReactApp/store/GenreList.ts new file mode 100644 index 0000000..af52616 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/store/GenreList.ts @@ -0,0 +1,51 @@ +import fetch from 'isomorphic-fetch'; +import { typeName, isActionType, Action, Reducer } from '../TypedRedux'; +import { ActionCreator } from './'; + +// ----------------- +// STATE - This defines the type of data maintained in the Redux store. + +export interface GenresListState { + genres: Genre[]; +} + +export interface Genre { + GenreId: string; + Name: string; +} + +// ----------------- +// ACTIONS - These are serializable (hence replayable) descriptions of state transitions. +// They do not themselves have any side-effects; they just describe something that is going to happen. +// Use @typeName and isActionType for type detection that works even after serialization/deserialization. + +@typeName("RECEIVE_GENRES_LIST") +class ReceiveGenresList extends Action { + constructor(public genres: Genre[]) { + super(); + } +} + +// ---------------- +// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition. +// They don't directly mutate state, but they can have external side-effects (such as loading data). + +export const actionCreators = { + requestGenresList: (): ActionCreator => (dispatch, getState) => { + fetch('/api/genres') + .then(results => results.json()) + .then(genres => dispatch(new ReceiveGenresList(genres))); + } +}; + +// ---------------- +// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state. +// For unrecognized actions, must return the existing state (or default initial state if none was supplied). + +export const reducer: Reducer = (state, action) => { + if (isActionType(action, ReceiveGenresList)) { + return { genres: action.genres }; + } else { + return state || { genres: [] }; + } +}; diff --git a/samples/react/MusicStore/ReactApp/store/index.ts b/samples/react/MusicStore/ReactApp/store/index.ts new file mode 100644 index 0000000..5c9c8ff --- /dev/null +++ b/samples/react/MusicStore/ReactApp/store/index.ts @@ -0,0 +1,27 @@ +import { ActionCreatorGeneric } from '../TypedRedux'; +import * as FeaturedAlbums from './FeaturedAlbums'; +import * as GenreList from './GenreList'; +import * as GenreDetails from './GenreDetails'; +import * as AlbumDetails from './AlbumDetails'; + +// The top-level state object +export interface ApplicationState { + featuredAlbums: FeaturedAlbums.FeaturedAlbumsState; + genreList: GenreList.GenresListState, + genreDetails: GenreDetails.GenreDetailsState, + albumDetails: AlbumDetails.AlbumDetailsState +} + +// Whenever an action is dispatched, Redux will update each top-level application state property using +// the reducer with the matching name. It's important that the names match exactly, and that the reducer +// acts on the corresponding ApplicationState property type. +export const reducers = { + featuredAlbums: FeaturedAlbums.reducer, + genreList: GenreList.reducer, + genreDetails: GenreDetails.reducer, + albumDetails: AlbumDetails.reducer +}; + +// This type can be used as a hint on action creators so that its 'dispatch' and 'getState' params are +// correctly typed to match your store. +export type ActionCreator = ActionCreatorGeneric; diff --git a/samples/react/MusicStore/ReactApp/styles/styles.css b/samples/react/MusicStore/ReactApp/styles/styles.css new file mode 100644 index 0000000..a8fc8a1 --- /dev/null +++ b/samples/react/MusicStore/ReactApp/styles/styles.css @@ -0,0 +1,3 @@ +body { + padding-top: 50px; +} diff --git a/samples/react/MusicStore/SiteSettings.cs b/samples/react/MusicStore/SiteSettings.cs new file mode 100644 index 0000000..50a86c2 --- /dev/null +++ b/samples/react/MusicStore/SiteSettings.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MusicStore +{ + public class SiteSettings + { + public string DefaultAdminUsername { get; set; } + public string DefaultAdminPassword { get; set; } + } +} diff --git a/samples/react/MusicStore/Startup.cs b/samples/react/MusicStore/Startup.cs new file mode 100755 index 0000000..c36fb56 --- /dev/null +++ b/samples/react/MusicStore/Startup.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using Microsoft.AspNet.Authorization; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Hosting; +using Microsoft.AspNet.Identity.EntityFramework; +using Microsoft.AspNet.SpaServices; +using Microsoft.Data.Entity; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MusicStore.Apis; +using MusicStore.Models; + +namespace MusicStore +{ + public class Startup + { + public Startup(IHostingEnvironment env) + { + // Set up configuration sources. + var builder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfigurationRoot Configuration { get; set; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.Configure(settings => + { + settings.DefaultAdminUsername = Configuration["DefaultAdminUsername"]; + settings.DefaultAdminPassword = Configuration["DefaultAdminPassword"]; + }); + + // Add MVC services to the services container. + services.AddMvc(); + + // Uncomment the following line to add Web API services which makes it easier to port Web API 2 controllers. + // You will also need to add the Microsoft.AspNet.Mvc.WebApiCompatShim package to the 'dependencies' section of project.json. + // services.AddWebApiConventions(); + + // Add EF services to the service container + services.AddEntityFramework() + .AddSqlite() + .AddDbContext(options => { + options.UseSqlite(Configuration["DbConnectionString"]); + }); + + // Add Identity services to the services container + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + // Uncomment the following line to add Web API services which makes it easier to port Web API 2 controllers. + // You will also need to add the Microsoft.AspNet.Mvc.WebApiCompatShim package to the 'dependencies' section of project.json. + // services.AddWebApiConventions(); + + // Configure Auth + services.Configure(options => + { + options.AddPolicy("app-ManageStore", new AuthorizationPolicyBuilder().RequireClaim("app-ManageStore", "Allowed").Build()); + }); + + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + Mapper.CreateMap(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + loggerFactory.AddConsole(LogLevel.Warning); + + // Initialize the sample data + SampleData.InitializeMusicStoreDatabaseAsync(app.ApplicationServices).Wait(); + + if (env.IsDevelopment()) { + app.UseDeveloperExceptionPage(); + } else { + app.UseExceptionHandler("/Home/Error"); + } + + app.UseIISPlatformHandler(); + + // In dev mode, the JS/TS/etc is compiled and served dynamically and supports hot replacement. + // In production, we assume you've used webpack to emit the prebuilt content to disk. + if (env.IsDevelopment()) { + app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { + HotModuleReplacement = true, + ReactHotModuleReplacement = true + }); + } + + app.UseStaticFiles(); + + app.UseMvc(routes => { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + + routes.MapSpaFallbackRoute( + name: "spa-fallback", + defaults: new { controller = "Home", action = "Index" }); + }); + } + + // Entry point for the application. + public static void Main(string[] args) => Microsoft.AspNet.Hosting.WebApplication.Run(args); + } +} diff --git a/samples/react/MusicStore/Views/Home/Index.cshtml b/samples/react/MusicStore/Views/Home/Index.cshtml new file mode 100755 index 0000000..db4dc4f --- /dev/null +++ b/samples/react/MusicStore/Views/Home/Index.cshtml @@ -0,0 +1,10 @@ +@{ + ViewData["Title"] = "Home Page"; +} + +
    Loading...
    + +@section scripts { + + +} diff --git a/samples/react/MusicStore/Views/Shared/Error.cshtml b/samples/react/MusicStore/Views/Shared/Error.cshtml new file mode 100755 index 0000000..a288cb0 --- /dev/null +++ b/samples/react/MusicStore/Views/Shared/Error.cshtml @@ -0,0 +1,6 @@ +@{ + ViewData["Title"] = "Error"; +} + +

    Error.

    +

    An error occurred while processing your request.

    diff --git a/samples/react/MusicStore/Views/Shared/_Layout.cshtml b/samples/react/MusicStore/Views/Shared/_Layout.cshtml new file mode 100755 index 0000000..93395f5 --- /dev/null +++ b/samples/react/MusicStore/Views/Shared/_Layout.cshtml @@ -0,0 +1,11 @@ + + + + + @ViewData["Title"] + + + @RenderBody() + @RenderSection("scripts", required: false) + + diff --git a/samples/react/MusicStore/Views/_ViewImports.cshtml b/samples/react/MusicStore/Views/_ViewImports.cshtml new file mode 100755 index 0000000..a07c195 --- /dev/null +++ b/samples/react/MusicStore/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using MusicStore +@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers" diff --git a/samples/react/MusicStore/Views/_ViewStart.cshtml b/samples/react/MusicStore/Views/_ViewStart.cshtml new file mode 100755 index 0000000..66b5da2 --- /dev/null +++ b/samples/react/MusicStore/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/samples/react/MusicStore/appsettings.json b/samples/react/MusicStore/appsettings.json new file mode 100755 index 0000000..980ba08 --- /dev/null +++ b/samples/react/MusicStore/appsettings.json @@ -0,0 +1,11 @@ +{ + "DbConnectionString": "Data Source=music-db.sqlite", + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Warning", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/react/MusicStore/package.json b/samples/react/MusicStore/package.json new file mode 100644 index 0000000..ce2001a --- /dev/null +++ b/samples/react/MusicStore/package.json @@ -0,0 +1,34 @@ +{ + "name": "MusicStore", + "version": "0.0.0", + "devDependencies": { + "babel-loader": "^6.2.1", + "babel-plugin-react-transform": "^2.0.0", + "babel-preset-es2015": "^6.3.13", + "babel-preset-react": "^6.3.13", + "css-loader": "^0.23.1", + "express": "^4.13.4", + "file-loader": "^0.8.5", + "react-transform-hmr": "^1.0.2", + "style-loader": "^0.13.0", + "ts-loader": "^0.8.0", + "typescript": "^1.7.5", + "url-loader": "^0.5.7", + "webpack": "^1.12.12", + "webpack-dev-middleware": "^1.5.1", + "webpack-hot-middleware": "^2.6.4" + }, + "dependencies": { + "bootstrap": "^3.3.6", + "isomorphic-fetch": "^2.2.1", + "react": "^0.14.7", + "react-bootstrap": "^0.28.2", + "react-dom": "^0.14.7", + "react-redux": "^4.2.1", + "react-router": "^2.0.0-rc5", + "react-router-bootstrap": "^0.20.1", + "react-router-redux": "^2.1.0", + "redux": "^3.2.1", + "redux-thunk": "^1.0.3" + } +} diff --git a/samples/react/MusicStore/project.json b/samples/react/MusicStore/project.json new file mode 100755 index 0000000..b74a0fd --- /dev/null +++ b/samples/react/MusicStore/project.json @@ -0,0 +1,53 @@ +{ + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "tooling": { + "defaultNamespace": "MusicStore" + }, + + "dependencies": { + "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final", + "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final", + "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", + "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc1-final", + "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final", + "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final", + "Microsoft.AspNet.Tooling.Razor": "1.0.0-rc1-final", + "Microsoft.Extensions.Configuration.FileProviderExtensions" : "1.0.0-rc1-final", + "Microsoft.Extensions.Configuration.Json": "1.0.0-rc1-final", + "Microsoft.Extensions.Logging": "1.0.0-rc1-final", + "Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final", + "Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final", + "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-rc1-*", + "Microsoft.AspNet.ReactServices": "1.0.0-alpha7", + "EntityFramework.SQLite": "7.0.0-rc1-*", + "AutoMapper": "4.1.1" + }, + + "commands": { + "web": "Microsoft.AspNet.Server.Kestrel" + }, + + "frameworks": { + "dnx451": {}, + "dnxcore50": {} + }, + + "exclude": [ + "wwwroot", + "node_modules" + ], + "publishExclude": [ + "node_modules", + "**.xproj", + "**.user", + "**.vspscc" + ], + "scripts": { + "prepublish": [ + "npm install" + ] + } +} diff --git a/samples/react/MusicStore/tsconfig.json b/samples/react/MusicStore/tsconfig.json new file mode 100644 index 0000000..863f693 --- /dev/null +++ b/samples/react/MusicStore/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "target": "es6", + "jsx": "preserve", + "sourceMap": true, + "experimentalDecorators": true + }, + "exclude": [ + "node_modules" + ] +} diff --git a/samples/react/MusicStore/tsd.json b/samples/react/MusicStore/tsd.json new file mode 100644 index 0000000..f5347d6 --- /dev/null +++ b/samples/react/MusicStore/tsd.json @@ -0,0 +1,42 @@ +{ + "version": "v4", + "repo": "borisyankov/DefinitelyTyped", + "ref": "master", + "path": "typings", + "bundle": "typings/tsd.d.ts", + "installed": { + "react/react.d.ts": { + "commit": "e69fe60f2d6377ea4fae539493997b098f52cad1" + }, + "react/react-dom.d.ts": { + "commit": "86dbea8fc37d9473fee465da4f0a21bea4f8cbd9" + }, + "redux/redux.d.ts": { + "commit": "e69fe60f2d6377ea4fae539493997b098f52cad1" + }, + "webpack/webpack-env.d.ts": { + "commit": "717a5fdb079f8dd7c19f1b22f7f656dd990f0ccf" + }, + "react-redux/react-redux.d.ts": { + "commit": "717a5fdb079f8dd7c19f1b22f7f656dd990f0ccf" + }, + "react-bootstrap/react-bootstrap.d.ts": { + "commit": "e69fe60f2d6377ea4fae539493997b098f52cad1" + }, + "react-router/react-router.d.ts": { + "commit": "e69fe60f2d6377ea4fae539493997b098f52cad1" + }, + "react-router/history.d.ts": { + "commit": "e69fe60f2d6377ea4fae539493997b098f52cad1" + }, + "react-router-bootstrap/react-router-bootstrap.d.ts": { + "commit": "e69fe60f2d6377ea4fae539493997b098f52cad1" + }, + "react-router-redux/react-router-redux.d.ts": { + "commit": "e69fe60f2d6377ea4fae539493997b098f52cad1" + }, + "redux-thunk/redux-thunk.d.ts": { + "commit": "e69fe60f2d6377ea4fae539493997b098f52cad1" + } + } +} diff --git a/samples/react/MusicStore/typings/react-bootstrap/react-bootstrap.d.ts b/samples/react/MusicStore/typings/react-bootstrap/react-bootstrap.d.ts new file mode 100644 index 0000000..c6e30d0 --- /dev/null +++ b/samples/react/MusicStore/typings/react-bootstrap/react-bootstrap.d.ts @@ -0,0 +1,976 @@ +// Type definitions for react-bootstrap +// Project: https://github.com/react-bootstrap/react-bootstrap +// Definitions by: Walker Burgin +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/// + +declare module "react-bootstrap" { + // Import React + import React = require("react"); + + + //