Compare commits

..

1 Commits

Author SHA1 Message Date
SteveSandersonMS
135e264ae2 Example of adding font-awesome 2016-09-28 11:02:04 +01:00
648 changed files with 53478 additions and 6813 deletions

4
.gitignore vendored
View File

@@ -13,6 +13,7 @@ PublishProfiles/
*.docstates
_ReSharper.*
nuget.exe
project.lock.json
*net45.csproj
*net451.csproj
*k10.csproj
@@ -36,7 +37,4 @@ npm-debug.log
/templates/*/node_modules/
/templates/*/wwwroot/dist/
/templates/*/ClientApp/dist/
/templates/*/yarn.lock
.vscode/
/templates/*/Properties/launchSettings.json

View File

@@ -10,10 +10,11 @@ addons:
- libssl-dev
- libunwind8
- zlib1g
mono: none
mono:
- 4.0.5
os:
- linux
- osx
osx_image: xcode7.1
script:
- ./build.sh
- ./build.sh verify

View File

@@ -1,40 +1,50 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.4
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{27304DDE-AFB2-4F8B-B765-E3E2F11E886C}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{E6E88944-4800-40BA-8AF5-069EA3ADFEB8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.NodeServices", "src\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj", "{66B77203-1469-41DF-92F2-2BE6900BD36F}"
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.NodeServices", "src\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.xproj", "{B0FA4175-8B29-4904-9780-28B3C24B0567}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.NodeServices.Sockets", "src\Microsoft.AspNetCore.NodeServices.Sockets\Microsoft.AspNetCore.NodeServices.Sockets.csproj", "{F46DEF99-6FAA-4406-B5D8-6FF34EF669E3}"
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NodeServicesExamples", "samples\misc\NodeServicesExamples\NodeServicesExamples.xproj", "{6D4BCDD6-7951-449B-BE55-CB7F014B7430}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SpaServices", "src\Microsoft.AspNetCore.SpaServices\Microsoft.AspNetCore.SpaServices.csproj", "{66B071A8-EFC8-4A06-BEF6-06B99AE27EEC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{99EAF1FE-22C8-4526-BE78-74B24125D37F}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{78DAC76C-1092-45AB-BF0D-594B8C7B6569}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
global.json = global.json
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{23836492-E7F4-4376-85BF-A635C304AC46}"
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MusicStore", "samples\angular\MusicStore\MusicStore.xproj", "{1A74148F-9DC0-435D-B5AC-7D1B0D3D5E0B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{E6A161EA-646C-4033-9090-95BE809AB8D9}"
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ReactGrid", "samples\react\ReactGrid\ReactGrid.xproj", "{ABF90A5B-F4E0-438C-A6E4-9549FB43690B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LatencyTest", "samples\misc\LatencyTest\LatencyTest.csproj", "{1931B19A-EC42-4D56-B2D0-FB06D17244DA}"
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.AngularServices", "src\Microsoft.AspNetCore.AngularServices\Microsoft.AspNetCore.AngularServices.xproj", "{421807E6-B62C-417B-B901-46C5DEDAA8F1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Webpack", "samples\misc\Webpack\Webpack.csproj", "{DE479DC3-1461-4EAD-A188-4AF7AA4AE344}"
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.ReactServices", "src\Microsoft.AspNetCore.ReactServices\Microsoft.AspNetCore.ReactServices.xproj", "{B04381DE-991F-4831-A0B5-FE1BD3EF80C4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NodeServicesExamples", "samples\misc\NodeServicesExamples\NodeServicesExamples.csproj", "{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}"
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.SpaServices", "src\Microsoft.AspNetCore.SpaServices\Microsoft.AspNetCore.SpaServices.xproj", "{4624F728-6DFF-44B6-93B5-3C7D9C94BF3F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{1598B415-73F1-4B37-B3B4-0A10677ABB2D}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Templates", "Templates", "{727E6D58-6830-4792-96C6-E138A33959FB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{E415FE14-13B0-469F-836D-95059E6BAA6E}"
ProjectSection(SolutionItems) = preProject
src\build\common.props = src\build\common.props
src\build\Key.snk = src\build\Key.snk
EndProjectSection
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Angular2Spa", "templates\Angular2Spa\Angular2Spa.xproj", "{8F5CB8A9-3086-4B49-A1C2-32A9F89BCA11}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ReactReduxSpa", "templates\ReactReduxSpa\ReactReduxSpa.xproj", "{DBFC6DB0-A6D1-4694-A108-1C604B988DA3}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ReactSpa", "templates\ReactSpa\ReactSpa.xproj", "{E9D1A695-F0E6-46F2-B5E3-72F4AF805387}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "WebApplicationBasic", "templates\WebApplicationBasic\WebApplicationBasic.xproj", "{CB4398D6-B7F1-449A-AE02-828769679232}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{E0771531-BE20-40CD-A1B0-A57E09511060}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Webpack", "samples\misc\Webpack\Webpack.xproj", "{A8905301-8492-42FD-9E83-F715A0FDC3A2}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LatencyTest", "samples\misc\LatencyTest\LatencyTest.xproj", "{A64AF9D9-72AA-4433-BE1D-DC2524B6808A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "React", "React", "{E0EBA813-4478-4C02-B11D-FB3793113FE4}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MusicStore", "samples\react\MusicStore\MusicStore.xproj", "{C870A92C-9E3F-4BF2-82B8-5758545A8B7C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Angular", "Angular", "{4867A616-83D6-48DC-964D-6AE743596631}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -42,41 +52,79 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{66B77203-1469-41DF-92F2-2BE6900BD36F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66B77203-1469-41DF-92F2-2BE6900BD36F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66B77203-1469-41DF-92F2-2BE6900BD36F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66B77203-1469-41DF-92F2-2BE6900BD36F}.Release|Any CPU.Build.0 = Release|Any CPU
{F46DEF99-6FAA-4406-B5D8-6FF34EF669E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F46DEF99-6FAA-4406-B5D8-6FF34EF669E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F46DEF99-6FAA-4406-B5D8-6FF34EF669E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F46DEF99-6FAA-4406-B5D8-6FF34EF669E3}.Release|Any CPU.Build.0 = Release|Any CPU
{66B071A8-EFC8-4A06-BEF6-06B99AE27EEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{66B071A8-EFC8-4A06-BEF6-06B99AE27EEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66B071A8-EFC8-4A06-BEF6-06B99AE27EEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66B071A8-EFC8-4A06-BEF6-06B99AE27EEC}.Release|Any CPU.Build.0 = Release|Any CPU
{1931B19A-EC42-4D56-B2D0-FB06D17244DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1931B19A-EC42-4D56-B2D0-FB06D17244DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1931B19A-EC42-4D56-B2D0-FB06D17244DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1931B19A-EC42-4D56-B2D0-FB06D17244DA}.Release|Any CPU.Build.0 = Release|Any CPU
{DE479DC3-1461-4EAD-A188-4AF7AA4AE344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE479DC3-1461-4EAD-A188-4AF7AA4AE344}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE479DC3-1461-4EAD-A188-4AF7AA4AE344}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE479DC3-1461-4EAD-A188-4AF7AA4AE344}.Release|Any CPU.Build.0 = Release|Any CPU
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE}.Release|Any CPU.Build.0 = Release|Any CPU
{B0FA4175-8B29-4904-9780-28B3C24B0567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0FA4175-8B29-4904-9780-28B3C24B0567}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0FA4175-8B29-4904-9780-28B3C24B0567}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0FA4175-8B29-4904-9780-28B3C24B0567}.Release|Any CPU.Build.0 = Release|Any CPU
{6D4BCDD6-7951-449B-BE55-CB7F014B7430}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D4BCDD6-7951-449B-BE55-CB7F014B7430}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D4BCDD6-7951-449B-BE55-CB7F014B7430}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D4BCDD6-7951-449B-BE55-CB7F014B7430}.Release|Any CPU.Build.0 = Release|Any CPU
{1A74148F-9DC0-435D-B5AC-7D1B0D3D5E0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A74148F-9DC0-435D-B5AC-7D1B0D3D5E0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A74148F-9DC0-435D-B5AC-7D1B0D3D5E0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A74148F-9DC0-435D-B5AC-7D1B0D3D5E0B}.Release|Any CPU.Build.0 = Release|Any CPU
{ABF90A5B-F4E0-438C-A6E4-9549FB43690B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ABF90A5B-F4E0-438C-A6E4-9549FB43690B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ABF90A5B-F4E0-438C-A6E4-9549FB43690B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABF90A5B-F4E0-438C-A6E4-9549FB43690B}.Release|Any CPU.Build.0 = Release|Any CPU
{421807E6-B62C-417B-B901-46C5DEDAA8F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{421807E6-B62C-417B-B901-46C5DEDAA8F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{421807E6-B62C-417B-B901-46C5DEDAA8F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{421807E6-B62C-417B-B901-46C5DEDAA8F1}.Release|Any CPU.Build.0 = Release|Any CPU
{B04381DE-991F-4831-A0B5-FE1BD3EF80C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B04381DE-991F-4831-A0B5-FE1BD3EF80C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B04381DE-991F-4831-A0B5-FE1BD3EF80C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B04381DE-991F-4831-A0B5-FE1BD3EF80C4}.Release|Any CPU.Build.0 = Release|Any CPU
{4624F728-6DFF-44B6-93B5-3C7D9C94BF3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4624F728-6DFF-44B6-93B5-3C7D9C94BF3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4624F728-6DFF-44B6-93B5-3C7D9C94BF3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4624F728-6DFF-44B6-93B5-3C7D9C94BF3F}.Release|Any CPU.Build.0 = Release|Any CPU
{8F5CB8A9-3086-4B49-A1C2-32A9F89BCA11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F5CB8A9-3086-4B49-A1C2-32A9F89BCA11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F5CB8A9-3086-4B49-A1C2-32A9F89BCA11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F5CB8A9-3086-4B49-A1C2-32A9F89BCA11}.Release|Any CPU.Build.0 = Release|Any CPU
{DBFC6DB0-A6D1-4694-A108-1C604B988DA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBFC6DB0-A6D1-4694-A108-1C604B988DA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBFC6DB0-A6D1-4694-A108-1C604B988DA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBFC6DB0-A6D1-4694-A108-1C604B988DA3}.Release|Any CPU.Build.0 = Release|Any CPU
{E9D1A695-F0E6-46F2-B5E3-72F4AF805387}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9D1A695-F0E6-46F2-B5E3-72F4AF805387}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9D1A695-F0E6-46F2-B5E3-72F4AF805387}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9D1A695-F0E6-46F2-B5E3-72F4AF805387}.Release|Any CPU.Build.0 = Release|Any CPU
{CB4398D6-B7F1-449A-AE02-828769679232}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB4398D6-B7F1-449A-AE02-828769679232}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB4398D6-B7F1-449A-AE02-828769679232}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB4398D6-B7F1-449A-AE02-828769679232}.Release|Any CPU.Build.0 = Release|Any CPU
{A8905301-8492-42FD-9E83-F715A0FDC3A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8905301-8492-42FD-9E83-F715A0FDC3A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8905301-8492-42FD-9E83-F715A0FDC3A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8905301-8492-42FD-9E83-F715A0FDC3A2}.Release|Any CPU.Build.0 = Release|Any CPU
{A64AF9D9-72AA-4433-BE1D-DC2524B6808A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A64AF9D9-72AA-4433-BE1D-DC2524B6808A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A64AF9D9-72AA-4433-BE1D-DC2524B6808A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A64AF9D9-72AA-4433-BE1D-DC2524B6808A}.Release|Any CPU.Build.0 = Release|Any CPU
{C870A92C-9E3F-4BF2-82B8-5758545A8B7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C870A92C-9E3F-4BF2-82B8-5758545A8B7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C870A92C-9E3F-4BF2-82B8-5758545A8B7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C870A92C-9E3F-4BF2-82B8-5758545A8B7C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{66B77203-1469-41DF-92F2-2BE6900BD36F} = {27304DDE-AFB2-4F8B-B765-E3E2F11E886C}
{F46DEF99-6FAA-4406-B5D8-6FF34EF669E3} = {27304DDE-AFB2-4F8B-B765-E3E2F11E886C}
{66B071A8-EFC8-4A06-BEF6-06B99AE27EEC} = {27304DDE-AFB2-4F8B-B765-E3E2F11E886C}
{E6A161EA-646C-4033-9090-95BE809AB8D9} = {23836492-E7F4-4376-85BF-A635C304AC46}
{1931B19A-EC42-4D56-B2D0-FB06D17244DA} = {E6A161EA-646C-4033-9090-95BE809AB8D9}
{DE479DC3-1461-4EAD-A188-4AF7AA4AE344} = {E6A161EA-646C-4033-9090-95BE809AB8D9}
{93EFCC5F-C6EE-4623-894F-A42B22C0B6FE} = {E6A161EA-646C-4033-9090-95BE809AB8D9}
{6D4BCDD6-7951-449B-BE55-CB7F014B7430} = {E0771531-BE20-40CD-A1B0-A57E09511060}
{1A74148F-9DC0-435D-B5AC-7D1B0D3D5E0B} = {4867A616-83D6-48DC-964D-6AE743596631}
{ABF90A5B-F4E0-438C-A6E4-9549FB43690B} = {E0EBA813-4478-4C02-B11D-FB3793113FE4}
{8F5CB8A9-3086-4B49-A1C2-32A9F89BCA11} = {727E6D58-6830-4792-96C6-E138A33959FB}
{DBFC6DB0-A6D1-4694-A108-1C604B988DA3} = {727E6D58-6830-4792-96C6-E138A33959FB}
{E9D1A695-F0E6-46F2-B5E3-72F4AF805387} = {727E6D58-6830-4792-96C6-E138A33959FB}
{CB4398D6-B7F1-449A-AE02-828769679232} = {727E6D58-6830-4792-96C6-E138A33959FB}
{E0771531-BE20-40CD-A1B0-A57E09511060} = {E6E88944-4800-40BA-8AF5-069EA3ADFEB8}
{A8905301-8492-42FD-9E83-F715A0FDC3A2} = {E0771531-BE20-40CD-A1B0-A57E09511060}
{A64AF9D9-72AA-4433-BE1D-DC2524B6808A} = {E0771531-BE20-40CD-A1B0-A57E09511060}
{E0EBA813-4478-4C02-B11D-FB3793113FE4} = {E6E88944-4800-40BA-8AF5-069EA3ADFEB8}
{C870A92C-9E3F-4BF2-82B8-5758545A8B7C} = {E0EBA813-4478-4C02-B11D-FB3793113FE4}
{4867A616-83D6-48DC-964D-6AE743596631} = {E6E88944-4800-40BA-8AF5-069EA3ADFEB8}
EndGlobalSection
EndGlobal

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="AspNetCorePreview2Final" value="https://dotnet.myget.org/F/dotnet-2-0-0-preview2-final/api/v3/index.json" />
<add key="AspNetCore" value="https://dotnet.myget.org/F/aspnetcore-ci-release/api/v3/index.json" />
<add key="AspNetCoreTools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

8
NuGet.master.config Executable file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="AspNetVNext" value="https://www.myget.org/f/aspnetmaster/api/v3/index.json" />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View File

@@ -1,18 +1,10 @@
# JavaScriptServices
AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/gprilrckx116vc9m/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/javascriptservices/branch/dev)
This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo.
## What is this?
`JavaScriptServices` is a set of client-side technologies for ASP.NET Core. It provides infrastructure that you'll find useful if you:
- Use Angular / React / Vue / Aurelia / Knockout / etc.
- Build your client-side resources using Webpack.
- Execute JavaScript on the server at runtime.
Read [Building Single Page Applications on ASP.NET Core with JavaScriptServices](https://blogs.msdn.microsoft.com/webdev/2017/02/14/building-single-page-applications-on-asp-net-core-with-javascriptservices/) for more details.
`JavaScriptServices` is a set of technologies for ASP.NET Core developers. It provides infrastructure that you'll find useful if you use Angular 2 / React / Knockout / etc. on the client, or if you build your client-side resources using Webpack, or otherwise want to execute JavaScript on the server at runtime.
This repo contains:
@@ -23,26 +15,27 @@ This repo contains:
* Hot module replacement (HMR) ([docs](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#webpack-hot-module-replacement))
* Server-side and client-side routing integration ([docs](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#routing-helper-mapspafallbackroute))
* Server-side and client-side validation integration
* "Cache priming" for Angular 2 apps
* "Lazy loading" for Knockout apps
* A Yeoman generator that creates preconfigured app starting points ([guide](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/))
* Samples and docs
It's cross-platform (Windows, Linux, or macOS) and works with .NET Core 1.0.1 or later.
Everything here is cross-platform, and works with .NET Core 1.0.1 or later on Windows, Linux, or OS X.
## Creating new applications
If you want to build a brand-new ASP.NET Core app that uses Angular / React / Knockout on the client, consider starting with the `aspnetcore-spa` generator. This lets you choose your client-side framework. It generates a starting point that includes applicable features such as Webpack dev middleware, server-side prerendering, and efficient production builds. It's much easier than configuring everything to work together manually!
If you want to build a brand-new ASP.NET Core app that uses Angular 2 / React / Knockout on the client, consider starting with the `aspnetcore-spa` generator. This lets you choose your client-side framework, and generates a starting point that includes applicable features such as Webpack dev middleware, server-side prerendering, and efficient production builds. It's much easier than configuring everything to work together manually!
To do this, install Yeoman and these generator templates:
To do this, first install Yeoman and these generator templates:
npm install -g yo generator-aspnetcore-spa
Generate your new application starting point:
Then you can generate your new application starting point:
cd some-empty-directory
yo aspnetcore-spa
Once the generator has run and restored all the dependencies, you can start up your new ASP.NET Core SPA:
Finally, once the generator has run and restored all the dependencies, you can start up your new ASP.NET Core Single Page Application:
dotnet run
@@ -57,11 +50,11 @@ If you have an existing ASP.NET Core application, or if you just want to use the
* Most applications developers don't need to use this directly, but you can do so if you want to implement your own functionality that involves calling Node.js code from .NET at runtime.
* Find [documentation and usage examples here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.NodeServices#microsoftaspnetcorenodeservices).
* `Microsoft.AspNetCore.SpaServices`
* This provides infrastructure that's generally useful when building Single Page Applications (SPAs) with technologies such as Angular or React (for example, server-side prerendering and webpack middleware). Internally, it uses the `NodeServices` package to implement its features.
* This provides infrastructure that's generally useful when building Single Page Applications (SPAs) with technologies such as Angular 2 or React (for example, server-side prerendering and webpack middleware). Internally, it uses the `NodeServices` package to implement its features.
* Find [documentation and usage examples here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#microsoftaspnetcorespaservices).
* `Microsoft.AspNetCore.AngularServices`
* This builds on the `SpaServices` package and includes features specific to Angular. Currently, this includes validation helpers.
* The code is [here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.AngularServices). You'll find a usage example for [the validation helper here](https://github.com/aspnet/JavaScriptServices/blob/dev/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.ts).
* This builds on the `SpaServices` package and includes features specific to Angular 2. Currently, this includes validation helpers and a "cache priming" feature, which let you pre-evaluate ajax requests on the server so that client-side code doesn't need to make network calls once it's loaded.
* The code is [here](https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.AngularServices), and you'll find a usage example for [the validation helper here](https://github.com/aspnet/JavaScriptServices/blob/dev/samples/angular/MusicStore/wwwroot/ng-app/components/admin/album-edit/album-edit.ts), and for the [cache priming here](https://github.com/aspnet/JavaScriptServices/blob/dev/samples/angular/MusicStore/Views/Home/Index.cshtml#L7-8). Full docs are to be written.
There was previously a `Microsoft.AspNetCore.ReactServices` but this is not currently needed - all applicable functionality is in `Microsoft.AspNetCore.SpaServices`, because it's sufficiently general. We might add a new `Microsoft.AspNetCore.ReactServices` package in the future if new React-specific requirements emerge.
@@ -69,32 +62,28 @@ If you want to build a helper library for some other SPA framework, you can do s
## Samples and templates
Inside this repo, [the `templates` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/templates) contains the application starting points that the `aspnetcore-spa` generator emits. You can clone this repo and run those applications directly. But it's easier to [use the Yeoman tool to run the generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/).
Inside this repo, [the `templates` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/templates) contains the application starting points that the `aspnetcore-spa` generator emits. If you want, you can clone this repo and run those applications directly. But it's easier to [use the Yeoman tool to run the generator](http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/).
The [`samples` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/samples) contains examples of:
- Using the JavaScript services family of packages with Angular and React.
- A standalone `NodeServices` usage for runtime code transpilation and image processing.
Also in this repo, [the `samples` directory](https://github.com/aspnet/JavaScriptServices/tree/dev/samples) contains examples of using the JavaScript services family of packages with Angular 2 and React, plus examples of standalone `NodeServices` usage for runtime code transpilation and image processing.
**To run the samples:**
* Clone this repo
* At the repo's root directory (the one containing `src`, `samples`, etc.), run `dotnet restore`
* Change directory to the sample you want to run (for example, `cd samples/angular/MusicStore`)
* Change directory to the sample you want to run (e.g., `cd samples/angular/MusicStore`)
* Restore Node dependencies by running `npm install`
* If you're trying to run the Angular "Music Store" sample, then also run `gulp` (which you need to have installed globally). None of the other samples require this.
* If you're trying to run the Angular 2 "Music Store" sample, then also run `gulp` (which you need to have installed globally). None of the other samples require this.
* Run the application (`dotnet run`)
* Browse to [http://localhost:5000](http://localhost:5000)
## Contributing
If you're interested in contributing to the various packages, samples, and project templates in this repo, that's great! You can run the code in this repo as follows:
If you're interested in contributing to the various packages, samples, and project templates in this repo, that's great! You can run the code in this repo just by:
* Clone the repo
* Run `dotnet restore` at the repo root dir
* Go to whatever sample or template you want to run (for example, `cd templates/AngularSpa`)
* Restore NPM dependencies (run `npm install`)
* If the sample/template you're trying to run has a file called `webpack.config.vendor.js` at its root, run `webpack --config webpack.config.vendor.js`. If it has a file called `webpack.config.js`, run `webpack` (no args). You might need to install webpack first, by running `npm install -g webpack`.
* Launch it (`dotnet run`)
* Cloning the repo
* Running `dotnet restore` at the repo root dir
* Going to whatever sample or template you want to run (e.g., `cd templates/Angular2Spa`)
* Restoring NPM dependencies (run `npm install`)
* Launching it (`dotnet run`)
If you're planning to submit a pull request, and if it's more than a trivial fix (for example, for a typo), it's usually a good idea first to file an issue describing what you're proposing to do and how it will work. Then you can find out if it's likely that such a pull request will be accepted, and how it fits into wider ongoing plans.
If you're planning to submit a pull request, and if it's more than a trivial fix (e.g., for a typo), it's usually a good idea first to file an issue describing what you're proposing to do and how it will work. Then you can find out if it's likely that such a pull request will be accepted, and how it fits into wider ongoing plans.

View File

@@ -1,40 +1,7 @@
init:
- git config --global core.autocrlf true
install:
- ps: Install-Product node 6.9.2 x64
# .NET Core SDK binaries
# Download .NET Core 2.0 Preview 2 SDK and add to PATH
- ps: $urlCurrent = "https://dotnetcli.azureedge.net/dotnet/Sdk/2.0.0-preview2-006497/dotnet-sdk-2.0.0-preview2-006497-win-x64.zip"
- ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetsdk"
- ps: mkdir $env:DOTNET_INSTALL_DIR -Force | Out-Null
- ps: $tempFileCurrent = [System.IO.Path]::GetTempFileName()
- ps: (New-Object System.Net.WebClient).DownloadFile($urlCurrent, $tempFileCurrent)
- ps: Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($tempFileCurrent, $env:DOTNET_INSTALL_DIR)
- ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path"
build_script:
- ps: Push-Location
- cd templates/package-builder
- npm install
- npm run build
- ps: Pop-Location
artifacts:
- path: templates\package-builder\dist\artifacts\generator-aspnetcore-spa.tar.gz
name: generator-aspnetcore-spa
- path: templates\package-builder\dist\artifacts\*.nupkg
name: Microsoft.AspNetCore.SpaTemplates
type: NuGetPackage
# - ps: .\build.ps1
- build.cmd verify
clone_depth: 1
test_script:
- dotnet restore
- ps: Push-Location
- cd test
- npm install selenium-standalone
- ps: Start-Process node './start-selenium.js'
- npm install
- npm test
on_finish :
- ps: Pop-Location
# After running tests, upload results to Appveyor
- ps: (new-object net.webclient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\test\tmp\junit\*.xml))
test: off
deploy: off

View File

@@ -1,6 +1,6 @@
$ErrorActionPreference = "Stop"
function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries)
function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $retries)
{
while($true)
{
@@ -19,7 +19,7 @@ function DownloadWithRetry([string] $url, [string] $downloadLocation, [int] $ret
Start-Sleep -Seconds 10
}
else
else
{
$exception = $_.Exception
throw $exception
@@ -43,18 +43,18 @@ $buildFolder = ".build"
$buildFile="$buildFolder\KoreBuild.ps1"
if (!(Test-Path $buildFolder)) {
Write-Host "Downloading KoreBuild from $koreBuildZip"
Write-Host "Downloading KoreBuild from $koreBuildZip"
$tempFolder=$env:TEMP + "\KoreBuild-" + [guid]::NewGuid()
New-Item -Path "$tempFolder" -Type directory | Out-Null
$localZipFile="$tempFolder\korebuild.zip"
DownloadWithRetry -url $koreBuildZip -downloadLocation $localZipFile -retries 6
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($localZipFile, $tempFolder)
New-Item -Path "$buildFolder" -Type directory | Out-Null
copy-item "$tempFolder\**\build\*" $buildFolder -Recurse
@@ -64,4 +64,4 @@ if (!(Test-Path $buildFolder)) {
}
}
&"$buildFile" @args
&"$buildFile" $args

View File

@@ -12,12 +12,12 @@ buildFile="$buildFolder/KoreBuild.sh"
if test ! -d $buildFolder; then
echo "Downloading KoreBuild from $koreBuildZip"
tempFolder="/tmp/KoreBuild-$(uuidgen)"
tempFolder="/tmp/KoreBuild-$(uuidgen)"
mkdir $tempFolder
localZipFile="$tempFolder/korebuild.zip"
retries=6
until (wget -O $localZipFile $koreBuildZip 2>/dev/null || curl -o $localZipFile --location $koreBuildZip 2>/dev/null)
do
@@ -29,18 +29,18 @@ if test ! -d $buildFolder; then
echo "Waiting 10 seconds before retrying. Retries left: $retries"
sleep 10s
done
unzip -q -d $tempFolder $localZipFile
mkdir $buildFolder
cp -r $tempFolder/**/build/** $buildFolder
chmod +x $buildFile
# Cleanup
if test -d $tempFolder; then
rm -rf $tempFolder
if test ! -d $tempFolder; then
rm -rf $tempFolder
fi
fi
$buildFile -r $repoFolder "$@"
$buildFile -r $repoFolder "$@"

View File

@@ -1,23 +0,0 @@
<Project>
<Import Project="dependencies.props" />
<Import Project="..\version.props" />
<PropertyGroup>
<Product>Microsoft ASP.NET Core</Product>
<RepositoryUrl>https://github.com/aspnet/javascriptservices</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)Key.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
<VersionSuffix Condition="'$(VersionSuffix)'!='' AND '$(BuildNumber)' != ''">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Internal.AspNetCore.Sdk" Version="$(InternalAspNetCoreSdkVersion)" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)'=='.NETFramework'">
<PackageReference Include="NETStandard.Library" Version="$(NETStandardImplicitPackageVersion)" />
<PackageReference Include="NETStandard.Library.NETFramework" Version="$(NETStandardLibraryNETFrameworkVersion)" />
</ItemGroup>
</Project>

View File

@@ -1,12 +0,0 @@
<Project>
<PropertyGroup>
<AspNetCoreVersion>2.0.0-*</AspNetCoreVersion>
<AutoMapperVersion>5.0.2</AutoMapperVersion>
<InternalAspNetCoreSdkVersion>2.1.0-*</InternalAspNetCoreSdkVersion>
<JsonNetVersion>10.0.1</JsonNetVersion>
<NETStandardImplicitPackageVersion>2.0.0-*</NETStandardImplicitPackageVersion>
<NETStandardLibraryNETFrameworkVersion>2.0.0-*</NETStandardLibraryNETFrameworkVersion>
<RuntimeFrameworkVersion Condition="'$(TargetFramework)'=='netcoreapp2.0'">2.0.0-*</RuntimeFrameworkVersion>
<ThreadingDataflowVersion>4.8.0-*</ThreadingDataflowVersion>
</PropertyGroup>
</Project>

3
global.json Normal file
View File

@@ -0,0 +1,3 @@
{
"projects": ["src"]
}

7
makefile.shade Executable file
View File

@@ -0,0 +1,7 @@
var VERSION='0.1'
var FULL_VERSION='0.1'
var AUTHORS='Microsoft Open Technologies, Inc.'
use-standard-lifecycle
k-standard-goals

22
pack-local.sh Executable file
View File

@@ -0,0 +1,22 @@
versionSuffix=$1
dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
projects=(
./src/Microsoft.AspNetCore.NodeServices
./src/Microsoft.AspNetCore.SpaServices
./src/Microsoft.AspNetCore.AngularServices
./src/Microsoft.AspNetCore.ReactServices
)
if [ -z "$versionSuffix" ]; then
echo "Usage: pack-local.sh <versionsuffix>"
echo "Example: pack-local.sh beta-000001"
exit 1
fi
pushd $dir > /dev/null
for proj in "${projects[@]}"; do
dotnet pack $proj --version-suffix $versionSuffix -o ./artifacts/
done
popd > /dev/null

10
samples/angular/MusicStore/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
/node_modules/
/wwwroot/lib/
/wwwroot/ng-app/**/*.js
/project.lock.json
/music-db.sqlite
/Properties/launchSettings.json
# Obviously you don't really want your DB to go in wwwroot, but due to https://github.com/aspnet/Microsoft.Data.Sqlite/issues/188
# it currently does when run from IIS Express. Will resolve this once RC2 is out, which is supposed to eliminate the inconsistency.
/wwwroot/music-db.sqlite

