├── .dockerignore
├── .gitattributes
├── .gitignore
├── AlbumViewer.png
├── AlbumViewerMobile.png
├── AlbumViewerNetCore.sln
├── AlbumViewerSqlServerData.sql
├── AlbumViewer_Docker.websurge
├── AlbumViewer_WebSurge_HttpRequests.websurge
├── CONTRIBUTING.md
├── LICENSE
├── Readme.md
├── docker-compose.yml
├── docker
└── app
│ └── Dockerfile
└── src
├── AlbumViewerAngular
├── .angular-cli - Copy.json
├── .angular-cli.json
├── .editorconfig
├── .gitignore
├── README.md
├── package.json
├── src
│ ├── app
│ │ ├── albums
│ │ │ ├── albumDisplay.html
│ │ │ ├── albumDisplay.ts
│ │ │ ├── albumEditor.html
│ │ │ ├── albumEditor.ts
│ │ │ ├── albumList.html
│ │ │ ├── albumList.ts
│ │ │ ├── albumService.ts
│ │ │ ├── albumSongList.html
│ │ │ └── albumSongList.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── artists
│ │ │ ├── artistDisplay.html
│ │ │ ├── artistDisplay.ts
│ │ │ ├── artistEditor.html
│ │ │ ├── artistEditor.ts
│ │ │ ├── artistList.html
│ │ │ ├── artistList.ts
│ │ │ └── artistService.ts
│ │ ├── business
│ │ │ ├── HttpClient.ts
│ │ │ ├── appConfiguration.ts
│ │ │ ├── entities.ts
│ │ │ └── userInfo.ts
│ │ ├── common
│ │ │ ├── animations.ts
│ │ │ ├── appFooter.html
│ │ │ ├── appFooter.ts
│ │ │ ├── appHeader.html
│ │ │ ├── appHeader.ts
│ │ │ ├── errorDisplay.ts
│ │ │ ├── login.html
│ │ │ └── login.ts
│ │ └── options
│ │ │ ├── about.html
│ │ │ ├── about.ts
│ │ │ ├── options.html
│ │ │ └── options.ts
│ ├── css
│ │ └── albumviewer.css
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── images
│ │ ├── RockLogo.png
│ │ ├── artists.png
│ │ ├── artists32.png
│ │ ├── gear.png
│ │ ├── headphone-head.png
│ │ ├── icon.png
│ │ ├── record.png
│ │ ├── search.png
│ │ ├── search_box.png
│ │ ├── search_box2.png
│ │ ├── webconnectionicon.png
│ │ ├── webconnectionlogo.png
│ │ └── westwindtext.png
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── scripts
│ │ └── web-animations.min.js
│ ├── touch-icon.png
│ ├── touch-icon32.png
│ ├── tsconfig.app.json
│ └── tsconfig.spec.json
├── tsconfig.json
└── tslint.json
├── AlbumViewerAngularSimple
├── .angular-cli.json
├── .editorconfig
├── .gitignore
├── README.md
├── package.json
├── src
│ ├── app
│ │ ├── albums
│ │ │ ├── albumEditor.html
│ │ │ ├── albumEditor.ts
│ │ │ ├── albumList.html
│ │ │ └── albumList.ts
│ │ ├── app-routing.module.ts
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ └── business
│ │ │ └── entities.ts
│ ├── assets
│ │ └── .gitkeep
│ ├── css
│ │ ├── albumviewer.css
│ │ └── albumviewer.min.css
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── images
│ │ ├── RockLogo.png
│ │ ├── artists.png
│ │ ├── artists32.png
│ │ ├── gear.png
│ │ ├── headphone-head.png
│ │ ├── icon.png
│ │ ├── record.png
│ │ ├── search.png
│ │ ├── search_box.png
│ │ ├── search_box2.png
│ │ ├── webconnectionicon.png
│ │ ├── webconnectionlogo.png
│ │ └── westwindtext.png
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.css
│ ├── touch-icon.png
│ ├── touch-icon32.png
│ ├── tsconfig.app.json
│ └── tsconfig.spec.json
├── tsconfig.json
└── tslint.json
├── AlbumViewerBusiness
├── AccountRepository.cs
├── AlbumRepository.cs
├── AlbumViewerBusiness.csproj
├── ArtistRepository.cs
├── Models
│ ├── AlbumViewerContext.cs
│ ├── AlbumViewerDataImporter.cs
│ └── AlbumViewerEntities.cs
├── Properties
│ └── AssemblyInfo.cs
└── Westwind.BusinessObjects
│ ├── EntityFrameworkRepository.cs
│ └── Utilities
│ ├── DataAccessBase.cs
│ ├── DataUtils.cs
│ ├── DbSetExtensions.cs
│ └── ReflectionUtils.cs
├── AlbumViewerNetCore
├── .editorconfig
├── AlbumViewer.png
├── AlbumViewerNetCore.csproj
├── AlbumViewer_WebSurge_HttpRequests.websurge
├── Controllers
│ ├── AccountController.cs
│ ├── AlbumViewerApiController.cs
│ ├── AlbumViewerMvcController.NOTUSED
│ ├── HelloWorldController.cs
│ ├── HomeController.cs
│ └── TagHelpers
│ │ └── ErrorDisplayTagHelper.cs
├── Program.cs
├── Project_Readme.html
├── Properties
│ └── launchSettings.json
├── Startup.cs
├── Views
│ ├── AlbumViewerMvc
│ │ ├── Album.cshtml
│ │ ├── Albums.cshtml
│ │ ├── Artists.cshtml
│ │ └── TagHelpers.cshtml
│ ├── Home
│ │ ├── About.cshtml
│ │ ├── Contact.cshtml
│ │ ├── Error.cshtml
│ │ └── Index.cshtml
│ ├── Shared
│ │ ├── Error.cshtml
│ │ └── _Layout.cshtml
│ ├── _GlobalImport.cshtml
│ ├── _ViewImports.cshtml
│ └── _ViewStart.cshtml
├── _Code
│ └── ErrorHandling
│ │ ├── ApiError.cs
│ │ ├── ApiException.cs
│ │ └── ApiExceptionFilter.cs
├── albums.js
├── appsettings.json
├── bundleconfig.json
├── runtimeconfig.template.json
├── web.config
└── wwwroot
│ ├── 3rdpartylicenses.txt
│ ├── favicon.ico
│ ├── fontawesome-webfont.674f50d287a8c48dc19b.eot
│ ├── fontawesome-webfont.912ec66d7572ff821749.svg
│ ├── fontawesome-webfont.af7ae505a9eed503f8b8.woff2
│ ├── fontawesome-webfont.b06871f281fee6b241d6.ttf
│ ├── fontawesome-webfont.fee66e712a8a08eef580.woff
│ ├── glyphicons-halflings-regular.448c34a56d699c29117a.woff2
│ ├── glyphicons-halflings-regular.89889688147bd7575d63.svg
│ ├── glyphicons-halflings-regular.e18bbf611f2a2e43afc0.ttf
│ ├── glyphicons-halflings-regular.f4769f9bdb7466be6508.eot
│ ├── glyphicons-halflings-regular.fa2772327f55d8198301.woff
│ ├── images
│ ├── RockLogo.png
│ ├── artists.png
│ ├── artists32.png
│ ├── gear.png
│ ├── headphone-head.png
│ ├── icon.png
│ ├── record.png
│ ├── search.png
│ ├── search_box.png
│ ├── search_box2.png
│ ├── webconnectionicon.png
│ ├── webconnectionlogo.png
│ └── westwindtext.png
│ ├── index.html
│ ├── inline.6ab1992b1d6b43743130.bundle.js
│ ├── main.d5f34125c7ed7eefbff9.bundle.js
│ ├── polyfills.bebf6cc3ec946aa280a0.bundle.js
│ ├── scripts.aabcab8cf45f67dea95f.bundle.js
│ ├── styles.15901b41a374afce732a.bundle.css
│ ├── touch-icon.png
│ └── vendor.5013a47f17b192bbdbea.bundle.js
└── Westwind.Utilities
├── Data
├── ValidationErrorCollection.cs
└── ValidationErrors.cs
├── Properties
└── AssemblyInfo.cs
├── Utilities
├── DataUtils.cs
└── StringUtils.cs
└── Westwind.Utilities.csproj
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 |
3 | **/Obj/
4 | **/obj/
5 | **/bin/
6 | **/Bin/
7 | .vs/
8 | *.xap
9 | *.user
10 | /TestResults
11 | *.vspscc
12 | *.vssscc
13 | *.suo
14 | *.cache
15 | *.docstates
16 | _ReSharper.*
17 | *.csproj.user
18 | *[Rr]e[Ss]harper.user
19 | _ReSharper.*/
20 | packages/*
21 | artifacts/*
22 | msbuild.log
23 | PublishProfiles/
24 | *.psess
25 | *.vsp
26 | *.pidb
27 | *.userprefs
28 | *DS_Store
29 | *.ncrunchsolution
30 | *.log
31 | *.vspx
32 | /.symbols
33 | nuget.exe
34 | *net45.csproj
35 | *k10.csproj
36 | App_Data/
37 | bower_components
38 | node_modules
39 | *.sln.ide
40 | *.ng.ts
41 | *.sln.ide
42 | .build/
43 | .testpublish/
44 | launchSettings.json
45 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.doc diff=astextplain
2 | *.DOC diff=astextplain
3 | *.docx diff=astextplain
4 | *.DOCX diff=astextplain
5 | *.dot diff=astextplain
6 | *.DOT diff=astextplain
7 | *.pdf diff=astextplain
8 | *.PDF diff=astextplain
9 | *.rtf diff=astextplain
10 | *.RTF diff=astextplain
11 |
12 | *.jpg binary
13 | *.png binary
14 | *.gif binary
15 |
16 | *.cs text=auto diff=csharp
17 | *.vb text=auto
18 | *.resx text=auto
19 | *.c text=auto
20 | *.cpp text=auto
21 | *.cxx text=auto
22 | *.h text=auto
23 | *.hxx text=auto
24 | *.py text=auto
25 | *.rb text=auto
26 | *.java text=auto
27 | *.html text=auto
28 | *.htm text=auto
29 | *.css text=auto
30 | *.scss text=auto
31 | *.sass text=auto
32 | *.less text=auto
33 | *.js text=auto
34 | *.lisp text=auto
35 | *.clj text=auto
36 | *.sql text=auto
37 | *.php text=auto
38 | *.lua text=auto
39 | *.m text=auto
40 | *.asm text=auto
41 | *.erl text=auto
42 | *.fs text=auto
43 | *.fsx text=auto
44 | *.hs text=auto
45 |
46 | *.csproj text=auto
47 | *.vbproj text=auto
48 | *.fsproj text=auto
49 | *.dbproj text=auto
50 | *.sln text=auto eol=crlf
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /tmp
4 | /nginx/wwwroot
5 |
6 | # dependencies
7 | node_modules/
8 | bower_components/
9 |
10 | # IDEs and editors
11 | /.idea
12 | /.vs
13 | .vscode
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # Dotnet
22 | [Oo]bj/
23 | [Bb]in/
24 | *.xap
25 | *.user
26 | /TestResults
27 | *.vspscc
28 | *.vssscc
29 | *.suo
30 | *.cache
31 | *.docstates
32 | *.log
33 | *.log.*
34 | _ReSharper.*
35 | *.csproj.user
36 | *[Rr]e[Ss]harper.user
37 | _ReSharper.*/
38 | packages/*
39 | artifacts/*
40 | msbuild.log
41 | PublishProfiles/
42 | *.psess
43 | *.vsp
44 | *.pidb
45 | *.userprefs
46 | *DS_Store
47 | *.ncrunchsolution
48 | *.log
49 | *.vspx
50 | /.symbols
51 | nuget.exe
52 | build/
53 | *net45.csproj
54 | *k10.csproj
55 | App_Data/
56 | *.sln.ide
57 | */*.Spa/public/*
58 | */*.Spa/wwwroot/*
59 | *.ng.ts
60 | *.sln.ide
61 | __*.htm
62 |
63 | # Application
64 | AlbumViewer/
65 | src/AlbumViewerVNext/wwwroot/web.config__X
66 | thumbs.db
67 | .vs
68 | .vs/config/applicationhost.config
69 | publishprofile.xml
70 | .idea
71 | dist
72 | /AlbumViewerVnext.azurewebsites.net.PublishSettings
73 | src/AlbumViewerAspNet5/project.lock.json
74 | AlbumViewer.publishsettings
75 | *.sqlite
76 | *.lock.json
77 | src/AlbumViewerBusiness/project.lock.json
78 | src/AlbumViewerNetCore/project.lock.json
79 | src/Westwind.Utilitities/project.lock.json
80 |
--------------------------------------------------------------------------------
/AlbumViewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/AlbumViewer.png
--------------------------------------------------------------------------------
/AlbumViewerMobile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/AlbumViewerMobile.png
--------------------------------------------------------------------------------
/AlbumViewerNetCore.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26730.3
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0A329D36-19A2-4F54-B3C7-17640DDBF44C}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B10A5E44-A157-44F6-95C6-7AF5626B5D4C}"
9 | ProjectSection(SolutionItems) = preProject
10 | AlbumViewer_WebSurge_HttpRequests.websurge = AlbumViewer_WebSurge_HttpRequests.websurge
11 | Readme.md = Readme.md
12 | EndProjectSection
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AlbumViewerNetCore", "src\AlbumViewerNetCore\AlbumViewerNetCore.csproj", "{00D86129-ADE5-403F-96AE-9263C01BE2E4}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AlbumViewerBusiness", "src\AlbumViewerBusiness\AlbumViewerBusiness.csproj", "{209EAF20-38E2-4C67-9557-9247A07213E6}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Westwind.Utilities", "src\Westwind.Utilities\Westwind.Utilities.csproj", "{4D936F4A-C6BD-4724-810F-949AB4D0BD36}"
19 | EndProject
20 | Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "AlbumViewerAngular", "src\AlbumViewerAngular\", "{293C677F-D21F-417A-AC50-7BAB1917AF1C}"
21 | ProjectSection(WebsiteProperties) = preProject
22 | TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0"
23 | Debug.AspNetCompiler.VirtualPath = "/localhost_37063"
24 | Debug.AspNetCompiler.PhysicalPath = "src\AlbumViewerAngular\"
25 | Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_37063\"
26 | Debug.AspNetCompiler.Updateable = "true"
27 | Debug.AspNetCompiler.ForceOverwrite = "true"
28 | Debug.AspNetCompiler.FixedNames = "false"
29 | Debug.AspNetCompiler.Debug = "True"
30 | Release.AspNetCompiler.VirtualPath = "/localhost_37063"
31 | Release.AspNetCompiler.PhysicalPath = "src\AlbumViewerAngular\"
32 | Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_37063\"
33 | Release.AspNetCompiler.Updateable = "true"
34 | Release.AspNetCompiler.ForceOverwrite = "true"
35 | Release.AspNetCompiler.FixedNames = "false"
36 | Release.AspNetCompiler.Debug = "False"
37 | VWDPort = "37063"
38 | SlnRelativePath = "src\AlbumViewerAngular\"
39 | EndProjectSection
40 | EndProject
41 | Global
42 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
43 | Debug|Any CPU = Debug|Any CPU
44 | Release|Any CPU = Release|Any CPU
45 | EndGlobalSection
46 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
47 | {00D86129-ADE5-403F-96AE-9263C01BE2E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48 | {00D86129-ADE5-403F-96AE-9263C01BE2E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
49 | {00D86129-ADE5-403F-96AE-9263C01BE2E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
50 | {00D86129-ADE5-403F-96AE-9263C01BE2E4}.Release|Any CPU.Build.0 = Release|Any CPU
51 | {209EAF20-38E2-4C67-9557-9247A07213E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
52 | {209EAF20-38E2-4C67-9557-9247A07213E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
53 | {209EAF20-38E2-4C67-9557-9247A07213E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
54 | {209EAF20-38E2-4C67-9557-9247A07213E6}.Release|Any CPU.Build.0 = Release|Any CPU
55 | {4D936F4A-C6BD-4724-810F-949AB4D0BD36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
56 | {4D936F4A-C6BD-4724-810F-949AB4D0BD36}.Debug|Any CPU.Build.0 = Debug|Any CPU
57 | {4D936F4A-C6BD-4724-810F-949AB4D0BD36}.Release|Any CPU.ActiveCfg = Release|Any CPU
58 | {4D936F4A-C6BD-4724-810F-949AB4D0BD36}.Release|Any CPU.Build.0 = Release|Any CPU
59 | {293C677F-D21F-417A-AC50-7BAB1917AF1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
60 | {293C677F-D21F-417A-AC50-7BAB1917AF1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
61 | {293C677F-D21F-417A-AC50-7BAB1917AF1C}.Release|Any CPU.ActiveCfg = Debug|Any CPU
62 | {293C677F-D21F-417A-AC50-7BAB1917AF1C}.Release|Any CPU.Build.0 = Debug|Any CPU
63 | EndGlobalSection
64 | GlobalSection(SolutionProperties) = preSolution
65 | HideSolutionNode = FALSE
66 | EndGlobalSection
67 | GlobalSection(NestedProjects) = preSolution
68 | {00D86129-ADE5-403F-96AE-9263C01BE2E4} = {0A329D36-19A2-4F54-B3C7-17640DDBF44C}
69 | {209EAF20-38E2-4C67-9557-9247A07213E6} = {0A329D36-19A2-4F54-B3C7-17640DDBF44C}
70 | {4D936F4A-C6BD-4724-810F-949AB4D0BD36} = {0A329D36-19A2-4F54-B3C7-17640DDBF44C}
71 | {293C677F-D21F-417A-AC50-7BAB1917AF1C} = {0A329D36-19A2-4F54-B3C7-17640DDBF44C}
72 | EndGlobalSection
73 | GlobalSection(ExtensibilityGlobals) = postSolution
74 | SolutionGuid = {9FAF684A-A9E1-46AD-8ED6-149D0825737E}
75 | EndGlobalSection
76 | EndGlobal
77 |
--------------------------------------------------------------------------------
/AlbumViewer_Docker.websurge:
--------------------------------------------------------------------------------
1 | GET http://localhost:8081/index.html HTTP/1.1
2 | Accept-Encoding: gzip,deflate
3 |
4 | ------------------------------------------------------------------
5 |
6 | GET http://localhost:8081/api/artist/1 HTTP/1.1
7 | Accept-Encoding: gzip,deflate
8 |
9 | ------------------------------------------------------------------
10 |
11 | GET http://localhost:8081/api/helloworld HTTP/1.1
12 | Accept-Encoding: gzip,deflate
13 |
14 | ------------------------------------------------------------------
15 |
16 |
17 | ----- Start WebSurge Options -----
18 |
19 | {
20 | "NoProgressEvents": true,
21 | "DelayTimeMs": -1,
22 | "NoContentDecompression": false,
23 | "CaptureMinimalResponseData": false,
24 | "MaxResponseSize": 0,
25 | "ReplaceCookieValue": null,
26 | "ReplaceAuthorization": null,
27 | "ReplaceQueryStringValuePairs": null,
28 | "ReplaceDomain": "",
29 | "Username": null,
30 | "Password": null,
31 | "RandomizeRequests": false,
32 | "RequestTimeoutMs": 15000,
33 | "WarmupSeconds": 2,
34 | "ReloadTemplates": false,
35 | "FormattedPreviewTheme": "visualstudio",
36 | "LastThreads": 8,
37 | "IgnoreCertificateErrors": false,
38 | "LastSecondsToRun": 30
39 | }
40 |
41 | // This file was generated by West Wind WebSurge
42 | // Get your free copy at http://websurge.west-wind.com
43 | // to easily test or load test the requests in this file.
44 |
45 | ----- End WebSurge Options -----
46 |
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Since this project is intended to support a specific use case guide, contributions are limited to bug fixes or security issues. If you have a question, feel free to open an issue!
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Docker Samples
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.2'
2 |
3 | services:
4 | db:
5 | image: dockersamples/tidb:nanoserver-1809
6 | ports:
7 | - "3306:4000"
8 |
9 | app:
10 | image: dockersamples/dotnet-album-viewer
11 | build:
12 | context: .
13 | dockerfile: docker/app/Dockerfile
14 | ports:
15 | - "80:80"
16 | environment:
17 | - "Data:Provider=MySQL"
18 | - "Data:ConnectionString=Server=db;Port=4000;Database=AlbumViewer;User=root;SslMode=None"
19 | depends_on:
20 | - db
21 |
22 | networks:
23 | default:
24 | external:
25 | name: nat
26 |
--------------------------------------------------------------------------------
/docker/app/Dockerfile:
--------------------------------------------------------------------------------
1 | # escape=`
2 | FROM mcr.microsoft.com/dotnet/sdk:2.1-nanoserver-1809 AS builder
3 |
4 | WORKDIR /album-viewer
5 | COPY AlbumViewerNetCore.sln .
6 | COPY src/AlbumViewerNetCore/AlbumViewerNetCore.csproj src/AlbumViewerNetCore/AlbumViewerNetCore.csproj
7 | COPY src/AlbumViewerBusiness/AlbumViewerBusiness.csproj src/AlbumViewerBusiness/AlbumViewerBusiness.csproj
8 | COPY src/Westwind.Utilities/Westwind.Utilities.csproj src/Westwind.Utilities/Westwind.Utilities.csproj
9 | RUN dotnet restore src/AlbumViewerNetCore/AlbumViewerNetCore.csproj
10 |
11 | COPY src src
12 | RUN dotnet publish .\src\AlbumViewerNetCore\AlbumViewerNetCore.csproj
13 |
14 | # app image
15 | FROM mcr.microsoft.com/dotnet/aspnet:2.1-nanoserver-1809
16 |
17 | WORKDIR /album-viewer
18 | COPY --from=builder /album-viewer/src/AlbumViewerNetCore/bin/Debug/netcoreapp2.0/publish/ .
19 | CMD ["dotnet", "AlbumViewerNetCore.dll"]
20 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/.angular-cli - Copy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "album-viewer"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "../AlbumViewerNetCore/wwwroot",
10 | "assets": [
11 | "images",
12 | "favicon.ico",
13 | "touch-icon.png"
14 | ],
15 | "index": "index.html",
16 | "main": "main.ts",
17 | "polyfills": "polyfills.ts",
18 | "test": "test.ts",
19 | "tsconfig": "tsconfig.app.json",
20 | "testTsconfig": "tsconfig.spec.json",
21 | "prefix": "app",
22 |
23 | "styles": [
24 | "../node_modules/bootstrap/dist/css/bootstrap.css",
25 | "../node_modules/bootstrap/dist/css/bootstrap-theme.css",
26 | "../node_modules/font-awesome/css/font-awesome.css",
27 | "../node_modules/toastr/build/toastr.css",
28 | "./css/albumviewer.css"
29 | ],
30 | "scripts": [
31 | "../node_modules/jquery/dist/jquery.js",
32 | "../node_modules/toastr/toastr.js",
33 | "../node_modules/bootstrap/dist/js/bootstrap.js",
34 | "../node_modules/bootstrap-3-typeahead/bootstrap3-typeahead.js"
35 | ],
36 | "environmentSource": "environments/environment.ts",
37 | "environments": {
38 | "dev": "environments/environment.ts",
39 | "prod": "environments/environment.prod.ts"
40 | }
41 | }
42 | ],
43 | "e2e": {
44 | "protractor": {
45 | "config": "./protractor.conf.js"
46 | }
47 | },
48 | "lint": [
49 | {
50 | "project": "src/tsconfig.app.json",
51 | "exclude": "**/node_modules/**"
52 | },
53 | {
54 | "project": "src/tsconfig.spec.json",
55 | "exclude": "**/node_modules/**"
56 | },
57 | {
58 | "project": "e2e/tsconfig.e2e.json",
59 | "exclude": "**/node_modules/**"
60 | }
61 | ],
62 | "test": {
63 | "karma": {
64 | "config": "./karma.conf.js"
65 | }
66 | },
67 | "defaults": {
68 | "styleExt": "css",
69 | "component": {}
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "album-viewer"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "../AlbumViewerNetCore/wwwroot",
10 | "assets": [
11 | "images",
12 | "favicon.ico",
13 | "touch-icon.png"
14 | ],
15 | "index": "index.html",
16 | "main": "main.ts",
17 | "polyfills": "polyfills.ts",
18 | "test": "test.ts",
19 | "tsconfig": "tsconfig.app.json",
20 | "testTsconfig": "tsconfig.spec.json",
21 | "prefix": "app",
22 |
23 | "styles": [
24 | "../node_modules/bootstrap/dist/css/bootstrap.css",
25 | "../node_modules/bootstrap/dist/css/bootstrap-theme.css",
26 | "../node_modules/font-awesome/css/font-awesome.css",
27 | "../node_modules/toastr/build/toastr.css",
28 | "./css/albumviewer.css"
29 | ],
30 | "scripts": [
31 | "../node_modules/jquery/dist/jquery.js",
32 | "../node_modules/toastr/toastr.js",
33 | "../node_modules/bootstrap/dist/js/bootstrap.js",
34 | "../node_modules/bootstrap-3-typeahead/bootstrap3-typeahead.js"
35 | ],
36 | "environmentSource": "environments/environment.ts",
37 | "environments": {
38 | "dev": "environments/environment.ts",
39 | "prod": "environments/environment.prod.ts"
40 | }
41 | }
42 | ],
43 | "e2e": {
44 | "protractor": {
45 | "config": "./protractor.conf.js"
46 | }
47 | },
48 | "lint": [
49 | {
50 | "project": "src/tsconfig.app.json",
51 | "exclude": "**/node_modules/**"
52 | },
53 | {
54 | "project": "src/tsconfig.spec.json",
55 | "exclude": "**/node_modules/**"
56 | },
57 | {
58 | "project": "e2e/tsconfig.e2e.json",
59 | "exclude": "**/node_modules/**"
60 | }
61 | ],
62 | "test": {
63 | "karma": {
64 | "config": "./karma.conf.js"
65 | }
66 | },
67 | "defaults": {
68 | "styleExt": "css",
69 | "component": {}
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 |
10 | # IDEs and editors
11 | /.idea
12 | .project
13 | .classpath
14 | .c9/
15 | *.launch
16 | .settings/
17 | *.sublime-workspace
18 |
19 | # IDE - VSCode
20 | .vscode/*
21 | !.vscode/settings.json
22 | !.vscode/tasks.json
23 | !.vscode/launch.json
24 | !.vscode/extensions.json
25 |
26 | # misc
27 | /.sass-cache
28 | /connect.lock
29 | /coverage/*
30 | /libpeerconnection.log
31 | npm-debug.log
32 | testem.log
33 | /typings
34 |
35 | # e2e
36 | /e2e/*.js
37 | /e2e/*.map
38 |
39 | #System Files
40 | .DS_Store
41 | Thumbs.db
42 | package-lock.json
43 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/README.md:
--------------------------------------------------------------------------------
1 | # AlbumViewer
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.0.
4 |
5 | ## Development server
6 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
7 |
8 | ## Code scaffolding
9 |
10 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
11 |
12 | ## Build
13 |
14 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
15 |
16 | ## Running unit tests
17 |
18 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
19 |
20 | ## Running end-to-end tests
21 |
22 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
23 | Before running the tests make sure you are serving the app via `ng serve`.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
28 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "album-viewer",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve",
8 | "build": "ng build",
9 | "build:prod": "ng build --prod --aot",
10 | "build:asp": "npm run build:prod & npm run clean & npm run copy",
11 | "test": "ng test",
12 | "lint": "ng lint",
13 | "e2e": "ng e2e",
14 | "copy": "xcopy /s /q .\\dist\\*.* ..\\AlbumViewerNetCore\\wwwroot\\",
15 | "clean": "rd /s /q ..\\AlbumViewerNetCore\\wwwroot\\"
16 | },
17 | "private": true,
18 | "dependencies": {
19 | "@angular/animations": "^4.0.0",
20 | "@angular/common": "^4.0.0",
21 | "@angular/compiler": "^4.0.0",
22 | "@angular/core": "^4.0.0",
23 | "@angular/forms": "^4.0.0",
24 | "@angular/http": "^4.0.0",
25 | "@angular/platform-browser": "^4.0.0",
26 | "@angular/platform-browser-dynamic": "^4.0.0",
27 | "@angular/router": "^4.0.0",
28 | "core-js": "^2.4.1",
29 | "rxjs": "^5.4.1",
30 | "zone.js": "^0.8.14",
31 | "bootstrap": "^3.3.7",
32 | "bootstrap-3-typeahead": "^4.0.2",
33 | "font-awesome": "^4.7.0",
34 | "jquery": "^3.1.1",
35 | "toastr": "^2.1.2"
36 | },
37 | "devDependencies": {
38 | "@angular/cli": "^1.0.1",
39 | "@angular/compiler-cli": "^4.1.0",
40 | "@types/jasmine": "2.5.38",
41 | "@types/node": "^6.0.70",
42 | "codelyzer": "~2.0.0",
43 | "jasmine-core": "~2.5.2",
44 | "jasmine-spec-reporter": "~3.2.0",
45 | "karma": "~1.4.1",
46 | "karma-chrome-launcher": "~2.0.0",
47 | "karma-cli": "~1.0.1",
48 | "karma-coverage-istanbul-reporter": "^0.2.0",
49 | "karma-jasmine": "~1.1.0",
50 | "karma-jasmine-html-reporter": "^0.2.2",
51 | "protractor": "~5.1.0",
52 | "ts-node": "~2.0.0",
53 | "tslint": "~4.4.2",
54 | "typescript": "^2.3.1",
55 | "@types/jquery": "^2.0.40",
56 | "@types/toastr": "^2.1.32"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/albums/albumDisplay.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/albums/albumDisplay.ts:
--------------------------------------------------------------------------------
1 | import {Album} from './../business/entities';
2 | import {Component, OnInit, Input, style, animate, state, transition, trigger, OnDestroy} from '@angular/core';
3 | import {AlbumService} from "./albumService";
4 | import {ActivatedRoute, Router} from "@angular/router";
5 | import {ErrorInfo} from "../common/errorDisplay";
6 | import {AppConfiguration} from "../business/appConfiguration";
7 | import {slideIn, slideInLeft} from "../common/animations";
8 |
9 | @Component({
10 | selector: 'album-display',
11 | templateUrl: './albumDisplay.html',
12 | animations: [ slideIn ]
13 | })
14 | export class AlbumDisplay implements OnInit {
15 |
16 | @Input() album:Album = new Album();
17 | error = new ErrorInfo();
18 | aniFrame = 'in';
19 |
20 | constructor(private route: ActivatedRoute,
21 | private router: Router,
22 | private config: AppConfiguration,
23 | private albumService: AlbumService) {
24 | }
25 |
26 | ngOnInit() {
27 | this.config.isSearchAllowed = false;
28 | this.config.activeTab = "albums";
29 | this.aniFrame = 'in';
30 |
31 | //console.log("AlbumDisplay");
32 | if(!this.album.Title) {
33 | var id = this.route.snapshot.params["id"];
34 | if (id < 1)
35 | return;
36 |
37 | this.albumService.getAlbum(id)
38 | .subscribe( result => {
39 | this.album = result;
40 | }, err => this.error.error(err));
41 | }
42 | }
43 |
44 | ngOnDestroy() {
45 | this.aniFrame = 'out';
46 | console.log("ngDestroy")
47 | }
48 |
49 | deleteAlbum(album) {
50 | this.albumService.deleteAlbum(album)
51 | .subscribe( result =>{
52 | this.error.info("Album '" + album.Title + "' has been deleted.");
53 | setTimeout(() => this.router.navigate(["/albums"]), 1500);
54 | },
55 | (err)=> this.error.error(err));
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/albums/albumEditor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
107 |
108 |
109 |
Preview
110 |
111 |
112 |
{{album.Title}}
113 |
by {{album.Artist.ArtistName}} {{(album.Year ? 'in ' + album.Year : '')}}
114 | -
Buy on Amazon
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/albums/albumEditor.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit, ElementRef} from '@angular/core';
2 | import {Album} from "../business/entities";
3 | import {AlbumService} from "./albumService";
4 | import {Router, ActivatedRoute} from "@angular/router";
5 | import {ErrorInfo} from "../common/errorDisplay";
6 | import {AppConfiguration} from "../business/appConfiguration";
7 | import {UserInfo} from "../business/userInfo";
8 |
9 | //declare var $:any ;
10 | declare var $:any;
11 | declare var toastr:any;
12 | declare var window:any;
13 |
14 | import {slideInLeft, slideIn} from "../common/animations";
15 |
16 | @Component({
17 | selector: 'album-editor',
18 | templateUrl: 'albumEditor.html',
19 | animations: [ slideIn ]
20 | })
21 | export class AlbumEditor implements OnInit {
22 | constructor(private route: ActivatedRoute,
23 | private router: Router,
24 | private albumService: AlbumService,
25 | private config:AppConfiguration,
26 | private user:UserInfo) {
27 | }
28 |
29 | album: Album = new Album();
30 | error: ErrorInfo = new ErrorInfo();
31 | loaded = false;
32 | aniFrame = 'in';
33 |
34 | ngOnInit() {
35 | if (!this.user.isAuthenticated) {
36 | this.router.navigate(['/login']);
37 | return;
38 | }
39 |
40 | this.config.isSearchAllowed = false;
41 | this.bandTypeAhead();
42 |
43 |
44 | var id = this.route.snapshot.params["id"];
45 | if (id < 1) {
46 | this.loaded = true;
47 | this.album = this.albumService.newAlbum();
48 | return;
49 | }
50 |
51 |
52 |
53 | this.albumService.getAlbum(id)
54 | .subscribe(result => {
55 | this.album = result;
56 | this.loaded = true;
57 | },
58 | err => {
59 | this.error.error(err);
60 | });
61 | }
62 |
63 | saveAlbum(album) {
64 | return this.albumService.saveAlbum(album)
65 | .subscribe((album: Album) => {
66 | var msg = album.Title + " has been saved."
67 | this.error.info(msg);
68 | toastr.success(msg);
69 | window.document.getElementById("MainView").scrollTop = 0;
70 |
71 | setTimeout(function () {
72 | this.router.navigate(["/album", album.Id]);
73 | }, 1500)
74 | },
75 | err => {
76 | let msg = `Unable to save album: ${err.message}`;
77 | this.error.error(msg);
78 | toastr.error(msg);
79 |
80 | if (err.response && err.response.status == 401) {
81 | this.user.isAuthenticated = false;
82 | this.router.navigate(["login"]);
83 | }
84 | });
85 |
86 | };
87 |
88 | bandTypeAhead() {
89 | var $input:any = $("#BandName");
90 | var config = this.config;
91 |
92 | // delay slightly to ensure that the
93 | // typeahead component is loaded when
94 | // doing a full browser refresh
95 | setTimeout( function () {
96 | $input.typeahead({
97 | source: [],
98 | autoselect: true,
99 | minLength: 0
100 | });
101 |
102 | $input.keyup( function() {
103 | let s = $(this).val();
104 | let url = config.urls.url("artistLookup") + s;
105 |
106 | $.getJSON(url,
107 | (data) => {
108 | $input.data('typeahead').source = data;
109 | });
110 | });
111 |
112 | },1000);
113 |
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/albums/albumList.html:
--------------------------------------------------------------------------------
1 |
41 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/albums/albumList.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit } from '@angular/core';
2 | import { AlbumService } from './albumService';
3 | import { Album } from '../business/entities';
4 |
5 | import {AppConfiguration} from "../business/appConfiguration";
6 | import {Router} from "@angular/router";
7 | import {ErrorInfo} from "../common/errorDisplay";
8 | import {slideIn, slideInLeft} from "../common/animations";
9 | import { UserInfo } from "../business/userInfo";
10 |
11 | //import * as $ from 'jquery';
12 | declare var $:any;
13 | declare var toastr:any;
14 |
15 |
16 | @Component({
17 | selector: 'album-list',
18 | templateUrl: './albumList.html',
19 | animations: [ slideIn ]
20 | })
21 | export class AlbumList implements OnInit {
22 |
23 | constructor(private router:Router,
24 | private albumService: AlbumService,
25 | private config: AppConfiguration) {
26 | }
27 |
28 | albumList: Album[] = [];
29 | busy: boolean = true;
30 | error:ErrorInfo = new ErrorInfo();
31 |
32 |
33 | ngOnInit() {
34 | this.getAlbums();
35 |
36 | this.config.isSearchAllowed = true;
37 | this.config.activeTab = "albums";
38 | this.config.searchText = "";
39 |
40 | // ??? Non-DOM way to do this?
41 | setTimeout(() => {
42 | $("#SearchBox").focus();
43 | },200);
44 | }
45 |
46 | get filteredAlbumList() {
47 | if (this.config.searchText && this.config.searchText.length > 1) {
48 | var lsearchText = this.config.searchText.toLowerCase();
49 | return this.albumList.filter((a) =>
50 | a.Title.toLowerCase().includes(lsearchText) ||
51 | a.Artist.ArtistName.toLowerCase().includes(lsearchText)
52 | );
53 | }
54 | return this.albumList;
55 | }
56 |
57 | getAlbums() {
58 | this.busy = true;
59 | this.albumList = [];
60 | this.albumService.getAlbums()
61 | .subscribe(albums => {
62 | this.albumList = albums;
63 | this.busy = false;
64 |
65 | // reset scroll position of the list
66 | setTimeout(()=> $("#MainView").scrollTop(this.albumService.listScrollPos), 100);
67 | }, err => {
68 | this.error.error(err);
69 | this.busy = false;
70 | });
71 | }
72 |
73 | albumClick(album: Album) {
74 | this.albumService.listScrollPos = $("#MainView").scrollTop();
75 | this.router.navigate(['/album', album.Id]);
76 | }
77 |
78 |
79 | addAlbum() {
80 |
81 | }
82 |
83 | deleteAlbum(album: Album) {
84 |
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/albums/albumService.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Album, Artist, Track } from '../business/entities';
3 | import {AppConfiguration} from "../business/appConfiguration";
4 | import {ErrorInfo} from "../common/errorDisplay";
5 | import {Observable} from "rxjs";
6 | import {Http, RequestOptions} from "@angular/http";
7 |
8 |
9 | @Injectable()
10 | export class AlbumService {
11 | constructor(private http: Http,
12 | private config:AppConfiguration) {
13 | }
14 |
15 | albumList: Album[] = [];
16 | album: Album = new Album();
17 |
18 | //artistList: Artist[] = [];
19 | listScrollPos = 0;
20 |
21 | getAlbums(): Observable {
22 | return this.http.get(this.config.urls.url("albums"))
23 | .map( response => {
24 | this.albumList = response.json();
25 | return this.albumList;
26 | })
27 | .catch( new ErrorInfo().parseObservableResponseError );
28 | }
29 |
30 | getAlbum(id):Observable {
31 | return this.http.get(this.config.urls.url("album",id))
32 | .map( response => {
33 | this.album = response.json();
34 |
35 | if (!this.albumList || this.albumList.length < 1)
36 | this.getAlbums(); // load up albums in background
37 |
38 | return this.album;
39 | })
40 | .catch( new ErrorInfo().parseObservableResponseError );
41 | }
42 |
43 | newAlbum():Album {
44 | this.album = new Album();
45 | return this.album;
46 | }
47 | saveAlbum(album):Observable {
48 | return this.http.post(this.config.urls.url("album"),
49 | album,
50 | new RequestOptions( {withCredentials:true}) )
51 |
52 | .map( response => {
53 | this.album = response.json();
54 |
55 | // explicitly update the list with the updated data
56 | this.updateAlbum(this.album);
57 |
58 | return this.album;
59 | })
60 | .catch( new ErrorInfo().parseObservableResponseError );
61 | }
62 |
63 | deleteAlbum(album:Album):Observable {
64 | return this.http.delete(this.config.urls.url("album",album.Id),
65 | this.config.requestOptions)
66 | .map((response)=> {
67 | let result = response.json();
68 | if (result)
69 | this.removeAlbum(album);
70 | })
71 | .catch( new ErrorInfo().parsePromiseResponseError );
72 | }
73 |
74 |
75 | /**
76 | * Updates the .albumList property by updating the actual
77 | * index entry in the existing list, adding new entries and
78 | * removing 0 entries.
79 | * @param album - the album to update
80 | */
81 | updateAlbum(album) {
82 | var i = this.albumList.findIndex((a) => (a.Id == album.Id));
83 | if (i > -1)
84 | this.albumList[i] = album;
85 | else {
86 | this.albumList.push(album);
87 | this.albumList.sort((a:Album,b:Album)=> {
88 | var aTitle = a.Title.toLocaleLowerCase();
89 | var bTitle = b.Title.toLocaleLowerCase();
90 | if(aTitle > bTitle)
91 | return 1;
92 | if (aTitle a.Id != 0);
99 | }
100 |
101 | removeAlbum(album){
102 | this.albumList = this.albumList.filter( (a)=> a.Id != album.Id );
103 | }
104 | addSong(track:Track) {
105 | this.album.Tracks.push(track);
106 | }
107 |
108 | removeSong(track:Track) {
109 | var idx = this.album.Tracks.findIndex((t) => track.Id == t.Id);
110 | if (idx > -1)
111 | this.album.Tracks.splice(idx, 1);
112 | }
113 |
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/albums/albumSongList.html:
--------------------------------------------------------------------------------
1 |
2 |
5 | Add Song
6 |
7 |
8 |
9 |
10 |
25 |
26 |
30 | Save Track
31 |
32 |
35 | Cancel
36 |
37 |
38 |
39 |
40 |
41 |
42 | {{track.SongName}}
43 | {{track.Length}}
44 |
45 |
47 | Remove
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/albums/albumSongList.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit, Input} from '@angular/core';
2 | import {Track} from "../business/entities";
3 | import {AlbumService} from "./albumService";
4 |
5 | @Component({
6 | //moduleId: module.id,
7 | selector: 'album-songlist',
8 | templateUrl: 'albumSongList.html'
9 | })
10 | export class AlbumSongList implements OnInit {
11 | constructor(private albumService:AlbumService) {
12 | }
13 |
14 | ngOnInit() {
15 | }
16 |
17 | @Input() tracks: Track[] = [];
18 | @Input() allowEditing: boolean = false;
19 | isSongVisible = false;
20 | track:Track = new Track();
21 |
22 | addTrack(track){
23 | this.track = new Track();
24 | this.isSongVisible = true;
25 | }
26 | cancelTrack() {
27 | this.track = new Track();
28 | this.isSongVisible = false;
29 | }
30 |
31 | saveTrack(track){
32 | this.albumService.addSong(track);
33 | this.isSongVisible = false;
34 | }
35 |
36 | removeTrack(track: Track) {
37 | this.albumService.removeSong(track);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { AlbumList } from './albums/albumList';
5 | import { AlbumDisplay} from './albums/albumDisplay';
6 | import {AlbumEditor } from './albums/albumEditor';
7 | import { ArtistList } from './artists/artistList';
8 | import { ArtistDisplay} from "./artists/artistDisplay";
9 | import {OptionsComponent} from "./Options/options";
10 | import {LoginComponent} from "./common/login";
11 | import {AboutComponent} from "./options/about";
12 |
13 | const routes: Routes = [
14 | {path: '', redirectTo: "albums", pathMatch: 'full'},
15 | {path: '', redirectTo: 'albums', pathMatch: 'full'},
16 | {path: 'albums', component: AlbumList },
17 | {path: 'album/:id', component: AlbumDisplay },
18 | {path: 'album/edit/:id', component: AlbumEditor },
19 | {path: 'artists', component: ArtistList },
20 | {path: 'artist/:id', component: ArtistDisplay },
21 | {path: 'options', component: OptionsComponent },
22 | { path: 'login', component: LoginComponent },
23 | { path: 'logout', component: LoginComponent },
24 | { path: 'about', component: AboutComponent}
25 | ];
26 |
27 | @NgModule({
28 | imports: [RouterModule.forRoot(routes)],
29 | exports: [RouterModule],
30 | providers: []
31 | })
32 | export class AppRoutingModule { }
33 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/app/app.component.css
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | © West Wind Technologies
18 |
19 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 |
4 | import { AppComponent } from './app.component';
5 |
6 | describe('AppComponent', () => {
7 | beforeEach(async(() => {
8 | TestBed.configureTestingModule({
9 | imports: [
10 | RouterTestingModule
11 | ],
12 | declarations: [
13 | AppComponent
14 | ],
15 | }).compileComponents();
16 | }));
17 |
18 | it('should create the app', async(() => {
19 | const fixture = TestBed.createComponent(AppComponent);
20 | const app = fixture.debugElement.componentInstance;
21 | expect(app).toBeTruthy();
22 | }));
23 |
24 | it(`should have as title 'app works!'`, async(() => {
25 | const fixture = TestBed.createComponent(AppComponent);
26 | const app = fixture.debugElement.componentInstance;
27 | expect(app.title).toEqual('app works!');
28 | }));
29 |
30 | it('should render title in a h1 tag', async(() => {
31 | const fixture = TestBed.createComponent(AppComponent);
32 | fixture.detectChanges();
33 | const compiled = fixture.debugElement.nativeElement;
34 | expect(compiled.querySelector('h1').textContent).toContain('app works!');
35 | }));
36 | });
37 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import {UserInfo} from "./business/userInfo";
3 |
4 | @Component({
5 | selector: 'app-root',
6 | templateUrl: './app.component.html',
7 | styleUrls: ['./app.component.css']
8 | })
9 | export class AppComponent {
10 | title = 'Test Application';
11 |
12 | constructor(private user:UserInfo){
13 | this.user.checkAuthentication()
14 | .subscribe();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/artists/artistDisplay.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
23 |
{{artist.ArtistName}}
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
37 |
38 |
39 |
40 |
Albums
41 |
42 |
43 |
45 |
51 |
52 |
53 |
54 |
55 |
{{album.Title}}
56 |
57 | {{(album.Year ? 'in ' + album.Year : '')}}
58 |
59 |
{{album.Description}}
60 |
61 |
62 |
63 |
64 |
65 |
66 | Add Album
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/artists/artistDisplay.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild} from '@angular/core';
2 | import { ArtistService} from "./artistService";
3 | import { Artist, Album} from "../business/entities";
4 | import { AppConfiguration} from "../business/appConfiguration";
5 | import { ActivatedRoute, Router} from "@angular/router";
6 |
7 | import {UserInfo} from "../business/userInfo";
8 | import {ErrorInfo} from "../common/errorDisplay";
9 | import { ArtistEditor } from "./artistEditor";
10 | import {slideIn} from "../common/animations";
11 |
12 |
13 |
14 | @Component({
15 | //moduleId: module.id,
16 | selector: 'artist-display',
17 | templateUrl: './artistDisplay.html',
18 | animations: [ slideIn ]
19 | })
20 | export class ArtistDisplay implements OnInit {
21 | // reference a child editor component
22 | @ViewChild(ArtistEditor) editor:ArtistEditor;
23 |
24 | artist: Artist = new Artist();
25 | albums: Album[] = [];
26 | artistId = null;
27 | formActive = true;
28 |
29 | error:ErrorInfo = new ErrorInfo();
30 |
31 | constructor(private route: ActivatedRoute,
32 | private artistService: ArtistService,
33 | private config: AppConfiguration,
34 | private router: Router,
35 | private user: UserInfo) {
36 | }
37 |
38 |
39 | ngOnInit() {
40 | this.config.isSearchAllowed = false;
41 |
42 | var id = this.route.snapshot.params["id"];
43 | if (id < 1)
44 | return;
45 |
46 | this.artistService.getArtist(id)
47 | .subscribe(
48 | (result: any) => {
49 | this.artist = result.Artist;
50 | this.albums = result.Albums;
51 | },
52 | (err) => {
53 | this.error.error(err);
54 | });
55 | }
56 |
57 | editArtist() {
58 | if (!this.user.isAuthenticated) {
59 | this.router.navigate(["login"]);
60 | return;
61 | }
62 |
63 | this.editor.showEditor();
64 | };
65 |
66 | albumClick(album) {
67 | //window.location.hash = "album/" + album.Id;
68 | this.router.navigate(['/album', album.Id]);
69 | }
70 |
71 |
72 |
73 | addAlbum() {
74 |
75 | }
76 |
77 | deleteArtist(artist:Artist) {
78 | this.artistService.deleteArtist(artist)
79 | .subscribe((result) => {
80 | this.error.info("Album deleted.");
81 | setTimeout(()=> {
82 | this.router.navigate(["/artists"]);
83 | this.artistService.artistList =
84 | this.artistService.artistList.filter( art=> art.Id != artist.Id );
85 | }, 1200);
86 | }, (err)=> { console.log(err); this.error.error(err) });
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/artists/artistEditor.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
10 |
11 |
14 |
15 |
16 |
17 | Artist Name:
18 |
22 |
23 |
24 |
25 | Bio:
26 |
32 |
33 |
34 | Image Url:
35 |
38 |
39 |
40 | Amazon Url:
41 |
44 |
45 |
46 |
47 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/artists/artistEditor.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit, Input } from '@angular/core';
2 | import {ArtistService} from "./artistService";
3 | import {Artist, Album} from "../business/entities";
4 |
5 | import {ErrorInfo} from "../common/errorDisplay";
6 | import {AppConfiguration} from "../business/appConfiguration";
7 | import {UserInfo} from "../business/userInfo";
8 |
9 | declare var $: any;
10 |
11 | @Component({
12 | //moduleId: module.id,
13 | selector: 'artist-editor',
14 | templateUrl: 'artistEditor.html'
15 | })
16 | export class ArtistEditor implements OnInit {
17 | @Input() artist: Artist = new Artist();
18 | albums: Album[] = [];
19 | formActive = false;
20 | error: ErrorInfo = new ErrorInfo();
21 |
22 | constructor(private artistService: ArtistService,
23 | private config: AppConfiguration,
24 | private user:UserInfo) {
25 | console.log("ArtistEditor ctor");
26 | }
27 |
28 | ngOnInit() {
29 | this.config.isSearchAllowed = false;
30 | }
31 |
32 |
33 | showEditor() {
34 | ( $("#EditModal")).modal("show");
35 | }
36 |
37 |
38 |
39 | saveArtist(artist) {
40 | this.artistService.saveArtist(artist)
41 | .subscribe( result => {
42 | this.artist = result.Artist;
43 | this.albums = result.Albums;
44 |
45 | ( $("#EditModal")).modal("hide");
46 |
47 | this.formActive = false;
48 | setTimeout(()=> {
49 | this.formActive = true;
50 | }, 0);
51 |
52 | this.error.info("Artist has been saved");
53 | },
54 | err => {
55 | this.error.error(err);
56 |
57 | if (err.response && err.response.status == 401) {
58 | window.location.hash="login";
59 | }
60 | });
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/artists/artistList.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/artists/artistList.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import { Artist } from '../business/entities';
3 | import {AppConfiguration} from "../business/appConfiguration";
4 | import { ArtistService } from './artistService';
5 | import {Router} from "@angular/router";
6 | import {ErrorInfo} from "../common/errorDisplay";
7 |
8 | import {slideIn, slideInLeft} from "../common/animations";
9 |
10 | declare var $:any;
11 |
12 | @Component({
13 | selector: 'artist-list',
14 | templateUrl: 'artistList.html',
15 | animations: [ slideInLeft ]
16 | })
17 | export class ArtistList implements OnInit {
18 | constructor(private router:Router,
19 | private artistService: ArtistService,
20 | private config: AppConfiguration) {
21 |
22 | }
23 |
24 | artistList: Artist[] = [];
25 | error:ErrorInfo = new ErrorInfo();
26 |
27 | ngOnInit() {
28 | this.getArtists();
29 |
30 | this.config.searchText = "";
31 | this.config.isSearchAllowed = true;
32 | this.config.activeTab = "artists";
33 |
34 | setTimeout(() => {
35 | $("#SearchBox").focus();
36 | },200);
37 | }
38 |
39 | get filteredArtistList() {
40 | if(this.config.searchText && this.config.searchText.length > 1) {
41 | var lsearchText = this.config.searchText.toLowerCase();
42 | return this.artistList.filter((a) =>
43 | a.ArtistName.toLowerCase().includes(lsearchText)
44 | );
45 | }
46 | return this.artistList;
47 | }
48 |
49 |
50 |
51 |
52 | getArtists() {
53 | this.artistService.getArtists()
54 | .subscribe( artists => {
55 | this.artistList = artists;
56 |
57 | setTimeout(() => {
58 | $("#MainView").scrollTop(this.artistService.listScrollPos);
59 | this.artistService.listScrollPos = 0;
60 | }, 20);
61 | return this.artistList;
62 | },
63 | err => { this.error.error(err) }
64 | );
65 | }
66 |
67 | artistClick(artist:Artist) {
68 | // Manual Navigation
69 | this.router.navigate(['/artist', artist.Id]);
70 | this.artistService.listScrollPos = $("#MainView").scrollTop();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/artists/artistService.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Artist, Album, Track } from '../business/entities';
3 |
4 | import {AppConfiguration} from "../business/appConfiguration";
5 | import {Http, RequestOptions} from "@angular/http";
6 | import {HttpClient} from "../business/HttpClient";
7 | import {ErrorInfo} from "../common/errorDisplay";
8 | import {Observable} from "rxjs";
9 |
10 |
11 | @Injectable()
12 | export class ArtistService {
13 | constructor(private http: Http,private httpClient:HttpClient,
14 | private config: AppConfiguration) {
15 | console.log("ArtistService ctor");
16 | }
17 |
18 | artistList: Artist[] = [];
19 | artist: Artist = null;
20 | albums: Album[] = [];
21 | error:string = "";
22 |
23 | listScrollPos = 0;
24 |
25 |
26 | getArtists(force: boolean = false): Observable {
27 |
28 | // use locally cached version
29 | if (force !== true && (this.artistList && this.artistList.length > 0))
30 | return Observable.of(this.artistList) as Observable;
31 |
32 | return this.http.get(this.config.urls.url("artists"))
33 | .map(response => {
34 | this.artistList = response.json();
35 |
36 | return this.artistList;
37 | })
38 | .catch( new ErrorInfo().parseObservableResponseError);
39 | }
40 |
41 |
42 | getArtist(id) {
43 | return this.http.get(this.config.urls.url("artist",id),
44 | this.config.requestOptions)
45 | .map(response => {
46 | var result = response.json();
47 | this.artist = result.Artist;
48 | this.artist.Albums = result.Albums;
49 |
50 | if(!this.artistList || this.artistList.length < 1)
51 | this.getArtists();
52 |
53 | return result;
54 | })
55 | .catch( new ErrorInfo().parseObservableResponseError );
56 | }
57 |
58 | saveArtist(artist) {
59 |
60 | return this.http.post(this.config.urls.url("saveArtist"),artist,
61 | new RequestOptions( {withCredentials:true} ))
62 | .map( response => {
63 | var result = response.json();
64 | this.artist = result.Artist;
65 | this.artist.Albums = result.Albums;
66 |
67 | this.updateArtist(result.Artist);
68 |
69 | return result;
70 | })
71 | .catch( new ErrorInfo().parseObservableResponseError);
72 | }
73 |
74 | // Update the artistList with an artist
75 | updateArtist(artist) {
76 |
77 | var idx = this.artistList.findIndex( (art:Artist) => art.Id == artist.Id );
78 |
79 | if (idx < 0)
80 | this.artistList.push(artist);
81 | else {
82 | this.artistList[idx] = artist;
83 | }
84 | }
85 |
86 | deleteArtist(artist:Artist) {
87 | return this.http.delete(this.config.urls.url("artist",artist.Id),
88 | new RequestOptions( {withCredentials:true} ))
89 | .map(response => {
90 | return response.json(); // boolean
91 | })
92 | .catch( new ErrorInfo().parseObservableResponseError);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/business/HttpClient.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from "@angular/core";
2 | import {Http, RequestOptionsArgs, Response} from "@angular/http";
3 | import {UserInfo} from "./userInfo";
4 | import {Observable} from "rxjs";
5 |
6 | /**
7 | * Wrapper around the Http provider to allow customizing HTTP requests
8 | */
9 | @Injectable()
10 | export class HttpClient {
11 |
12 | constructor(private http:Http, private user:UserInfo) {
13 | }
14 |
15 | get(url:string, requestOptions?:RequestOptionsArgs) {
16 | this.ensureOptions(!requestOptions);
17 | return this.http
18 | .get(url, requestOptions)
19 | .catch( response => {
20 | if (response.status == 401)
21 | this.user.isAuthenticated = false;
22 |
23 | return Observable.throw(response);
24 | })
25 | }
26 |
27 | post(url:string, data:any, requestOptions:RequestOptionsArgs) {
28 | this.ensureOptions(!requestOptions)
29 |
30 | return this.http
31 | .post(url, data, requestOptions)
32 | .catch( response => {
33 | if (response.status == 401)
34 | this.user.isAuthenticated = false;
35 |
36 | return Observable.throw(response);
37 | });
38 | }
39 |
40 | put(url:string, data:any, requestOptions:RequestOptionsArgs) {
41 | //this.ensureOptions(!requestOptions);
42 |
43 | return this.http
44 | .put(url, data, requestOptions)
45 | .catch(response => {
46 | if (response.status == 401)
47 | this.user.isAuthenticated = false;
48 |
49 | return Observable.throw(response);
50 | });
51 | }
52 |
53 | delete(url:string, requestOptions:RequestOptionsArgs) {
54 |
55 | this.ensureOptions (!requestOptions);
56 |
57 | return this.http.delete(url,requestOptions)
58 | .catch(response => {
59 | if (response.status == 401)
60 | this.user.isAuthenticated = false;
61 |
62 | return Observable.throw(response);
63 | });
64 | }
65 |
66 |
67 | ensureOptions(requestOptions:RequestOptionsArgs):RequestOptionsArgs {
68 |
69 |
70 | // if (!requestOptions)
71 | // requestOptions = {
72 | // withCredentials: true
73 | // };
74 | // else
75 | // requestOptions.withCredentials = true;
76 |
77 | return requestOptions;
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/business/appConfiguration.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 |
4 | import {RequestOptions} from "@angular/http";
5 | import {ApplicationStats} from "./entities";
6 | declare var toastr: any;
7 | declare var location: any;
8 |
9 | @Injectable()
10 | export class AppConfiguration {
11 | constructor(){
12 | this.setToastrOptions();
13 | console.log("AppConfiguration ctor");
14 |
15 | if(location.port && (location.port == "3000") || (location.port== "4200") )
16 | this.urls.baseUrl = "http://localhost:5000/"; // kestrel
17 |
18 | //this.urls.baseUrl = "http://localhost:26448/"; // iis Express
19 | //this.urls.baseUrl = "http://localhost/albumviewer/"; // iis
20 | //this.urls.baseUrl = "https://samples.west-wind.com/AlbumViewerCore/"; // online
21 | }
22 |
23 | // top level search text
24 | searchText = "";
25 | activeTab = "albums";
26 | isSearchAllowed = true;
27 | applicationStats:ApplicationStats = new ApplicationStats();
28 |
29 | urls = {
30 | baseUrl: "./",
31 | //baseUrl: "http://localhost/albumviewer/",
32 | //baseUrl: "http://localhost:5000/",
33 | //baseUrl: "https://albumviewer2swf.west-wind.com/",
34 | albums: "api/albums",
35 | album: "api/album",
36 | artists: "api/artists",
37 | artist: "api/artist",
38 | artistLookup: "api/artistlookup?search=",
39 | saveArtist: "api/artist",
40 | login: "api/login", //"api/login",
41 | logout: "api/logout",
42 | isAuthenticated: "api/isAuthenticated",
43 | reloadData: "api/reloadData",
44 | applicationStats: "api/applicationstats",
45 | url: (name,parm1?,parm2?,parm3?) => {
46 | var url = this.urls.baseUrl + this.urls[name];
47 | if (parm1)
48 | url += "/" + parm1;
49 | if (parm2)
50 | url += "/" + parm2;
51 | if (parm3)
52 | url += "/" + parm3;
53 |
54 | return url;
55 | }
56 | };
57 |
58 |
59 | setToastrOptions() {
60 | toastr.options.closeButton = true;
61 | toastr.options.positionClass = "toast-bottom-right";
62 | }
63 |
64 | /**
65 | * Http Request options to for requests
66 | * @type {RequestOptions}
67 | */
68 | requestOptions = new RequestOptions({ withCredentials: true });
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/business/entities.ts:
--------------------------------------------------------------------------------
1 | import {Injectable, InjectionToken} from '@angular/core';
2 |
3 | @Injectable()
4 | export class Album {
5 | Id:number = 0;
6 | ArtistId:number = 0;
7 | Title:string = null;
8 | Description:string = null;
9 | Year:number = 0;
10 | ImageUrl:string = null;
11 | AmazonUrl:string = null;
12 | SpotifyUrl:string = null;
13 |
14 | Artist:Artist = new Artist();
15 | Tracks:Track[] = [];
16 | }
17 |
18 | @Injectable()
19 | export class Artist {
20 | Id:number = 0;
21 | ArtistName:string = null;
22 | Description:string = null;
23 | ImageUrl:string = null;
24 | AmazonUrl:string = null;
25 | AlbumCount:number = 0;
26 | Albums:Album[] = [];
27 | }
28 |
29 |
30 | @Injectable()
31 | export class Track {
32 | Id:number = 0;
33 | AlbumId:number = 0;
34 | SongName:string = null;
35 | Length:string = null;
36 | Bytes:number = 0;
37 | UnitPrice:number = 0;
38 | }
39 |
40 | @Injectable()
41 | export class ApplicationStats {
42 | OsPlatform:string = null;
43 | }
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/business/userInfo.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import {Http, RequestOptions} from "@angular/http";
3 | import {AppConfiguration} from "./appConfiguration";
4 | import {Observable} from "rxjs";
5 | import {ErrorInfo} from "../common/errorDisplay";
6 |
7 | @Injectable()
8 | export class UserInfo {
9 |
10 | isAdmin = false;
11 | userName:string = null;
12 | sessionStarted = new Date();
13 |
14 | private _isAuthenticated = false;
15 | set isAuthenticated(val) {
16 | this._isAuthenticated = val;
17 | // cache authentication
18 | localStorage.setItem('av_isAuthenticated', val.toString());
19 | }
20 | get isAuthenticated() {
21 | return this._isAuthenticated;
22 | };
23 |
24 |
25 | constructor(private http: Http,
26 | private config: AppConfiguration) {
27 | // initialize isAuthenticate from localstorage
28 | var isAuthenticated = localStorage.getItem("av_isAuthenticated");
29 | this._isAuthenticated = !isAuthenticated || isAuthenticated === 'false' ? false : true;
30 | }
31 |
32 |
33 | login(username, password) {
34 | return this.http.post(this.config.urls.url("login"), {
35 | username: username,
36 | password: password
37 | }, new RequestOptions({withCredentials:true}))
38 | .catch( (response) => {
39 | if(response.status === 401)
40 | this.isAuthenticated = false;
41 |
42 | return new ErrorInfo().parseObservableResponseError(response);
43 | });
44 | }
45 |
46 | logout() {
47 | return this.http.get(this.config.urls.url("logout"),
48 | new RequestOptions({withCredentials:true}))
49 | .map(
50 | (response) => {
51 | this.isAuthenticated = false;
52 | return true;
53 | }
54 | );
55 | }
56 |
57 | /**
58 | * Calls to the server to check authentication and then
59 | * updates the local isAuthenticated flag
60 | * @returns {Observable}
61 | */
62 | checkAuthentication() {
63 | var url = this.config.urls.url( "isAuthenticated" );
64 | console.log(url);
65 | return this.http.get(url,
66 | new RequestOptions({withCredentials: true}))
67 | .map( (response) => {
68 | let result = response.json();
69 | this.isAuthenticated = result;
70 | return result;
71 | })
72 | .catch( (response) => {
73 | this.isAuthenticated = false;
74 | return Observable.throw( response );
75 | });
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/common/animations.ts:
--------------------------------------------------------------------------------
1 | import { trigger, state, style, transition, animate } from '@angular/core';
2 |
3 | export const slideIn = trigger('slideIn', [
4 | state('*', style({
5 | transform: 'translateX(100%)',
6 | })),
7 | state('in', style({
8 | transform: 'translateX(0)',
9 | })),
10 | state('out', style({
11 | transform: 'translateX(-100%)',
12 | })),
13 | transition('* => in', animate('400ms ease-in')),
14 | transition('in => out', animate('400ms ease-out'))
15 | ]);
16 |
17 | export const slideInLeft = trigger('slideInLeft', [
18 | state('*', style({
19 | transform: 'translateX(-100%)',
20 | })),
21 | state('in', style({
22 | transform: 'translateX(0)',
23 | })),
24 | state('out', style({
25 | transform: 'translateX(100%)',
26 | })),
27 | transition('* => in', animate('300ms ease-in')),
28 | transition('in => out', animate('300ms ease-out'))
29 | ]);
30 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/common/appFooter.html:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/common/appFooter.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import {AppConfiguration} from "../business/appConfiguration";
3 |
4 | @Component({
5 | //moduleId: module.id,
6 | selector: 'app-footer',
7 | templateUrl: 'appFooter.html'
8 | })
9 | export class AppFooter implements OnInit {
10 | constructor(public config:AppConfiguration) {
11 |
12 | }
13 |
14 | ngOnInit() {
15 |
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/common/appHeader.html:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
40 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/common/appHeader.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import {AppConfiguration} from "../business/appConfiguration";
3 |
4 | @Component({
5 | //moduleId: module.id,
6 | selector: 'app-header',
7 | templateUrl: 'appHeader.html'
8 | })
9 | export class AppHeader implements OnInit {
10 | constructor(public config:AppConfiguration) {
11 | }
12 |
13 | ngOnInit() {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/common/errorDisplay.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit, Input } from '@angular/core';
2 | import { Response} from "@angular/http";
3 | import {Observable} from "rxjs";
4 |
5 | /**
6 | * A Bootstrap based alert display
7 | */
8 | @Component({
9 | //moduleId: module.id,
10 | selector: 'error-display',
11 | //templateUrl: 'errorDisplay.html'
12 | template: `
13 |
15 |
17 |
18 |
19 |
20 |
21 |
22 | {{error.header}}
23 |
24 |
27 |
28 |
{{error.message}}
29 |
30 | `
31 | })
32 |
33 | export class ErrorDisplay implements OnInit {
34 | constructor() {
35 | }
36 |
37 | /**
38 | * Error object that is bound to the component.
39 | * @type {ErrorInfo}
40 | */
41 | @Input() error: ErrorInfo = new ErrorInfo();
42 |
43 | ngOnInit() { }
44 | }
45 |
46 | export class ErrorInfo {
47 | constructor() {
48 | this.reset();
49 | }
50 |
51 | message:string;
52 | icon:string;
53 | dismissable:boolean;
54 | header:string;
55 | imageIcon:string;
56 | iconColor:string;
57 |
58 | response:Response = null;
59 |
60 | reset() {
61 | this.message = "";
62 | this.header = "";
63 | this.dismissable = false;
64 | this.icon = "warning";
65 | this.imageIcon = "warning";
66 | this.iconColor = "inherit";
67 | }
68 |
69 | /**
70 | * Low level method to set message properties
71 | * @param msg - the message to set to
72 | * @param icon? - sets the icon property (warning*)
73 | * @param iconColor? - sets the icon color (left as is)
74 | */
75 | show(msg:string, icon?:string, iconColor?:string) {
76 | this.message = msg;
77 | this.icon = icon ? icon : "warning";
78 | if (iconColor)
79 | this.iconColor = iconColor;
80 |
81 | this.fixupIcons();
82 |
83 |
84 | // if(this.icon == "warning")
85 | // toastr.warning(this.message);
86 | // if(this.icon == "info")
87 | // toastr.info(this.message);
88 | // if (this.icon == "success")
89 | // toastr.success(this.message);
90 | }
91 |
92 | /**
93 | * Displays an error alert
94 | * @param msg - Either a message string or error object with .message property
95 | */
96 | error(msg) {
97 | if(typeof(msg) === 'object' && msg.message)
98 | this.message = msg.message;
99 | else
100 | this.message = msg;
101 |
102 | this.show(this.message,"warning");
103 | }
104 |
105 | /**
106 | * DIsplays an info style alert
107 | * @param msg - message to display
108 | */
109 | info(msg) {
110 | this.show(msg,"info");
111 | }
112 |
113 | /**
114 | * Fixes up icons and colors based on standard icon settings
115 | * this method is called in internally after any of the helper
116 | * methods are called. You can call this when setting any icon
117 | * related properties manually.
118 | */
119 | fixupIcons() {
120 | var err = this;
121 |
122 | if (err.icon === "info")
123 | err.imageIcon = "info-circle";
124 | if (err.icon === "error" || err.icon === "danger" || err.icon === "warning") {
125 | err.imageIcon = "warning";
126 | err.iconColor = "firebrick";
127 | }
128 | if (err.icon === "success") {
129 | err.imageIcon = "check";
130 | err.iconColor = "green";
131 | }
132 | }
133 |
134 | /**
135 | * Parse a toPromise() .catch() clause error
136 | * from a response object and returns an errorInfo object
137 | * @param response
138 | * @returns {Promise|Promise}
139 | */
140 | parsePromiseResponseError(response) {
141 |
142 | if (response.hasOwnProperty("message"))
143 | return Promise.reject(response);
144 | if (response.hasOwnProperty("Message")) {
145 | response.message = response.Message;
146 | return Promise.reject(response);
147 | }
148 |
149 | let err = new ErrorInfo();
150 | err.response = response;
151 | err.message = response.statusText;
152 |
153 | try {
154 | let data = response.json();
155 | if (data && data.message)
156 | err.message = data.message;
157 | }
158 | catch(ex) {
159 |
160 | }
161 |
162 | return Promise.reject(err);
163 | }
164 |
165 | parseObservableResponseError(response):Observable {
166 | if (response.hasOwnProperty("message"))
167 | return Observable.throw(response);
168 | if (response.hasOwnProperty("Message")) {
169 | response.message = response.Message;
170 | return Observable.throw(response);
171 | }
172 |
173 | let err = new ErrorInfo();
174 | err.response = response;
175 | err.message = response.statusText;
176 |
177 | try {
178 | let data = response.json();
179 | if (data && data.message)
180 | err.message = data.message;
181 | }
182 | catch(ex) { }
183 |
184 | if (!err.message)
185 | err.message = "Unknown server failure.";
186 |
187 | return Observable.throw(err);
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/common/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 | Please sign in
12 |
13 |
14 |
15 |
16 |
32 |
33 |
50 | You can use: uid: test / pwd: test
51 |
52 |
53 |
57 |
58 | Login
59 |
60 |
61 |
62 |
65 |
66 | Logout
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/common/login.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import {UserInfo} from "../business/userInfo";
3 | import {ErrorInfo} from "./errorDisplay";
4 |
5 | declare var toastr:any;
6 |
7 | import {ActivatedRoute, Router} from "@angular/router";
8 |
9 | @Component({
10 | //moduleId: module.id,
11 | selector: 'login',
12 | templateUrl: 'login.html'
13 | })
14 | export class LoginComponent implements OnInit {
15 | username:string = "";
16 | password:string = "";
17 | error: ErrorInfo = new ErrorInfo();
18 |
19 | constructor(public user:UserInfo,
20 | private route:ActivatedRoute,
21 | private router: Router)
22 | { }
23 |
24 | ngOnInit() {
25 |
26 | if (this.route.snapshot.url[0].path == "logout")
27 | this.logout();
28 | }
29 |
30 | login() {
31 | this.user.login(this.username,this.password)
32 | .subscribe(() => {
33 | this.user.isAuthenticated = true;
34 | toastr.success("You are logged in.");
35 | this.router.navigate(["/albums"]);
36 | },
37 | (err)=> {
38 | this.error.error(err);
39 | this.password="";
40 | toastr.warning("Login failed: " + err.message);
41 | });
42 | }
43 |
44 | logout() {
45 | this.user.logout()
46 | .subscribe((success) => {
47 | toastr.success("Logged out.");
48 | this.router.navigate(["/albums"]);
49 | });
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/options/about.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
13 |
ASP.NET Core API backend
14 |
15 | This application runs an ASP.NET Core API server with a
16 | JSON Service backend.
17 |
18 |
19 |
20 |
21 |
Angular 2.0 Front End
22 |
23 | The client interface uses Angular 2.0 to provide the front en logic and
24 | UI management features. There are album and artist services that
25 | talk to the backend interface and page level components and child
26 | components that handle individual view pages of the application.
27 |
28 |
29 |
30 |
31 |
Mobile enabled based on modified Bootstrap Interface
32 |
33 | The base Bootstrap framework is used for the core UI features
34 | of the interface. The base template has been customized for
35 | this application.
36 |
37 |
38 | The application is mobile focused and uses custom resizing
39 | and toolbar relocation based on the screen/device size used.
40 | The application should be comfortably usable from phone up to full screen
41 | desktop applications.
42 |
43 |
44 |
45 |
46 |
Back to Application
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/options/about.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 |
3 | @Component({
4 | selector: 'about',
5 | templateUrl: 'about.html'
6 | })
7 | export class AboutComponent {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/options/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/app/options/options.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import {AppConfiguration} from "../business/appConfiguration";
3 | import {UserInfo} from "../business/userInfo";
4 | import {ErrorInfo} from "../common/errorDisplay";
5 | import {Http} from "@angular/http";
6 |
7 | declare var toastr:any;
8 | declare var window: any;
9 | @Component({
10 | selector: 'options',
11 | templateUrl: 'options.html'
12 | })
13 | export class OptionsComponent implements OnInit {
14 | error: ErrorInfo = new ErrorInfo();
15 |
16 | constructor(public config: AppConfiguration,
17 | public user: UserInfo,
18 | private http: Http) {
19 | }
20 |
21 | ngOnInit() {
22 | this.config.isSearchAllowed = false;
23 |
24 | if (this.config.applicationStats.OsPlatform == null) {
25 | this.http.get(this.config.urls.url("applicationStats"))
26 | .subscribe(response => {
27 | this.config.applicationStats = response.json();
28 | },response=> {
29 | let obsErr = new ErrorInfo().parseObservableResponseError(response);
30 | let msg = ( obsErr).error.message;
31 | toastr.error("Get Application Stats failed: " + msg);
32 | });
33 | }
34 | }
35 |
36 | reloadData() {
37 | if (!this.user.isAuthenticated)
38 | window.location.hash = "login";
39 |
40 | this.http.get(this.config.urls.url("reloadData"), {withCredentials: true})
41 | .subscribe(
42 | response => {
43 | let success = response.json();
44 | if (success)
45 | toastr.success("Data has been reloaded.");
46 | else
47 | toastr.error("Unable to reload data");
48 | }, response => {
49 | let obsErr = new ErrorInfo().parseObservableResponseError(response);
50 | let msg = ( obsErr).error.message;
51 | toastr.error("Data reload failed: " + msg);
52 |
53 | return obsErr;
54 | });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/favicon.ico
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/RockLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/RockLogo.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/artists.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/artists.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/artists32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/artists32.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/gear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/gear.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/headphone-head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/headphone-head.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/icon.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/record.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/search.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/search_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/search_box.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/search_box2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/search_box2.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/webconnectionicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/webconnectionicon.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/webconnectionlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/webconnectionlogo.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/images/westwindtext.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/images/westwindtext.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 | West Wind Album Viewer
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
Getting things ready...
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule);
12 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/set';
35 |
36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
37 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
38 |
39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */
40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
41 |
42 |
43 | /** Evergreen browsers require these. **/
44 | import 'core-js/es6/reflect';
45 | import 'core-js/es7/reflect';
46 |
47 |
48 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/
49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
50 |
51 |
52 |
53 | /***************************************************************************************************
54 | * Zone JS is required by Angular itself.
55 | */
56 | import 'zone.js/dist/zone'; // Included with Angular CLI.
57 |
58 |
59 |
60 | /***************************************************************************************************
61 | * APPLICATION IMPORTS
62 | */
63 |
64 | /**
65 | * Date, currency, decimal and percent pipes.
66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
67 | */
68 | // import 'intl'; // Run `npm install --save intl`.
69 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/touch-icon.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/touch-icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngular/src/touch-icon32.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2016",
10 | "dom"
11 | ],
12 | "outDir": "../out-tsc/app",
13 | "target": "es5",
14 | "module": "es2015",
15 | "baseUrl": "",
16 | "types": []
17 | },
18 | "exclude": [
19 | "test.ts",
20 | "**/*.spec.ts"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2016"
10 | ],
11 | "outDir": "../out-tsc/spec",
12 | "module": "commonjs",
13 | "target": "es6",
14 | "baseUrl": "",
15 | "types": [
16 | "jasmine",
17 | "node"
18 | ]
19 | },
20 | "files": [
21 | "test.ts"
22 | ],
23 | "include": [
24 | "**/*.spec.ts"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "target": "es5",
11 | "typeRoots": [
12 | "node_modules/@types"
13 | ],
14 | "lib": [
15 | "es2016",
16 | "dom"
17 | ]
18 | }
19 | }
--------------------------------------------------------------------------------
/src/AlbumViewerAngular/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "eofline": true,
15 | "forin": true,
16 | "import-blacklist": [
17 | true,
18 | "rxjs"
19 | ],
20 | "import-spacing": true,
21 | "indent": [
22 | true,
23 | "spaces"
24 | ],
25 | "interface-over-type-literal": true,
26 | "label-position": true,
27 | "max-line-length": [
28 | true,
29 | 140
30 | ],
31 | "member-access": false,
32 | "member-ordering": [
33 | true,
34 | {
35 | "order": [
36 | "static-field",
37 | "instance-field",
38 | "static-method",
39 | "instance-method"
40 | ]
41 | }
42 | ],
43 | "no-arg": true,
44 | "no-bitwise": true,
45 | "no-console": [
46 | true,
47 | "debug",
48 | "info",
49 | "time",
50 | "timeEnd",
51 | "trace"
52 | ],
53 | "no-construct": true,
54 | "no-debugger": true,
55 | "no-duplicate-super": true,
56 | "no-empty": false,
57 | "no-empty-interface": true,
58 | "no-eval": true,
59 | "no-inferrable-types": [
60 | true,
61 | "ignore-params"
62 | ],
63 | "no-misused-new": true,
64 | "no-non-null-assertion": true,
65 | "no-shadowed-variable": true,
66 | "no-string-literal": false,
67 | "no-string-throw": true,
68 | "no-switch-case-fall-through": true,
69 | "no-trailing-whitespace": true,
70 | "no-unnecessary-initializer": true,
71 | "no-unused-expression": true,
72 | "no-use-before-declare": true,
73 | "no-var-keyword": true,
74 | "object-literal-sort-keys": false,
75 | "one-line": [
76 | true,
77 | "check-open-brace",
78 | "check-catch",
79 | "check-else",
80 | "check-whitespace"
81 | ],
82 | "prefer-const": true,
83 | "quotemark": [
84 | true,
85 | "single"
86 | ],
87 | "radix": true,
88 | "semicolon": [
89 | true,
90 | "always"
91 | ],
92 | "triple-equals": [
93 | true,
94 | "allow-null-check"
95 | ],
96 | "typedef-whitespace": [
97 | true,
98 | {
99 | "call-signature": "nospace",
100 | "index-signature": "nospace",
101 | "parameter": "nospace",
102 | "property-declaration": "nospace",
103 | "variable-declaration": "nospace"
104 | }
105 | ],
106 | "typeof-compare": true,
107 | "unified-signatures": true,
108 | "variable-name": false,
109 | "whitespace": [
110 | true,
111 | "check-branch",
112 | "check-decl",
113 | "check-operator",
114 | "check-separator",
115 | "check-type"
116 | ],
117 | "directive-selector": [
118 | true,
119 | "attribute",
120 | "app",
121 | "camelCase"
122 | ],
123 | "component-selector": [
124 | true,
125 | "element",
126 | "app",
127 | "kebab-case"
128 | ],
129 | "use-input-property-decorator": true,
130 | "use-output-property-decorator": true,
131 | "use-host-property-decorator": true,
132 | "no-input-rename": true,
133 | "no-output-rename": true,
134 | "use-life-cycle-interface": true,
135 | "use-pipe-transform-interface": true,
136 | "component-class-suffix": true,
137 | "directive-class-suffix": true,
138 | "no-access-missing-member": true,
139 | "templates-use-public": true,
140 | "invoke-injectable": true
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "album-viewer"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "../AlbumViewerNetCore/wwwroot",
10 | "index": "index.html",
11 | "main": "main.ts",
12 | "polyfills": "polyfills.ts",
13 | "test": "test.ts",
14 | "tsconfig": "tsconfig.app.json",
15 | "testTsconfig": "tsconfig.spec.json",
16 | "prefix": "app",
17 | "assets": [
18 | "images",
19 | "favicon.ico",
20 | "touch-icon.png"
21 | ],
22 | "styles": [
23 | "../node_modules/bootstrap/dist/css/bootstrap.css",
24 | "../node_modules/bootstrap/dist/css/bootstrap-theme.css",
25 | "../node_modules/font-awesome/css/font-awesome.css",
26 | "../node_modules/toastr/build/toastr.css",
27 | "./css/albumviewer.css"
28 | ],
29 | "scripts": [
30 | "../node_modules/jquery/dist/jquery.js",
31 | "../node_modules/toastr/toastr.js",
32 | "../node_modules/bootstrap/dist/js/bootstrap.js",
33 | "../node_modules/bootstrap-3-typeahead/bootstrap3-typeahead.js"
34 | ],
35 | "environmentSource": "environments/environment.ts",
36 | "environments": {
37 | "dev": "environments/environment.ts",
38 | "prod": "environments/environment.prod.ts"
39 | }
40 | }
41 | ],
42 | "e2e": {
43 | "protractor": {
44 | "config": "./protractor.conf.js"
45 | }
46 | },
47 | "lint": [
48 | {
49 | "project": "src/tsconfig.app.json"
50 | },
51 | {
52 | "project": "src/tsconfig.spec.json"
53 | },
54 | {
55 | "project": "e2e/tsconfig.e2e.json"
56 | }
57 | ],
58 | "test": {
59 | "karma": {
60 | "config": "./karma.conf.js"
61 | }
62 | },
63 | "defaults": {
64 | "styleExt": "css",
65 | "component": {}
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 |
10 | # IDEs and editors
11 | /.idea
12 | /.vs
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage/*
31 | /libpeerconnection.log
32 | npm-debug.log
33 | testem.log
34 | /typings
35 |
36 | # e2e
37 | /e2e/*.js
38 | /e2e/*.map
39 |
40 | #System Files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/README.md:
--------------------------------------------------------------------------------
1 | # AlbumViewer
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.0.
4 |
5 | ## Development server
6 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
7 |
8 | ## Code scaffolding
9 |
10 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
11 |
12 | ## Build
13 |
14 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
15 |
16 | ## Running unit tests
17 |
18 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
19 |
20 | ## Running end-to-end tests
21 |
22 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
23 | Before running the tests make sure you are serving the app via `ng serve`.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
28 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "album-viewer",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve",
8 | "build": "ng build",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e"
12 | },
13 | "private": true,
14 | "dependencies": {
15 | "@angular/common": "^2.4.0",
16 | "@angular/compiler": "^2.4.0",
17 | "@angular/core": "^2.4.0",
18 | "@angular/forms": "^2.4.0",
19 | "@angular/http": "^2.4.0",
20 | "@angular/platform-browser": "^2.4.0",
21 | "@angular/platform-browser-dynamic": "^2.4.0",
22 | "@angular/router": "^3.4.0",
23 | "bootstrap": "^3.3.7",
24 | "bootstrap-3-typeahead": "^4.0.2",
25 | "core-js": "^2.4.1",
26 | "font-awesome": "^4.7.0",
27 | "jquery": "^3.1.1",
28 | "rxjs": "^5.1.0",
29 | "toastr": "^2.1.2",
30 | "zone.js": "^0.7.6"
31 | },
32 | "devDependencies": {
33 | "@angular/cli": "1.0.0-rc.0",
34 | "@angular/compiler-cli": "^2.4.0",
35 | "@types/jasmine": "2.5.38",
36 | "@types/node": "~6.0.60",
37 | "codelyzer": "~2.0.0",
38 | "jasmine-core": "~2.5.2",
39 | "jasmine-spec-reporter": "~3.2.0",
40 | "karma": "~1.4.1",
41 | "karma-chrome-launcher": "~2.0.0",
42 | "karma-cli": "~1.0.1",
43 | "karma-jasmine": "~1.1.0",
44 | "karma-jasmine-html-reporter": "^0.2.2",
45 | "karma-coverage-istanbul-reporter": "^0.2.0",
46 | "protractor": "~5.1.0",
47 | "ts-node": "~2.0.0",
48 | "tslint": "~4.4.2",
49 | "typescript": "~2.0.0"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/app/albums/albumEditor.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {ActivatedRoute} from "@angular/router";
3 | import {Http} from "@angular/http";
4 | import {Album} from "../business/entities";
5 |
6 | @Component({
7 | selector: 'album-editor',
8 | templateUrl: 'albumEditor.html'
9 | })
10 | export class albumEditorComponent implements OnInit {
11 | constructor(private route:ActivatedRoute,
12 | private http:Http) {
13 | }
14 |
15 | album:Album = new Album();
16 | errorMessage = "";
17 | baseUrl = "http://localhost:5000/api/";
18 |
19 | ngOnInit() {
20 | var id = this.route.snapshot.params["id"];
21 | if (id < 1)
22 | return;
23 |
24 | this.loadAlbum(id);
25 | }
26 |
27 | loadAlbum(id) {
28 | this.errorMessage = "";
29 | this.http
30 | .get(`${this.baseUrl}album/${id}`)
31 | .subscribe(response => {
32 | this.album = response.json();
33 | console.log(this.album);
34 | }, response => {
35 | this.errorMessage = "Unable to load album.";
36 | });
37 | }
38 |
39 | saveAlbum(album) {
40 | return this.http
41 | .post(`${this.baseUrl}album`,album)
42 | .subscribe(response => {
43 | this.album = response.json();
44 | this.errorMessage = album.Title + " has been saved."
45 | },
46 | response => {
47 | this.errorMessage = "Unable to save album.";
48 | });
49 | };
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/app/albums/albumList.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/app/albums/albumList.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import {Http} from "@angular/http";
3 |
4 | @Component({
5 | selector: 'album-list',
6 | templateUrl: 'albumList.html'
7 | })
8 | export class albumListComponent {
9 | constructor(private http:Http) {
10 | this.getAlbums();
11 | }
12 |
13 | albumList = [];
14 | errorMessage:string = null;
15 | busy = false;
16 |
17 | //baseUrl = "http://samples.west-wind.com/AlbumViewerCore/api/";
18 | baseUrl = "http://localhost:5000/api/";
19 |
20 | getAlbums() {
21 | this.busy = true;
22 | this.albumList = [];
23 |
24 | var url = this.baseUrl + "albums";
25 | console.log(url);
26 | this.http.get(url)
27 | .subscribe( (response)=> {
28 | this.albumList = response.json();
29 | this.busy = false;
30 | },(error)=> {
31 | this.errorMessage = "Request failed.";
32 | });
33 | }
34 |
35 |
36 |
37 | }
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import {albumListComponent} from "./albums/albumList";
4 | import {HashLocationStrategy, LocationStrategy} from "@angular/common";
5 | import {albumEditorComponent} from "./albums/albumEditor";
6 |
7 | const routes: Routes = [
8 | {path: '', redirectTo: "albums", pathMatch: 'full'},
9 | { path: "albums", component: albumListComponent },
10 | {path: 'album/edit/:id', component: albumEditorComponent },
11 | //{ path: "albums/:id", component: albumDisplay }
12 | ];
13 |
14 | @NgModule({
15 | imports: [RouterModule.forRoot(routes)],
16 | exports: [RouterModule],
17 | providers: [
18 | // make sure you use this for Hash Urls rather than HTML 5 routing
19 | {
20 | provide: LocationStrategy,
21 | useClass: HashLocationStrategy
22 | }
23 | ]
24 | })
25 | export class AppRoutingModule { }
26 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/app/app.component.css
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
11 |
12 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | © West Wind Technologies
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 |
4 | import { AppComponent } from './app.component';
5 |
6 | describe('AppComponent', () => {
7 | beforeEach(async(() => {
8 | TestBed.configureTestingModule({
9 | imports: [
10 | RouterTestingModule
11 | ],
12 | declarations: [
13 | AppComponent
14 | ],
15 | }).compileComponents();
16 | }));
17 |
18 | it('should create the app', async(() => {
19 | const fixture = TestBed.createComponent(AppComponent);
20 | const app = fixture.debugElement.componentInstance;
21 | expect(app).toBeTruthy();
22 | }));
23 |
24 | it(`should have as title 'app works!'`, async(() => {
25 | const fixture = TestBed.createComponent(AppComponent);
26 | const app = fixture.debugElement.componentInstance;
27 | expect(app.title).toEqual('app works!');
28 | }));
29 |
30 | it('should render title in a h1 tag', async(() => {
31 | const fixture = TestBed.createComponent(AppComponent);
32 | fixture.detectChanges();
33 | const compiled = fixture.debugElement.nativeElement;
34 | expect(compiled.querySelector('h1').textContent).toContain('app works!');
35 | }));
36 | });
37 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: './app.component.html',
6 | styleUrls: ['./app.component.css']
7 | })
8 | export class AppComponent {
9 | title = 'app works!';
10 | time = new Date();
11 | name = 'Rick';
12 |
13 | interval = setInterval(
14 | () => this.time = new Date(),
15 | 1000);
16 | }
17 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule } from '@angular/forms';
4 | import { HttpModule } from '@angular/http';
5 |
6 | import { AppRoutingModule } from './app-routing.module';
7 | import { AppComponent } from './app.component';
8 | import {Track, Artist, Album} from "./business/entities";
9 | import {albumListComponent} from "./albums/albumList";
10 | import {albumEditorComponent} from "./albums/albumEditor";
11 |
12 |
13 | @
14 | NgModule({
15 | declarations: [
16 | AppComponent,
17 | albumListComponent,
18 | albumEditorComponent
19 | ],
20 | imports: [
21 | BrowserModule,
22 | FormsModule,
23 | HttpModule,
24 | AppRoutingModule
25 | ],
26 | providers: [
27 | Album,
28 | Artist,
29 | Track
30 | ],
31 | bootstrap: [AppComponent]
32 | })
33 | export class AppModule { }
34 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/app/business/entities.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable()
4 | export class Album {
5 | Id:number = 0;
6 | ArtistId:number = 0;
7 | Title:string = null;
8 | Description:string = null;
9 | Year:number = 0;
10 | ImageUrl:string = null;
11 | AmazonUrl:string = null;
12 | SpotifyUrl:string = null;
13 |
14 | Artist:Artist = new Artist();
15 | Tracks:Track[] = [];
16 | }
17 |
18 | @Injectable()
19 | export class Artist {
20 | Id:number = 0;
21 | ArtistName:string = null;
22 | Description:string = null;
23 | ImageUrl:string = null;
24 | AmazonUrl:string = null;
25 | AlbumCount:number = 0;
26 | Albums:Album[] = [];
27 | }
28 |
29 | @Injectable()
30 | export class Track {
31 | Id:number = 0;
32 | AlbumId:number = 0;
33 | SongName:string = null;
34 | Length:string = null;
35 | Bytes:number = 0;
36 | UnitPrice:number = 0;
37 |
38 | }
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/css/albumviewer.min.css:
--------------------------------------------------------------------------------
1 | body{}.page-header-text{font-size:1.8em;font-weight:bold;color:#4682b4}h2,h3{font-family:'Trebuchet MS','Lucida Sans Unicode','Lucida Grande','Lucida Sans',Arial,sans-serif;font-weight:bold}h3{color:#4682b4}#TitleBar{position:fixed;top:18px;left:8px;z-index:100}#TopMenu{}#SearchBox{width:220px}@media(max-width:500px){#SearchBox{width:135px}}#TopMenu input{color:#535353;padding:4px 5px;height:auto}.banner{background:#535353;color:#e1e1e1;border-bottom:solid 1px #000;position:fixed;top:0;left:0;padding-top:15px;height:58px;width:100%;z-index:10}.banner.active{color:#fff;font-weight:bolder}.banner nav{display:inline-block;margin-top:2px;margin-right:3px;padding:0}.banner nav a,.banner nav input{display:inline-block;padding:7px 5px;color:#e1e1e1;text-decoration:none}.banner nav a:hover{color:#fff;background:#767676;text-decoration:none;border-bottom:solid 4px silver;border-radius:2px}.banner nav a.active,.banner nav a.selected{color:#fff;font-weight:bold;border-bottom:solid 4px #ffa500;border-radius:2px}@media(max-width:640px){.banner nav a span{display:none}}.album{margin-bottom:10px;margin-right:5px;padding:10px;border:1px solid silver;border-radius:6px;height:135px;overflow-y:hidden;float:left;max-width:300px;transition:background linear 275ms;position:relative}.album:hover{background:#4682b4;color:#fff !important;cursor:pointer}.album:hover .album-overlay{display:block}.album-overlay{position:relative;float:right;background:#034e8d;color:#whitesmoke;padding:5px;min-width:40px;display:none;margin-top:-10px;margin-right:-10px;border-radius:3px;opacity:.85;z-index:1000;box-shadow:2px 2px 4px #535353}.album-overlay:hover{opacity:1}.album-overlay a,.album-overlay a:hover{color:#f5f5dc;text-decoration:none}.album-overlay a:hover{color:#fff}.album-title{font-weight:bold}.album-title-big{font-weight:bold;font-size:1.55em}.album-descript{font-size:.85em;overflow:hidden}.album-artist{font-size:.85em;font-style:italic;color:#2a5fb4}.album-year{font-size:.85em;color:#4682b4}.album-image,.album-image-big{width:72px;float:left;border-radius:35px}.album-image-big{width:auto;float:none;max-width:95%;border-radius:4px;box-shadow:2px 2px 4px #535353}@media(max-width:768px){.album-image-big{margin-bottom:10px}}.song{font-size:.85em;padding:5px;border-bottom:1px dashed silver}.separator{border-bottom:1px solid #d3d3d3;margin:5px auto 15px;height:1px}input.ng-invalid,textarea.ng-invalid,select.ng-invalid{background:#ffc0cb}@media(max-width:690px){.album{max-width:100% !important;min-height:100px}}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{border-bottom:solid 2px transparent}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a:hover{text-shadow:0 -1px 0 rgba(0,0,0,.25);color:#fff;background:#535353 !important;border-bottom:solid 2px #ff8c00;border-radius:2px}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a.selected{text-shadow:0 -1px 0 rgba(0,0,0,.25);color:#fff;background:#535353 !important;border-bottom:solid 2px #ffa500;border-radius:2px}.modal-dialog{margin:30px auto}.modal-header{padding:15px 15px 5px}.modal-body{padding:10px 20px 5px 20px}@-webkit-keyframes slideOutLeft{to{-webkit-transform:translateX(-100%);transform:translateX(-100%)}}@keyframes slideOutLeft{to{-webkit-transform:translateX(-100%);transform:translateX(-100%)}}@-webkit-keyframes slideInRight{from{-webkit-transform:translateX(100%);transform:translateX(100%)}to{-webkit-transform:translateX(0%);transform:translateX(0)}}@keyframes slideInRight{from{-webkit-transform:translateX(100%);transform:translateX(100%)}to{-webkit-transform:translateX(0%);transform:translateX(0)}}.slide-animation.ng-enter{-webkit-animation:slideInRight .7s both ease-in;animation:slideInRight .7s both ease-in;z-index:9999}.slide-animation.ng-leave{-webkit-animation:slideOutLeft .35s both ease-in;animation:slideOutLeft .35s both ease-in;z-index:8888}[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none !important}
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/favicon.ico
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/RockLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/RockLogo.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/artists.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/artists.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/artists32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/artists32.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/gear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/gear.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/headphone-head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/headphone-head.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/icon.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/record.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/search.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/search_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/search_box.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/search_box2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/search_box2.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/webconnectionicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/webconnectionicon.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/webconnectionlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/webconnectionlogo.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/images/westwindtext.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/images/westwindtext.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AlbumViewer
6 |
7 |
8 |
9 |
10 |
11 |
12 | Loading...
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule);
12 |
13 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/set';
35 |
36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
37 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
38 |
39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */
40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
41 |
42 |
43 | /** Evergreen browsers require these. **/
44 | import 'core-js/es6/reflect';
45 | import 'core-js/es7/reflect';
46 |
47 |
48 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/
49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
50 |
51 |
52 |
53 | /***************************************************************************************************
54 | * Zone JS is required by Angular itself.
55 | */
56 | import 'zone.js/dist/zone'; // Included with Angular CLI.
57 |
58 |
59 |
60 | /***************************************************************************************************
61 | * APPLICATION IMPORTS
62 | */
63 |
64 | /**
65 | * Date, currency, decimal and percent pipes.
66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
67 | */
68 | // import 'intl'; // Run `npm install --save intl`.
69 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/touch-icon.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/touch-icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerAngularSimple/src/touch-icon32.png
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2016",
10 | "dom"
11 | ],
12 | "outDir": "../out-tsc/app",
13 | "target": "es5",
14 | "module": "es2015",
15 | "baseUrl": "",
16 | "types": []
17 | },
18 | "exclude": [
19 | "test.ts",
20 | "**/*.spec.ts"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2016"
10 | ],
11 | "outDir": "../out-tsc/spec",
12 | "module": "commonjs",
13 | "target": "es6",
14 | "baseUrl": "",
15 | "types": [
16 | "jasmine",
17 | "node"
18 | ]
19 | },
20 | "files": [
21 | "test.ts"
22 | ],
23 | "include": [
24 | "**/*.spec.ts"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "lib": [
11 | "es2016"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/AlbumViewerAngularSimple/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "callable-types": true,
7 | "class-name": true,
8 | "comment-format": [
9 | true,
10 | "check-space"
11 | ],
12 | "curly": true,
13 | "eofline": true,
14 | "forin": true,
15 | "import-blacklist": [true, "rxjs"],
16 | "import-spacing": true,
17 | "indent": [
18 | true,
19 | "spaces"
20 | ],
21 | "interface-over-type-literal": true,
22 | "label-position": true,
23 | "max-line-length": [
24 | true,
25 | 140
26 | ],
27 | "member-access": false,
28 | "member-ordering": [
29 | true,
30 | "static-before-instance",
31 | "variables-before-functions"
32 | ],
33 | "no-arg": true,
34 | "no-bitwise": true,
35 | "no-console": [
36 | true,
37 | "debug",
38 | "info",
39 | "time",
40 | "timeEnd",
41 | "trace"
42 | ],
43 | "no-construct": true,
44 | "no-debugger": true,
45 | "no-duplicate-variable": true,
46 | "no-empty": false,
47 | "no-empty-interface": true,
48 | "no-eval": true,
49 | "no-inferrable-types": [true, "ignore-params"],
50 | "no-shadowed-variable": true,
51 | "no-string-literal": false,
52 | "no-string-throw": true,
53 | "no-switch-case-fall-through": true,
54 | "no-trailing-whitespace": true,
55 | "no-unused-expression": true,
56 | "no-use-before-declare": true,
57 | "no-var-keyword": true,
58 | "object-literal-sort-keys": false,
59 | "one-line": [
60 | true,
61 | "check-open-brace",
62 | "check-catch",
63 | "check-else",
64 | "check-whitespace"
65 | ],
66 | "prefer-const": true,
67 | "quotemark": [
68 | true,
69 | "single"
70 | ],
71 | "radix": true,
72 | "semicolon": [
73 | "always"
74 | ],
75 | "triple-equals": [
76 | true,
77 | "allow-null-check"
78 | ],
79 | "typedef-whitespace": [
80 | true,
81 | {
82 | "call-signature": "nospace",
83 | "index-signature": "nospace",
84 | "parameter": "nospace",
85 | "property-declaration": "nospace",
86 | "variable-declaration": "nospace"
87 | }
88 | ],
89 | "typeof-compare": true,
90 | "unified-signatures": true,
91 | "variable-name": false,
92 | "whitespace": [
93 | true,
94 | "check-branch",
95 | "check-decl",
96 | "check-operator",
97 | "check-separator",
98 | "check-type"
99 | ],
100 |
101 | "directive-selector": [true, "attribute", "app", "camelCase"],
102 | "component-selector": [true, "element", "app", "kebab-case"],
103 | "use-input-property-decorator": true,
104 | "use-output-property-decorator": true,
105 | "use-host-property-decorator": true,
106 | "no-input-rename": true,
107 | "no-output-rename": true,
108 | "use-life-cycle-interface": true,
109 | "use-pipe-transform-interface": true,
110 | "component-class-suffix": true,
111 | "directive-class-suffix": true,
112 | "no-access-missing-member": true,
113 | "templates-use-public": true,
114 | "invoke-injectable": true
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/AlbumViewerBusiness/AccountRepository.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Newtonsoft.Json;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using Westwind.BusinessObjects;
8 |
9 | namespace AlbumViewerBusiness
10 | {
11 | ///
12 | /// Account repository used to validate and manage user accounts
13 | ///
14 |
15 | public class AccountRepository : EntityFrameworkRepository
16 | {
17 | public AccountRepository(AlbumViewerContext context)
18 | : base(context)
19 | { }
20 |
21 | public async Task Authenticate(string username, string password)
22 | {
23 | // TODO: Do proper password hashing - for now DEMO CODE
24 | // var hashedPassword = AppUtils.HashPassword(password);
25 | var hashedPassword = password;
26 |
27 | var user = await Context.Users.FirstOrDefaultAsync(usr =>
28 | usr.Username == username &&
29 | usr.Password == hashedPassword);
30 | if (user == null)
31 | return false;
32 |
33 | return true;
34 | }
35 |
36 | public async Task AuthenticateAndLoadUser(string username, string password)
37 | {
38 | // TODO: Do proper password hashing - for now DEMO CODE
39 | // var hashedPassword = AppUtils.HashPassword(password);
40 | var hashedPassword = password;
41 |
42 | var user = await Context.Users
43 | .FirstOrDefaultAsync(usr => usr.Username == username &&
44 | usr.Password == hashedPassword);
45 | return user;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/AlbumViewerBusiness/AlbumViewerBusiness.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/AlbumViewerBusiness/ArtistRepository.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.EntityFrameworkCore;
7 | using Westwind.BusinessObjects;
8 | using Westwind.Utilities;
9 |
10 | namespace AlbumViewerBusiness
11 | {
12 | public class ArtistRepository : EntityFrameworkRepository
13 | {
14 | public ArtistRepository(AlbumViewerContext context)
15 | : base(context)
16 | { }
17 |
18 | public async Task> GetAllArtists()
19 | {
20 | return await Context.Artists
21 | .OrderBy(art => art.ArtistName)
22 | .Select(art => new ArtistWithAlbumCount()
23 | {
24 | ArtistName = art.ArtistName,
25 | Description = art.Description,
26 | ImageUrl = art.ImageUrl,
27 | Id = art.Id,
28 | AmazonUrl = art.AmazonUrl,
29 | AlbumCount = Context.Albums.Count(alb => alb.ArtistId == art.Id)
30 | })
31 | .ToListAsync();
32 | }
33 |
34 | ///
35 | /// Returns a list of albums for a given artist
36 | ///
37 | ///
38 | ///
39 | public async Task> GetAlbumsForArtist(int artistId)
40 | {
41 | return await Context.Albums
42 | .Include(a => a.Tracks)
43 | .Include(a => a.Artist)
44 | .Where(a => a.ArtistId == artistId)
45 | .ToListAsync();
46 | }
47 |
48 |
49 | ///
50 | /// Artist look up by name - used for auto-complete box returns
51 | ///
52 | ///
53 | ///
54 | public async Task> ArtistLookup(string search = null)
55 | {
56 | if (string.IsNullOrEmpty(search))
57 | return new List();
58 |
59 | var repo = new AlbumRepository(Context);
60 |
61 | var term = search.ToLower();
62 | return await repo.Context.Artists
63 | .Where(a => a.ArtistName.ToLower().StartsWith(term))
64 | .Select(a => new ArtistLookupItem
65 | {
66 | name = a.ArtistName,
67 | id = a.ArtistName
68 | })
69 | .ToListAsync();
70 | }
71 |
72 |
73 |
74 | public async Task DeleteArtist(int id)
75 | {
76 | bool result = false;
77 | using (var tx = Context.Database.BeginTransaction())
78 | {
79 | var artist = await Context.Artists.FirstOrDefaultAsync(art => art.Id == id);
80 |
81 | // already gone
82 | if (artist == null)
83 | return true;
84 |
85 | var albumIds = await Context.Albums.Where(alb => alb.ArtistId == id).Select(alb => alb.Id).ToListAsync();
86 |
87 | var albumRepo = new AlbumRepository(Context);
88 |
89 | foreach (var albumId in albumIds)
90 | {
91 | // don't run async or we get p
92 | result = await albumRepo.DeleteAlbum(albumId);
93 | if (!result)
94 | return false;
95 | }
96 |
97 | Context.Artists.Remove(artist);
98 |
99 | result = await SaveAsync(); // just save
100 | if (!result)
101 | return false;
102 |
103 | tx.Commit();
104 |
105 | return result;
106 | }
107 | }
108 |
109 | protected override bool OnValidate(Artist entity)
110 | {
111 | if (entity == null)
112 | {
113 | ValidationErrors.Add("No artist to validate.");
114 | return false;
115 | }
116 |
117 | if (string.IsNullOrEmpty(entity.ArtistName))
118 | ValidationErrors.Add("Please enter a artist name.","AritistName");
119 | else if (string.IsNullOrEmpty(entity.Description) || entity.Description.Length < 30)
120 | ValidationErrors.Add("Description must be at least 30 characters.", "Description");
121 |
122 | return ValidationErrors.Count == 0;
123 | }
124 |
125 | }
126 |
127 | public class ArtistLookupItem
128 | {
129 | public string name { get; set; }
130 | public string id { get; set; }
131 | }
132 | }
--------------------------------------------------------------------------------
/src/AlbumViewerBusiness/Models/AlbumViewerContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using System;
3 |
4 |
5 | namespace AlbumViewerBusiness
6 | {
7 | public class AlbumViewerContext : DbContext
8 | {
9 | public string ConnectionString { get; set; }
10 |
11 | public AlbumViewerContext(DbContextOptions options) : base(options)
12 | {
13 | }
14 |
15 | public DbSet Albums { get; set; }
16 | public DbSet Artists { get; set; }
17 | public DbSet Tracks { get; set; }
18 | public DbSet Users { get; set; }
19 |
20 |
21 | protected override void OnModelCreating(ModelBuilder builder)
22 | {
23 | base.OnModelCreating(builder);
24 | }
25 |
26 |
27 | //protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
28 | //{
29 | // base.OnConfiguring(optionsBuilder);
30 |
31 | // if (optionsBuilder.IsConfigured)
32 | // return;
33 |
34 | // // Auto configuration
35 | // ConnectionString = Configuration.GetValue("Data:AlbumViewer:ConnectionString");
36 | // optionsBuilder.UseSqlServer(ConnectionString);
37 | //}
38 |
39 | }
40 | }
--------------------------------------------------------------------------------
/src/AlbumViewerBusiness/Models/AlbumViewerDataImporter.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace AlbumViewerBusiness
8 | {
9 |
10 | ///
11 | /// This class imports Albums, artists and tracks from the
12 | /// wwwroot/data/albums.js file which contains all the data
13 | /// in a single graph.
14 | ///
15 | public class AlbumViewerDataImporter
16 | {
17 | public static bool EnsureAlbumData(AlbumViewerContext context, string jsonDataFilePath)
18 | {
19 | bool hasData = false;
20 | try
21 | {
22 | hasData = context.Albums.Any();
23 | }
24 | catch
25 | {
26 | context.Database.EnsureCreated(); // just create the schema as is no migrations
27 | hasData = context.Albums.Any();
28 | }
29 |
30 |
31 | if (!hasData)
32 | {
33 | string json = System.IO.File.ReadAllText(jsonDataFilePath);
34 | return ImportFromJson(context, json) > 0;
35 | }
36 |
37 |
38 | return true;
39 | }
40 |
41 | ///
42 | /// Imports data from json
43 | ///
44 | ///
45 | ///
46 | public static int ImportFromJson(AlbumViewerContext context, string json)
47 | {
48 | var albums = JsonConvert.DeserializeObject(json);
49 |
50 | foreach (var album in albums)
51 | {
52 | // clear out primary/identity keys so insert works
53 | album.Id = 0;
54 | album.ArtistId = 0;
55 | album.Artist.Id = 0;
56 |
57 | var existingArtist = context.Artists.Where(a => a.ArtistName == album.Artist.ArtistName).FirstOrDefault();
58 | if (existingArtist == null)
59 | {
60 | context.Artists.Add(album.Artist);
61 | }
62 | else
63 | {
64 | album.Artist = existingArtist;
65 | album.ArtistId = existingArtist.Id;
66 | }
67 |
68 | if (album.Tracks != null)
69 | {
70 | foreach (var track in album.Tracks)
71 | {
72 | track.Id = 0;
73 | context.Add(track);
74 | }
75 | }
76 | context.Add(album);
77 |
78 | try
79 | {
80 | context.SaveChanges();
81 | }
82 | catch
83 | {
84 | Console.WriteLine("Error adding: " + album.ArtistId);
85 | }
86 | }
87 |
88 | var user = new User()
89 | {
90 | Username = "test",
91 | Password = "test",
92 | Fullname = "Test User",
93 | };
94 | context.Users.Add(user);
95 | context.SaveChanges();
96 |
97 | return 1;
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/src/AlbumViewerBusiness/Models/AlbumViewerEntities.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Microsoft.EntityFrameworkCore;
5 |
6 | namespace AlbumViewerBusiness
7 | {
8 | public class Album
9 | {
10 | public int Id { get; set; }
11 | public int ArtistId { get; set; }
12 | public string Title { get; set; }
13 | public string Description { get; set; }
14 | public int Year { get; set; }
15 | public string ImageUrl { get; set; }
16 | public string AmazonUrl { get; set; }
17 | public string SpotifyUrl { get; set; }
18 |
19 | public virtual Artist Artist { get; set; }
20 | public virtual IList Tracks { get; set; }
21 |
22 | public Album()
23 | {
24 | Artist = new Artist();
25 | Tracks = new List();
26 | }
27 |
28 | }
29 |
30 | public class Artist
31 | {
32 | public int Id { get; set; }
33 | public string ArtistName { get; set; }
34 | public string Description { get; set; }
35 | public string ImageUrl { get; set; }
36 | public string AmazonUrl { get; set; }
37 |
38 | //public List Albums { get; set; }
39 | }
40 |
41 | public class ArtistWithAlbumCount : Artist
42 | {
43 | public int AlbumCount { get; set; }
44 | }
45 |
46 | public class Track
47 | {
48 | public int Id { get; set; }
49 | public int AlbumId { get; set; }
50 | public string SongName { get; set; }
51 | public string Length { get; set; }
52 | public int Bytes { get; set; }
53 | public decimal UnitPrice { get; set; }
54 |
55 | public override string ToString()
56 | {
57 | return SongName;
58 | }
59 | }
60 |
61 | public class User
62 | {
63 | public int Id { get; set; }
64 |
65 | public string Username { get; set; }
66 |
67 | public string Password { get; set; }
68 |
69 | public string Fullname { get; set; }
70 | }
71 | }
--------------------------------------------------------------------------------
/src/AlbumViewerBusiness/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTrademark("")]
9 |
10 | // Setting ComVisible to false makes the types in this assembly not visible
11 | // to COM components. If you need to access a type in this assembly from
12 | // COM, set the ComVisible attribute to true on that type.
13 | [assembly: ComVisible(false)]
14 |
15 | // The following GUID is for the ID of the typelib if this project is exposed to COM
16 | [assembly: Guid("209eaf20-38e2-4c67-9557-9247a07213e6")]
17 |
--------------------------------------------------------------------------------
/src/AlbumViewerBusiness/Westwind.BusinessObjects/Utilities/DbSetExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Linq.Expressions;
5 | using System.Threading.Tasks;
6 | using Microsoft.EntityFrameworkCore;
7 | using Microsoft.EntityFrameworkCore.Infrastructure;
8 | using Microsoft.EntityFrameworkCore.Internal;
9 | using System.Reflection;
10 | using Microsoft.EntityFrameworkCore.Metadata.Internal;
11 |
12 | namespace Westwind.BusinessObjects
13 | {
14 |
15 | ///
16 | /// Add missing Find() method to dbContext
17 | ///
18 | public static class DbSetExtensions
19 | {
20 |
21 | ///
22 | /// Manual implementation of Find()
23 | ///
24 | ///
25 | ///
26 | ///
27 | ///
28 | ///
29 | /// based on post: https://weblogs.asp.net/ricardoperes/implementing-missing-features-in-entity-framework-core?__r=8d3ec9c92c78bf8
30 | ///
31 | public static TEntity Find(this DbSet set, params object[] keyValues) where TEntity : class
32 | {
33 | // This doesn't work
34 | //var svcs = set.GetInfrastructure().GetService();
35 | ///var context = svcs.CurrentContext.Context;
36 |
37 |
38 | var prop = set.GetType()
39 | .GetField("_context", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
40 |
41 | var context = prop
42 | .GetValue(set) as DbContext;
43 |
44 | var entityType = context.Model.FindEntityType(typeof(TEntity));
45 | var keys = entityType.GetKeys();
46 | var entries = context.ChangeTracker.Entries();
47 | var parameter = Expression.Parameter(typeof(TEntity), "x");
48 | IQueryable query = context.Set();
49 |
50 | //first, check if the entity exists in the cache
51 | var i = 0;
52 |
53 | //iterate through the key properties
54 | foreach (var property in keys.SelectMany(x => x.Properties))
55 | {
56 | var keyValue = keyValues[i];
57 |
58 | //try to get the entity from the local cache
59 | entries = entries.Where(e => keyValue.Equals(e.Property(property.Name).CurrentValue));
60 |
61 | //build a LINQ expression for loading the entity from the store
62 | var expression = Expression.Lambda(
63 | Expression.Equal(
64 | Expression.Property(parameter, property.Name),
65 | Expression.Constant(keyValue)),
66 | parameter) as Expression>;
67 |
68 | query = query.Where(expression);
69 |
70 | i++;
71 | }
72 |
73 | var entity = entries.Select(x => x.Entity).FirstOrDefault();
74 | if (entity != null)
75 | return entity;
76 |
77 | //second, try to load the entity from the data store
78 | entity = query.FirstOrDefault();
79 |
80 | return entity;
81 | }
82 |
83 | ///
84 | /// Returns an entity key for a give entity
85 | ///
86 | ///
87 | ///
88 | ///
89 | ///
90 | public static object[] GetEntityKey(this DbContext context, T entity) where T : class
91 | {
92 | var state = context.Entry(entity);
93 | var metadata = state.Metadata;
94 | var key = metadata.FindPrimaryKey();
95 | var props = key.Properties.ToArray();
96 |
97 | return props.Select(x => x.GetGetter().GetClrValue(entity)).ToArray();
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | indent_style = tab
3 | indent_size = 4
4 | charset = utf-8
5 | trim_trailing_whitespace = true
6 | insert_final_newline = true
7 | end_of_line = crlf
8 | # editorconfig-tools is unable to ignore longs strings or urls
9 | max_line_length = null
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/AlbumViewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/AlbumViewer.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/AlbumViewerNetCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netcoreapp2.0
4 | true
5 | true
6 | $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
7 |
8 |
9 |
10 |
11 |
12 | PreserveNewest
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | PreserveNewest
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Controllers/AccountController.cs:
--------------------------------------------------------------------------------
1 | using AlbumViewerBusiness;
2 | using System.Threading.Tasks;
3 | using System.Security.Claims;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.AspNetCore.Authentication.Cookies;
6 | using Microsoft.AspNetCore.Authorization;
7 | using Microsoft.AspNetCore.Authentication;
8 | using Microsoft.AspNetCore.Cors;
9 |
10 | namespace AlbumViewerAspNetCore
11 | {
12 | [ServiceFilter(typeof(ApiExceptionFilter))]
13 | [EnableCors("CorsPolicy")]
14 | public class AccountController : Controller
15 | {
16 | private AccountRepository accountRepo;
17 |
18 | public AccountController(AccountRepository actRepo)
19 | {
20 | accountRepo = actRepo;
21 | }
22 |
23 |
24 | [AllowAnonymous]
25 | [HttpPost]
26 | [Route("api/login")]
27 | public async Task Login([FromBody] User loginUser)
28 | {
29 | var user = await accountRepo.AuthenticateAndLoadUser(loginUser.Username, loginUser.Password);
30 | if (user == null)
31 | throw new ApiException("Invalid Login Credentials", 401);
32 |
33 | var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
34 | identity.AddClaim(new Claim(ClaimTypes.Name, user.Username)) ;
35 |
36 | if (user.Fullname == null)
37 | user.Fullname = string.Empty;
38 | identity.AddClaim(new Claim("FullName", user.Fullname));
39 |
40 |
41 | //context.Authenticate | Challenge | SignInAsync("scheme"); // Calls 2.0 auth stack
42 |
43 | await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
44 | new ClaimsPrincipal(identity));
45 |
46 | return true;
47 | }
48 |
49 | [AllowAnonymous]
50 | [HttpGet]
51 | [Route("api/logout")]
52 | public async Task Logout()
53 | {
54 | await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
55 | return true;
56 | }
57 |
58 | [HttpGet]
59 | [Route("api/isAuthenticated")]
60 | public bool IsAuthenthenticated()
61 | {
62 | return User.Identity.IsAuthenticated;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Controllers/AlbumViewerMvcController.NOTUSED:
--------------------------------------------------------------------------------
1 | #if false
2 | using System.Runtime;
3 | using AlbumViewerBusiness;
4 | using System;
5 | using System.Linq;
6 |
7 | using System.Reflection;
8 | using System.Threading.Tasks;
9 | using AlbumViewerAspNet5;
10 | using Microsoft.AspNetCore.Mvc;
11 | using Microsoft.EntityFrameworkCore;
12 |
13 | // For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
14 |
15 | namespace MusicStoreVNext
16 | {
17 | public class AlbumViewerMvcController : Controller
18 | {
19 | AlbumViewerContext context;
20 | private readonly IServiceProvider serviceProvider;
21 |
22 | public AlbumViewerMvcController(AlbumViewerContext ctx, IServiceProvider svcProvider)
23 | {
24 | context = ctx;
25 | serviceProvider = svcProvider;
26 | //this.environment = environment;
27 | }
28 |
29 |
30 | public async Task Index()
31 | {
32 | return await Albums();
33 | }
34 |
35 | [Route("albums")]
36 | public async Task Albums()
37 | {
38 |
39 | var result = await context.Albums
40 | .Include(ctx => ctx.Tracks)
41 | .Include(ctx => ctx.Artist)
42 | .OrderBy(alb => alb.Title)
43 | .ToListAsync();
44 |
45 | return View("Albums", result);
46 | }
47 |
48 |
49 | [Route("album/{id:int}")]
50 | public async Task Album(int id)
51 | {
52 | var albumRepo = new AlbumRepository(context);
53 | var album = await albumRepo.Load(id);
54 |
55 | return View("Album", album);
56 | }
57 |
58 | [Route("artists")]
59 | public async Task Artists()
60 | {
61 | var artists = await context.Artists
62 | .OrderBy(art => art.ArtistName)
63 | .Select(art => new ArtistWithAlbumCount()
64 | {
65 | ArtistName = art.ArtistName,
66 | Description = art.Description,
67 | ImageUrl = art.ImageUrl,
68 | Id = art.Id,
69 | AmazonUrl = art.AmazonUrl,
70 | AlbumCount = context.Albums.Count(alb => alb.ArtistId == art.Id)
71 | })
72 | .ToAsyncEnumerable()
73 | .ToList();
74 |
75 | return View("Artists", artists);
76 | }
77 |
78 |
79 |
80 | [HttpGet,HttpPost]
81 | public ActionResult TagHelpers(Album album = null)
82 | {
83 | if (album == null || (string.IsNullOrEmpty(album.Description) && string.IsNullOrEmpty(album.Title)))
84 | {
85 | album = new Album()
86 | {
87 | Id = 1,
88 | Title = "Highway to Hell",
89 | Description = "AC/DC's best"
90 | };
91 | }
92 | else
93 | {
94 |
95 | if (ModelState.IsValid)
96 | {
97 | ModelState.Remove("Title");
98 | album.Title = album.Title + " updated.";
99 | }
100 | }
101 |
102 | return View(album);
103 | }
104 |
105 |
106 |
107 | }
108 | }
109 | #endif
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Controllers/HelloWorldController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 |
7 | // For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
8 |
9 | namespace AlbumViewerNetCore.Controllers
10 | {
11 | public class HelloWorldController : Controller
12 | {
13 |
14 | [HttpGet]
15 | [Route("index2.html")]
16 | public string Index()
17 | {
18 | return "Hello Index page test";
19 | }
20 |
21 | [HttpGet]
22 | [Route("api/helloworld")]
23 | public object HelloWorld(string name = null)
24 | {
25 | if (string.IsNullOrEmpty(name))
26 | name = "Johnny Doe";
27 |
28 | return new
29 | {
30 | helloMessage = "Hello " + name,
31 | Time = DateTime.Now
32 | };
33 | }
34 |
35 |
36 | [HttpGet("api/applicationstats")]
37 | public object GetApplicationStats()
38 | {
39 | var stats = new
40 | {
41 | OsPlatform = System.Runtime.InteropServices.RuntimeInformation.OSDescription,
42 | HostName = System.Environment.MachineName,
43 | Ip = HttpContext.Connection.LocalIpAddress.ToString()
44 | };
45 |
46 | return stats;
47 | }
48 |
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Controllers/HomeController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 |
7 | namespace AlbumViewerAspNet5
8 | {
9 | public class HomeController : Controller
10 | {
11 | public IActionResult Index()
12 | {
13 | return Redirect("index.html");
14 | }
15 |
16 | public IActionResult About()
17 | {
18 | ViewBag.Message = "Your application description page.";
19 |
20 | return View();
21 | }
22 |
23 | public IActionResult Contact()
24 | {
25 | ViewBag.Message = "Your contact page...";
26 |
27 | return View("~/views/home/contact.cshtml");
28 | }
29 |
30 | public string ThrowError()
31 | {
32 | string name = null;
33 | return name.ToLower();
34 | }
35 |
36 | public IActionResult Error()
37 | {
38 | return View("~/Views/Shared/Error.cshtml");
39 | }
40 |
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Controllers/TagHelpers/ErrorDisplayTagHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Razor.TagHelpers;
2 |
3 | namespace AlbumViewerAspNet5.Controllers.TagHelpers
4 | {
5 |
6 | ///
7 | /// TagHelper that display a bootstrap alert window conditionally
8 | /// if a message or header are set on this helper. Otherwise - nothing
9 | /// displays.
10 | ///
11 | [HtmlTargetElement("error-display")]
12 | public class ErrorDisplayTagHelper : TagHelper
13 | {
14 | ///
15 | /// the main message that gets displayed
16 | ///
17 | [HtmlAttributeName("message")]
18 | public string message { get; set; }
19 |
20 | ///
21 | /// Optional header that is displayed in big text. Use for
22 | /// 'noisy' warnings and stop errors only please :-)
23 | /// The message is displayed below the header.
24 | ///
25 | [HtmlAttributeName("header")]
26 | public string header { get; set; }
27 |
28 | ///
29 | /// Font-awesome icon name without the fa- prefix.
30 | /// Example: info-circle, warning, lightbulb-o
31 | /// If none is specified - warning is used
32 | ///
33 | [HtmlAttributeName("icon")]
34 | public string icon { get; set; }
35 |
36 | ///
37 | /// CSS class. Handled here so we can capture the existing
38 | /// class value and append the BootStrap alert class.
39 | ///
40 | [HtmlAttributeName("class")]
41 | public string cssClass { get; set; }
42 |
43 | ///
44 | /// Optional - specifies the alert class used on the top level
45 | /// window. If not specified uses the same as the icon.
46 | /// Override this if the icon and alert classes are different
47 | /// (often they are not).
48 | ///
49 | [HtmlAttributeName("alert-class")]
50 | public string alertClass { get; set; }
51 |
52 | ///
53 | /// If true embeds the message text as HTML. Use this
54 | /// flag if you need to display HTML text. If false
55 | /// the text is HtmlEncoded.
56 | ///
57 | [HtmlAttributeName("message-as-html")]
58 | public bool messageAsHtml { get; set; }
59 |
60 | ///
61 | /// If true embeds the message text as HTML. Use this
62 | /// flag if you need to display HTML text. If false
63 | /// the text is HtmlEncoded.
64 | ///
65 | [HtmlAttributeName("header-as-html")]
66 | public bool headerAsHtml { get; set; }
67 |
68 | public override void Process(TagHelperContext context, TagHelperOutput output)
69 | {
70 |
71 |
72 | if (string.IsNullOrEmpty(message) && string.IsNullOrEmpty(header))
73 | return;
74 |
75 | if (string.IsNullOrEmpty(icon))
76 | icon = "warning";
77 | if (icon == "warning" || icon == "error" || icon == "danger")
78 | icon = icon + " error";
79 |
80 | if (string.IsNullOrEmpty(alertClass))
81 | alertClass = icon;
82 |
83 | string messageText = !messageAsHtml ? System.Net.WebUtility.HtmlEncode(message) : message;
84 |
85 | output.TagName = "div";
86 |
87 | // fix up CSS class
88 | if (cssClass != null)
89 | cssClass = cssClass + " alert alert-" + alertClass;
90 | else
91 | cssClass = "alert alert-" + alertClass;
92 | output.Attributes.Add("class", cssClass);
93 |
94 | if (string.IsNullOrEmpty(header))
95 | output.Content.SetContent($" {messageText}");
96 | else
97 | {
98 | output.Content.SetContent(
99 | $" {header} \r\n" +
100 | " \r\n" +
101 | $"{messageText}\r\n");
102 | }
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore;
7 | using Microsoft.AspNetCore.Hosting;
8 | using Microsoft.Extensions.Configuration;
9 | using Microsoft.Extensions.Logging;
10 |
11 | namespace AlbumViewerNetCore
12 | {
13 | public class Program
14 | {
15 | public static void Main(string[] args)
16 | {
17 | BuildWebHost(args).Run();
18 | }
19 |
20 | public static IWebHost BuildWebHost(string[] args)
21 | {
22 | return WebHost.CreateDefaultBuilder(args)
23 | .UseStartup()
24 | .Build();
25 | }
26 |
27 |
28 |
29 |
30 |
31 | //public static void Main(string[] args)
32 | // {
33 | // // use this to allow command line parameters in the config
34 | // var configuration = new ConfigurationBuilder()
35 | // .AddCommandLine(args)
36 | // .Build();
37 |
38 |
39 | // var hostUrl = configuration["hosturl"];
40 | // if (string.IsNullOrEmpty(hostUrl))
41 | // hostUrl = "http://0.0.0.0:5000";
42 |
43 |
44 |
45 | // var host = new WebHostBuilder()
46 | // .UseConfiguration(configuration)
47 | // //.UseUrls(hostUrl)
48 | // .UseContentRoot(Directory.GetCurrentDirectory())
49 | // .UseKestrel()
50 | // .UseIISIntegration()
51 | // .UseStartup()
52 | // .Build();
53 |
54 | // host.Run();
55 | // }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:26448/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "AlbumViewerNetCore": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "launchUrl": "http://localhost:5000",
22 | "environmentVariables": {
23 | "ASPNETCORE_ENVIRONMENT": "Development"
24 | }
25 | },
26 | "web": {
27 | "commandName": "web",
28 | "environmentVariables": {
29 | "ASPNETCORE_ENVIRONMENT": "Development"
30 | }
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/AlbumViewerMvc/Album.cshtml:
--------------------------------------------------------------------------------
1 | @model AlbumViewerBusiness.Album
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | @Model.Title
20 |
21 |
22 | @Model.Title
23 |
24 |
25 |
29 |
30 | @Model.Description
31 |
32 |
33 |
34 |
35 |
36 | @foreach (var song in @Model.Tracks)
37 | {
38 |
39 | @song.SongName
40 | @song.Length
41 |
42 | }
43 |
44 |
45 |
46 | More from
47 |
48 | @Model.Artist.ArtistName
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/AlbumViewerMvc/Albums.cshtml:
--------------------------------------------------------------------------------
1 | @model IEnumerable
2 |
3 |
4 |
Add Album
5 |
6 |
7 | @foreach (var album in Model.ToList())
8 | {
9 |
10 |
14 |
15 |
16 |
17 |
@album.Title
18 |
19 | @if (album.Artist != null)
20 | { by @album.Artist.ArtistName }
21 | @if (album.Year > 0)
22 | { in @album.Year }
23 |
@album.Description
24 |
25 |
26 |
27 | }
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/AlbumViewerMvc/Artists.cshtml:
--------------------------------------------------------------------------------
1 | @model List
2 |
3 | @{
4 | ViewBag.Title = "title";
5 | Layout = "_Layout";
6 | }
7 |
27 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/AlbumViewerMvc/TagHelpers.cshtml:
--------------------------------------------------------------------------------
1 | @addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
2 | @addTagHelper "*,AlbumViewerAspNet5"
3 |
4 | @model AlbumViewerBusiness.Album
5 | @*
6 | For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
7 | *@
8 | @{
9 | ViewBag.Title = "TagHelper Sample";
10 | }
11 |
37 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/Home/About.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "About";
3 | }
4 | @ViewBag.Title.
5 | @ViewBag.Message
6 |
7 | Use this area to provide additional information.
8 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/Home/Contact.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Contact";
3 | }
4 | @ViewBag.Title.
5 | @ViewBag.Message
6 |
7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P:
11 | 425.555.0100
12 |
13 |
14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com
17 |
18 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/Home/Error.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Error";
3 |
4 | }
5 | An error occurred
6 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/Home/Index.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Home Page";
3 | }
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Learn how to build ASP.NET apps that can run anywhere.
19 |
20 | Learn More
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | There are powerful new features in Visual Studio for building modern web apps.
32 |
33 | Learn More
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Bring in libraries from NuGet, Bower, and npm, and automate tasks using Grunt or Gulp.
45 |
46 | Learn More
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Learn how Microsoft's Azure cloud platform allows you to build, deploy, and scale web apps.
58 |
59 | Learn More
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
This application consists of:
71 |
72 | Sample pages showing basic nav
73 | Theming using Bootstrap
74 |
75 |
76 |
77 |
Customize app
78 |
88 |
89 |
90 |
Run & Deploy
91 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewBag.Title = "Error";
3 | }
4 |
5 | Error.
6 | An error occurred while processing your request.
7 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/_GlobalImport.cshtml:
--------------------------------------------------------------------------------
1 | @using System.Threading.Tasks
2 | @using AlbumViewerAspNet5
3 | @using AlbumViewerBusiness
4 | @addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
5 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using AlbumViewerNetCore
2 | @using AlbumViewerAspNet5
3 | @using AlbumViewerBusiness
4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
5 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/_Code/ErrorHandling/ApiError.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc.ModelBinding;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using Westwind.Utilities;
6 |
7 | namespace AlbumViewerAspNetCore
8 | {
9 |
10 | public class ApiError
11 | {
12 | public string message { get; set; }
13 | public bool isError { get; set; }
14 | public string detail { get; set; }
15 | public ValidationErrorCollection errors { get; set; }
16 |
17 | public ApiError(string message)
18 | {
19 | this.message = message;
20 | isError = true;
21 | }
22 |
23 | public ApiError(ModelStateDictionary modelState)
24 | {
25 | this.isError = true;
26 | if (modelState != null && modelState.Any(m => m.Value.Errors.Count > 0))
27 | {
28 | message = "Please correct the specified errors and try again.";
29 | //errors = modelState.SelectMany(m => m.Value.Errors).ToDictionary(m => m.Key, m=> m.ErrorMessage);
30 | //errors = modelState.SelectMany(m => m.Value.Errors.Select( me => new KeyValuePair( m.Key,me.ErrorMessage) ));
31 | //errors = modelState.SelectMany(m => m.Value.Errors.Select(me => new ModelError { FieldName = m.Key, ErrorMessage = me.ErrorMessage }));
32 | }
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/_Code/ErrorHandling/ApiException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Westwind.Utilities;
5 |
6 | namespace AlbumViewerAspNetCore
7 | {
8 |
9 | public class ApiException : Exception
10 | {
11 | public int StatusCode { get; set; }
12 |
13 | public ValidationErrorCollection Errors { get; set; }
14 |
15 | public ApiException(string message,
16 | int statusCode = 500,
17 | ValidationErrorCollection errors = null) :
18 | base(message)
19 | {
20 | StatusCode = statusCode;
21 | Errors = errors;
22 | }
23 | public ApiException(Exception ex, int statusCode = 500) : base(ex.Message)
24 | {
25 | StatusCode = statusCode;
26 | }
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/_Code/ErrorHandling/ApiExceptionFilter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc.Filters;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.Extensions.Logging;
8 | using Serilog;
9 | using Westwind.Utilities;
10 |
11 | namespace AlbumViewerAspNetCore
12 | {
13 | public class ApiExceptionFilter : ExceptionFilterAttribute
14 | {
15 | private ILogger _Logger;
16 |
17 | public ApiExceptionFilter(ILogger logger)
18 | {
19 | _Logger = logger;
20 | }
21 |
22 |
23 | public override void OnException(ExceptionContext context)
24 | {
25 | ApiError apiError = null;
26 | if (context.Exception is ApiException)
27 | {
28 | // handle explicit 'known' API errors
29 | var ex = context.Exception as ApiException;
30 | context.Exception = null;
31 | apiError = new ApiError(ex.Message);
32 | apiError.errors = ex.Errors;
33 |
34 | context.HttpContext.Response.StatusCode = ex.StatusCode;
35 |
36 | _Logger.LogWarning($"Application thrown error: {ex.Message}", ex);
37 | }
38 | else if (context.Exception is UnauthorizedAccessException)
39 | {
40 | apiError = new ApiError("Unauthorized Access");
41 | context.HttpContext.Response.StatusCode = 401;
42 | _Logger.LogWarning("Unauthorized Access in Controller Filter.");
43 | }
44 | else
45 | {
46 | // Unhandled errors
47 | #if !DEBUG
48 | var msg = "An unhandled error occurred.";
49 | string stack = null;
50 | #else
51 | var msg = context.Exception.GetBaseException().Message;
52 | string stack = context.Exception.StackTrace;
53 | #endif
54 |
55 | apiError = new ApiError(msg);
56 | apiError.detail = stack;
57 |
58 | context.HttpContext.Response.StatusCode = 500;
59 |
60 | // handle logging here
61 | _Logger.LogError(new EventId(0), context.Exception, msg);
62 | }
63 |
64 | // always return a JSON result
65 | context.Result = new JsonResult(apiError);
66 |
67 | base.OnException(context);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Data": {
3 | "Provider": "MySQL",
4 | "ConnectionString": "Server=172.18.245.137;Port=4000;Database=albumviewer;User=root;SslMode=None"
5 | },
6 | "Logging": {
7 | "IncludeScopes": false,
8 | "LogLevel": {
9 | "Default": "Information",
10 | "System": "Warning",
11 | "Microsoft": "Warning",
12 | "Trace": "Information"
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/bundleconfig.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "outputFileName": "wwwroot/css/albumviewer.css",
4 | "inputFiles": [
5 | "wwwroot/css/albumviewer.css"
6 | ]
7 | }
8 | ]
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/runtimeconfig.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "System.GC.Server": true
3 | }
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/3rdpartylicenses.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/3rdpartylicenses.txt
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/fontawesome-webfont.674f50d287a8c48dc19b.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/fontawesome-webfont.674f50d287a8c48dc19b.eot
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/fontawesome-webfont.af7ae505a9eed503f8b8.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/fontawesome-webfont.af7ae505a9eed503f8b8.woff2
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/fontawesome-webfont.b06871f281fee6b241d6.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/fontawesome-webfont.b06871f281fee6b241d6.ttf
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/fontawesome-webfont.fee66e712a8a08eef580.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/fontawesome-webfont.fee66e712a8a08eef580.woff
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/glyphicons-halflings-regular.448c34a56d699c29117a.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/glyphicons-halflings-regular.448c34a56d699c29117a.woff2
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/glyphicons-halflings-regular.e18bbf611f2a2e43afc0.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/glyphicons-halflings-regular.e18bbf611f2a2e43afc0.ttf
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/glyphicons-halflings-regular.f4769f9bdb7466be6508.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/glyphicons-halflings-regular.f4769f9bdb7466be6508.eot
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/glyphicons-halflings-regular.fa2772327f55d8198301.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/glyphicons-halflings-regular.fa2772327f55d8198301.woff
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/RockLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/RockLogo.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/artists.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/artists.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/artists32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/artists32.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/gear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/gear.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/headphone-head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/headphone-head.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/icon.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/record.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/search.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/search_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/search_box.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/search_box2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/search_box2.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/webconnectionicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/webconnectionicon.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/webconnectionlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/webconnectionlogo.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/images/westwindtext.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dockersamples/dotnet-album-viewer/2c8df52588566d9eadf1e637c47a41eb8ecc5220/src/AlbumViewerNetCore/wwwroot/images/westwindtext.png
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/index.html:
--------------------------------------------------------------------------------
1 | West Wind Album Viewer Getting things ready...
--------------------------------------------------------------------------------
/src/AlbumViewerNetCore/wwwroot/inline.6ab1992b1d6b43743130.bundle.js:
--------------------------------------------------------------------------------
1 | !function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var n=window.webpackJsonp;window.webpackJsonp=function(t,c,a){for(var u,i,f,l=0,s=[];l
2 |
3 | netstandard2.0
4 |
5 |
6 |
--------------------------------------------------------------------------------