View File

@@ -0,0 +1,216 @@
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
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<ActionResult> 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<ActionResult> 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<ActionResult> 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<ActionResult> 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: Add null checking and return 404 in that case
return Json(albumResult);
}
[HttpPost]
[Authorize("app-ManageStore")]
public async Task<ActionResult> CreateAlbum([FromBody]AlbumChangeDto album)
{
if (!ModelState.IsValid)
{
// Return the model errors
return BadRequest(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<ActionResult> UpdateAlbum(int albumId, [FromBody] AlbumChangeDto album)
{
if (!ModelState.IsValid)
{
// Return the model errors
return BadRequest(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<ActionResult> DeleteAlbum(int albumId)
{
var album = await _storeContext.Albums.SingleOrDefaultAsync(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 : IValidatableObject
{
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 IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// An example of object-level (i.e., multi-property) validation
if (this.GenreId == 13 /* Indie */) {
switch (SentimentAnalysis.GetSentiment(Title)) {
case SentimentAnalysis.SentimentResult.Positive:
yield return new ValidationResult("Sounds too positive. Indie music requires more ambiguity.");
break;
case SentimentAnalysis.SentimentResult.Negative:
yield return new ValidationResult("Sounds too negative. Indie music requires more ambiguity.");
break;
}
}
}
}
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; }
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
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<ActionResult> Lookup()
{
var artists = await _storeContext.Artists
.OrderBy(a => a.Name)
.ToListAsync();
return Json(artists);
}
}
}

View File

@@ -0,0 +1,70 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
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<ActionResult> GenreList()
{
var genres = await _storeContext.Genres
//.Include(g => g.Albums)
.OrderBy(g => g.Name)
.ToListAsync();
return Json(genres);
}
[HttpGet("genre-lookup")]
public async Task<ActionResult> Lookup()
{
var genres = await _storeContext.Genres
.Select(g => new { g.GenreId, g.Name })
.ToListAsync();
return Json(genres);
}
[HttpGet("menu")]
public async Task<ActionResult> 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<ActionResult> 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);
}
}
}

View File

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

View File

@@ -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<OrderDetail>();
}
[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(decimal), "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<OrderDetail> OrderDetails { get; set; }
}
}

View File

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

View File

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

View File

@@ -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<Album>();
}
public int GenreId { get; set; }
[Required]
public string Name { get; set; }
public string Description { get; set; }
[JsonIgnore]
public virtual ICollection<Album> Albums { get; set; }
}
}

View File

@@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace MusicStore.Models
{
public class ApplicationUser : IdentityUser { }
public class MusicStoreContext : IdentityDbContext<ApplicationUser>
{
public MusicStoreContext(DbContextOptions options) : base(options)
{
}
public DbSet<Album> Albums { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Genre> Genres { get; set; }
public DbSet<CartItem> CartItems { get; set; }
public DbSet<OrderDetail> OrderDetails { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
// Configure pluralization
builder.Entity<Album>().ToTable("Albums");
builder.Entity<Artist>().ToTable("Artists");
builder.Entity<Order>().ToTable("Orders");
builder.Entity<Genre>().ToTable("Genres");
builder.Entity<CartItem>().ToTable("CartItems");
builder.Entity<OrderDetail>().ToTable("OrderDetails");
base.OnModelCreating(builder);
}
}
}

View File

@@ -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<OrderDetail>();
}
[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<OrderDetail> OrderDetails { get; set; }
}
}

View File

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

View File

@@ -0,0 +1,915 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
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<MusicStoreContext>())
{
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<TEntity>(
IServiceProvider serviceProvider,
Func<TEntity, object> propertyToMatch, IEnumerable<TEntity> entities)
where TEntity : class
{
// Query in a separate context so that we can attach existing entities as modified
List<TEntity> existingData;
using (var scope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
using (var db = scope.ServiceProvider.GetService<MusicStoreContext>())
{
existingData = db.Set<TEntity>().ToList();
}
using (var scope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
using (var db = scope.ServiceProvider.GetService<MusicStoreContext>())
{
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<string, Genre> genres, Dictionary<string, Artist> 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 19902000", 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<string, Artist> artists;
public static Dictionary<string, Artist> 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<string, Artist>();
foreach (Artist artist in artistsList)
{
artist.ArtistId = artistId++;
artists.Add(artist.Name, artist);
}
}
return artists;
}
}
private static Dictionary<string, Genre> genres;
public static Dictionary<string, Genre> 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<string, Genre>();
// 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;
}
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Linq;
using System.Text.RegularExpressions;
namespace MusicStore.Models
{
// Obviously this is not a serious sentiment analyser. It is only here to provide an amusing demonstration of cross-property
// validation in AlbumsApiController.
public static class SentimentAnalysis
{
private static string[] positiveSentimentWords = new[] { "happy", "fun", "joy", "love", "delight", "bunny", "bunnies", "asp.net" };
private static string[] negativeSentimentWords = new[] { "sad", "pain", "despair", "hate", "scorn", "death", "package management" };
public static SentimentResult GetSentiment(string text) {
var numPositiveWords = CountWordOccurrences(text, positiveSentimentWords);
var numNegativeWords = CountWordOccurrences(text, negativeSentimentWords);
if (numPositiveWords > numNegativeWords) {
return SentimentResult.Positive;
} else if (numNegativeWords > numPositiveWords) {
return SentimentResult.Negative;
} else {
return SentimentResult.Neutral;
}
}
private static int CountWordOccurrences(string text, string[] words)
{
// Very simplistic matching technique for this sample. Not scalable and not really even correct.
return new Regex(string.Join("|", words), RegexOptions.IgnoreCase).Matches(text).Count;
}
public enum SentimentResult {
Negative,
Neutral,
Positive,
}
}
}

View File

@@ -0,0 +1,207 @@
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
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<CartItem> 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;
}
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Mvc;
namespace MusicStore.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Error()
{
return View("~/Views/Shared/Error.cshtml");
}
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using System;
using Microsoft.AspNetCore.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);
}
}
}

View File

@@ -0,0 +1,150 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace MusicStore.Infrastructure
{
public interface IPagedList<T>
{
IEnumerable<T> Data { get; }
int Page { get; }
int PageSize { get; }
int TotalCount { get; }
}
internal class PagedList<T> : IPagedList<T>
{
public PagedList(IEnumerable<T> data, int page, int pageSize, int totalCount)
{
Data = data;
Page = page;
PageSize = pageSize;
TotalCount = totalCount;
}
public IEnumerable<T> 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<T> ToPagedList<T>(this IQueryable<T> 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<T>(data, pagingConfig.Page, pagingConfig.PageSize, query.Count());
}
public static Task<IPagedList<TModel>> ToPagedListAsync<TModel, TProperty>(this IQueryable<TModel> query, int page, int pageSize, string sortExpression, Expression<Func<TModel, TProperty>> defaultSortExpression, SortDirection defaultSortDirection = SortDirection.Ascending)
where TModel : class
{
return ToPagedListAsync<TModel, TProperty, TModel>(query, page, pageSize, sortExpression, defaultSortExpression, defaultSortDirection, null);
}
public static async Task<IPagedList<TResult>> ToPagedListAsync<TModel, TProperty, TResult>(this IQueryable<TModel> query, int page, int pageSize, string sortExpression, Expression<Func<TModel, TProperty>> defaultSortExpression, SortDirection defaultSortDirection, Func<TModel, TResult> 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<TResult>();
return new PagedList<TResult>(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; }
}
}
}

View File

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

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
namespace MusicStore.Infrastructure
{
public static class SortExpression
{
private const string SORT_DIRECTION_DESC = " DESC";
public static IQueryable<TModel> SortBy<TModel, TProperty>(this IQueryable<TModel> query, string sortExpression, Expression<Func<TModel, TProperty>> defaultSortExpression, SortDirection defaultSortDirection = SortDirection.Ascending) where TModel : class
{
return SortBy(query, sortExpression ?? Create(defaultSortExpression, defaultSortDirection));
}
public static string Create<TModel, TProperty>(Expression<Func<TModel, TProperty>> 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<T> SortBy<T>(this IQueryable<T> 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<T>)source.Provider.CreateQuery(methodCallExpression);
}
}
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>1a74148f-9dc0-435d-b5ac-7d1b0d3d5e0b</ProjectGuid>
<RootNamespace>MusicStore</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>5068</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using AutoMapper;
using MusicStore.Apis;
using MusicStore.Models;
namespace MusicStore
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = null;
});
// Add EF services to the service container
services.AddEntityFramework()
.AddEntityFrameworkSqlite()
.AddDbContext<MusicStoreContext>(options => {
options.UseSqlite("Data Source=music-db.sqlite");
});
// Add Identity services to the services container
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<MusicStoreContext>()
.AddDefaultTokenProviders();
// Configure Auth
services.Configure<AuthorizationOptions>(options =>
{
options.AddPolicy("app-ManageStore", new AuthorizationPolicyBuilder().RequireClaim("app-ManageStore", "Allowed").Build());
});
Mapper.Initialize(cfg =>
{
cfg.CreateMap<AlbumChangeDto, Album>();
cfg.CreateMap<Album, AlbumChangeDto>();
cfg.CreateMap<Album, AlbumResultDto>();
cfg.CreateMap<AlbumResultDto, Album>();
cfg.CreateMap<Artist, ArtistResultDto>();
cfg.CreateMap<ArtistResultDto, Artist>();
cfg.CreateMap<Genre, GenreResultDto>();
cfg.CreateMap<GenreResultDto, Genre>();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
// Initialize the sample data
SampleData.InitializeMusicStoreDatabaseAsync(app.ApplicationServices).Wait();
app.UseStaticFiles();
loggerFactory.AddConsole();
// Add MVC to the request pipeline.
app.UseMvc(routes =>
{
// Matches requests that correspond to an existent controller/action pair
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
// Matches any other request that doesn't appear to have a filename extension (defined as 'having a dot in the last URI segment').
// This means you'll correctly get 404s for /some/dir/non-existent-image.png instead of returning the SPA HTML.
// However, it means requests like /customers/isaac.newton will *not* be mapped into the SPA, so if you need to accept
// URIs like that you'll need to match all URIs, e.g.:
// routes.MapRoute("spa-fallback", "{*anything}", new { controller = "Home", action = "Index" });
// (which of course will match /customers/isaac.png too, so in that case it would serve the PNG image at that URL if one is on disk,
// or the SPA HTML if not).
routes.MapSpaFallbackRoute("spa-fallback", new { controller = "Home", action = "Index" });
});
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseKestrel()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

View File

@@ -0,0 +1,23 @@
@{
ViewData["Title"] = "Home Page";
}
<cache vary-by="@Context.Request.Path">
<app asp-prerender-module="wwwroot/ng-app/boot-server">Loading...</app>
@await Html.PrimeCacheAsync(Url.Action("GenreMenuList", "GenresApi"))
@await Html.PrimeCacheAsync(Url.Action("MostPopular", "AlbumsApi"))
</cache>
@section scripts {
<script src="~/lib/angular2/bundles/angular2-polyfills.js"></script>
<script src="~/lib/traceur/bin/traceur-runtime.js"></script>
<script src="~/lib/es6-module-loader/dist/es6-module-loader-sans-promises.js"></script>
<script src="~/lib/systemjs/dist/system.src.js"></script>
<script src="~/system.config.js"></script>
<script src="~/lib/rxjs/bundles/Rx.js"></script>
<script src="~/lib/angular2/bundles/angular2.dev.js"></script>
<script src="~/lib/angular2/bundles/router.dev.js"></script>
<script src="~/lib/angular2/bundles/http.dev.js"></script>
<script src="~/lib/angular2-aspnet/bundles/angular2-aspnet.js"></script>
<script>System.import('./ng-app/boot-client');</script>
}

View File

@@ -0,0 +1,6 @@
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

View File

@@ -0,0 +1,40 @@
<!doctype html>
<html lang="">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Music Store</title>
<base href="/" />
<environment names="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="hidden" asp-fallback-test-property="visibility" asp-fallback-test-value="hidden" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
</environment>
</head>
<body>
@RenderBody()
<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal">
</script>
</environment>
@RenderSection("scripts", required: false)
</body>
</html>

View File

@@ -0,0 +1,4 @@
@using MusicStore
@using Microsoft.AspNetCore.AngularServices
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"

53
samples/angular/MusicStore/gulpfile.js vendored Executable file
View File

@@ -0,0 +1,53 @@
/// <binding AfterBuild='build' Clean='clean' />
"use strict";
var path = require('path');
var gulp = require('gulp');
var del = require('del');
var typescript = require('gulp-typescript');
var inlineNg2Template = require('gulp-inline-ng2-template');
var sourcemaps = require('gulp-sourcemaps');
var webroot = "./wwwroot/";
var config = {
libBase: 'node_modules',
lib: [
require.resolve('bootstrap/dist/css/bootstrap.css'),
path.dirname(require.resolve('bootstrap/dist/fonts/glyphicons-halflings-regular.woff')) + '/**',
require.resolve('angular2/bundles/angular2-polyfills.js'),
require.resolve('traceur/bin/traceur-runtime.js'),
require.resolve('es6-module-loader/dist/es6-module-loader-sans-promises.js'),
require.resolve('systemjs/dist/system.src.js'),
require.resolve('angular2/bundles/angular2.dev.js'),
require.resolve('angular2/bundles/router.dev.js'),
require.resolve('angular2/bundles/http.dev.js'),
require.resolve('angular2-aspnet/bundles/angular2-aspnet.js'),
require.resolve('jquery/dist/jquery.js'),
require.resolve('bootstrap/dist/js/bootstrap.js'),
require.resolve('rxjs/bundles/Rx.js')
]
};
gulp.task('build.lib', function () {
return gulp.src(config.lib, { base: config.libBase })
.pipe(gulp.dest(webroot + 'lib'));
});
gulp.task('build', ['build.lib'], function () {
var tsProject = typescript.createProject('./tsconfig.json', { typescript: require('typescript') });
var tsSrcInlined = gulp.src([webroot + '**/*.ts', 'typings/**/*.d.ts'], { base: webroot })
.pipe(inlineNg2Template({ base: webroot }));
return tsSrcInlined
.pipe(sourcemaps.init())
.pipe(typescript(tsProject))
.pipe(sourcemaps.write())
.pipe(gulp.dest(webroot));
});
gulp.task('clean', function () {
return del([webroot + 'lib']);
});
gulp.task('default', ['build']);

View File

@@ -0,0 +1,30 @@
{
"name": "MusicStore",
"version": "0.0.0",
"dependencies": {
"angular2": "2.0.0-beta.15",
"angular2-aspnet": "^0.0.6",
"angular2-universal": "0.98.1",
"aspnet-prerendering": "^1.0.1",
"bootstrap": "^3.3.5",
"css": "^2.2.1",
"es6-module-loader": "0.15.0",
"es6-shim": "^0.35.0",
"isomorphic-fetch": "^2.2.1",
"jquery": "^2.1.4",
"less": "^2.5.3",
"preboot": "2.0.5",
"rxjs": "5.0.0-beta.2",
"systemjs": "^0.19.3",
"traceur": "0.0.106",
"zone.js": "^0.6.10"
},
"devDependencies": {
"del": "^2.0.2",
"gulp": "^3.9.0",
"gulp-inline-ng2-template": "0.0.7",
"gulp-sourcemaps": "^1.6.0",
"gulp-typescript": "^2.9.0",
"typescript": "^1.6.2"
}
}

View File

@@ -0,0 +1,79 @@
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"tooling": {
"defaultNamespace": "MusicStore"
},
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.1",
"type": "platform"
},
"Microsoft.AspNetCore.AngularServices": "1.0.0-*",
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
},
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.EntityFrameworkCore.SQLite": "1.0.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Configuration.CommandLine": "1.0.0",
"Microsoft.Extensions.Logging": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
"AutoMapper": "5.0.2"
},
"tools": {
"Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final",
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
"Microsoft.DotNet.Watcher.Tools": "1.0.0-preview2-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"publishOptions": {
"include": [
"appsettings.json",
"ClientApp",
"node_modules",
"typings",
"Views",
"tsconfig.json",
"tsd.json",
"web.config",
"webpack.*.js",
"wwwroot"
]
},
"scripts": {
"prepublish": [
"npm install",
"gulp"
],
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}

View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"sourceMap": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"noLib": false
},
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,12 @@
{
"version": "v4",
"repo": "borisyankov/DefinitelyTyped",
"ref": "master",
"path": "typings",
"bundle": "typings/tsd.d.ts",
"installed": {
"es6-shim/es6-shim.d.ts": {
"commit": "ec9eb4b28c74665a602c22db3457f0a76fa0fa23"
}
}
}

View File

@@ -0,0 +1,668 @@
// Type definitions for es6-shim v0.31.2
// Project: https://github.com/paulmillr/es6-shim
// Definitions by: Ron Buckton <http://github.com/rbuckton>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare type PropertyKey = string | number | symbol;
interface IteratorResult<T> {
done: boolean;
value?: T;
}
interface IterableShim<T> {
/**
* Shim for an ES6 iterable. Not intended for direct use by user code.
*/
"_es6-shim iterator_"(): Iterator<T>;
}
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
interface IterableIteratorShim<T> extends IterableShim<T>, Iterator<T> {
/**
* Shim for an ES6 iterable iterator. Not intended for direct use by user code.
*/
"_es6-shim iterator_"(): IterableIteratorShim<T>;
}
interface StringConstructor {
/**
* Return the String value whose elements are, in order, the elements in the List elements.
* If length is 0, the empty string is returned.
*/
fromCodePoint(...codePoints: number[]): string;
/**
* String.raw is intended for use as a tag function of a Tagged Template String. When called
* as such the first argument will be a well formed template call site object and the rest
* parameter will contain the substitution values.
* @param template A well-formed template string call site representation.
* @param substitutions A set of substitution values.
*/
raw(template: TemplateStringsArray, ...substitutions: any[]): string;
}
interface String {
/**
* Returns a nonnegative integer Number less than 1114112 (0x110000) that is the code point
* value of the UTF-16 encoded code point starting at the string element at position pos in
* the String resulting from converting this object to a String.
* If there is no element at that position, the result is undefined.
* If a valid UTF-16 surrogate pair does not begin at pos, the result is the code unit at pos.
*/
codePointAt(pos: number): number;
/**
* Returns true if searchString appears as a substring of the result of converting this
* object to a String, at one or more positions that are
* greater than or equal to position; otherwise, returns false.
* @param searchString search string
* @param position If position is undefined, 0 is assumed, so as to search all of the String.
*/
includes(searchString: string, position?: number): boolean;
/**
* Returns true if the sequence of elements of searchString converted to a String is the
* same as the corresponding elements of this object (converted to a String) starting at
* endPosition length(this). Otherwise returns false.
*/
endsWith(searchString: string, endPosition?: number): boolean;
/**
* Returns a String value that is made from count copies appended together. If count is 0,
* T is the empty String is returned.
* @param count number of copies to append
*/
repeat(count: number): string;
/**
* Returns true if the sequence of elements of searchString converted to a String is the
* same as the corresponding elements of this object (converted to a String) starting at
* position. Otherwise returns false.
*/
startsWith(searchString: string, position?: number): boolean;
/**
* Returns an <a> HTML anchor element and sets the name attribute to the text value
* @param name
*/
anchor(name: string): string;
/** Returns a <big> HTML element */
big(): string;
/** Returns a <blink> HTML element */
blink(): string;
/** Returns a <b> HTML element */
bold(): string;
/** Returns a <tt> HTML element */
fixed(): string
/** Returns a <font> HTML element and sets the color attribute value */
fontcolor(color: string): string
/** Returns a <font> HTML element and sets the size attribute value */
fontsize(size: number): string;
/** Returns a <font> HTML element and sets the size attribute value */
fontsize(size: string): string;
/** Returns an <i> HTML element */
italics(): string;
/** Returns an <a> HTML element and sets the href attribute value */
link(url: string): string;
/** Returns a <small> HTML element */
small(): string;
/** Returns a <strike> HTML element */
strike(): string;
/** Returns a <sub> HTML element */
sub(): string;
/** Returns a <sup> HTML element */
sup(): string;
/**
* Shim for an ES6 iterable. Not intended for direct use by user code.
*/
"_es6-shim iterator_"(): IterableIteratorShim<string>;
}
interface ArrayConstructor {
/**
* Creates an array from an array-like object.
* @param arrayLike An array-like object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from<T, U>(arrayLike: ArrayLike<T>, mapfn: (v: T, k: number) => U, thisArg?: any): Array<U>;
/**
* Creates an array from an iterable object.
* @param iterable An iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from<T, U>(iterable: IterableShim<T>, mapfn: (v: T, k: number) => U, thisArg?: any): Array<U>;
/**
* Creates an array from an array-like object.
* @param arrayLike An array-like object to convert to an array.
*/
from<T>(arrayLike: ArrayLike<T>): Array<T>;
/**
* Creates an array from an iterable object.
* @param iterable An iterable object to convert to an array.
*/
from<T>(iterable: IterableShim<T>): Array<T>;
/**
* Returns a new array from a set of elements.
* @param items A set of elements to include in the new array object.
*/
of<T>(...items: T[]): Array<T>;
}
interface Array<T> {
/**
* Returns the value of the first element in the array where predicate is true, and undefined
* otherwise.
* @param predicate find calls predicate once for each element of the array, in ascending
* order, until it finds one where predicate returns true. If such an element is found, find
* immediately returns that element value. Otherwise, find returns undefined.
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
find(predicate: (value: T, index: number, obj: Array<T>) => boolean, thisArg?: any): T;
/**
* Returns the index of the first element in the array where predicate is true, and undefined
* otherwise.
* @param predicate find calls predicate once for each element of the array, in ascending
* order, until it finds one where predicate returns true. If such an element is found, find
* immediately returns that element value. Otherwise, find returns undefined.
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
findIndex(predicate: (value: T) => boolean, thisArg?: any): number;
/**
* Returns the this object after filling the section identified by start and end with value
* @param value value to fill array section with
* @param start index to start filling the array at. If start is negative, it is treated as
* length+start where length is the length of the array.
* @param end index to stop filling the array at. If end is negative, it is treated as
* length+end.
*/
fill(value: T, start?: number, end?: number): T[];
/**
* Returns the this object after copying a section of the array identified by start and end
* to the same array starting at position target
* @param target If target is negative, it is treated as length+target where length is the
* length of the array.
* @param start If start is negative, it is treated as length+start. If end is negative, it
* is treated as length+end.
* @param end If not specified, length of the this object is used as its default value.
*/
copyWithin(target: number, start: number, end?: number): T[];
/**
* Returns an array of key, value pairs for every entry in the array
*/
entries(): IterableIteratorShim<[number, T]>;
/**
* Returns an list of keys in the array
*/
keys(): IterableIteratorShim<number>;
/**
* Returns an list of values in the array
*/
values(): IterableIteratorShim<T>;
/**
* Shim for an ES6 iterable. Not intended for direct use by user code.
*/
"_es6-shim iterator_"(): IterableIteratorShim<T>;
}
interface NumberConstructor {
/**
* The value of Number.EPSILON is the difference between 1 and the smallest value greater than 1
* that is representable as a Number value, which is approximately:
* 2.2204460492503130808472633361816 x 1016.
*/
EPSILON: number;
/**
* Returns true if passed value is finite.
* Unlike the global isFininte, Number.isFinite doesn't forcibly convert the parameter to a
* number. Only finite values of the type number, result in true.
* @param number A numeric value.
*/
isFinite(number: number): boolean;
/**
* Returns true if the value passed is an integer, false otherwise.
* @param number A numeric value.
*/
isInteger(number: number): boolean;
/**
* Returns a Boolean value that indicates whether a value is the reserved value NaN (not a
* number). Unlike the global isNaN(), Number.isNaN() doesn't forcefully convert the parameter
* to a number. Only values of the type number, that are also NaN, result in true.
* @param number A numeric value.
*/
isNaN(number: number): boolean;
/**
* Returns true if the value passed is a safe integer.
* @param number A numeric value.
*/
isSafeInteger(number: number): boolean;
/**
* The value of the largest integer n such that n and n + 1 are both exactly representable as
* a Number value.
* The value of Number.MIN_SAFE_INTEGER is 9007199254740991 2^53 1.
*/
MAX_SAFE_INTEGER: number;
/**
* The value of the smallest integer n such that n and n 1 are both exactly representable as
* a Number value.
* The value of Number.MIN_SAFE_INTEGER is 9007199254740991 ((2^53 1)).
*/
MIN_SAFE_INTEGER: number;
/**
* Converts a string to a floating-point number.
* @param string A string that contains a floating-point number.
*/
parseFloat(string: string): number;
/**
* Converts A string to an integer.
* @param s A string to convert into a number.
* @param radix A value between 2 and 36 that specifies the base of the number in numString.
* If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal.
* All other strings are considered decimal.
*/
parseInt(string: string, radix?: number): number;
}
interface ObjectConstructor {
/**
* Copy the values of all of the enumerable own properties from one or more source objects to a
* target object. Returns the target object.
* @param target The target object to copy to.
* @param sources One or more source objects to copy properties from.
*/
assign(target: any, ...sources: any[]): any;
/**
* Returns true if the values are the same value, false otherwise.
* @param value1 The first value.
* @param value2 The second value.
*/
is(value1: any, value2: any): boolean;
/**
* Sets the prototype of a specified object o to object proto or null. Returns the object o.
* @param o The object to change its prototype.
* @param proto The value of the new prototype or null.
* @remarks Requires `__proto__` support.
*/
setPrototypeOf(o: any, proto: any): any;
}
interface RegExp {
/**
* Returns a string indicating the flags of the regular expression in question. This field is read-only.
* The characters in this string are sequenced and concatenated in the following order:
*
* - "g" for global
* - "i" for ignoreCase
* - "m" for multiline
* - "u" for unicode
* - "y" for sticky
*
* If no flags are set, the value is the empty string.
*/
flags: string;
}
interface Math {
/**
* Returns the number of leading zero bits in the 32-bit binary representation of a number.
* @param x A numeric expression.
*/
clz32(x: number): number;
/**
* Returns the result of 32-bit multiplication of two numbers.
* @param x First number
* @param y Second number
*/
imul(x: number, y: number): number;
/**
* Returns the sign of the x, indicating whether x is positive, negative or zero.
* @param x The numeric expression to test
*/
sign(x: number): number;
/**
* Returns the base 10 logarithm of a number.
* @param x A numeric expression.
*/
log10(x: number): number;
/**
* Returns the base 2 logarithm of a number.
* @param x A numeric expression.
*/
log2(x: number): number;
/**
* Returns the natural logarithm of 1 + x.
* @param x A numeric expression.
*/
log1p(x: number): number;
/**
* Returns the result of (e^x - 1) of x (e raised to the power of x, where e is the base of
* the natural logarithms).
* @param x A numeric expression.
*/
expm1(x: number): number;
/**
* Returns the hyperbolic cosine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
cosh(x: number): number;
/**
* Returns the hyperbolic sine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
sinh(x: number): number;
/**
* Returns the hyperbolic tangent of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
tanh(x: number): number;
/**
* Returns the inverse hyperbolic cosine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
acosh(x: number): number;
/**
* Returns the inverse hyperbolic sine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
asinh(x: number): number;
/**
* Returns the inverse hyperbolic tangent of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
atanh(x: number): number;
/**
* Returns the square root of the sum of squares of its arguments.
* @param values Values to compute the square root for.
* If no arguments are passed, the result is +0.
* If there is only one argument, the result is the absolute value.
* If any argument is +Infinity or -Infinity, the result is +Infinity.
* If any argument is NaN, the result is NaN.
* If all arguments are either +0 or 0, the result is +0.
*/
hypot(...values: number[]): number;
/**
* Returns the integral part of the a numeric expression, x, removing any fractional digits.
* If x is already an integer, the result is x.
* @param x A numeric expression.
*/
trunc(x: number): number;
/**
* Returns the nearest single precision float representation of a number.
* @param x A numeric expression.
*/
fround(x: number): number;
/**
* Returns an implementation-dependent approximation to the cube root of number.
* @param x A numeric expression.
*/
cbrt(x: number): number;
}
interface PromiseLike<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => TResult | PromiseLike<TResult>): PromiseLike<TResult>;
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => void): PromiseLike<TResult>;
}
/**
* Represents the completion of an asynchronous operation
*/
interface Promise<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => TResult | PromiseLike<TResult>): Promise<TResult>;
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => void): Promise<TResult>;
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch(onrejected?: (reason: any) => T | PromiseLike<T>): Promise<T>;
catch(onrejected?: (reason: any) => void): Promise<T>;
}
interface PromiseConstructor {
/**
* A reference to the prototype.
*/
prototype: Promise<any>;
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T>(values: IterableShim<T | PromiseLike<T>>): Promise<T[]>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T>(values: IterableShim<T | PromiseLike<T>>): Promise<T>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject(reason: any): Promise<void>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject<T>(reason: any): Promise<T>;
/**
* Creates a new resolved promise for the provided value.
* @param value A promise.
* @returns A promise whose internal state matches the provided promise.
*/
resolve<T>(value: T | PromiseLike<T>): Promise<T>;
/**
* Creates a new resolved promise .
* @returns A resolved promise.
*/
resolve(): Promise<void>;
}
declare var Promise: PromiseConstructor;
interface Map<K, V> {
clear(): void;
delete(key: K): boolean;
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void;
get(key: K): V;
has(key: K): boolean;
set(key: K, value?: V): Map<K, V>;
size: number;
entries(): IterableIteratorShim<[K, V]>;
keys(): IterableIteratorShim<K>;
values(): IterableIteratorShim<V>;
}
interface MapConstructor {
new <K, V>(): Map<K, V>;
new <K, V>(iterable: IterableShim<[K, V]>): Map<K, V>;
prototype: Map<any, any>;
}
declare var Map: MapConstructor;
interface Set<T> {
add(value: T): Set<T>;
clear(): void;
delete(value: T): boolean;
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void;
has(value: T): boolean;
size: number;
entries(): IterableIteratorShim<[T, T]>;
keys(): IterableIteratorShim<T>;
values(): IterableIteratorShim<T>;
}
interface SetConstructor {
new <T>(): Set<T>;
new <T>(iterable: IterableShim<T>): Set<T>;
prototype: Set<any>;
}
declare var Set: SetConstructor;
interface WeakMap<K, V> {
delete(key: K): boolean;
get(key: K): V;
has(key: K): boolean;
set(key: K, value?: V): WeakMap<K, V>;
}
interface WeakMapConstructor {
new <K, V>(): WeakMap<K, V>;
new <K, V>(iterable: IterableShim<[K, V]>): WeakMap<K, V>;
prototype: WeakMap<any, any>;
}
declare var WeakMap: WeakMapConstructor;
interface WeakSet<T> {
add(value: T): WeakSet<T>;
delete(value: T): boolean;
has(value: T): boolean;
}
interface WeakSetConstructor {
new <T>(): WeakSet<T>;
new <T>(iterable: IterableShim<T>): WeakSet<T>;
prototype: WeakSet<any>;
}
declare var WeakSet: WeakSetConstructor;
declare namespace Reflect {
function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any;
function construct(target: Function, argumentsList: ArrayLike<any>): any;
function defineProperty(target: any, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean;
function deleteProperty(target: any, propertyKey: PropertyKey): boolean;
function enumerate(target: any): IterableIteratorShim<any>;
function get(target: any, propertyKey: PropertyKey, receiver?: any): any;
function getOwnPropertyDescriptor(target: any, propertyKey: PropertyKey): PropertyDescriptor;
function getPrototypeOf(target: any): any;
function has(target: any, propertyKey: PropertyKey): boolean;
function isExtensible(target: any): boolean;
function ownKeys(target: any): Array<PropertyKey>;
function preventExtensions(target: any): boolean;
function set(target: any, propertyKey: PropertyKey, value: any, receiver?: any): boolean;
function setPrototypeOf(target: any, proto: any): boolean;
}
declare module "es6-shim" {
var String: StringConstructor;
var Array: ArrayConstructor;
var Number: NumberConstructor;
var Math: Math;
var Object: ObjectConstructor;
var Map: MapConstructor;
var Set: SetConstructor;
var WeakMap: WeakMapConstructor;
var WeakSet: WeakSetConstructor;
var Promise: PromiseConstructor;
namespace Reflect {
function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any;
function construct(target: Function, argumentsList: ArrayLike<any>): any;
function defineProperty(target: any, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean;
function deleteProperty(target: any, propertyKey: PropertyKey): boolean;
function enumerate(target: any): Iterator<any>;
function get(target: any, propertyKey: PropertyKey, receiver?: any): any;
function getOwnPropertyDescriptor(target: any, propertyKey: PropertyKey): PropertyDescriptor;
function getPrototypeOf(target: any): any;
function has(target: any, propertyKey: PropertyKey): boolean;
function isExtensible(target: any): boolean;
function ownKeys(target: any): Array<PropertyKey>;
function preventExtensions(target: any): boolean;
function set(target: any, propertyKey: PropertyKey, value: any, receiver?: any): boolean;
function setPrototypeOf(target: any, proto: any): boolean;
}
}

View File

@@ -0,0 +1,2 @@
/// <reference path="es6-shim/es6-shim.d.ts" />

View File

@@ -0,0 +1,7 @@
// This file is a workaround for angular2-universal-preview version 0.84.2 relying on the declaration of
// Node's 'url' module. Ideally it would not declare dependencies on Node APIs except where it also supplies
// the definitions itself.
declare module 'url' {
export interface Url {}
}

View File

@@ -0,0 +1,3 @@
body {
padding-top: 50px;
}

View File

@@ -0,0 +1,14 @@
@base: #f938ab;
.box-shadow(@style, @c) when (iscolor(@c)) {
-webkit-box-shadow: @style @c;
box-shadow: @style @c;
}
.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
.box-shadow(@style, rgba(0, 0, 0, @alpha));
}
.box {
color: saturate(@base, 5%);
border-color: lighten(@base, 30%);
div { .box-shadow(0 0 5px, 30%) }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,8 @@
import { bootstrap } from 'angular2/platform/browser';
import { FormBuilder } from 'angular2/common';
import * as router from 'angular2/router';
import { Http, HTTP_PROVIDERS } from 'angular2/http';
import { CACHE_PRIMED_HTTP_PROVIDERS } from 'angular2-aspnet';
import { App } from './components/app/app';
bootstrap(App, [router.ROUTER_BINDINGS, HTTP_PROVIDERS, CACHE_PRIMED_HTTP_PROVIDERS, FormBuilder]);

View File

@@ -0,0 +1,30 @@
import 'angular2-universal/polyfills';
import { FormBuilder } from 'angular2/common';
import * as ngCore from 'angular2/core';
import * as ngRouter from 'angular2/router';
import * as ngUniversal from 'angular2-universal';
import { BASE_URL, ORIGIN_URL, REQUEST_URL } from 'angular2-universal/common';
import { App } from './components/app/app';
export default function (params: any): Promise<{ html: string, globals?: any }> {
const serverBindings = [
ngCore.provide(BASE_URL, { useValue: '/' }),
ngCore.provide(ORIGIN_URL, { useValue: params.origin }),
ngCore.provide(REQUEST_URL, { useValue: params.url }),
ngUniversal.NODE_HTTP_PROVIDERS,
ngUniversal.NODE_ROUTER_PROVIDERS,
FormBuilder
];
return ngUniversal.bootloader({
directives: [App],
providers: serverBindings,
async: true,
preboot: false,
// TODO: Render just the <app> component instead of wrapping it inside an extra HTML document
// Waiting on https://github.com/angular/universal/issues/347
template: '<!DOCTYPE html>\n<html><head></head><body><app></app></body></html>'
}).serializeApplication().then(html => {
return { html };
});
}

View File

@@ -0,0 +1,3 @@
<h1>Store Manager</h1>
<router-outlet></router-outlet>

View File

@@ -0,0 +1,19 @@
import * as ng from 'angular2/core';
import * as router from 'angular2/router';
import { AlbumsList } from '../albums-list/albums-list';
import { AlbumDetails } from '../album-details/album-details';
import { AlbumEdit } from '../album-edit/album-edit';
@ng.Component({
selector: 'admin-home',
templateUrl: './ng-app/components/admin/admin-home/admin-home.html',
directives: [router.ROUTER_DIRECTIVES]
})
@router.RouteConfig([
{ path: 'albums', name: 'Albums', component: AlbumsList },
{ path: 'album/details/:albumId', name: 'AlbumDetails', component: AlbumDetails },
{ path: 'album/edit/:albumId', name: 'AlbumEdit', component: AlbumEdit }
])
export class AdminHome {
}

View File

@@ -0,0 +1,17 @@
<div class="modal fade">
<div class="modal-dialog" *ngIf="album">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Delete {{ album.Title }}</h4>
</div>
<div class="modal-body">
<p>Really delete <strong>{{ album.Title }}</strong> by <strong>{{ album.Artist.Name }}</strong>?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger">Confirm Delete</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

View File

@@ -0,0 +1,20 @@
import * as ng from 'angular2/core';
import * as models from '../../../models/models';
@ng.Component({
selector: 'album-delete-prompt',
templateUrl: './ng-app/components/admin/album-delete-prompt/album-delete-prompt.html'
})
export class AlbumDeletePrompt {
public album: models.Album;
constructor(@ng.Inject(ng.ElementRef) private _elementRef: ng.ElementRef) {
}
public show(album: models.Album) {
this.album = album;
// Consider rewriting this using Angular 2's "Renderer" API so as to avoid direct DOM access
(<any>window).jQuery(".modal", this._elementRef.nativeElement).modal();
}
}

View File

@@ -0,0 +1,50 @@
<h2>Album <small>Details</small></h2>
<hr />
<form class="form-horizontal" role="form" *ngIf="albumData">
<div class="form-group">
<label class="col-md-2 control-label">Artist</label>
<div class="col-md-10">
<p class="form-control-static">{{ albumData.Artist.Name }}</p>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Genre</label>
<div class="col-md-10">
<p class="form-control-static">{{ albumData.Genre.Name }}</p>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Title</label>
<div class="col-md-10">
<p class="form-control-static">{{ albumData.Title }}</p>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Price</label>
<div class="col-md-10">
<p class="form-control-static">{{ albumData.Price | currency:'USD':true }}</p>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Album Art URL</label>
<div class="col-md-10">
<p class="form-control-static">{{ albumData.AlbumArtUrl }}</p>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Album Art</label>
<div class="col-md-10">
<img alt="{{ albumData.Title }}" src="{{ albumData.AlbumArtUrl }}">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<a class="btn btn-primary" [routerLink]="['/Admin/AlbumEdit', { albumId: albumData.AlbumId }]">Edit</a>
<button type="button" class="btn btn-danger" (click)="deleteprompt.show(albumData)">Delete</button>
<a class="btn btn-default" [routerLink]="['/Admin/Albums']">Back to List</a>
</div>
</div>
</form>
<album-delete-prompt #deleteprompt></album-delete-prompt>

View File

@@ -0,0 +1,20 @@
import * as ng from 'angular2/core';
import * as router from 'angular2/router';
import * as models from '../../../models/models';
import { Http, HTTP_BINDINGS } from 'angular2/http';
import { AlbumDeletePrompt } from '../album-delete-prompt/album-delete-prompt';
@ng.Component({
selector: 'album-details',
templateUrl: './ng-app/components/admin/album-details/album-details.html',
directives: [router.ROUTER_DIRECTIVES, AlbumDeletePrompt]
})
export class AlbumDetails {
public albumData: models.Album;
constructor(http: Http, routeParam: router.RouteParams) {
http.get('/api/albums/' + routeParam.params['albumId']).subscribe(result => {
this.albumData = result.json();
});
}
}

View File

@@ -0,0 +1,47 @@
<h2>Album <small>Edit</small></h2>
<hr />
<form class="form-horizontal" [ngFormModel]="form" (ngSubmit)="onSubmitModelBased()">
<form-field label="Artist" [validate]="form.controls.ArtistId">
<select class="form-control" ngControl="ArtistId">
<option value="0">-- choose Artist --</option>
<option *ngFor="#artist of artists" [value]="artist.ArtistId">{{ artist.Name }}</option>
</select>
</form-field>
<form-field label="Genre" [validate]="form.controls.GenreId">
<select class="form-control" ngControl="GenreId">
<option value="0">-- choose Genre --</option>
<option *ngFor="#genre of genres" [value]="genre.GenreId">{{ genre.Name }}</option>
</select>
</form-field>
<form-field label="Title" [validate]="form.controls.Title">
<input class="form-control" type="text" ngControl="Title">
</form-field>
<form-field label="Price" [validate]="form.controls.Price">
<div class="input-group">
<span class="input-group-addon">$</span>
<input class="form-control" type="text" ngControl="Price">
</div>
</form-field>
<form-field label="Album Art URL" [validate]="form.controls.AlbumArtUrl">
<input class="form-control" ngControl="AlbumArtUrl">
</form-field>
<form-field label="Album Art">
<img src="{{ form.controls.AlbumArtUrl.value }}">
</form-field>
<form-field>
<div *ngIf="changesSaved" class="alert alert-success"><b>Done!</b> Your changes were saved.</div>
<div *ngFor="#errorMessage of formErrors" class="alert alert-danger">{{ errorMessage }}</div>
<button type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-danger" (click)="deleteprompt.show(originalAlbum)">Delete</button>
<a class="btn btn-default" [routerLink]="['/Admin/Albums']">Back to List</a>
</form-field>
</form>
<album-delete-prompt #deleteprompt></album-delete-prompt>

View File

@@ -0,0 +1,94 @@
import * as ng from 'angular2/core';
import { Observable } from 'rxjs';
import { Control, ControlGroup, FormBuilder, Validators, FORM_DIRECTIVES } from 'angular2/common';
import * as router from 'angular2/router';
import * as models from '../../../models/models';
import { Http, HTTP_BINDINGS, Headers, Response } from 'angular2/http';
import { AlbumDeletePrompt } from '../album-delete-prompt/album-delete-prompt';
import { FormField } from '../form-field/form-field';
import * as AspNet from 'angular2-aspnet';
@ng.Component({
selector: 'album-edit',
templateUrl: './ng-app/components/admin/album-edit/album-edit.html',
directives: [router.ROUTER_DIRECTIVES, AlbumDeletePrompt, FormField, FORM_DIRECTIVES]
})
export class AlbumEdit {
public form: ControlGroup;
public artists: models.Artist[];
public genres: models.Genre[];
public originalAlbum: models.Album;
public changesSaved: boolean;
public formErrors: string[] = [];
private _http: Http;
constructor(fb: FormBuilder, http: Http, routeParam: router.RouteParams) {
this._http = http;
var albumId = parseInt(routeParam.params['albumId']);
http.get('/api/albums/' + albumId).subscribe(result => {
var json = result.json();
this.originalAlbum = json;
(<Control>this.form.controls['Title']).updateValue(json.Title);
(<Control>this.form.controls['Price']).updateValue(json.Price);
(<Control>this.form.controls['ArtistId']).updateValue(json.ArtistId);
(<Control>this.form.controls['GenreId']).updateValue(json.GenreId);
(<Control>this.form.controls['AlbumArtUrl']).updateValue(json.AlbumArtUrl);
});
http.get('/api/artists/lookup').subscribe(result => {
this.artists = result.json();
});
http.get('/api/genres/genre-lookup').subscribe(result => {
this.genres = result.json();
});
this.form = fb.group(<any>{
AlbumId: fb.control(albumId),
ArtistId: fb.control(0, Validators.required),
GenreId: fb.control(0, Validators.required),
Title: fb.control('', Validators.required),
Price: fb.control('', Validators.compose([Validators.required, AlbumEdit._validatePrice])),
AlbumArtUrl: fb.control('', Validators.required)
});
this.form.valueChanges.subscribe(() => {
this.changesSaved = false;
});
}
public onSubmitModelBased() {
// Force all fields to show any validation errors even if they haven't been touched
Object.keys(this.form.controls).forEach(controlName => {
this.form.controls[controlName].markAsTouched();
});
if (this.form.valid) {
var controls = this.form.controls;
var albumId = this.originalAlbum.AlbumId;
this._putJson(`/api/albums/${ albumId }/update`, this.form.value).subscribe(successResponse => {
this.changesSaved = true;
}, errorResponse => {
AspNet.Validation.showValidationErrors(errorResponse, this.form);
});
}
}
private static _validatePrice(control: Control): { [key: string]: boolean } {
return /^\d+\.\d+$/.test(control.value) ? null : { Price: true };
}
// Need feedback on whether this really is the easiest way to PUT some JSON
private _putJson(url: string, body: any): Observable<Response> {
return this._http.put(url, JSON.stringify(body), {
headers: new Headers({ 'Content-Type': 'application/json' })
});
}
private ngDoCheck() {
this.formErrors = this.form.dirty ? Object.keys(this.form.errors || {}) : [];
}
}

View File

@@ -0,0 +1,44 @@
<h2>Albums</h2>
<album-delete-prompt #deleteprompt></album-delete-prompt>
<table class="table">
<thead>
<tr>
<th><a>Genre</a></th>
<th><a>Artist</a></th>
<th><a (click)="sortBy('Title')">Title</a></th>
<th><a (click)="sortBy('Price')">Price</a></th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="#row of rows">
<td>{{ row.Genre.Name }}</td>
<td>{{ row.Artist.Name }}</td>
<td>{{ row.Title }}</td>
<td>{{ row.Price | currency:'USD':true }}</td>
<td>
<div class="btn-group btn-group-xs">
<a class="btn btn-default" [routerLink]="['/Admin/AlbumDetails', { albumId: row.AlbumId }]">Details</a>
<a class="btn btn-default" [routerLink]="['/Admin/AlbumEdit', { albumId: row.AlbumId }]">Edit</a>
<a class="btn btn-default" (click)="deleteprompt.show(row)">Delete</a>
</div>
</td>
</tr>
</tbody>
</table>
<div class="btn-group">
<button class="btn btn-default" [disabled]="!canGoBack" (click)="goToPage(1)">First</button>
<button class="btn btn-default" [disabled]="!canGoBack" (click)="goToPage(pageIndex - 1)">Previous</button>
<button class="btn" *ngFor="#page of pageLinks"
[ngClass]="{ 'btn-info': page.isCurrent, 'btn-default': !page.isCurrent }"
(click)="goToPage(page.index)">
{{ page.text }}
</button>
<button class="btn btn-default" [disabled]="!canGoForward" (click)="goToPage(pageIndex + 1)">Next</button>
<button class="btn btn-default" [disabled]="!canGoForward" (click)="goToLast()">Last</button>
</div>
<p>{{ totalCount }} total albums</p>

View File

@@ -0,0 +1,68 @@
import * as ng from 'angular2/core';
import * as router from 'angular2/router';
import { Http, HTTP_BINDINGS } from 'angular2/http';
import * as models from '../../../models/models';
import { AlbumDeletePrompt } from '../album-delete-prompt/album-delete-prompt';
@ng.Component({
selector: 'albums-list',
templateUrl: './ng-app/components/admin/albums-list/albums-list.html',
directives: [router.ROUTER_DIRECTIVES, AlbumDeletePrompt]
})
export class AlbumsList {
public rows: models.Album[];
public canGoBack: boolean;
public canGoForward: boolean;
public pageLinks: any[];
public totalCount: number;
public get pageIndex() {
return this._pageIndex;
}
private _http: Http;
private _pageIndex = 1;
private _sortBy = "Title";
private _sortByDesc = false;
constructor(http: Http) {
this._http = http;
this.refreshData();
}
public sortBy(col: string) {
this._sortByDesc = col === this._sortBy ? !this._sortByDesc : false;
this._sortBy = col;
this.refreshData();
}
public goToPage(pageIndex: number) {
this._pageIndex = pageIndex;
this.refreshData();
}
public goToLast() {
this.goToPage(this.pageLinks[this.pageLinks.length - 1].index);
}
refreshData() {
var sortBy = this._sortBy + (this._sortByDesc ? ' DESC' : '');
this._http.get(`/api/albums?page=${ this._pageIndex }&pageSize=50&sortBy=${ sortBy }`).subscribe(result => {
var json = result.json();
this.rows = json.Data;
var numPages = Math.ceil(json.TotalCount / json.PageSize);
this.pageLinks = [];
for (var i = 1; i <= numPages; i++) {
this.pageLinks.push({
index: i,
text: i.toString(),
isCurrent: i === json.Page
});
}
this.canGoBack = this.pageLinks.length && !this.pageLinks[0].isCurrent;
this.canGoForward = this.pageLinks.length && !this.pageLinks[this.pageLinks.length - 1].isCurrent;
this.totalCount = json.TotalCount;
});
}
}

View File

@@ -0,0 +1,9 @@
<div class="form-group" [class.has-error]="errorMessages.length">
<label class="col-md-2 control-label">{{ label }}</label>
<div class="col-md-5">
<ng-content></ng-content>
<div class="alert alert-danger" role="alert" *ngIf="errorMessages.length">
<p *ngFor="#message of errorMessages">{{ message }}</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,19 @@
import * as ng from 'angular2/core';
import { AbstractControl } from 'angular2/common';
@ng.Component({
selector: 'form-field',
properties: ['label', 'validate'],
templateUrl: './ng-app/components/admin/form-field/form-field.html'
})
export class FormField {
public errorMessages: string[] = [];
private validate: AbstractControl;
private ngDoCheck() {
var errors = (this.validate && this.validate.dirty && this.validate.errors) || {};
this.errorMessages = Object.keys(errors).map(key => {
return 'Error: ' + key;
});
}
}

View File

@@ -0,0 +1,30 @@
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" [routerLink]="['/Home']">Music Store</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a [routerLink]="['/Home']">Home</a></li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown">Store <b class="caret"></b></a>
<ul class="dropdown-menu">
<li *ngFor="#genre of genres">
<a title="{{ genre.Description }}" [routerLink]="['/Genre', { genreId: genre.GenreId }]">
{{ genre.Name }}
</a>
</li>
<li class="divider"></li>
<li>
<a [routerLink]="['/GenresList']">More…</a>
</li>
</ul>
</li>
<li><a [routerLink]="['/Admin/Albums']">Admin</a></li>
</ul>
</div>
</div>
</nav>
<div class="container body-content">
<router-outlet></router-outlet>
</div>

View File

@@ -0,0 +1,33 @@
import * as ng from 'angular2/core';
import * as router from 'angular2/router';
import { Http, HTTP_BINDINGS } from 'angular2/http';
import { Home } from '../public/home/home';
import { AlbumDetails } from '../public/album-details/album-details';
import { GenreContents } from '../public/genre-contents/genre-contents';
import { GenresList } from '../public/genres-list/genres-list';
import { AdminHome } from '../admin/admin-home/admin-home';
import * as models from '../../models/models';
@ng.Component({
selector: 'app',
templateUrl: './ng-app/components/app/app.html',
styleUrls: ['./ng-app/components/app/app.css'],
directives: [router.ROUTER_DIRECTIVES]
})
@router.RouteConfig([
{ path: '/', component: Home, name: 'Home' },
{ path: '/album/:albumId', component: AlbumDetails, name: 'Album' },
{ path: '/genre/:genreId', component: GenreContents, name: 'Genre' },
{ path: '/genres', component: GenresList, name: 'GenresList' },
{ path: '/admin/...', component: AdminHome, name: 'Admin' }
])
export class App {
public genres: models.Genre[];
constructor(http: Http) {
http.get('/api/genres/menu').subscribe(result => {
this.genres = result.json();
});
}
}

View File

@@ -0,0 +1,26 @@
<div *ngIf="albumData">
<h2>{{ albumData.Title }}</h2>
<p>
<img alt="{{ albumData.Title }}" src="{{ albumData.AlbumArtUrl }}">
</p>
<div id="album-details">
<p>
<em>Genre:</em>
{{ albumData.Genre.Name }}
</p>
<p>
<em>Artist:</em>
{{ albumData.Artist.Name }}
</p>
<p>
<em>Price:</em>
{{ albumData.Price | currency:'USD':true }}
</p>
<p class="button">
<!-- TODO: Shopping cart functionality -->
Add to cart
</p>
</div>
</div>

View File

@@ -0,0 +1,18 @@
import * as ng from 'angular2/core';
import * as router from 'angular2/router';
import { Http } from 'angular2/http';
import * as models from '../../../models/models';
@ng.Component({
selector: 'album-details',
templateUrl: './ng-app/components/public/album-details/album-details.html'
})
export class AlbumDetails {
public albumData: models.Album;
constructor(http: Http, routeParam: router.RouteParams) {
http.get('/api/albums/' + routeParam.params['albumId']).subscribe(result => {
this.albumData = result.json();
});
}
}

View File

@@ -0,0 +1,4 @@
<a [routerLink]="['/Album', { albumId: albumData.AlbumId }]">
<img alt="{{ albumData.Title }}" src="{{ albumData.AlbumArtUrl }}">
<h4>{{ albumData.Title }}</h4>
</a>

View File

@@ -0,0 +1,12 @@
import * as ng from 'angular2/core';
import * as router from 'angular2/router';
import * as models from '../../../models/models';
@ng.Component({
selector: 'album-tile',
properties: ['albumData'],
templateUrl: './ng-app/components/public/album-tile/album-tile.html',
directives: [router.ROUTER_DIRECTIVES]
})
export class AlbumTile {
}

View File

@@ -0,0 +1,7 @@
<h3>Albums</h3>
<ul class="list-unstyled">
<li *ngFor="#album of albums" class="col-lg-2 col-md-2 col-sm-2 col-xs-4 container">
<album-tile [albumData]="album"></album-tile>
</li>
</ul>

View File

@@ -0,0 +1,20 @@
import * as ng from 'angular2/core';
import * as router from 'angular2/router';
import { Http } from 'angular2/http';
import * as models from '../../../models/models';
import { AlbumTile } from '../album-tile/album-tile';
@ng.Component({
selector: 'genre-contents',
templateUrl: './ng-app/components/public/genre-contents/genre-contents.html',
directives: [AlbumTile]
})
export class GenreContents {
public albums: models.Album[];
constructor(http: Http, routeParam: router.RouteParams) {
http.get(`/api/genres/${ routeParam.params['genreId'] }/albums`).subscribe(result => {
this.albums = result.json();
});
}
}

View File

@@ -0,0 +1,13 @@
<h3>Browse Genres</h3>
<p *ngIf="genres">
Select from {{ genres.length }} genres:
</p>
<ul class="list-group">
<li *ngFor="#genre of genres" class="list-group-item">
<a title="{{genre.Description}}" [routerLink]="['/Genre', { genreId: genre.GenreId }]">
{{ genre.Name }}
</a>
</li>
</ul>

View File

@@ -0,0 +1,19 @@
import * as ng from 'angular2/core';
import * as router from 'angular2/router';
import { Http } from 'angular2/http';
import * as models from '../../../models/models';
@ng.Component({
selector: 'genres-list',
templateUrl: './ng-app/components/public/genres-list/genres-list.html',
directives: [router.ROUTER_DIRECTIVES]
})
export class GenresList {
public genres: models.Genre[];
constructor(http: Http) {
http.get('/api/genres').subscribe(result => {
this.genres = result.json();
});
}
}

View File

@@ -0,0 +1,10 @@
<div class="jumbotron">
<h1>MVC Music Store</h1>
<img src="/Images/home-showcase.png">
</div>
<ul class="row list-unstyled" id="album-list">
<li *ngFor="#album of mostPopular" class="col-lg-2 col-md-2 col-sm-2 col-xs-4 container">
<album-tile [albumData]="album"></album-tile>
</li>
</ul>

View File

@@ -0,0 +1,19 @@
import * as ng from 'angular2/core';
import { Http } from 'angular2/http';
import { AlbumTile } from '../album-tile/album-tile';
import * as models from '../../../models/models';
@ng.Component({
selector: 'home',
templateUrl: './ng-app/components/public/home/home.html',
directives: [AlbumTile]
})
export class Home {
public mostPopular: models.Album[];
constructor(http: Http) {
http.get('/api/albums/mostPopular').subscribe(result => {
this.mostPopular = result.json();
});
}
}

View File

@@ -0,0 +1,16 @@
export interface Album {
AlbumId: number;
Title: string;
AlbumArtUrl: string;
}
export interface Genre {
GenreId: number;
Name: string;
Description: string;
}
export interface Artist {
ArtistId: number;
Name: string;
}

View File

@@ -0,0 +1,3 @@
System.config({
defaultJSExtensions: true
});

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" />
</handlers>
<httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false" forwardWindowsAuthToken="false" startupTimeLimit="3600" />
</system.webServer>
</configuration>

View File

@@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\build\common.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<IsPackable>false</IsPackable>
<OutputType>exe</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.NodeServices.Sockets\Microsoft.AspNetCore.NodeServices.Sockets.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(AspNetCoreVersion)" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.25123" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25123</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>a64af9d9-72aa-4433-be1d-dc2524b6808a</ProjectGuid>
<RootNamespace>LatencyTest</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -3,7 +3,6 @@ using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.NodeServices.Sockets;
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApplication
@@ -17,10 +16,7 @@ namespace ConsoleApplication
// Set up the DI system
var services = new ServiceCollection();
services.AddNodeServices(options => {
// To compare with Socket hosting, uncomment the following line
// Since .NET Core 1.1, the HTTP hosting model has become basically as fast as the Socket hosting model
//options.UseSocketHosting();
options.HostingModel = NodeServicesOptions.DefaultNodeHostingModel;
options.ProjectPath = Directory.GetCurrentDirectory();
options.WatchFileExtensions = new string[] {}; // Don't watch anything
});

View File

@@ -0,0 +1,19 @@
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
},
"Microsoft.AspNetCore.NodeServices": "1.0.0-*",
"Microsoft.Extensions.DependencyInjection": "1.0.0"
},
"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
}

View File

@@ -1,6 +1,5 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.NodeServices;
namespace NodeServicesExamples.Controllers
{
@@ -16,21 +15,8 @@ namespace NodeServicesExamples.Controllers
return View();
}
public async Task<IActionResult> Chart([FromServices] INodeServices nodeServices)
public IActionResult ImageResizing()
{
var options = new { width = 400, height = 200, showArea = true, showPoint = true, fullWidth = true };
var data = new
{
labels = new[] { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" },
series = new[] {
new[] { 1, 5, 2, 5, 4, 3 },
new[] { 2, 3, 4, 8, 1, 2 },
new[] { 5, 4, 3, 2, 1, 0 }
}
};
ViewData["ChartMarkup"] = await nodeServices.InvokeAsync<string>("./Node/renderChart", "line", options, data);
return View();
}

View File

@@ -0,0 +1,64 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.NodeServices;
using Microsoft.AspNetCore.StaticFiles;
namespace NodeServicesExamples.Controllers
{
public class ResizeImageController : Controller
{
private const int MaxDimension = 1000;
private static string[] AllowedMimeTypes = new[] { "image/jpeg", "image/png", "image/gif" };
private IHostingEnvironment _environment;
private INodeServices _nodeServices;
public ResizeImageController(IHostingEnvironment environment, INodeServices nodeServices)
{
_environment = environment;
_nodeServices = nodeServices;
}
[Route("resize/{*imagePath}")]
public async Task<IActionResult> Index(string imagePath, int maxWidth, int maxHeight)
{
// Validate incoming params
if (maxWidth < 0 || maxHeight < 0 || maxWidth > MaxDimension || maxHeight > MaxDimension
|| (maxWidth + maxHeight) == 0)
{
return BadRequest("Invalid dimensions");
}
var mimeType = GetContentType(imagePath);
if (Array.IndexOf(AllowedMimeTypes, mimeType) < 0)
{
return BadRequest("Disallowed image format");
}
// Locate source image on disk
var fileInfo = _environment.WebRootFileProvider.GetFileInfo(imagePath);
if (!fileInfo.Exists)
{
return NotFound();
}
// Invoke Node and pipe the result to the response
var imageStream = await _nodeServices.InvokeAsync<Stream>(
"./Node/resizeImage",
fileInfo.PhysicalPath,
mimeType,
maxWidth,
maxHeight);
return File(imageStream, mimeType);
}
private string GetContentType(string path)
{
string result;
return new FileExtensionContentTypeProvider().TryGetContentType(path, out result) ? result : null;
}
}
}

View File

@@ -1,8 +0,0 @@
var generate = require('node-chartist');
module.exports = function (callback, type, options, data) {
generate(type, options, data).then(
result => callback(null, result), // Success case
error => callback(error) // Error case
);
};

View File

@@ -0,0 +1,8 @@
var sharp = require('sharp');
module.exports = function(result, physicalPath, mimeType, maxWidth, maxHeight) {
// Invoke the 'sharp' NPM module, and have it pipe the resulting image data back to .NET
sharp(physicalPath)
.resize(maxWidth || null, maxHeight || null)
.pipe(result.stream);
}

View File

@@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\build\common.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.NodeServices\Microsoft.AspNetCore.NodeServices.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="$(AspNetCoreVersion)" />
</ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
<Exec Command="npm install" />
</Target>
</Project>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>6d4bcdd6-7951-449b-be55-cb7f014b7430</ProjectGuid>
<RootNamespace>NodeServicesExamples</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\JavaScriptServices.sln\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>2018</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -41,6 +41,7 @@ namespace NodeServicesExamples
});
app.UseStaticFiles();
loggerFactory.AddConsole();
app.UseMvc(routes =>
{
routes.MapRoute(
@@ -52,11 +53,6 @@ namespace NodeServicesExamples
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.ConfigureLogging(factory =>
{
factory.AddConsole();
factory.AddDebug();
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseKestrel()

View File

@@ -1,12 +0,0 @@
<h1>Server-rendered chart</h1>
<p>
This sample demonstrates how arbitrary NPM modules can be invoked from .NET code.
</p>
<p>
In this case, we use <code>node-chartist</code> to render the following chart on the server. The output is
identical to what you'd get if you used <a href='https://gionkunz.github.io/chartist-js/'>chartist.js</a>
on the client, except that in this example, we're not executing any client-side code at all.
</p>
@Html.Raw(ViewData["ChartMarkup"])

View File

@@ -0,0 +1,34 @@
<h1>Image Resizing</h1>
<p>
This sample shows how the NPM module <a href="https://www.npmjs.com/package/sharp"><code>sharp</code></a>
can be used for dynamic image resizing from within an ASP.NET Core application. There is one copy of the
following image on disk, but we can set up an MVC action method that returns it resized to fit within an
arbitrary width and height.
</p>
<p>
<strong>Dependencies:</strong> On Windows and Linux, there are no native dependencies. Just running
<code>npm install</code> is enough. On OS X, however, you need to have <code>libvips</code> installed,
which you can get through <a href="http://brew.sh/">Homebrew</a> by running
<code>brew install homebrew/science/vips</code>.
</p>
<h3>100px wide [<a href="/resize/images/parrot.jpg?maxWidth=100">open</a>]</h3>
<img src="/resize/images/parrot.jpg?maxWidth=100" />
<h3>200px wide [<a href="/resize/images/parrot.jpg?maxWidth=200">open</a>]</h3>
<img src="/resize/images/parrot.jpg?maxWidth=200" />
<h3>400px wide [<a href="/resize/images/parrot.jpg?maxWidth=400">open</a>]</h3>
<img src="/resize/images/parrot.jpg?maxWidth=400" />
<h3>800px wide [<a href="/resize/images/parrot.jpg?maxWidth=800">open</a>]</h3>
<img src="/resize/images/parrot.jpg?maxWidth=800" />
<p>
<strong>Credit:</strong>
<em><a href="https://www.flickr.com/photos/dcoetzee/3572948635">Parrot</a>
by <a href="https://www.flickr.com/photos/dcoetzee/">D Coetzee</a>
is dedicated to the <a href="http://creativecommons.org/publicdomain/zero/1.0/">public domain (CC0)</a></em>
</p>

View File

@@ -7,6 +7,7 @@
</p>
<ul>
<li><a asp-action="ES2015Transpilation">ES2015 transpilation</a></li>
<li><a asp-action="Chart">Server-side chart rendering</a></li>
</ul>
<li><a asp-action="ES2015Transpilation">ES2015 transpilation</a>
<li><a asp-action="ImageResizing">Image resizing</a>
</li>

View File

@@ -1,9 +1,8 @@
<!doctype html>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>NodeServices Examples</title>
<link rel="stylesheet" href="~/css/chartist.min.css" />
</head>
<body>
@RenderBody()

View File

@@ -4,6 +4,6 @@
"dependencies": {
"babel-core": "^6.7.4",
"babel-preset-es2015": "^6.6.0",
"node-chartist": "^1.0.2"
"sharp": "^0.15.0"
}
}

Some files were not shown because too many files have changed in this diff Show More