diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..4603878 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,18 @@ +init: + - git config --global core.autocrlf true +branches: + only: + - master + - release + - dev + - /^(.*\/)?ci-.*$/ +build_script: + - ps: .\run.ps1 default-build +clone_depth: 1 +environment: + global: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: 1 +test: off +deploy: off +os: Visual Studio 2017 diff --git a/.deployment b/.deployment new file mode 100644 index 0000000..38d6c46 --- /dev/null +++ b/.deployment @@ -0,0 +1,2 @@ ++[config] ++project = src/MusicStore/MusicStore.csproj diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..64ff041 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,4 @@ +Contributing +====== + +Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/dev/CONTRIBUTING.md) in the Home repo. diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..0a0ea64 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,12 @@ + + + + + + + + true + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..894b1d0 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,6 @@ + + + $(MicrosoftNETCoreApp20PackageVersion) + $(MicrosoftNETCoreApp21PackageVersion) + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..7b2956e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,14 @@ +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/MusicStore.sln b/MusicStore.sln new file mode 100644 index 0000000..caee14a --- /dev/null +++ b/MusicStore.sln @@ -0,0 +1,64 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.12 +MinimumVisualStudioVersion = 15.0.26730.03 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7D749BDA-4638-4517-B66A-D40DEDEEB141}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + NuGet.config = NuGet.config + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{B7B176B6-8D4D-4EF1-BBD2-DDA650C78FFF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{363D2681-31A6-48C9-90BB-9ACFF4A41F06}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MusicStore", "samples\MusicStore\MusicStore.csproj", "{3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MusicStore.Test", "test\MusicStore.Test\MusicStore.Test.csproj", "{CA663205-77DE-4E55-B300-85594181B5A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MusicStore.E2ETests", "test\MusicStore.E2ETests\MusicStore.E2ETests.csproj", "{72A5F455-121F-4954-BF28-D712C6BE88EA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{88A30728-49E5-46C5-9CEC-9D8FD346A043}" + ProjectSection(SolutionItems) = preProject + build\repo.targets = build\repo.targets + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + RuntimeStore|Any CPU = RuntimeStore|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Release|Any CPU.Build.0 = Release|Any CPU + {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.RuntimeStore|Any CPU.ActiveCfg = RuntimeStore|Any CPU + {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.RuntimeStore|Any CPU.Build.0 = RuntimeStore|Any CPU + {CA663205-77DE-4E55-B300-85594181B5A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA663205-77DE-4E55-B300-85594181B5A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA663205-77DE-4E55-B300-85594181B5A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA663205-77DE-4E55-B300-85594181B5A9}.Release|Any CPU.Build.0 = Release|Any CPU + {CA663205-77DE-4E55-B300-85594181B5A9}.RuntimeStore|Any CPU.ActiveCfg = Release|Any CPU + {CA663205-77DE-4E55-B300-85594181B5A9}.RuntimeStore|Any CPU.Build.0 = Release|Any CPU + {72A5F455-121F-4954-BF28-D712C6BE88EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72A5F455-121F-4954-BF28-D712C6BE88EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72A5F455-121F-4954-BF28-D712C6BE88EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72A5F455-121F-4954-BF28-D712C6BE88EA}.Release|Any CPU.Build.0 = Release|Any CPU + {72A5F455-121F-4954-BF28-D712C6BE88EA}.RuntimeStore|Any CPU.ActiveCfg = Release|Any CPU + {72A5F455-121F-4954-BF28-D712C6BE88EA}.RuntimeStore|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0} = {B7B176B6-8D4D-4EF1-BBD2-DDA650C78FFF} + {CA663205-77DE-4E55-B300-85594181B5A9} = {363D2681-31A6-48C9-90BB-9ACFF4A41F06} + {72A5F455-121F-4954-BF28-D712C6BE88EA} = {363D2681-31A6-48C9-90BB-9ACFF4A41F06} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4AC7A44E-260A-4E70-88A7-77A0027E12A5} + EndGlobalSection +EndGlobal diff --git a/MusicStore.sln.DotSettings b/MusicStore.sln.DotSettings new file mode 100644 index 0000000..4f3db97 --- /dev/null +++ b/MusicStore.sln.DotSettings @@ -0,0 +1,108 @@ + + + <?xml version="1.0" encoding="utf-16"?><Profile name="MusicStore"><HtmlReformatCode>True</HtmlReformatCode><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSRemoveCodeRedundancies>True</CSRemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><XMLReformatCode>True</XMLReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSharpFormatDocComments>True</CSharpFormatDocComments></Profile> + + MusicStore + MusicStore + False + False + False + SEPARATE + True + True + True + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + ALWAYS_ADD + True + 1 + 1 + True + False + False + False + True + LINE_BREAK + False + True + False + True + True + True + True + True + 140 + CHOP_ALWAYS + True + + True + False + True + + True + + + Copyright (c) .NET Foundation. All rights reserved. +See License.txt in the project root for license information + True + True + True + Side by side + Side by side + False + False + False + True + False + False + True + False + False + True + $object$_On$event$ + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + $object$_On$event$ + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True \ No newline at end of file diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000..e32bddf --- /dev/null +++ b/NuGet.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..7574a9b --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +MusicStore (test application) +============================= + +AppVeyor: [![AppVeyor][appveyor-badge]][appveyor-build] + +Travis: [![Travis][travis-badge]][travis-build] + +[appveyor-badge]: https://ci.appveyor.com/api/projects/status/ja8a7j6jscj7k3xa/branch/dev?svg=true +[appveyor-build]: https://ci.appveyor.com/project/aspnetci/MusicStore/branch/dev +[travis-badge]: https://travis-ci.org/aspnet/MusicStore.svg?branch=dev +[travis-build]: https://travis-ci.org/aspnet/MusicStore + +This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo. + +## About this repo + +This repository is a test application used for ASP.NET Core internal test processes. +It is not intended to be a representative sample of how to use ASP.NET Core. + +Samples and docs for ASP.NET Core can be found here: . diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..c0050bd --- /dev/null +++ b/build.cmd @@ -0,0 +1,2 @@ +@ECHO OFF +PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE" diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..98a4b22 --- /dev/null +++ b/build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs) +chmod +x "$DIR/run.sh"; sync +"$DIR/run.sh" default-build "$@" diff --git a/build/dependencies.props b/build/dependencies.props new file mode 100644 index 0000000..37a350b --- /dev/null +++ b/build/dependencies.props @@ -0,0 +1,45 @@ + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 0.5.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.1.0-preview2-30131 + 2.0.0 + 2.1.0-preview2-26130-04 + 15.3.0 + 4.5.0-preview2-26130-01 + 2.3.1 + 2.4.0-beta.1.build3945 + + + diff --git a/build/repo.props b/build/repo.props new file mode 100644 index 0000000..78b0ce5 --- /dev/null +++ b/build/repo.props @@ -0,0 +1,14 @@ + + + + + + Internal.AspNetCore.Universe.Lineup + https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json + + + + + + + diff --git a/build/repo.targets b/build/repo.targets new file mode 100644 index 0000000..9d4c8a8 --- /dev/null +++ b/build/repo.targets @@ -0,0 +1,39 @@ + + + + + Configuration=RuntimeStore + + + + + $(RepositoryRoot)test\MusicStore.E2ETests\MusicStore.E2ETests.csproj + + + + + + + + + + + + + + + + + + VSTestTestCaseFilter=E2ETests=NanoServer + + + + + + VSTestTestCaseFilter=smoketests=usestore + + + + diff --git a/build/sources.props b/build/sources.props new file mode 100644 index 0000000..9feff29 --- /dev/null +++ b/build/sources.props @@ -0,0 +1,16 @@ + + + + + $(DotNetRestoreSources) + + $(RestoreSources); + https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; + https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; + + + $(RestoreSources); + https://api.nuget.org/v3/index.json; + + + diff --git a/korebuild-lock.txt b/korebuild-lock.txt new file mode 100644 index 0000000..89d0ad3 --- /dev/null +++ b/korebuild-lock.txt @@ -0,0 +1,2 @@ +version:2.1.0-preview2-15707 +commithash:e74e53f129ab34332947fea7ac7b7591b027cb22 diff --git a/korebuild.json b/korebuild.json new file mode 100644 index 0000000..bd5d51a --- /dev/null +++ b/korebuild.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", + "channel": "dev" +} diff --git a/run.cmd b/run.cmd new file mode 100644 index 0000000..d52d5c7 --- /dev/null +++ b/run.cmd @@ -0,0 +1,2 @@ +@ECHO OFF +PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" diff --git a/run.ps1 b/run.ps1 new file mode 100644 index 0000000..27dcf84 --- /dev/null +++ b/run.ps1 @@ -0,0 +1,196 @@ +#!/usr/bin/env powershell +#requires -version 4 + +<# +.SYNOPSIS +Executes KoreBuild commands. + +.DESCRIPTION +Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`. + +.PARAMETER Command +The KoreBuild command to run. + +.PARAMETER Path +The folder to build. Defaults to the folder containing this script. + +.PARAMETER Channel +The channel of KoreBuild to download. Overrides the value from the config file. + +.PARAMETER DotNetHome +The directory where .NET Core tools will be stored. + +.PARAMETER ToolsSource +The base url where build tools can be downloaded. Overrides the value from the config file. + +.PARAMETER Update +Updates KoreBuild to the latest version even if a lock file is present. + +.PARAMETER ConfigFile +The path to the configuration file that stores values. Defaults to korebuild.json. + +.PARAMETER ToolsSourceSuffix +The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores. + +.PARAMETER Arguments +Arguments to be passed to the command + +.NOTES +This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. +When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. + +The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set +in the file are overridden by command line parameters. + +.EXAMPLE +Example config file: +```json +{ + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", + "channel": "dev", + "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" +} +``` +#> +[CmdletBinding(PositionalBinding = $false)] +param( + [Parameter(Mandatory = $true, Position = 0)] + [string]$Command, + [string]$Path = $PSScriptRoot, + [Alias('c')] + [string]$Channel, + [Alias('d')] + [string]$DotNetHome, + [Alias('s')] + [string]$ToolsSource, + [Alias('u')] + [switch]$Update, + [string]$ConfigFile, + [string]$ToolsSourceSuffix, + [Parameter(ValueFromRemainingArguments = $true)] + [string[]]$Arguments +) + +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +# +# Functions +# + +function Get-KoreBuild { + + $lockFile = Join-Path $Path 'korebuild-lock.txt' + + if (!(Test-Path $lockFile) -or $Update) { + Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix + } + + $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 + if (!$version) { + Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" + } + $version = $version.TrimStart('version:').Trim() + $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) + + if (!(Test-Path $korebuildPath)) { + Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" + New-Item -ItemType Directory -Path $korebuildPath | Out-Null + $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" + + try { + $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" + Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix + if (Get-Command -Name 'Expand-Archive' -ErrorAction Ignore) { + # Use built-in commands where possible as they are cross-plat compatible + Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath + } + else { + # Fallback to old approach for old installations of PowerShell + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) + } + } + catch { + Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore + throw + } + finally { + Remove-Item $tmpfile -ErrorAction Ignore + } + } + + return $korebuildPath +} + +function Join-Paths([string]$path, [string[]]$childPaths) { + $childPaths | ForEach-Object { $path = Join-Path $path $_ } + return $path +} + +function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) { + if ($RemotePath -notlike 'http*') { + Copy-Item $RemotePath $LocalPath + return + } + + $retries = 10 + while ($retries -gt 0) { + $retries -= 1 + try { + Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath + return + } + catch { + Write-Verbose "Request failed. $retries retries remaining" + } + } + + Write-Error "Download failed: '$RemotePath'." +} + +# +# Main +# + +# Load configuration or set defaults + +$Path = Resolve-Path $Path +if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' } + +if (Test-Path $ConfigFile) { + try { + $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json + if ($config) { + if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } + if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} + } + } + catch { + Write-Warning "$ConfigFile could not be read. Its settings will be ignored." + Write-Warning $Error[0] + } +} + +if (!$DotNetHome) { + $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` + elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` + elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` + else { Join-Path $PSScriptRoot '.dotnet'} +} + +if (!$Channel) { $Channel = 'dev' } +if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } + +# Execute + +$korebuildPath = Get-KoreBuild +Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') + +try { + Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile + Invoke-KoreBuildCommand $Command @Arguments +} +finally { + Remove-Module 'KoreBuild' -ErrorAction Ignore +} diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..834961f --- /dev/null +++ b/run.sh @@ -0,0 +1,231 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# +# variables +# + +RESET="\033[0m" +RED="\033[0;31m" +YELLOW="\033[0;33m" +MAGENTA="\033[0;95m" +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +[ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet" +verbose=false +update=false +repo_path="$DIR" +channel='' +tools_source='' +tools_source_suffix='' + +# +# Functions +# +__usage() { + echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]" + echo "" + echo "Arguments:" + echo " command The command to be run." + echo " ... Arguments passed to the command. Variable number of arguments allowed." + echo "" + echo "Options:" + echo " --verbose Show verbose output." + echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.." + echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json." + echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet." + echo " --path The directory to build. Defaults to the directory containing the script." + echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file." + echo " --tools-source-suffix|-ToolsSourceSuffix The suffix to append to tools-source. Useful for query strings." + echo " -u|--update Update to the latest KoreBuild even if the lock file is present." + echo "" + echo "Description:" + echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be." + echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel." + + if [[ "${1:-}" != '--no-exit' ]]; then + exit 2 + fi +} + +get_korebuild() { + local version + local lock_file="$repo_path/korebuild-lock.txt" + if [ ! -f "$lock_file" ] || [ "$update" = true ]; then + __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix" + fi + version="$(grep 'version:*' -m 1 "$lock_file")" + if [[ "$version" == '' ]]; then + __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'" + return 1 + fi + version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version" + + { + if [ ! -d "$korebuild_path" ]; then + mkdir -p "$korebuild_path" + local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip" + tmpfile="$(mktemp)" + echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}" + if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then + unzip -q -d "$korebuild_path" "$tmpfile" + fi + rm "$tmpfile" || true + fi + + source "$korebuild_path/KoreBuild.sh" + } || { + if [ -d "$korebuild_path" ]; then + echo "Cleaning up after failed installation" + rm -rf "$korebuild_path" || true + fi + return 1 + } +} + +__error() { + echo -e "${RED}error: $*${RESET}" 1>&2 +} + +__warn() { + echo -e "${YELLOW}warning: $*${RESET}" +} + +__machine_has() { + hash "$1" > /dev/null 2>&1 + return $? +} + +__get_remote_file() { + local remote_path=$1 + local local_path=$2 + local remote_path_suffix=$3 + + if [[ "$remote_path" != 'http'* ]]; then + cp "$remote_path" "$local_path" + return 0 + fi + + local failed=false + if __machine_has wget; then + wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true + else + failed=true + fi + + if [ "$failed" = true ] && __machine_has curl; then + failed=false + curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true + fi + + if [ "$failed" = true ]; then + __error "Download failed: $remote_path" 1>&2 + return 1 + fi +} + +# +# main +# + +command="${1:-}" +shift + +while [[ $# -gt 0 ]]; do + case $1 in + -\?|-h|--help) + __usage --no-exit + exit 0 + ;; + -c|--channel|-Channel) + shift + channel="${1:-}" + [ -z "$channel" ] && __usage + ;; + --config-file|-ConfigFile) + shift + config_file="${1:-}" + [ -z "$config_file" ] && __usage + if [ ! -f "$config_file" ]; then + __error "Invalid value for --config-file. $config_file does not exist." + exit 1 + fi + ;; + -d|--dotnet-home|-DotNetHome) + shift + DOTNET_HOME="${1:-}" + [ -z "$DOTNET_HOME" ] && __usage + ;; + --path|-Path) + shift + repo_path="${1:-}" + [ -z "$repo_path" ] && __usage + ;; + -s|--tools-source|-ToolsSource) + shift + tools_source="${1:-}" + [ -z "$tools_source" ] && __usage + ;; + --tools-source-suffix|-ToolsSourceSuffix) + shift + tools_source_suffix="${1:-}" + [ -z "$tools_source_suffix" ] && __usage + ;; + -u|--update|-Update) + update=true + ;; + --verbose|-Verbose) + verbose=true + ;; + --) + shift + break + ;; + *) + break + ;; + esac + shift +done + +if ! __machine_has unzip; then + __error 'Missing required command: unzip' + exit 1 +fi + +if ! __machine_has curl && ! __machine_has wget; then + __error 'Missing required command. Either wget or curl is required.' + exit 1 +fi + +[ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json" +if [ -f "$config_file" ]; then + if __machine_has jq ; then + if jq '.' "$config_file" >/dev/null ; then + config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" + config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" + else + __warn "$config_file is invalid JSON. Its settings will be ignored." + fi + elif __machine_has python ; then + if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then + config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" + config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" + else + __warn "$config_file is invalid JSON. Its settings will be ignored." + fi + else + __warn 'Missing required command: jq or pyton. Could not parse the JSON file. Its settings will be ignored.' + fi + + [ ! -z "${config_channel:-}" ] && channel="$config_channel" + [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source" +fi + +[ -z "$channel" ] && channel='dev' +[ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' + +get_korebuild +set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" +invoke_korebuild_command "$command" "$@" diff --git a/samples/MusicStore/Areas/Admin/Controllers/StoreManagerController.cs b/samples/MusicStore/Areas/Admin/Controllers/StoreManagerController.cs new file mode 100644 index 0000000..9a75e4a --- /dev/null +++ b/samples/MusicStore/Areas/Admin/Controllers/StoreManagerController.cs @@ -0,0 +1,219 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using MusicStore.Models; +using MusicStore.ViewModels; + +namespace MusicStore.Areas.Admin.Controllers +{ + [Area("Admin")] + [Authorize("ManageStore")] + public class StoreManagerController : Controller + { + private readonly AppSettings _appSettings; + + public StoreManagerController(MusicStoreContext dbContext, IOptions options) + { + DbContext = dbContext; + _appSettings = options.Value; + } + + public MusicStoreContext DbContext { get; } + + // + // GET: /StoreManager/ + public async Task Index() + { + var albums = await DbContext.Albums + .Include(a => a.Genre) + .Include(a => a.Artist) + .ToListAsync(); + + return View(albums); + } + + // + // GET: /StoreManager/Details/5 + public async Task Details( + [FromServices] IMemoryCache cache, + int id) + { + var cacheKey = GetCacheKey(id); + + Album album; + if (!cache.TryGetValue(cacheKey, out album)) + { + album = await DbContext.Albums + .Where(a => a.AlbumId == id) + .Include(a => a.Artist) + .Include(a => a.Genre) + .FirstOrDefaultAsync(); + + if (album != null) + { + if (_appSettings.CacheDbResults) + { + //Remove it from cache if not retrieved in last 10 minutes. + cache.Set( + cacheKey, + album, + new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(10))); + } + } + } + + if (album == null) + { + cache.Remove(cacheKey); + return NotFound(); + } + + return View(album); + } + + // + // GET: /StoreManager/Create + public IActionResult Create() + { + ViewBag.GenreId = new SelectList(DbContext.Genres, "GenreId", "Name"); + ViewBag.ArtistId = new SelectList(DbContext.Artists, "ArtistId", "Name"); + return View(); + } + + // POST: /StoreManager/Create + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Create( + Album album, + [FromServices] IMemoryCache cache, + CancellationToken requestAborted) + { + if (ModelState.IsValid) + { + DbContext.Albums.Add(album); + await DbContext.SaveChangesAsync(requestAborted); + + var albumData = new AlbumData + { + Title = album.Title, + Url = Url.Action("Details", "Store", new { id = album.AlbumId }) + }; + + cache.Remove("latestAlbum"); + return RedirectToAction("Index"); + } + + ViewBag.GenreId = new SelectList(DbContext.Genres, "GenreId", "Name", album.GenreId); + ViewBag.ArtistId = new SelectList(DbContext.Artists, "ArtistId", "Name", album.ArtistId); + return View(album); + } + + // + // GET: /StoreManager/Edit/5 + public async Task Edit(int id) + { + var album = await DbContext.Albums. + Where(a => a.AlbumId == id). + FirstOrDefaultAsync(); + + if (album == null) + { + return NotFound(); + } + + ViewBag.GenreId = new SelectList(DbContext.Genres, "GenreId", "Name", album.GenreId); + ViewBag.ArtistId = new SelectList(DbContext.Artists, "ArtistId", "Name", album.ArtistId); + return View(album); + } + + // + // POST: /StoreManager/Edit/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Edit( + [FromServices] IMemoryCache cache, + Album album, + CancellationToken requestAborted) + { + if (ModelState.IsValid) + { + DbContext.Update(album); + await DbContext.SaveChangesAsync(requestAborted); + //Invalidate the cache entry as it is modified + cache.Remove(GetCacheKey(album.AlbumId)); + return RedirectToAction("Index"); + } + + ViewBag.GenreId = new SelectList(DbContext.Genres, "GenreId", "Name", album.GenreId); + ViewBag.ArtistId = new SelectList(DbContext.Artists, "ArtistId", "Name", album.ArtistId); + return View(album); + } + + // + // GET: /StoreManager/RemoveAlbum/5 + public async Task RemoveAlbum(int id) + { + var album = await DbContext.Albums.Where(a => a.AlbumId == id).FirstOrDefaultAsync(); + if (album == null) + { + return NotFound(); + } + + return View(album); + } + + // + // POST: /StoreManager/RemoveAlbum/5 + [HttpPost, ActionName("RemoveAlbum")] + public async Task RemoveAlbumConfirmed( + [FromServices] IMemoryCache cache, + int id, + CancellationToken requestAborted) + { + var album = await DbContext.Albums.Where(a => a.AlbumId == id).FirstOrDefaultAsync(); + if (album == null) + { + return NotFound(); + } + + DbContext.Albums.Remove(album); + await DbContext.SaveChangesAsync(requestAborted); + //Remove the cache entry as it is removed + cache.Remove(GetCacheKey(id)); + + return RedirectToAction("Index"); + } + + private static string GetCacheKey(int id) + { + return string.Format("album_{0}", id); + } + + // NOTE: this is used for end to end testing only + // + // GET: /StoreManager/GetAlbumIdFromName + // Note: Added for automated testing purpose. Application does not use this. + [HttpGet] + [SkipStatusCodePages] + [EnableCors("CorsPolicy")] + public async Task GetAlbumIdFromName(string albumName) + { + var album = await DbContext.Albums.Where(a => a.Title == albumName).FirstOrDefaultAsync(); + + if (album == null) + { + return NotFound(); + } + + return Content(album.AlbumId.ToString()); + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Areas/Admin/Views/StoreManager/Create.cshtml b/samples/MusicStore/Areas/Admin/Views/StoreManager/Create.cshtml new file mode 100644 index 0000000..3a15331 --- /dev/null +++ b/samples/MusicStore/Areas/Admin/Views/StoreManager/Create.cshtml @@ -0,0 +1,75 @@ +@model MusicStore.Models.Album + +@{ + ViewBag.Title = "Create"; +} + +

Create

+ +@using (Html.BeginForm()) +{ + @Html.AntiForgeryToken() + +
+

Album

+
+ @Html.ValidationSummary(true) + +
+ @Html.LabelFor(model => model.GenreId, "GenreId", new { @class = "control-label col-md-2" }) +
+ @Html.DropDownList("GenreId", String.Empty) + @Html.ValidationMessageFor(model => model.GenreId) +
+
+ +
+ @Html.LabelFor(model => model.ArtistId, "ArtistId", new { @class = "control-label col-md-2" }) +
+ @Html.DropDownList("ArtistId", String.Empty) + @Html.ValidationMessageFor(model => model.ArtistId) +
+
+ +
+ @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" }) +
+ @Html.EditorFor(model => model.Title) + @Html.ValidationMessageFor(model => model.Title) +
+
+ +
+ @Html.LabelFor(model => model.Price, new { @class = "control-label col-md-2" }) +
+ @Html.EditorFor(model => model.Price) + @Html.ValidationMessageFor(model => model.Price) +
+
+ +
+ @Html.LabelFor(model => model.AlbumArtUrl, new { @class = "control-label col-md-2" }) +
+ @Html.EditorFor(model => model.AlbumArtUrl) + @Html.ValidationMessageFor(model => model.AlbumArtUrl) +
+
+ +
+
+ +
+
+
+} + +
+ @Html.ActionLink("Back to List", "Index") +
+ +@section Scripts { + @*TODO : Until script helpers are available, adding script references manually*@ + @*@Scripts.Render("~/bundles/jqueryval")*@ + + +} \ No newline at end of file diff --git a/samples/MusicStore/Areas/Admin/Views/StoreManager/Details.cshtml b/samples/MusicStore/Areas/Admin/Views/StoreManager/Details.cshtml new file mode 100644 index 0000000..3e65c71 --- /dev/null +++ b/samples/MusicStore/Areas/Admin/Views/StoreManager/Details.cshtml @@ -0,0 +1,58 @@ +@model MusicStore.Models.Album + +@{ + ViewBag.Title = "Details"; +} + +

Details

+ +
+

Album

+
+
+
+ @Html.DisplayNameFor(model => model.Artist.Name) +
+ +
+ @Html.DisplayFor(model => model.Artist.Name) +
+ +
+ @Html.DisplayNameFor(model => model.Genre.Name) +
+ +
+ @Html.DisplayFor(model => model.Genre.Name) +
+ +
+ @Html.DisplayNameFor(model => model.Title) +
+ +
+ @Html.DisplayFor(model => model.Title) +
+ +
+ @Html.DisplayNameFor(model => model.Price) +
+ +
+ @Html.DisplayFor(model => model.Price) +
+ +
+ @Html.DisplayNameFor(model => model.AlbumArtUrl) +
+ +
+ @Html.DisplayFor(model => model.AlbumArtUrl) +
+ +
+
+

+ @Html.ActionLink("Edit", "Edit", new { id = Model.AlbumId }) | + @Html.ActionLink("Back to List", "Index") +

\ No newline at end of file diff --git a/samples/MusicStore/Areas/Admin/Views/StoreManager/Edit.cshtml b/samples/MusicStore/Areas/Admin/Views/StoreManager/Edit.cshtml new file mode 100644 index 0000000..3e9def0 --- /dev/null +++ b/samples/MusicStore/Areas/Admin/Views/StoreManager/Edit.cshtml @@ -0,0 +1,76 @@ +@model MusicStore.Models.Album + +@{ + ViewBag.Title = "Edit"; +} + +

Edit

+ +@using (Html.BeginForm()) +{ + @Html.AntiForgeryToken() + +
+

Album

+
+ @Html.ValidationSummary(true) + @Html.HiddenFor(model => model.AlbumId) + +
+ @Html.LabelFor(model => model.GenreId, "GenreId", new { @class = "control-label col-md-2" }) +
+ @Html.DropDownList("GenreId", String.Empty) + @Html.ValidationMessageFor(model => model.GenreId) +
+
+ +
+ @Html.LabelFor(model => model.ArtistId, "ArtistId", new { @class = "control-label col-md-2" }) +
+ @Html.DropDownList("ArtistId", String.Empty) + @Html.ValidationMessageFor(model => model.ArtistId) +
+
+ +
+ @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" }) +
+ @Html.EditorFor(model => model.Title) + @Html.ValidationMessageFor(model => model.Title) +
+
+ +
+ @Html.LabelFor(model => model.Price, new { @class = "control-label col-md-2" }) +
+ @Html.EditorFor(model => model.Price) + @Html.ValidationMessageFor(model => model.Price) +
+
+ +
+ @Html.LabelFor(model => model.AlbumArtUrl, new { @class = "control-label col-md-2" }) +
+ @Html.EditorFor(model => model.AlbumArtUrl) + @Html.ValidationMessageFor(model => model.AlbumArtUrl) +
+
+ +
+
+ +
+
+
+} + +
+ @Html.ActionLink("Back to List", "Index") +
+ +@section Scripts { + @*TODO : Until script helpers are available, adding script references manually*@ + @*@Scripts.Render("~/bundles/jqueryval")*@ + + +} \ No newline at end of file diff --git a/samples/MusicStore/Areas/Admin/Views/StoreManager/Index.cshtml b/samples/MusicStore/Areas/Admin/Views/StoreManager/Index.cshtml new file mode 100644 index 0000000..bc30dd2 --- /dev/null +++ b/samples/MusicStore/Areas/Admin/Views/StoreManager/Index.cshtml @@ -0,0 +1,66 @@ +@model IEnumerable + +@{ + ViewBag.Title = "Index"; +} + +

Index

+ +

+ @Html.ActionLink("Create New", "Create") +

+ + + + + + + + + + @foreach (var item in Model) + { + + + + + + + + } + +
+ @Html.DisplayNameFor(model => model.Genre.Name) + + @Html.DisplayNameFor(model => model.FirstOrDefault().Artist.Name) + + @Html.DisplayNameFor(model => model.FirstOrDefault().Title) + + @Html.DisplayNameFor(model => model.FirstOrDefault().Price) +
+ @Html.DisplayFor(modelItem => item.Genre.Name) + + @if (item.Artist.Name.Length <= 25) + { + @item.Artist.Name + } + else + { + @item.Artist.Name.Substring(0, 25)... + } + + @if (item.Title.Length <= 25) + { + @item.Title + } + else + { + @item.Title.Substring(0, 25)... + } + + @Html.DisplayFor(modelItem => item.Price) + + @Html.ActionLink("Edit", "Edit", new { id = item.AlbumId }) | + @Html.ActionLink("Details", "Details", new { id = item.AlbumId }) | + @Html.ActionLink("Delete", "RemoveAlbum", new { id = item.AlbumId }) +
\ No newline at end of file diff --git a/samples/MusicStore/Areas/Admin/Views/StoreManager/RemoveAlbum.cshtml b/samples/MusicStore/Areas/Admin/Views/StoreManager/RemoveAlbum.cshtml new file mode 100644 index 0000000..49cdeaa --- /dev/null +++ b/samples/MusicStore/Areas/Admin/Views/StoreManager/RemoveAlbum.cshtml @@ -0,0 +1,29 @@ +@model MusicStore.Models.Album + +@{ + ViewBag.Title = "Delete"; +} + +@if (Model != null) +{ +

Delete Confirmation

+ +

+ Are you sure you want to delete the album titled + @Model.Title? +

+ + @using (Html.BeginForm()) + { +

+ +

+

+ @Html.ActionLink("Back to List", "Index") +

+ } +} +else +{ + @Html.Label(null, "Unable to locate the album") +} \ No newline at end of file diff --git a/samples/MusicStore/Areas/Admin/Views/_ViewStart.cshtml b/samples/MusicStore/Areas/Admin/Views/_ViewStart.cshtml new file mode 100644 index 0000000..ab23e9a --- /dev/null +++ b/samples/MusicStore/Areas/Admin/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "/Views/Shared/_Layout.cshtml"; +} \ No newline at end of file diff --git a/samples/MusicStore/Components/CartSummaryComponent.cs b/samples/MusicStore/Components/CartSummaryComponent.cs new file mode 100644 index 0000000..f2917af --- /dev/null +++ b/samples/MusicStore/Components/CartSummaryComponent.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using MusicStore.Models; + +namespace MusicStore.Components +{ + [ViewComponent(Name = "CartSummary")] + public class CartSummaryComponent : ViewComponent + { + public CartSummaryComponent(MusicStoreContext dbContext) + { + DbContext = dbContext; + } + + private MusicStoreContext DbContext { get; } + + public async Task InvokeAsync() + { + var cart = ShoppingCart.GetCart(DbContext, HttpContext); + + var cartItems = await cart.GetCartItems(); + + ViewBag.CartCount = cartItems.Sum(c => c.Count); + ViewBag.CartSummary = string.Join("\n", cartItems.Select(c => c.Album.Title).Distinct()); + + return View(); + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Components/GenreMenuComponent.cs b/samples/MusicStore/Components/GenreMenuComponent.cs new file mode 100644 index 0000000..94f4d18 --- /dev/null +++ b/samples/MusicStore/Components/GenreMenuComponent.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MusicStore.Models; + +namespace MusicStore.Components +{ + [ViewComponent(Name = "GenreMenu")] + public class GenreMenuComponent : ViewComponent + { + public GenreMenuComponent(MusicStoreContext dbContext) + { + DbContext = dbContext; + } + + private MusicStoreContext DbContext { get; } + + public async Task InvokeAsync() + { + // TODO use nested sum https://github.com/aspnet/EntityFramework/issues/3792 + //.OrderByDescending( + // g => g.Albums.Sum(a => a.OrderDetails.Sum(od => od.Quantity))) + + var genres = await DbContext.Genres.Select(g => g.Name).Take(9).ToListAsync(); + + return View(genres); + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Components/ISystemClock.cs b/samples/MusicStore/Components/ISystemClock.cs new file mode 100644 index 0000000..9673d00 --- /dev/null +++ b/samples/MusicStore/Components/ISystemClock.cs @@ -0,0 +1,16 @@ +using System; + +namespace MusicStore.Components +{ + /// + /// Abstracts the system clock to facilitate testing. + /// + public interface ISystemClock + { + /// + /// Gets a DateTime object that is set to the current date and time on this computer, + /// expressed as the Coordinated Universal Time(UTC) + /// + DateTime UtcNow { get; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Components/SystemClock.cs b/samples/MusicStore/Components/SystemClock.cs new file mode 100644 index 0000000..68fa7b7 --- /dev/null +++ b/samples/MusicStore/Components/SystemClock.cs @@ -0,0 +1,22 @@ +using System; + +namespace MusicStore.Components +{ + /// + /// Provides access to the normal system clock. + /// + public class SystemClock : ISystemClock + { + /// + public DateTime UtcNow + { + get + { + // The clock measures whole seconds only, and truncates the milliseconds, + // because millisecond resolution is inconsistent among various underlying systems. + DateTime utcNow = DateTime.UtcNow; + return utcNow.AddMilliseconds(-utcNow.Millisecond); + } + } + } +} diff --git a/samples/MusicStore/Controllers/AccountController.cs b/samples/MusicStore/Controllers/AccountController.cs new file mode 100644 index 0000000..25e5e43 --- /dev/null +++ b/samples/MusicStore/Controllers/AccountController.cs @@ -0,0 +1,512 @@ +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MusicStore.Models; + +namespace MusicStore.Controllers +{ + [Authorize] + public class AccountController : Controller + { + private readonly ILogger _logger; + + public AccountController( + UserManager userManager, + SignInManager signInManager, + ILogger logger) + { + UserManager = userManager; + SignInManager = signInManager; + _logger = logger; + } + + public UserManager UserManager { get; } + + public SignInManager SignInManager { get; } + + // + // GET: /Account/Login + [AllowAnonymous] + public IActionResult Login(string returnUrl = null) + { + ViewBag.ReturnUrl = returnUrl; + return View(); + } + + // + // POST: /Account/Login + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task Login(LoginViewModel model, string returnUrl = null) + { + if (!ModelState.IsValid) + { + return View(model); + } + + // This doesn't count login failures towards account lockout + // To enable password failures to trigger account lockout, change to lockoutOnFailure: true + var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); + if (result.Succeeded) + { + _logger.LogInformation("Logged in {userName}.", model.Email); + return RedirectToLocal(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + _logger.LogWarning("Failed to log in {userName}.", model.Email); + ModelState.AddModelError("", "Invalid login attempt."); + return View(model); + } + } + + // + // GET: /Account/VerifyCode + [AllowAnonymous] + public async Task VerifyCode(string provider, bool rememberMe, string returnUrl = null) + { + var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + return View("Error"); + } + + // Remove before production +#if DEMO + if (user != null) + { + ViewBag.Code = await UserManager.GenerateTwoFactorTokenAsync(user, provider); + } +#endif + return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + } + + // + // POST: /Account/VerifyCode + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task VerifyCode(VerifyCodeViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + + // The following code protects for brute force attacks against the two factor codes. + // If a user enters incorrect codes for a specified amount of time then the user account + // will be locked out for a specified amount of time. + // You can configure the account lockout settings in IdentityConfig + var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser); + if (result.Succeeded) + { + return RedirectToLocal(model.ReturnUrl); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + ModelState.AddModelError("", "Invalid code."); + return View(model); + } + + } + + // + // GET: /Account/Register + [AllowAnonymous] + public IActionResult Register() + { + return View(); + } + + // + // POST: /Account/Register + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task Register(RegisterViewModel model) + { + if (ModelState.IsValid) + { + var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; + var result = await UserManager.CreateAsync(user, model.Password); + if (result.Succeeded) + { + _logger.LogInformation("User {userName} was created.", model.Email); + //Bug: Remember browser option missing? + //Uncomment this and comment the later part if account verification is not needed. + //await SignInManager.SignInAsync(user, isPersistent: false); + + // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771 + // Send an email with this link + string code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); + await MessageServices.SendEmailAsync(model.Email, "Confirm your account", + "Please confirm your account by clicking this link: link"); +#if !DEMO + return RedirectToAction("Index", "Home"); +#else + //To display the email link in a friendly page instead of sending email + ViewBag.Link = callbackUrl; + return View("DemoLinkDisplay"); +#endif + + } + AddErrors(result); + } + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // GET: /Account/ConfirmEmail + [AllowAnonymous] + public async Task ConfirmEmail(string userId, string code) + { + if (userId == null || code == null) + { + return View("Error"); + } + + var user = await UserManager.FindByIdAsync(userId); + if (user == null) + { + return View("Error"); + } + var result = await UserManager.ConfirmEmailAsync(user, code); + return View(result.Succeeded ? "ConfirmEmail" : "Error"); + } + + // + // GET: /Account/ForgotPassword + [AllowAnonymous] + public ActionResult ForgotPassword() + { + return View(); + } + + // + // POST: /Account/ForgotPassword + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ForgotPassword(ForgotPasswordViewModel model) + { + if (ModelState.IsValid) + { + var user = await UserManager.FindByNameAsync(model.Email); + if (user == null || !(await UserManager.IsEmailConfirmedAsync(user))) + { + // Don't reveal that the user does not exist or is not confirmed + return View("ForgotPasswordConfirmation"); + } + + // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771 + // Send an email with this link + string code = await UserManager.GeneratePasswordResetTokenAsync(user); + var callbackUrl = Url.Action("ResetPassword", "Account", new { code = code }, protocol: HttpContext.Request.Scheme); + await MessageServices.SendEmailAsync(model.Email, "Reset Password", + "Please reset your password by clicking here: link"); +#if !DEMO + return RedirectToAction("ForgotPasswordConfirmation"); +#else + //To display the email link in a friendly page instead of sending email + ViewBag.Link = callbackUrl; + return View("DemoLinkDisplay"); +#endif + } + + ModelState.AddModelError("", string.Format("We could not locate an account with email : {0}", model.Email)); + + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // GET: /Account/ForgotPasswordConfirmation + [AllowAnonymous] + public ActionResult ForgotPasswordConfirmation() + { + return View(); + } + + // + // GET: /Account/ResetPassword + [AllowAnonymous] + public ActionResult ResetPassword(string code) + { + //TODO: Fix this? + var resetPasswordViewModel = new ResetPasswordViewModel() { Code = code }; + return code == null ? View("Error") : View(resetPasswordViewModel); + } + + // + // POST: /Account/ResetPassword + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ResetPassword(ResetPasswordViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + var user = await UserManager.FindByNameAsync(model.Email); + if (user == null) + { + // Don't reveal that the user does not exist + return RedirectToAction("ResetPasswordConfirmation", "Account"); + } + var result = await UserManager.ResetPasswordAsync(user, model.Code, model.Password); + if (result.Succeeded) + { + return RedirectToAction("ResetPasswordConfirmation", "Account"); + } + AddErrors(result); + return View(); + } + + // + // GET: /Account/ResetPasswordConfirmation + [AllowAnonymous] + public ActionResult ResetPasswordConfirmation() + { + return View(); + } + + // + // POST: /Account/ExternalLogin + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public ActionResult ExternalLogin(string provider, string returnUrl = null) + { + // Request a redirect to the external login provider + var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }); + var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + return new ChallengeResult(provider, properties); + } + + // + // GET: /Account/SendCode + [AllowAnonymous] + public async Task SendCode(bool rememberMe, string returnUrl = null) + { + //TODO : Default rememberMe as well? + var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + return View("Error"); + } + var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(user); + var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); + return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe }); + } + + // + // POST: /Account/SendCode + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task SendCode(SendCodeViewModel model) + { + if (!ModelState.IsValid) + { + return View(); + } + + var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + return View("Error"); + } + + // Generate the token and send it + var code = await UserManager.GenerateTwoFactorTokenAsync(user, model.SelectedProvider); + if (string.IsNullOrWhiteSpace(code)) + { + return View("Error"); + } + + var message = "Your security code is: " + code; + if (model.SelectedProvider == "Email") + { + await MessageServices.SendEmailAsync(await UserManager.GetEmailAsync(user), "Security Code", message); + } + else if (model.SelectedProvider == "Phone") + { + await MessageServices.SendSmsAsync(await UserManager.GetPhoneNumberAsync(user), message); + } + + return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe }); + } + + // + // GET: /Account/ExternalLoginCallback + [AllowAnonymous] + public async Task ExternalLoginCallback(string returnUrl = null) + { + var loginInfo = await SignInManager.GetExternalLoginInfoAsync(); + if (loginInfo == null) + { + return RedirectToAction("Login"); + } + + // Sign in the user with this external login provider if the user already has a login + var result = await SignInManager.ExternalLoginSignInAsync(loginInfo.LoginProvider, loginInfo.ProviderKey, isPersistent: false); + if (result.Succeeded) + { + return RedirectToLocal(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false }); + } + if (result.IsLockedOut) + { + return View("Lockout"); + } + else + { + // If the user does not have an account, then prompt the user to create an account + ViewBag.ReturnUrl = returnUrl; + ViewBag.LoginProvider = loginInfo.LoginProvider; + // REVIEW: handle case where email not in claims? + var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email); + return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); + } + } + + // + // POST: /Account/ExternalLoginConfirmation + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null) + { + if (SignInManager.IsSignedIn(User)) + { + return RedirectToAction("Index", "Manage"); + } + + if (ModelState.IsValid) + { + // Get the information about the user from the external login provider + var info = await SignInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + return View("ExternalLoginFailure"); + } + var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; + var result = await UserManager.CreateAsync(user); + + // NOTE: Used for end to end testing only + //Just for automated testing adding a claim named 'ManageStore' - Not required for production + var manageClaim = info.Principal.Claims.Where(c => c.Type == "ManageStore").FirstOrDefault(); + if (manageClaim != null) + { + await UserManager.AddClaimAsync(user, manageClaim); + } + + if (result.Succeeded) + { + result = await UserManager.AddLoginAsync(user, info); + if (result.Succeeded) + { + await SignInManager.SignInAsync(user, isPersistent: false); + return RedirectToLocal(returnUrl); + } + } + AddErrors(result); + } + + ViewBag.ReturnUrl = returnUrl; + return View(model); + } + + // + // POST: /Account/LogOff + [HttpPost] + [ValidateAntiForgeryToken] + public async Task LogOff() + { + var userName = HttpContext.User.Identity.Name; + // clear all items from the cart + HttpContext.Session.Clear(); + + await SignInManager.SignOutAsync(); + + // TODO: Currently SignInManager.SignOut does not sign out OpenIdc and does not have a way to pass in a specific + // AuthType to sign out. + var appEnv = HttpContext.RequestServices.GetService(); + if (appEnv.EnvironmentName.StartsWith("OpenIdConnect")) + { + return new SignOutResult("OpenIdConnect", new AuthenticationProperties + { + RedirectUri = Url.Action("Index", "Home") + }); + } + + _logger.LogInformation("{userName} logged out.", userName); + return RedirectToAction("Index", "Home"); + } + + // + // GET: /Account/ExternalLoginFailure + [AllowAnonymous] + public ActionResult ExternalLoginFailure() + { + return View(); + } + + #region Helpers + + private void AddErrors(IdentityResult result) + { + foreach (var error in result.Errors) + { + ModelState.AddModelError("", error.Description); + _logger.LogWarning("Error in creating user: {error}", error.Description); + } + } + + private Task GetCurrentUserAsync() + { + return UserManager.GetUserAsync(HttpContext.User); + } + + private ActionResult RedirectToLocal(string returnUrl) + { + if (Url.IsLocalUrl(returnUrl)) + { + return Redirect(returnUrl); + } + else + { + return RedirectToAction("Index", "Home"); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/samples/MusicStore/Controllers/CheckoutController.cs b/samples/MusicStore/Controllers/CheckoutController.cs new file mode 100644 index 0000000..5f7a5e2 --- /dev/null +++ b/samples/MusicStore/Controllers/CheckoutController.cs @@ -0,0 +1,113 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MusicStore.Models; + +namespace MusicStore.Controllers +{ + [Authorize] + public class CheckoutController : Controller + { + private const string PromoCode = "FREE"; + + private readonly ILogger _logger; + + public CheckoutController(ILogger logger) + { + _logger = logger; + } + + // + // GET: /Checkout/ + public IActionResult AddressAndPayment() + { + return View(); + } + + // + // POST: /Checkout/AddressAndPayment + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task AddressAndPayment( + [FromServices] MusicStoreContext dbContext, + [FromForm] Order order, + CancellationToken requestAborted) + { + if (!ModelState.IsValid) + { + return View(order); + } + + var formCollection = await HttpContext.Request.ReadFormAsync(); + + try + { + if (string.Equals(formCollection["PromoCode"].FirstOrDefault(), PromoCode, + StringComparison.OrdinalIgnoreCase) == false) + { + return View(order); + } + else + { + order.Username = HttpContext.User.Identity.Name; + order.OrderDate = DateTime.Now; + + //Add the Order + dbContext.Orders.Add(order); + + //Process the order + var cart = ShoppingCart.GetCart(dbContext, HttpContext); + await cart.CreateOrder(order); + + _logger.LogInformation("User {userName} started checkout of {orderId}.", order.Username, order.OrderId); + + // Save all changes + await dbContext.SaveChangesAsync(requestAborted); + + return RedirectToAction("Complete", new { id = order.OrderId }); + } + } + catch + { + //Invalid - redisplay with errors + return View(order); + } + } + + // + // GET: /Checkout/Complete + + public async Task Complete( + [FromServices] MusicStoreContext dbContext, + int id) + { + var userName = HttpContext.User.Identity.Name; + + // Validate customer owns this order + bool isValid = await dbContext.Orders.AnyAsync( + o => o.OrderId == id && + o.Username == userName); + + if (isValid) + { + _logger.LogInformation("User {userName} completed checkout on order {orderId}.", userName, id); + return View(id); + } + else + { + _logger.LogError( + "User {userName} tried to checkout with an order ({orderId}) that doesn't belong to them.", + userName, + id); + return View("Error"); + } + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Controllers/HomeController.cs b/samples/MusicStore/Controllers/HomeController.cs new file mode 100644 index 0000000..992a4f2 --- /dev/null +++ b/samples/MusicStore/Controllers/HomeController.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using MusicStore.Models; + +namespace MusicStore.Controllers +{ + public class HomeController : Controller + { + private readonly AppSettings _appSettings; + + public HomeController(IOptions options) + { + _appSettings = options.Value; + } + // + // GET: /Home/ + public async Task Index( + [FromServices] MusicStoreContext dbContext, + [FromServices] IMemoryCache cache) + { + // Get most popular albums + var cacheKey = "topselling"; + List albums; + if (!cache.TryGetValue(cacheKey, out albums)) + { + albums = await GetTopSellingAlbumsAsync(dbContext, 6); + + if (albums != null && albums.Count > 0) + { + if (_appSettings.CacheDbResults) + { + // Refresh it every 10 minutes. + // Let this be the last item to be removed by cache if cache GC kicks in. + cache.Set( + cacheKey, + albums, + new MemoryCacheEntryOptions() + .SetAbsoluteExpiration(TimeSpan.FromMinutes(10)) + .SetPriority(CacheItemPriority.High)); + } + } + } + + return View(albums); + } + + public IActionResult Error() + { + return View("~/Views/Shared/Error.cshtml"); + } + + public IActionResult StatusCodePage() + { + return View("~/Views/Shared/StatusCodePage.cshtml"); + } + + public IActionResult AccessDenied() + { + return View("~/Views/Shared/AccessDenied.cshtml"); + } + + private Task> GetTopSellingAlbumsAsync(MusicStoreContext dbContext, int count) + { + // Group the order details by album and return + // the albums with the highest count + + return dbContext.Albums + .OrderByDescending(a => a.OrderDetails.Count) + .Take(count) + .ToListAsync(); + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Controllers/ManageController.cs b/samples/MusicStore/Controllers/ManageController.cs new file mode 100644 index 0000000..5e1ed74 --- /dev/null +++ b/samples/MusicStore/Controllers/ManageController.cs @@ -0,0 +1,365 @@ +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using MusicStore.Models; + +namespace MusicStore.Controllers +{ + [Authorize] + public class ManageController : Controller + { + public ManageController( + UserManager userManager, + SignInManager signInManager, + IAuthenticationSchemeProvider schemes) + { + UserManager = userManager; + SignInManager = signInManager; + SchemeProvider = schemes; + } + + public UserManager UserManager { get; } + + public SignInManager SignInManager { get; } + + public IAuthenticationSchemeProvider SchemeProvider { get; } + + // + // GET: /Manage/Index + public async Task Index(ManageMessageId? message = null) + { + ViewBag.StatusMessage = + message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed." + : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set." + : message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set." + : message == ManageMessageId.Error ? "An error has occurred." + : message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added." + : message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed." + : ""; + + var user = await GetCurrentUserAsync(); + var model = new IndexViewModel + { + HasPassword = await UserManager.HasPasswordAsync(user), + PhoneNumber = await UserManager.GetPhoneNumberAsync(user), + TwoFactor = await UserManager.GetTwoFactorEnabledAsync(user), + Logins = await UserManager.GetLoginsAsync(user), + BrowserRemembered = await SignInManager.IsTwoFactorClientRememberedAsync(user) + }; + + return View(model); + } + + // + // POST: /Manage/RemoveLogin + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RemoveLogin(string loginProvider, string providerKey) + { + ManageMessageId? message = ManageMessageId.Error; + var user = await GetCurrentUserAsync(); + if (user != null) + { + var result = await UserManager.RemoveLoginAsync(user, loginProvider, providerKey); + if (result.Succeeded) + { + await SignInManager.SignInAsync(user, isPersistent: false); + message = ManageMessageId.RemoveLoginSuccess; + } + } + return RedirectToAction("ManageLogins", new { Message = message }); + } + + // + // GET: /Account/AddPhoneNumber + public IActionResult AddPhoneNumber() + { + return View(); + } + + // + // POST: /Account/AddPhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task AddPhoneNumber(AddPhoneNumberViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + var user = await GetCurrentUserAsync(); + // Generate the token and send it + var code = await UserManager.GenerateChangePhoneNumberTokenAsync(user, model.Number); + await MessageServices.SendSmsAsync(model.Number, "Your security code is: " + code); + return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number }); + } + + // + // POST: /Manage/EnableTwoFactorAuthentication + [HttpPost] + [ValidateAntiForgeryToken] + public async Task EnableTwoFactorAuthentication() + { + var user = await GetCurrentUserAsync(); + if (user != null) + { + await UserManager.SetTwoFactorEnabledAsync(user, true); + // TODO: flow remember me somehow? + await SignInManager.SignInAsync(user, isPersistent: false); + } + return RedirectToAction("Index", "Manage"); + } + + // + // POST: /Manage/DisableTwoFactorAuthentication + [HttpPost] + [ValidateAntiForgeryToken] + public async Task DisableTwoFactorAuthentication() + { + var user = await GetCurrentUserAsync(); + if (user != null) + { + await UserManager.SetTwoFactorEnabledAsync(user, false); + await SignInManager.SignInAsync(user, isPersistent: false); + } + return RedirectToAction("Index", "Manage"); + } + + // + // GET: /Account/VerifyPhoneNumber + public async Task VerifyPhoneNumber(string phoneNumber) + { + // This code allows you exercise the flow without actually sending codes + // For production use please register a SMS provider in IdentityConfig and generate a code here. +#if DEMO + ViewBag.Code = await UserManager.GenerateChangePhoneNumberTokenAsync(await GetCurrentUserAsync(), phoneNumber); +#endif + return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber }); + } + + // + // POST: /Account/VerifyPhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task VerifyPhoneNumber(VerifyPhoneNumberViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + var user = await GetCurrentUserAsync(); + if (user != null) + { + var result = await UserManager.ChangePhoneNumberAsync(user, model.PhoneNumber, model.Code); + if (result.Succeeded) + { + await SignInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess }); + } + } + // If we got this far, something failed, redisplay form + ModelState.AddModelError("", "Failed to verify phone"); + return View(model); + } + + // + // GET: /Account/RemovePhoneNumber + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RemovePhoneNumber() + { + var user = await GetCurrentUserAsync(); + if (user != null) + { + var result = await UserManager.SetPhoneNumberAsync(user, null); + if (result.Succeeded) + { + await SignInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.RemovePhoneSuccess }); + } + } + return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error }); + } + + // + // GET: /Manage/ChangePassword + public IActionResult ChangePassword() + { + return View(); + } + + // + // POST: /Account/Manage + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ChangePassword(ChangePasswordViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + var user = await GetCurrentUserAsync(); + if (user != null) + { + var result = await UserManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); + if (result.Succeeded) + { + await SignInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction("Index", new { Message = ManageMessageId.ChangePasswordSuccess }); + } + AddErrors(result); + return View(model); + } + return RedirectToAction("Index", new { Message = ManageMessageId.Error }); + } + + // + // GET: /Manage/SetPassword + public IActionResult SetPassword() + { + return View(); + } + + // + // POST: /Manage/SetPassword + [HttpPost] + [ValidateAntiForgeryToken] + public async Task SetPassword(SetPasswordViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + + var user = await GetCurrentUserAsync(); + if (user != null) + { + var result = await UserManager.AddPasswordAsync(user, model.NewPassword); + if (result.Succeeded) + { + await SignInManager.SignInAsync(user, isPersistent: false); + return RedirectToAction("Index", new { Message = ManageMessageId.SetPasswordSuccess }); + } + AddErrors(result); + return View(model); + } + return RedirectToAction("Index", new { Message = ManageMessageId.Error }); + } + + // + // POST: /Manage/RememberBrowser + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RememberBrowser() + { + var user = await GetCurrentUserAsync(); + if (user != null) + { + await SignInManager.RememberTwoFactorClientAsync(user); + await SignInManager.SignInAsync(user, isPersistent: false); + } + return RedirectToAction("Index", "Manage"); + } + + // + // POST: /Manage/ForgetBrowser + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ForgetBrowser() + { + await SignInManager.ForgetTwoFactorClientAsync(); + return RedirectToAction("Index", "Manage"); + } + + // + // GET: /Account/Manage + public async Task ManageLogins(ManageMessageId? message = null) + { + ViewBag.StatusMessage = + message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed." + : message == ManageMessageId.AddLoginSuccess ? "The external login was added." + : message == ManageMessageId.Error ? "An error has occurred." + : ""; + var user = await GetCurrentUserAsync(); + if (user == null) + { + return View("Error"); + } + var userLogins = await UserManager.GetLoginsAsync(user); + var schemes = await SchemeProvider.GetAllSchemesAsync(); + var otherLogins = schemes.Where(auth => userLogins.All(ul => auth.Name != ul.LoginProvider)).ToList(); + ViewBag.ShowRemoveButton = user.PasswordHash != null || userLogins.Count > 1; + return View(new ManageLoginsViewModel + { + CurrentLogins = userLogins, + OtherLogins = otherLogins + }); + } + + // + // POST: /Manage/LinkLogin + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult LinkLogin(string provider) + { + // Request a redirect to the external login provider to link a login for the current user + var redirectUrl = Url.Action("LinkLoginCallback", "Manage"); + var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, UserManager.GetUserId(User)); + return new ChallengeResult(provider, properties); + } + + // + // GET: /Manage/LinkLoginCallback + public async Task LinkLoginCallback() + { + var user = await GetCurrentUserAsync(); + if (user == null) + { + return View("Error"); + } + + var loginInfo = await SignInManager.GetExternalLoginInfoAsync(await UserManager.GetUserIdAsync(user)); + if (loginInfo == null) + { + return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error }); + } + + var result = await UserManager.AddLoginAsync(user, loginInfo); + var message = result.Succeeded ? ManageMessageId.AddLoginSuccess : ManageMessageId.Error; + return RedirectToAction("ManageLogins", new { Message = message }); + } + + #region Helpers + + private void AddErrors(IdentityResult result) + { + foreach (var error in result.Errors) + { + ModelState.AddModelError("", error.Description); + } + } + + public enum ManageMessageId + { + AddPhoneSuccess, + AddLoginSuccess, + ChangePasswordSuccess, + SetTwoFactorSuccess, + SetPasswordSuccess, + RemoveLoginSuccess, + RemovePhoneSuccess, + Error + } + + private Task GetCurrentUserAsync() + { + return UserManager.GetUserAsync(HttpContext.User); + } + + #endregion + } +} \ No newline at end of file diff --git a/samples/MusicStore/Controllers/ShoppingCartController.cs b/samples/MusicStore/Controllers/ShoppingCartController.cs new file mode 100644 index 0000000..f99d999 --- /dev/null +++ b/samples/MusicStore/Controllers/ShoppingCartController.cs @@ -0,0 +1,113 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MusicStore.Models; +using MusicStore.ViewModels; + +namespace MusicStore.Controllers +{ + public class ShoppingCartController : Controller + { + private readonly ILogger _logger; + + public ShoppingCartController(MusicStoreContext dbContext, ILogger logger) + { + DbContext = dbContext; + _logger = logger; + } + + public MusicStoreContext DbContext { get; } + + // + // GET: /ShoppingCart/ + public async Task Index() + { + var cart = ShoppingCart.GetCart(DbContext, HttpContext); + + // Set up our ViewModel + var viewModel = new ShoppingCartViewModel + { + CartItems = await cart.GetCartItems(), + CartTotal = await cart.GetTotal() + }; + + // Return the view + return View(viewModel); + } + + // + // GET: /ShoppingCart/AddToCart/5 + + public async Task AddToCart(int id, CancellationToken requestAborted) + { + // Retrieve the album from the database + var addedAlbum = await DbContext.Albums + .SingleAsync(album => album.AlbumId == id); + + // Add it to the shopping cart + var cart = ShoppingCart.GetCart(DbContext, HttpContext); + + await cart.AddToCart(addedAlbum); + + await DbContext.SaveChangesAsync(requestAborted); + _logger.LogInformation("Album {albumId} was added to the cart.", addedAlbum.AlbumId); + + // Go back to the main store page for more shopping + return RedirectToAction("Index"); + } + + // + // AJAX: /ShoppingCart/RemoveFromCart/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task RemoveFromCart( + int id, + CancellationToken requestAborted) + { + // Retrieve the current user's shopping cart + var cart = ShoppingCart.GetCart(DbContext, HttpContext); + + // Get the name of the album to display confirmation + var cartItem = await DbContext.CartItems + .Where(item => item.CartItemId == id) + .Include(c => c.Album) + .SingleOrDefaultAsync(); + + string message; + int itemCount; + if (cartItem != null) + { + // Remove from cart + itemCount = cart.RemoveFromCart(id); + + await DbContext.SaveChangesAsync(requestAborted); + + string removed = (itemCount > 0) ? " 1 copy of " : string.Empty; + message = removed + cartItem.Album.Title + " has been removed from your shopping cart."; + } + else + { + itemCount = 0; + message = "Could not find this item, nothing has been removed from your shopping cart."; + } + + // Display the confirmation message + + var results = new ShoppingCartRemoveViewModel + { + Message = message, + CartTotal = await cart.GetTotal(), + CartCount = await cart.GetCount(), + ItemCount = itemCount, + DeleteId = id + }; + + _logger.LogInformation("Album {id} was removed from a cart.", id); + + return Json(results); + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Controllers/StoreController.cs b/samples/MusicStore/Controllers/StoreController.cs new file mode 100644 index 0000000..dfa2336 --- /dev/null +++ b/samples/MusicStore/Controllers/StoreController.cs @@ -0,0 +1,86 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using MusicStore.Models; + +namespace MusicStore.Controllers +{ + public class StoreController : Controller + { + private readonly AppSettings _appSettings; + + public StoreController(MusicStoreContext dbContext, IOptions options) + { + DbContext = dbContext; + _appSettings = options.Value; + } + + public MusicStoreContext DbContext { get; } + + // + // GET: /Store/ + public async Task Index() + { + var genres = await DbContext.Genres.ToListAsync(); + + return View(genres); + } + + // + // GET: /Store/Browse?genre=Disco + public async Task Browse(string genre) + { + // Retrieve Genre genre and its Associated associated Albums albums from database + var genreModel = await DbContext.Genres + .Include(g => g.Albums) + .Where(g => g.Name == genre) + .FirstOrDefaultAsync(); + + if (genreModel == null) + { + return NotFound(); + } + + return View(genreModel); + } + + public async Task Details( + [FromServices] IMemoryCache cache, + int id) + { + var cacheKey = string.Format("album_{0}", id); + Album album; + if (!cache.TryGetValue(cacheKey, out album)) + { + album = await DbContext.Albums + .Where(a => a.AlbumId == id) + .Include(a => a.Artist) + .Include(a => a.Genre) + .FirstOrDefaultAsync(); + + if (album != null) + { + if (_appSettings.CacheDbResults) + { + //Remove it from cache if not retrieved in last 10 minutes + cache.Set( + cacheKey, + album, + new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(10))); + } + } + } + + if (album == null) + { + return NotFound(); + } + + return View(album); + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/ForTesting/Mocks/Common/CustomStateDataFormat.cs b/samples/MusicStore/ForTesting/Mocks/Common/CustomStateDataFormat.cs new file mode 100644 index 0000000..22ab649 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/Common/CustomStateDataFormat.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Authentication; +using Newtonsoft.Json; + +namespace MusicStore.Mocks.Common +{ + public class CustomStateDataFormat : ISecureDataFormat + { + private static string _lastSavedAuthenticationProperties; + + public string Protect(AuthenticationProperties data, string purose) + { + return Protect(data); + } + + public string Protect(AuthenticationProperties data) + { + _lastSavedAuthenticationProperties = Serialize(data); + return "ValidStateData"; + } + + public AuthenticationProperties Unprotect(string state, string purpose) + { + return Unprotect(state); + } + + public AuthenticationProperties Unprotect(string state) + { + return state == "ValidStateData" ? DeSerialize(_lastSavedAuthenticationProperties) : null; + } + + private string Serialize(AuthenticationProperties data) + { + return JsonConvert.SerializeObject(data, Formatting.Indented); + } + + private AuthenticationProperties DeSerialize(string state) + { + return JsonConvert.DeserializeObject(state); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/Common/Helpers.cs b/samples/MusicStore/ForTesting/Mocks/Common/Helpers.cs new file mode 100644 index 0000000..2dfe3ad --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/Common/Helpers.cs @@ -0,0 +1,15 @@ +using System; + +namespace MusicStore.Mocks.Common +{ + internal class Helpers + { + internal static void ThrowIfConditionFailed(Func condition, string errorMessage) + { + if (!condition()) + { + throw new Exception(errorMessage); + } + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/Facebook/FacebookMockBackChannelHttpHandler.cs b/samples/MusicStore/ForTesting/Mocks/Facebook/FacebookMockBackChannelHttpHandler.cs new file mode 100644 index 0000000..3982964 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/Facebook/FacebookMockBackChannelHttpHandler.cs @@ -0,0 +1,57 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; +using Microsoft.AspNetCore.WebUtilities; +using MusicStore.Mocks.Common; + +namespace MusicStore.Mocks.Facebook +{ + /// + /// Summary description for FacebookMockBackChannelHttpHandler + /// + public class FacebookMockBackChannelHttpHandler : HttpMessageHandler + { + protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = new HttpResponseMessage(); + + if (request.RequestUri.AbsoluteUri.StartsWith("https://graph.facebook.com/v2.6/oauth/access_token")) + { + var formData = new FormCollection(await new FormReader(await request.Content.ReadAsStreamAsync()).ReadFormAsync()); + if (formData["grant_type"] == "authorization_code") + { + if (formData["code"] == "ValidCode") + { + Helpers.ThrowIfConditionFailed(() => ((string)formData["redirect_uri"]).EndsWith("signin-facebook"), "Redirect URI is not ending with /signin-facebook"); + Helpers.ThrowIfConditionFailed(() => formData["client_id"] == "[AppId]", "Invalid client Id received"); + Helpers.ThrowIfConditionFailed(() => formData["client_secret"] == "[AppSecret]", "Invalid client secret received"); + response.Content = new StringContent("{ \"access_token\": \"ValidAccessToken\", \"expires_in\": \"100\" }"); + return response; + } + response.StatusCode = (HttpStatusCode)400; + return response; + } + } + else if (request.RequestUri.AbsoluteUri.StartsWith("https://graph.facebook.com/v2.6/me")) + { + var queryParameters = new QueryCollection(QueryHelpers.ParseQuery(request.RequestUri.Query)); + Helpers.ThrowIfConditionFailed(() => queryParameters["appsecret_proof"].Count > 0, "appsecret_proof is empty"); + if (queryParameters["access_token"] == "ValidAccessToken") + { + response.Content = new StringContent("{\"id\":\"Id\",\"name\":\"AspnetvnextTest AspnetvnextTest\",\"first_name\":\"AspnetvnextTest\",\"last_name\":\"AspnetvnextTest\",\"link\":\"https:\\/\\/www.facebook.com\\/myLink\",\"username\":\"AspnetvnextTest.AspnetvnextTest.7\",\"gender\":\"male\",\"email\":\"AspnetvnextTest\\u0040test.com\",\"timezone\":-7,\"locale\":\"en_US\",\"verified\":true,\"updated_time\":\"2013-08-06T20:38:48+0000\",\"CertValidatorInvoked\":\"ValidAccessToken\"}"); + } + else + { + response.Content = new StringContent("{\"error\":{\"message\":\"Invalid OAuth access token.\",\"type\":\"OAuthException\",\"code\":190}}"); + } + return response; + } + + throw new NotImplementedException(request.RequestUri.AbsoluteUri); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs b/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs new file mode 100644 index 0000000..cf8bb10 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Facebook; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.Identity; +using MusicStore.Mocks.Common; + +namespace MusicStore.Mocks.Facebook +{ + internal class TestFacebookEvents + { + internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) + { + if (context.Principal != null) + { + Helpers.ThrowIfConditionFailed(() => context.AccessToken == "ValidAccessToken", ""); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Email)?.Value == "AspnetvnextTest@test.com", ""); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == "Id", ""); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst("urn:facebook:link")?.Value == "https://www.facebook.com/myLink", ""); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", ""); + Helpers.ThrowIfConditionFailed(() => context.User.SelectToken("id").ToString() == context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value, ""); + Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(100), ""); + Helpers.ThrowIfConditionFailed(() => context.AccessToken == "ValidAccessToken", ""); + context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); + } + + return Task.FromResult(0); + } + + internal static Task OnTicketReceived(TicketReceivedContext context) + { + if (context.Principal != null && context.Options.SignInScheme == IdentityConstants.ExternalScheme) + { + //This way we will know all events were fired. + var identity = context.Principal.Identities.First(); + var manageStoreClaim = identity?.Claims.Where(c => c.Type == "ManageStore" && c.Value == "false").FirstOrDefault(); + if (manageStoreClaim != null) + { + identity.RemoveClaim(manageStoreClaim); + identity.AddClaim(new Claim("ManageStore", "Allowed")); + } + } + + return Task.FromResult(0); + } + + internal static Task RedirectToAuthorizationEndpoint(RedirectContext context) + { + context.Response.Redirect(context.RedirectUri + "&custom_redirect_uri=custom"); + return Task.FromResult(0); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/Google/GoogleMockBackChannelHttpHandler.cs b/samples/MusicStore/ForTesting/Mocks/Google/GoogleMockBackChannelHttpHandler.cs new file mode 100644 index 0000000..f5eb4a9 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/Google/GoogleMockBackChannelHttpHandler.cs @@ -0,0 +1,55 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; + +namespace MusicStore.Mocks.Google +{ + /// + /// Summary description for GoogleMockBackChannelHttpHandler + /// + public class GoogleMockBackChannelHttpHandler : HttpMessageHandler + { + protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = new HttpResponseMessage(); + + if (request.RequestUri.AbsoluteUri.StartsWith("https://www.googleapis.com/oauth2/v4/token")) + { + var formData = new FormCollection(await new FormReader(await request.Content.ReadAsStreamAsync()).ReadFormAsync()); + if (formData["grant_type"] == "authorization_code") + { + if (formData["code"] == "ValidCode") + { + if (formData["redirect_uri"].Count > 0 && ((string)formData["redirect_uri"]).EndsWith("signin-google") && + formData["client_id"] == "[ClientId]" && formData["client_secret"] == "[ClientSecret]") + { + response.Content = new StringContent("{\"access_token\":\"ValidAccessToken\",\"refresh_token\":\"ValidRefreshToken\",\"token_type\":\"Bearer\",\"expires_in\":\"1200\",\"id_token\":\"Token\"}", Encoding.UTF8, "application/json"); + return response; + } + } + } + response.StatusCode = (HttpStatusCode)400; + return response; + } + else if (request.RequestUri.AbsoluteUri.StartsWith("https://www.googleapis.com/plus/v1/people/me")) + { + if (request.Headers.Authorization.Parameter == "ValidAccessToken") + { + response.Content = new StringContent("{ \"kind\": \"plus#person\",\n \"etag\": \"\\\"YFr-hUROXQN7IOa3dUHg9dQ8eq0/2hY18HdHEP8NLykSTVEiAhkKsBE\\\"\",\n \"gender\": \"male\",\n \"emails\": [\n {\n \"value\": \"AspnetvnextTest@gmail.com\",\n \"type\": \"account\"\n }\n ],\n \"objectType\": \"person\",\n \"id\": \"106790274378320830963\",\n \"displayName\": \"AspnetvnextTest AspnetvnextTest\",\n \"name\": {\n \"familyName\": \"AspnetvnextTest\",\n \"givenName\": \"FirstName\"\n },\n \"url\": \"https://plus.google.com/106790274378320830963\",\n \"image\": {\n \"url\": \"https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg?sz=50\"\n },\n \"isPlusUser\": true,\n \"language\": \"en\",\n \"circledByCount\": 0,\n \"verified\": false\n}\n", Encoding.UTF8, "application/json"); + } + else + { + response.Content = new StringContent("{\"error\":{\"message\":\"Invalid OAuth access token.\",\"type\":\"OAuthException\",\"code\":190}}"); + } + return response; + } + + throw new NotImplementedException(request.RequestUri.AbsoluteUri); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs b/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs new file mode 100644 index 0000000..4dec753 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Google; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.Identity; +using MusicStore.Mocks.Common; + +namespace MusicStore.Mocks.Google +{ + internal class TestGoogleEvents + { + internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) + { + if (context.Principal != null) + { + Helpers.ThrowIfConditionFailed(() => context.AccessToken == "ValidAccessToken", "Access token is not valid"); + Helpers.ThrowIfConditionFailed(() => context.RefreshToken == "ValidRefreshToken", "Refresh token is not valid"); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Email)?.Value == "AspnetvnextTest@gmail.com", "Email is not valid"); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == "106790274378320830963", "Id is not valid"); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Surname)?.Value == "AspnetvnextTest", "FamilyName is not valid"); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", "Name is not valid"); + Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(1200), "ExpiresIn is not valid"); + Helpers.ThrowIfConditionFailed(() => context.User != null, "User object is not valid"); + context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); + } + + return Task.FromResult(0); + } + + internal static Task OnTicketReceived(TicketReceivedContext context) + { + if (context.Principal != null && context.Options.SignInScheme == IdentityConstants.ExternalScheme) + { + //This way we will know all events were fired. + var identity = context.Principal.Identities.First(); + var manageStoreClaim = identity?.Claims.Where(c => c.Type == "ManageStore" && c.Value == "false").FirstOrDefault(); + if (manageStoreClaim != null) + { + identity.RemoveClaim(manageStoreClaim); + identity.AddClaim(new Claim("ManageStore", "Allowed")); + } + } + + return Task.FromResult(0); + } + + internal static Task RedirectToAuthorizationEndpoint(RedirectContext context) + { + context.Response.Redirect(context.RedirectUri + "&custom_redirect_uri=custom"); + return Task.FromResult(0); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/MicrosoftAccountMockBackChannelHandler.cs b/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/MicrosoftAccountMockBackChannelHandler.cs new file mode 100644 index 0000000..3ebf70a --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/MicrosoftAccountMockBackChannelHandler.cs @@ -0,0 +1,56 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; + +namespace MusicStore.Mocks.MicrosoftAccount +{ + /// + /// Summary description for MicrosoftAccountMockBackChannelHandler + /// + public class MicrosoftAccountMockBackChannelHandler : HttpMessageHandler + { + protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = new HttpResponseMessage(); + + if (request.RequestUri.AbsoluteUri.StartsWith("https://login.microsoftonline.com/common/oauth2/v2.0/token")) + { + var formData = new FormCollection(await new FormReader(await request.Content.ReadAsStreamAsync()).ReadFormAsync()); + if (formData["grant_type"] == "authorization_code") + { + if (formData["code"] == "ValidCode") + { + if (formData["redirect_uri"].Count > 0 && ((string)formData["redirect_uri"]).EndsWith("signin-microsoft") && + formData["client_id"] == "[ClientId]" && formData["client_secret"] == "[ClientSecret]") + { + response.Content = new StringContent("{\"token_type\":\"bearer\",\"expires_in\":3600,\"scope\":\"https://graph.microsoft.com/user.read\",\"access_token\":\"ValidAccessToken\",\"refresh_token\":\"ValidRefreshToken\",\"authentication_token\":\"ValidAuthenticationToken\"}"); + return response; + } + } + } + + response.StatusCode = (HttpStatusCode)400; + return response; + } + else if (request.RequestUri.AbsoluteUri.StartsWith("https://graph.microsoft.com/v1.0/me")) + { + if (request.Headers.Authorization.Parameter == "ValidAccessToken") + { + response.Content = new StringContent("{\r \"id\": \"fccf9a24999f4f4f\", \r \"displayName\": \"AspnetvnextTest AspnetvnextTest\", \r \"givenName\": \"AspnetvnextTest\", \r \"surname\": \"AspnetvnextTest\", \r \"link\": \"https://profile.live.com/\", \r \"gender\": null, \r \"locale\": \"en_US\", \r \"updated_time\": \"2013-08-27T22:18:14+0000\"\r}"); + } + else + { + response.Content = new StringContent("{\r \"error\": {\r \"code\": \"request_token_invalid\", \r \"message\": \"The access token isn't valid.\"\r }\r}", Encoding.UTF8, "text/javascript"); + } + return response; + } + + throw new NotImplementedException(request.RequestUri.AbsoluteUri); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs b/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs new file mode 100644 index 0000000..3639a7b --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.MicrosoftAccount; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.Identity; +using MusicStore.Mocks.Common; + +namespace MusicStore.Mocks.MicrosoftAccount +{ + internal class TestMicrosoftAccountEvents + { + internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) + { + if (context.Principal != null) + { + Helpers.ThrowIfConditionFailed(() => context.AccessToken == "ValidAccessToken", "Access token is not valid"); + Helpers.ThrowIfConditionFailed(() => context.RefreshToken == "ValidRefreshToken", "Refresh token is not valid"); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.GivenName)?.Value == "AspnetvnextTest", "Given name is not valid"); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Surname)?.Value == "AspnetvnextTest", "Surname is not valid"); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == "fccf9a24999f4f4f", "Id is not valid"); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", "Name is not valid"); + Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(3600), "ExpiresIn is not valid"); + Helpers.ThrowIfConditionFailed(() => context.User != null, "User object is not valid"); + Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == context.User.SelectToken("id").ToString(), "User id is not valid"); + context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); + } + + return Task.FromResult(0); + } + + internal static Task OnTicketReceived(TicketReceivedContext context) + { + if (context.Principal != null && context.Options.SignInScheme == IdentityConstants.ExternalScheme) + { + //This way we will know all events were fired. + var identity = context.Principal.Identities.First(); + var manageStoreClaim = identity?.Claims.Where(c => c.Type == "ManageStore" && c.Value == "false").FirstOrDefault(); + if (manageStoreClaim != null) + { + identity.RemoveClaim(manageStoreClaim); + identity.AddClaim(new Claim("ManageStore", "Allowed")); + } + } + + return Task.FromResult(0); + } + + internal static Task RedirectToAuthorizationEndpoint(RedirectContext context) + { + context.Response.Redirect(context.RedirectUri + "&custom_redirect_uri=custom"); + return Task.FromResult(0); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/CustomStringDataFormat.cs b/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/CustomStringDataFormat.cs new file mode 100644 index 0000000..dc433f9 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/CustomStringDataFormat.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Authentication; + +namespace MusicStore.Mocks.OpenIdConnect +{ + internal class CustomStringDataFormat : ISecureDataFormat + { + private const string _capturedNonce = "635579928639517715.OTRjOTVkM2EtMDRmYS00ZDE3LThhZGUtZWZmZGM4ODkzZGZkMDRlNDhkN2MtOWIwMC00ZmVkLWI5MTItMTUwYmQ4MzdmOWI0"; + + public string Protect(string data) + { + return "protectedString"; + } + + public string Protect(string data, string purpose) + { + return purpose + "protectedString"; + } + + public string Unprotect(string protectedText) + { + return protectedText == "protectedString" ? _capturedNonce : null; + } + + public string Unprotect(string protectedText, string purpose) + { + return protectedText == (purpose + "protectedString") ? _capturedNonce : null; + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/OpenIdConnectBackChannelHttpHandler.cs b/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/OpenIdConnectBackChannelHttpHandler.cs new file mode 100644 index 0000000..cc84ec5 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/OpenIdConnectBackChannelHttpHandler.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace MusicStore.Mocks.OpenIdConnect +{ + internal class OpenIdConnectBackChannelHttpHandler : HttpMessageHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = new HttpResponseMessage(); + + var basePath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "ForTesting", "Mocks", "OpenIdConnect")); + + if (request.RequestUri.AbsoluteUri == "https://login.windows.net/[tenantName].onmicrosoft.com/.well-known/openid-configuration") + { + response.Content = new StringContent(File.ReadAllText(Path.Combine(basePath, "openid-configuration.json"))); + } + else if (request.RequestUri.AbsoluteUri == "https://login.windows.net/common/discovery/keys") + { + response.Content = new StringContent(File.ReadAllText(Path.Combine(basePath, "keys.json"))); + } + else if (request.RequestUri.AbsoluteUri == "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/oauth2/token") + { + response.Content = new StringContent("{\"id_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImtyaU1QZG1Cdng2OHNrVDgtbVBBQjNCc2VlQSJ9.eyJhdWQiOiJjOTk0OTdhYS0zZWUyLTQ3MDctYjhhOC1jMzNmNTEzMjNmZWYiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC80YWZiYzY4OS04MDViLTQ4Y2YtYTI0Yy1kNGFhMzI0OGEyNDgvIiwiaWF0IjoxNDIyMzk1NzYzLCJuYmYiOjE0MjIzOTU3NjMsImV4cCI6MTQyMjM5OTY2MywidmVyIjoiMS4wIiwidGlkIjoiNGFmYmM2ODktODA1Yi00OGNmLWEyNGMtZDRhYTMyNDhhMjQ4IiwiYW1yIjpbInB3ZCJdLCJvaWQiOiJmODc2YWJlYi1kNmI1LTQ0ZTQtOTcxNi02MjY2YWMwMTgxYTgiLCJ1cG4iOiJ1c2VyM0BwcmFidXJhamdtYWlsLm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IlBVZGhjbFA1UGdJalNVOVAxUy1IZWxEYVNGU2YtbVhWMVk2MC1LMnZXcXciLCJnaXZlbl9uYW1lIjoiVXNlcjMiLCJmYW1pbHlfbmFtZSI6IlVzZXIzIiwibmFtZSI6IlVzZXIzIiwidW5pcXVlX25hbWUiOiJ1c2VyM0BwcmFidXJhamdtYWlsLm9ubWljcm9zb2Z0LmNvbSIsIm5vbmNlIjoiNjM1NTc5OTI4NjM5NTE3NzE1Lk9UUmpPVFZrTTJFdE1EUm1ZUzAwWkRFM0xUaGhaR1V0WldabVpHTTRPRGt6Wkdaa01EUmxORGhrTjJNdE9XSXdNQzAwWm1Wa0xXSTVNVEl0TVRVd1ltUTRNemRtT1dJMCIsImNfaGFzaCI6IkZHdDN3Y1FBRGUwUFkxUXg3TzFyNmciLCJwd2RfZXhwIjoiNjY5MzI4MCIsInB3ZF91cmwiOiJodHRwczovL3BvcnRhbC5taWNyb3NvZnRvbmxpbmUuY29tL0NoYW5nZVBhc3N3b3JkLmFzcHgifQ.coAdCkdMgnslMHagdU8IBgH7Z0dilRdMfKytyqPJuTr6sbmbhrAoAj-KeGwbKgzrd-BeDk_rW47dntWuuAqGrAOGzxXvS2dcSWgoEKoXuDccIL5b4rIomRpfJpaeE-YwiU3usyRvoQCpHmtOa0g7xVilIj3_1-9ylMgRDY5qcrtQ_hEZlGuYyiCPR0dw8WmNU7r6PKObG-o3Yk_RbEBHjnaWxKoJwrVUEZUQOJDAvlr6ZYEmGTlD_BM0Rc_0fJZPU7A3uN9PHLw1atm-chN06IDXf23R33JI_xFuEZnj9HZQ_eIzNCl7GFmUryK3FFgYJpIbsI0BIFuksSikXz33IA\", \"access_token\": \"access\"}"); + } + + return Task.FromResult(response); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/TestOpenIdConnectEvents.cs b/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/TestOpenIdConnectEvents.cs new file mode 100644 index 0000000..781aa24 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/TestOpenIdConnectEvents.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Http; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using MusicStore.Mocks.Common; + +namespace MusicStore.Mocks.OpenIdConnect +{ + internal class TestOpenIdConnectEvents + { + private static List eventsFired = new List(); + + internal static Task MessageReceived(MessageReceivedContext context) + { + Helpers.ThrowIfConditionFailed(() => context.ProtocolMessage != null, "ProtocolMessage is null."); + eventsFired.Add(nameof(MessageReceived)); + return Task.FromResult(0); + } + + internal static Task TokenValidated(TokenValidatedContext context) + { + Helpers.ThrowIfConditionFailed(() => context.Principal != null, "context.Principal is null."); + Helpers.ThrowIfConditionFailed(() => context.Principal.Identity != null, "context.Principal.Identity is null."); + Helpers.ThrowIfConditionFailed(() => !string.IsNullOrWhiteSpace(context.Principal.Identity.Name), "context.Principal.Identity.Name is null."); + eventsFired.Add(nameof(TokenValidated)); + return Task.FromResult(0); + } + + internal static Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context) + { + Helpers.ThrowIfConditionFailed(() => context.TokenEndpointRequest.Code == "AAABAAAAvPM1KaPlrEqdFSBzjqfTGGBtrTYVn589oKw4lLgJ6Svz0AhPVOJr0J2-Uu_KffGlqIbYlRAyxmt-vZ7VlSVdrWvOkNhK9OaAMaSD7LDoPbBTVMEkB0MdAgBTV34l2el-s8ZI02_9PvgQaORZs7n8eGaGbcoKAoxiDn2OcKuJVplXYgrGUwU4VpRaqe6RaNzuseM7qBFbLIv4Wps8CndE6W8ccmuu6EvGC6-H4uF9EZL7gU4nEcTcvkE4Qyt8do6VhTVfM1ygRNQgmV1BCig5t_5xfhL6-xWQdy15Uzn_Df8VSsyDXe8s9cxyKlqc_AIyLFy_NEiMQFUqjZWKd_rR3A8ugug15SEEGuo1kF3jMc7dVMdE6OF9UBd-Ax5ILWT7V4clnRQb6-CXB538DlolREfE-PowXYruFBA-ARD6rwAVtuVfCSbS0Zr4ZqfNjt6x8yQdK-OkdQRZ1thiZcZlm1lyb2EquGZ8Deh2iWBoY1uNcyjzhG-L43EivxtHAp6Y8cErhbo41iacgqOycgyJWxiB5J0HHkxD0nQ2RVVuY8Ybc9sdgyfKkkK2wZ3idGaRCdZN8Q9VBhWRXPDMqHWG8t3aZRtvJ_Xd3WhjNPJC0GpepUGNNQtXiEoIECC363o1z6PZC5-E7U3l9xK06BZkcfTOnggUiSWNCrxUKS44dNqaozdYlO5E028UgAEhJ4eDtcP3PZty-0j4j5Mw0F2FmyAA", + "context.TokenEndpointRequest.Code is invalid."); + eventsFired.Add(nameof(AuthorizationCodeReceived)); + + // Verify all events are fired. + if (eventsFired.Contains(nameof(RedirectToIdentityProvider)) && + eventsFired.Contains(nameof(MessageReceived)) && + eventsFired.Contains(nameof(TokenValidated)) && + eventsFired.Contains(nameof(AuthorizationCodeReceived))) + { + ((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim("ManageStore", "Allowed")); + } + + return Task.FromResult(0); + } + + internal static Task RedirectToIdentityProvider(RedirectContext context) + { + eventsFired.Add(nameof(RedirectToIdentityProvider)); + + if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout) + { + context.ProtocolMessage.PostLogoutRedirectUri = + context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase + new PathString("/Account/Login"); + } + + return Task.FromResult(0); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/keys.json b/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/keys.json new file mode 100644 index 0000000..5910c9b --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/keys.json @@ -0,0 +1,26 @@ +{ + "keys": [ + { + "kty": "RSA", + "use": "sig", + "kid": "kriMPdmBvx68skT8-mPAB3BseeA", + "x5t": "kriMPdmBvx68skT8-mPAB3BseeA", + "n": "kSCWg6q9iYxvJE2NIhSyOiKvqoWCO2GFipgH0sTSAs5FalHQosk9ZNTztX0ywS/AHsBeQPqYygfYVJL6/EgzVuwRk5txr9e3n1uml94fLyq/AXbwo9yAduf4dCHTP8CWR1dnDR+Qnz/4PYlWVEuuHHONOw/blbfdMjhY+C/BYM2E3pRxbohBb3x//CfueV7ddz2LYiH3wjz0QS/7kjPiNCsXcNyKQEOTkbHFi3mu0u13SQwNddhcynd/GTgWN8A+6SN1r4hzpjFKFLbZnBt77ACSiYx+IHK4Mp+NaVEi5wQtSsjQtI++XsokxRDqYLwus1I1SihgbV/STTg5enufuw==", + "e": "AQAB", + "x5c": [ + "MIIDPjCCAiqgAwIBAgIQsRiM0jheFZhKk49YD0SK1TAJBgUrDgMCHQUAMC0xKzApBgNVBAMTImFjY291bnRzLmFjY2Vzc2NvbnRyb2wud2luZG93cy5uZXQwHhcNMTQwMTAxMDcwMDAwWhcNMTYwMTAxMDcwMDAwWjAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkSCWg6q9iYxvJE2NIhSyOiKvqoWCO2GFipgH0sTSAs5FalHQosk9ZNTztX0ywS/AHsBeQPqYygfYVJL6/EgzVuwRk5txr9e3n1uml94fLyq/AXbwo9yAduf4dCHTP8CWR1dnDR+Qnz/4PYlWVEuuHHONOw/blbfdMjhY+C/BYM2E3pRxbohBb3x//CfueV7ddz2LYiH3wjz0QS/7kjPiNCsXcNyKQEOTkbHFi3mu0u13SQwNddhcynd/GTgWN8A+6SN1r4hzpjFKFLbZnBt77ACSiYx+IHK4Mp+NaVEi5wQtSsjQtI++XsokxRDqYLwus1I1SihgbV/STTg5enufuwIDAQABo2IwYDBeBgNVHQEEVzBVgBDLebM6bK3BjWGqIBrBNFeNoS8wLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldIIQsRiM0jheFZhKk49YD0SK1TAJBgUrDgMCHQUAA4IBAQCJ4JApryF77EKC4zF5bUaBLQHQ1PNtA1uMDbdNVGKCmSf8M65b8h0NwlIjGGGy/unK8P6jWFdm5IlZ0YPTOgzcRZguXDPj7ajyvlVEQ2K2ICvTYiRQqrOhEhZMSSZsTKXFVwNfW6ADDkN3bvVOVbtpty+nBY5UqnI7xbcoHLZ4wYD251uj5+lo13YLnsVrmQ16NCBYq2nQFNPuNJw6t3XUbwBHXpF46aLT1/eGf/7Xx6iy8yPJX4DyrpFTutDz882RWofGEO5t4Cw+zZg70dJ/hH/ODYRMorfXEW+8uKmXMKmX2wyxMKvfiPbTy5LmAU8Jvjs2tLg4rOBcXWLAIarZ" + ] + }, + { + "kty": "RSA", + "use": "sig", + "kid": "MnC_VZcATfM5pOYiJHMba9goEKY", + "x5t": "MnC_VZcATfM5pOYiJHMba9goEKY", + "n": "vIqz+4+ER/vNWLON9yv8hIYV737JQ6rCl6XfzOC628seYUPf0TaGk91CFxefhzh23V9Tkq+RtwN1Vs/z57hO82kkzL+cQHZX3bMJD+GEGOKXCEXURN7VMyZWMAuzQoW9vFb1k3cR1RW/EW/P+C8bb2dCGXhBYqPfHyimvz2WarXhntPSbM5XyS5v5yCw5T/Vuwqqsio3V8wooWGMpp61y12NhN8bNVDQAkDPNu2DT9DXB1g0CeFINp/KAS/qQ2Kq6TSvRHJqxRR68RezYtje9KAqwqx4jxlmVAQy0T3+T+IAbsk1wRtWDndhO6s1Os+dck5TzyZ/dNOhfXgelixLUQ==", + "e": "AQAB", + "x5c": [ + "MIIC4jCCAcqgAwIBAgIQQNXrmzhLN4VGlUXDYCRT3zANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE0MTAyODAwMDAwMFoXDTE2MTAyNzAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALyKs/uPhEf7zVizjfcr/ISGFe9+yUOqwpel38zgutvLHmFD39E2hpPdQhcXn4c4dt1fU5KvkbcDdVbP8+e4TvNpJMy/nEB2V92zCQ/hhBjilwhF1ETe1TMmVjALs0KFvbxW9ZN3EdUVvxFvz/gvG29nQhl4QWKj3x8opr89lmq14Z7T0mzOV8kub+cgsOU/1bsKqrIqN1fMKKFhjKaetctdjYTfGzVQ0AJAzzbtg0/Q1wdYNAnhSDafygEv6kNiquk0r0RyasUUevEXs2LY3vSgKsKseI8ZZlQEMtE9/k/iAG7JNcEbVg53YTurNTrPnXJOU88mf3TToX14HpYsS1ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfolx45w0i8CdAUjjeAaYdhG9+NDHxop0UvNOqlGqYJexqPLuvX8iyUaYxNGzZxFgGI3GpKfmQP2JQWQ1E5JtY/n8iNLOKRMwqkuxSCKJxZJq4Sl/m/Yv7TS1P5LNgAj8QLCypxsWrTAmq2HSpkeSk4JBtsYxX6uhbGM/K1sEktKybVTHu22/7TmRqWTmOUy9wQvMjJb2IXdMGLG3hVntN/WWcs5w8vbt1i8Kk6o19W2MjZ95JaECKjBDYRlhG1KmSBtrsKsCBQoBzwH/rXfksTO9JoUYLXiW0IppB7DhNH4PJ5hZI91R8rR0H3/bKkLSuDaKLWSqMhozdhXsIIKvJQ==" + ] + } + ] +} \ No newline at end of file diff --git a/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/openid-configuration.json b/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/openid-configuration.json new file mode 100644 index 0000000..802aeed --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/OpenIdConnect/openid-configuration.json @@ -0,0 +1,34 @@ +{ + "issuer": "https://sts.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/", + "authorization_endpoint": "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/oauth2/authorize", + "token_endpoint": "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/oauth2/token", + "token_endpoint_auth_methods_supported": [ + "client_secret_post", + "private_key_jwt" + ], + "jwks_uri": "https://login.windows.net/common/discovery/keys", + "response_types_supported": [ + "code", + "id_token", + "code id_token", + "token" + ], + "response_modes_supported": [ + "query", + "fragment", + "form_post" + ], + "subject_types_supported": [ + "pairwise" + ], + "scopes_supported": [ + "openid" + ], + "id_token_signing_alg_values_supported": [ + "RS256" + ], + "microsoft_multi_refresh_token": true, + "check_session_iframe": "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/oauth2/checksession", + "end_session_endpoint": "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/oauth2/logout", + "userinfo_endpoint": "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/openid/userinfo" +} \ No newline at end of file diff --git a/samples/MusicStore/ForTesting/Mocks/StartupOpenIdConnectTesting.cs b/samples/MusicStore/ForTesting/Mocks/StartupOpenIdConnectTesting.cs new file mode 100644 index 0000000..9f83bdd --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/StartupOpenIdConnectTesting.cs @@ -0,0 +1,167 @@ +using System; +using System.Globalization; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.AspNetCore.Localization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using MusicStore.Components; +using MusicStore.Mocks.Common; +using MusicStore.Mocks.OpenIdConnect; +using MusicStore.Models; + +namespace MusicStore +{ + public class StartupOpenIdConnectTesting + { + private readonly Platform _platform; + + public StartupOpenIdConnectTesting(IHostingEnvironment env) + { + //Below code demonstrates usage of multiple configuration sources. For instance a setting say 'setting1' is found in both the registered sources, + //then the later source will win. By this way a Local config can be overridden by a different setting while deployed remotely. + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("config.json") + .AddEnvironmentVariables(); //All environment variables in the process's context flow in as configuration values. + + Configuration = builder.Build(); + _platform = new Platform(); + } + + public IConfiguration Configuration { get; private set; } + + public void ConfigureServices(IServiceCollection services) + { + services.Configure(Configuration.GetSection("AppSettings")); + + // Add EF services to the services container + if (_platform.UseInMemoryStore) + { + services.AddDbContext(options => + options.UseInMemoryDatabase("Scratch")); + } + else + { + services.AddDbContext(options => + options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); + } + + // Add Identity services to the services container + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + // Create an Azure Active directory application and copy paste the following + services.AddAuthentication().AddOpenIdConnect(options => + { + options.Authority = "https://login.windows.net/[tenantName].onmicrosoft.com"; + options.ClientId = "c99497aa-3ee2-4707-b8a8-c33f51323fef"; + options.BackchannelHttpHandler = new OpenIdConnectBackChannelHttpHandler(); + options.StringDataFormat = new CustomStringDataFormat(); + options.StateDataFormat = new CustomStateDataFormat(); + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + options.UseTokenLifetime = false; + options.TokenValidationParameters.ValidateLifetime = false; + options.ProtocolValidator.RequireNonce = true; + options.ProtocolValidator.NonceLifetime = TimeSpan.FromDays(36500); + + options.Events = new OpenIdConnectEvents + { + OnMessageReceived = TestOpenIdConnectEvents.MessageReceived, + OnAuthorizationCodeReceived = TestOpenIdConnectEvents.AuthorizationCodeReceived, + OnRedirectToIdentityProvider = TestOpenIdConnectEvents.RedirectToIdentityProvider, + OnTokenValidated = TestOpenIdConnectEvents.TokenValidated, + }; + }); + + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", builder => + { + builder.WithOrigins("http://example.com"); + }); + }); + + // Add MVC services to the services container + services.AddMvc(); + + //Add InMemoryCache + services.AddSingleton(); + + // Add session related services. + services.AddMemoryCache(); + services.AddDistributedMemoryCache(); + services.AddSession(); + + // Add the system clock service + services.AddSingleton(); + + // Configure Auth + services.Configure(options => + { + options.AddPolicy("ManageStore", new AuthorizationPolicyBuilder().RequireClaim("ManageStore", "Allowed").Build()); + }); + } + + public void Configure(IApplicationBuilder app) + { + // force the en-US culture, so that the app behaves the same even on machines with different default culture + var supportedCultures = new[] { new CultureInfo("en-US") }; + + app.UseRequestLocalization(new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture("en-US"), + SupportedCultures = supportedCultures, + SupportedUICultures = supportedCultures + }); + + app.UseStatusCodePagesWithRedirects("~/Home/StatusCodePage"); + + // Display custom error page in production when error occurs + // During development use the ErrorPage middleware to display error information in the browser + app.UseDeveloperExceptionPage(); + + app.UseDatabaseErrorPage(); + + // Configure Session. + app.UseSession(); + + // Add static files to the request pipeline + app.UseStaticFiles(); + + // Add authentication to the request pipeline + app.UseAuthentication(); + + // Add MVC to the request pipeline + app.UseMvc(routes => + { + routes.MapRoute( + name: "areaRoute", + template: "{area:exists}/{controller}/{action}", + defaults: new { action = "Index" }); + + routes.MapRoute( + name: "default", + template: "{controller}/{action}/{id?}", + defaults: new { controller = "Home", action = "Index" }); + + routes.MapRoute( + name: "api", + template: "{controller}/{id?}"); + }); + + //Populates the MusicStore sample data + SampleData.InitializeMusicStoreDatabaseAsync(app.ApplicationServices).Wait(); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/StartupSocialTesting.cs b/samples/MusicStore/ForTesting/Mocks/StartupSocialTesting.cs new file mode 100644 index 0000000..ffe81ad --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/StartupSocialTesting.cs @@ -0,0 +1,209 @@ +using System; +using System.Globalization; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.Authentication.Twitter; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.AspNetCore.Localization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MusicStore.Components; +using MusicStore.Mocks.Common; +using MusicStore.Mocks.Facebook; +using MusicStore.Mocks.Google; +using MusicStore.Mocks.MicrosoftAccount; +using MusicStore.Mocks.Twitter; +using MusicStore.Models; + +namespace MusicStore +{ + public class StartupSocialTesting + { + private readonly Platform _platform; + + public StartupSocialTesting(IHostingEnvironment hostingEnvironment) + { + //Below code demonstrates usage of multiple configuration sources. For instance a setting say 'setting1' is found in both the registered sources, + //then the later source will win. By this way a Local config can be overridden by a different setting while deployed remotely. + var builder = new ConfigurationBuilder() + .SetBasePath(hostingEnvironment.ContentRootPath) + .AddJsonFile("config.json") + .AddEnvironmentVariables() //All environment variables in the process's context flow in as configuration values. + .AddJsonFile("configoverride.json", optional: true); // Used to override some configuration parameters that cannot be overridden by environment. + + Configuration = builder.Build(); + _platform = new Platform(); + } + + public IConfiguration Configuration { get; private set; } + + public void ConfigureServices(IServiceCollection services) + { + services.Configure(Configuration.GetSection("AppSettings")); + + // Add EF services to the services container + if (_platform.UseInMemoryStore) + { + services.AddDbContext(options => + options.UseInMemoryDatabase("Scratch")); + } + else + { + services.AddDbContext(options => + options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); + } + + // Add Identity services to the services container + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.ConfigureApplicationCookie(options => options.AccessDeniedPath = "/Home/AccessDenied"); + + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", builder => + { + builder.WithOrigins("http://example.com"); + }); + }); + + // Add MVC services to the services container + services.AddMvc(); + + //Add InMemoryCache + services.AddSingleton(); + + // Add session related services. + services.AddMemoryCache(); + services.AddDistributedMemoryCache(); + services.AddSession(); + + // Add the system clock service + services.AddSingleton(); + + // Configure Auth + services.AddAuthorization(options => + { + options.AddPolicy("ManageStore", new AuthorizationPolicyBuilder().RequireClaim("ManageStore", "Allowed").Build()); + }); + + services.AddAuthentication() + .AddFacebook(options => + { + options.AppId = "[AppId]"; + options.AppSecret = "[AppSecret]"; + options.Events = new OAuthEvents() + { + OnCreatingTicket = TestFacebookEvents.OnCreatingTicket, + OnTicketReceived = TestFacebookEvents.OnTicketReceived, + OnRedirectToAuthorizationEndpoint = TestFacebookEvents.RedirectToAuthorizationEndpoint + }; + options.BackchannelHttpHandler = new FacebookMockBackChannelHttpHandler(); + options.StateDataFormat = new CustomStateDataFormat(); + options.Scope.Add("email"); + options.Scope.Add("read_friendlists"); + options.Scope.Add("user_checkins"); + }).AddGoogle(options => + { + options.ClientId = "[ClientId]"; + options.ClientSecret = "[ClientSecret]"; + options.AccessType = "offline"; + options.Events = new OAuthEvents() + { + OnCreatingTicket = TestGoogleEvents.OnCreatingTicket, + OnTicketReceived = TestGoogleEvents.OnTicketReceived, + OnRedirectToAuthorizationEndpoint = TestGoogleEvents.RedirectToAuthorizationEndpoint + }; + options.StateDataFormat = new CustomStateDataFormat(); + options.BackchannelHttpHandler = new GoogleMockBackChannelHttpHandler(); + }).AddTwitter(options => + { + options.ConsumerKey = "[ConsumerKey]"; + options.ConsumerSecret = "[ConsumerSecret]"; + options.Events = new TwitterEvents() + { + OnCreatingTicket = TestTwitterEvents.OnCreatingTicket, + OnTicketReceived = TestTwitterEvents.OnTicketReceived, + OnRedirectToAuthorizationEndpoint = TestTwitterEvents.RedirectToAuthorizationEndpoint + }; + options.StateDataFormat = new CustomTwitterStateDataFormat(); + options.BackchannelHttpHandler = new TwitterMockBackChannelHttpHandler(); + }).AddMicrosoftAccount(options => + { + options.ClientId = "[ClientId]"; + options.ClientSecret = "[ClientSecret]"; + options.Events = new OAuthEvents() + { + OnCreatingTicket = TestMicrosoftAccountEvents.OnCreatingTicket, + OnTicketReceived = TestMicrosoftAccountEvents.OnTicketReceived, + OnRedirectToAuthorizationEndpoint = TestMicrosoftAccountEvents.RedirectToAuthorizationEndpoint + }; + options.BackchannelHttpHandler = new MicrosoftAccountMockBackChannelHandler(); + options.StateDataFormat = new CustomStateDataFormat(); + options.Scope.Add("wl.basic"); + options.Scope.Add("wl.signin"); + }); + + } + + public void Configure(IApplicationBuilder app) + { + // force the en-US culture, so that the app behaves the same even on machines with different default culture + var supportedCultures = new[] { new CultureInfo("en-US") }; + + app.UseRequestLocalization(new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture("en-US"), + SupportedCultures = supportedCultures, + SupportedUICultures = supportedCultures + }); + + app.UseStatusCodePagesWithRedirects("~/Home/StatusCodePage"); + + // Error page middleware displays a nice formatted HTML page for any unhandled exceptions in the request pipeline. + // Note: Not recommended for production. + app.UseDeveloperExceptionPage(); + + app.UseDatabaseErrorPage(); + + // Configure Session. + app.UseSession(); + + // Add static files to the request pipeline + app.UseStaticFiles(); + + // Add cookie-based authentication to the request pipeline + app.UseAuthentication(); + + // Add MVC to the request pipeline + app.UseMvc(routes => + { + routes.MapRoute( + name: "areaRoute", + template: "{area:exists}/{controller}/{action}", + defaults: new { action = "Index" }); + + routes.MapRoute( + name: "default", + template: "{controller}/{action}/{id?}", + defaults: new { controller = "Home", action = "Index" }); + + routes.MapRoute( + name: "api", + template: "{controller}/{id?}"); + }); + + //Populates the MusicStore sample data + SampleData.InitializeMusicStoreDatabaseAsync(app.ApplicationServices).Wait(); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/Twitter/CustomTwitterStateDataFormat.cs b/samples/MusicStore/ForTesting/Mocks/Twitter/CustomTwitterStateDataFormat.cs new file mode 100644 index 0000000..422ae29 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/Twitter/CustomTwitterStateDataFormat.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Twitter; +using Newtonsoft.Json; + +namespace MusicStore.Mocks.Twitter +{ + /// + /// Summary description for CustomTwitterStateDataFormat + /// + public class CustomTwitterStateDataFormat : ISecureDataFormat + { + private static string _lastSavedRequestToken; + + public string Protect(RequestToken data) + { + data.Token = "valid_oauth_token"; + _lastSavedRequestToken = Serialize(data); + return "valid_oauth_token"; + } + + public string Protect(RequestToken data, string purpose) + { + return Protect(data); + } + + public RequestToken Unprotect(string state) + { + return state == "valid_oauth_token" ? DeSerialize(_lastSavedRequestToken) : null; + } + + public RequestToken Unprotect(string state, string purpose) + { + return Unprotect(state); + } + + private string Serialize(RequestToken data) + { + return JsonConvert.SerializeObject(data, Formatting.Indented); + } + + private RequestToken DeSerialize(string state) + { + return JsonConvert.DeserializeObject(state); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/Twitter/TestTwitterEvents.cs b/samples/MusicStore/ForTesting/Mocks/Twitter/TestTwitterEvents.cs new file mode 100644 index 0000000..fee4bc7 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/Twitter/TestTwitterEvents.cs @@ -0,0 +1,50 @@ +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Twitter; +using Microsoft.AspNetCore.Identity; +using MusicStore.Mocks.Common; + +namespace MusicStore.Mocks.Twitter +{ + internal class TestTwitterEvents + { + internal static Task OnCreatingTicket(TwitterCreatingTicketContext context) + { + if (context.Principal != null) + { + Helpers.ThrowIfConditionFailed(() => context.UserId == "valid_user_id", "UserId is not valid"); + Helpers.ThrowIfConditionFailed(() => context.ScreenName == "valid_screen_name", "ScreenName is not valid"); + Helpers.ThrowIfConditionFailed(() => context.AccessToken == "valid_oauth_token", "AccessToken is not valid"); + Helpers.ThrowIfConditionFailed(() => context.AccessTokenSecret == "valid_oauth_token_secret", "AccessTokenSecret is not valid"); + context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); + } + + return Task.FromResult(0); + } + + internal static Task OnTicketReceived(TicketReceivedContext context) + { + if (context.Principal != null && context.Options.SignInScheme == IdentityConstants.ExternalScheme) + { + //This way we will know all Events were fired. + var identity = context.Principal.Identities.First(); + var manageStoreClaim = identity?.Claims.Where(c => c.Type == "ManageStore" && c.Value == "false").FirstOrDefault(); + if (manageStoreClaim != null) + { + identity.RemoveClaim(manageStoreClaim); + identity.AddClaim(new Claim("ManageStore", "Allowed")); + } + } + + return Task.FromResult(0); + } + + internal static Task RedirectToAuthorizationEndpoint(RedirectContext context) + { + context.Response.Redirect(context.RedirectUri + "&custom_redirect_uri=custom"); + return Task.FromResult(0); + } + } +} diff --git a/samples/MusicStore/ForTesting/Mocks/Twitter/TwitterMockBackChannelHttpHandler.cs b/samples/MusicStore/ForTesting/Mocks/Twitter/TwitterMockBackChannelHttpHandler.cs new file mode 100644 index 0000000..2ae3194 --- /dev/null +++ b/samples/MusicStore/ForTesting/Mocks/Twitter/TwitterMockBackChannelHttpHandler.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; + +namespace MusicStore.Mocks.Twitter +{ + /// + /// Summary description for TwitterMockBackChannelHttpHandler + /// + public class TwitterMockBackChannelHttpHandler : HttpMessageHandler + { + private static bool _requestTokenEndpointInvoked = false; + + protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = new HttpResponseMessage(); + + if (request.RequestUri.AbsoluteUri.StartsWith("https://api.twitter.com/oauth/access_token")) + { + var formData = new FormCollection(await new FormReader(await request.Content.ReadAsStreamAsync()).ReadFormAsync()); + if (formData["oauth_verifier"] == "valid_oauth_verifier") + { + if (_requestTokenEndpointInvoked) + { + var response_Form_data = new List>() + { + new KeyValuePair("oauth_token", "valid_oauth_token"), + new KeyValuePair("oauth_token_secret", "valid_oauth_token_secret"), + new KeyValuePair("user_id", "valid_user_id"), + new KeyValuePair("screen_name", "valid_screen_name"), + }; + + response.Content = new FormUrlEncodedContent(response_Form_data); + } + else + { + response.StatusCode = HttpStatusCode.InternalServerError; + response.Content = new StringContent("RequestTokenEndpoint is not invoked"); + } + return response; + } + response.StatusCode = (HttpStatusCode)400; + return response; + } + else if (request.RequestUri.AbsoluteUri.StartsWith("https://api.twitter.com/oauth/request_token")) + { + var response_Form_data = new List>() + { + new KeyValuePair("oauth_callback_confirmed", "true"), + new KeyValuePair("oauth_token", "valid_oauth_token"), + new KeyValuePair("oauth_token_secret", "valid_oauth_token_secret") + }; + + _requestTokenEndpointInvoked = true; + response.Content = new FormUrlEncodedContent(response_Form_data); + return response; + } + + throw new NotImplementedException(request.RequestUri.AbsoluteUri); + } + } +} diff --git a/samples/MusicStore/ForTesting/MusicStoreConfig.cs b/samples/MusicStore/ForTesting/MusicStoreConfig.cs new file mode 100644 index 0000000..c1bcba1 --- /dev/null +++ b/samples/MusicStore/ForTesting/MusicStoreConfig.cs @@ -0,0 +1,7 @@ +namespace MusicStore +{ + public class StoreConfig + { + public const string ConnectionStringKey = "Data__DefaultConnection__ConnectionString"; + } +} \ No newline at end of file diff --git a/samples/MusicStore/ForTesting/Readme.md b/samples/MusicStore/ForTesting/Readme.md new file mode 100644 index 0000000..b8c7e75 --- /dev/null +++ b/samples/MusicStore/ForTesting/Readme.md @@ -0,0 +1 @@ +The contents of this folder are used for end to end testing. \ No newline at end of file diff --git a/samples/MusicStore/MessageServices.cs b/samples/MusicStore/MessageServices.cs new file mode 100644 index 0000000..32d4c92 --- /dev/null +++ b/samples/MusicStore/MessageServices.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; + +namespace MusicStore +{ + public static class MessageServices + { + public static Task SendEmailAsync(string email, string subject, string message) + { + // Plug in your email service + return Task.FromResult(0); + } + + public static Task SendSmsAsync(string number, string message) + { + // Plug in your sms service + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Models/AccountViewModels.cs b/samples/MusicStore/Models/AccountViewModels.cs new file mode 100644 index 0000000..8b566d2 --- /dev/null +++ b/samples/MusicStore/Models/AccountViewModels.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace MusicStore.Models +{ + public class ExternalLoginConfirmationViewModel + { + [Required] + [Display(Name = "Email")] + public string Email { get; set; } + } + + public class ExternalLoginListViewModel + { + public string ReturnUrl { get; set; } + } + + public class SendCodeViewModel + { + public string SelectedProvider { get; set; } + public ICollection Providers { get; set; } + public string ReturnUrl { get; set; } + public bool RememberMe { get; set; } + } + + public class VerifyCodeViewModel + { + [Required] + public string Provider { get; set; } + + [Required] + [Display(Name = "Code")] + public string Code { get; set; } + public string ReturnUrl { get; set; } + + [Display(Name = "Remember this browser?")] + public bool RememberBrowser { get; set; } + + public bool RememberMe { get; set; } + } + + public class ForgotViewModel + { + [Required] + [Display(Name = "Email")] + public string Email { get; set; } + } + + public class LoginViewModel + { + [Required] + [Display(Name = "Email")] + [EmailAddress] + public string Email { get; set; } + + [Required] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } + + public class RegisterViewModel + { + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string Email { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } + + public class ResetPasswordViewModel + { + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string Email { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + + public string Code { get; set; } + } + + public class ForgotPasswordViewModel + { + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string Email { get; set; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Models/Album.cs b/samples/MusicStore/Models/Album.cs new file mode 100644 index 0000000..7121e1d --- /dev/null +++ b/samples/MusicStore/Models/Album.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace MusicStore.Models +{ + public class Album + { + [ScaffoldColumn(false)] + public int AlbumId { get; set; } + + public int GenreId { get; set; } + + public int ArtistId { get; set; } + + [Required] + [StringLength(160, MinimumLength = 2)] + public string Title { get; set; } + + [Required] + [Range(0.01, 100.00)] + + [DataType(DataType.Currency)] + [Column(TypeName = "decimal(18,2)")] + public decimal Price { get; set; } + + [Display(Name = "Album Art URL")] + [StringLength(1024)] + public string AlbumArtUrl { get; set; } + + public virtual Genre Genre { get; set; } + public virtual Artist Artist { get; set; } + public virtual List OrderDetails { get; set; } + + [ScaffoldColumn(false)] + [BindNever] + [Required] + public DateTime Created { get; set; } + + /// + /// TODO: Temporary hack to populate the orderdetails until EF does this automatically. + /// + public Album() + { + OrderDetails = new List(); + Created = DateTime.UtcNow; + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Models/Artist.cs b/samples/MusicStore/Models/Artist.cs new file mode 100644 index 0000000..43d677c --- /dev/null +++ b/samples/MusicStore/Models/Artist.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class Artist + { + public int ArtistId { get; set; } + + [Required] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Models/CartItem.cs b/samples/MusicStore/Models/CartItem.cs new file mode 100644 index 0000000..3311528 --- /dev/null +++ b/samples/MusicStore/Models/CartItem.cs @@ -0,0 +1,21 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class CartItem + { + [Key] + public int CartItemId { get; set; } + + [Required] + public string CartId { get; set; } + public int AlbumId { get; set; } + public int Count { get; set; } + + [DataType(DataType.DateTime)] + public DateTime DateCreated { get; set; } + + public virtual Album Album { get; set; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Models/Genre.cs b/samples/MusicStore/Models/Genre.cs new file mode 100644 index 0000000..29c9107 --- /dev/null +++ b/samples/MusicStore/Models/Genre.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace MusicStore.Models +{ + public class Genre + { + public int GenreId { get; set; } + + [Required] + public string Name { get; set; } + + public string Description { get; set; } + + public List Albums { get; set; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Models/ManageViewModels.cs b/samples/MusicStore/Models/ManageViewModels.cs new file mode 100644 index 0000000..4204972 --- /dev/null +++ b/samples/MusicStore/Models/ManageViewModels.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace MusicStore.Models +{ + public class IndexViewModel + { + public bool HasPassword { get; set; } + public IList Logins { get; set; } + public string PhoneNumber { get; set; } + public bool TwoFactor { get; set; } + public bool BrowserRemembered { get; set; } + } + + public class ManageLoginsViewModel + { + public IList CurrentLogins { get; set; } + public IList OtherLogins { get; set; } + } + + public class FactorViewModel + { + public string Purpose { get; set; } + } + + public class SetPasswordViewModel + { + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } + + public class ChangePasswordViewModel + { + [Required] + [DataType(DataType.Password)] + [Display(Name = "Current password")] + public string OldPassword { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } + + public class AddPhoneNumberViewModel + { + [Required] + [Phone] + [Display(Name = "Phone Number")] + public string Number { get; set; } + } + + public class VerifyPhoneNumberViewModel + { + [Required] + [Display(Name = "Code")] + public string Code { get; set; } + + [Required] + [Phone] + [Display(Name = "Phone Number")] + public string PhoneNumber { get; set; } + } + + public class ConfigureTwoFactorViewModel + { + public string SelectedProvider { get; set; } + public ICollection Providers { get; set; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Models/MusicStoreContext.cs b/samples/MusicStore/Models/MusicStoreContext.cs new file mode 100644 index 0000000..a638016 --- /dev/null +++ b/samples/MusicStore/Models/MusicStoreContext.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace MusicStore.Models +{ + public class ApplicationUser : IdentityUser { } + + public class MusicStoreContext : IdentityDbContext + { + public MusicStoreContext(DbContextOptions options) + : base(options) + { + // TODO: #639 + //ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + } + + public DbSet Albums { get; set; } + public DbSet Artists { get; set; } + public DbSet Orders { get; set; } + public DbSet Genres { get; set; } + public DbSet CartItems { get; set; } + public DbSet OrderDetails { get; set; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Models/Order.cs b/samples/MusicStore/Models/Order.cs new file mode 100644 index 0000000..3406b96 --- /dev/null +++ b/samples/MusicStore/Models/Order.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace MusicStore.Models +{ + //[Bind(Include = "FirstName,LastName,Address,City,State,PostalCode,Country,Phone,Email")] + public class Order + { + [BindNever] + [ScaffoldColumn(false)] + public int OrderId { get; set; } + + [BindNever] + [ScaffoldColumn(false)] + public System.DateTime OrderDate { get; set; } + + [BindNever] + [ScaffoldColumn(false)] + public string Username { get; set; } + + [Required] + [Display(Name = "First Name")] + [StringLength(160)] + public string FirstName { get; set; } + + [Required] + [Display(Name = "Last Name")] + [StringLength(160)] + public string LastName { get; set; } + + [Required] + [StringLength(70, MinimumLength = 3)] + public string Address { get; set; } + + [Required] + [StringLength(40)] + public string City { get; set; } + + [Required] + [StringLength(40)] + public string State { get; set; } + + [Required] + [Display(Name = "Postal Code")] + [StringLength(10, MinimumLength = 5)] + public string PostalCode { get; set; } + + [Required] + [StringLength(40)] + public string Country { get; set; } + + [Required] + [StringLength(24)] + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } + + [Required] + [Display(Name = "Email Address")] + [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", + ErrorMessage = "Email is not valid.")] + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [BindNever] + [ScaffoldColumn(false)] + [Column(TypeName = "decimal(18,2)")] + public decimal Total { get; set; } + + [BindNever] + public List OrderDetails { get; set; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Models/OrderDetail.cs b/samples/MusicStore/Models/OrderDetail.cs new file mode 100644 index 0000000..ee94681 --- /dev/null +++ b/samples/MusicStore/Models/OrderDetail.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace MusicStore.Models +{ + public class OrderDetail + { + public int OrderDetailId { get; set; } + + public int OrderId { get; set; } + + public int AlbumId { get; set; } + + public int Quantity { get; set; } + + [Column(TypeName = "decimal(18,2)")] + public decimal UnitPrice { get; set; } + + public virtual Album Album { get; set; } + + public virtual Order Order { get; set; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Models/SampleData.cs b/samples/MusicStore/Models/SampleData.cs new file mode 100644 index 0000000..959c267 --- /dev/null +++ b/samples/MusicStore/Models/SampleData.cs @@ -0,0 +1,965 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace MusicStore.Models +{ + public static class SampleData + { + const string imgUrl = "~/Images/placeholder.png"; + const string defaultAdminUserName = "DefaultAdminUserName"; + const string defaultAdminPassword = "DefaultAdminPassword"; + + public static async Task InitializeMusicStoreDatabaseAsync(IServiceProvider serviceProvider, bool createUsers = true) + { + using (var serviceScope = serviceProvider.CreateScope()) + { + var scopeServiceProvider = serviceScope.ServiceProvider; + var db = scopeServiceProvider.GetService(); + + if (await db.Database.EnsureCreatedAsync()) + { + await InsertTestData(scopeServiceProvider); + if (createUsers) + { + await CreateAdminUser(scopeServiceProvider); + } + } + } + } + + private static async Task InsertTestData(IServiceProvider serviceProvider) + { + var albums = GetAlbums(imgUrl, Genres, Artists); + + await AddOrUpdateAsync(serviceProvider, g => g.GenreId, Genres.Select(genre => genre.Value)); + await AddOrUpdateAsync(serviceProvider, a => a.ArtistId, Artists.Select(artist => artist.Value)); + await AddOrUpdateAsync(serviceProvider, a => a.AlbumId, albums); + } + + // TODO [EF] This may be replaced by a first class mechanism in EF + private static async Task AddOrUpdateAsync( + IServiceProvider serviceProvider, + Func propertyToMatch, IEnumerable entities) + where TEntity : class + { + // Query in a separate context so that we can attach existing entities as modified + List existingData; + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var db = serviceScope.ServiceProvider.GetService(); + existingData = db.Set().ToList(); + } + + using (var serviceScope = serviceProvider.GetRequiredService().CreateScope()) + { + var db = serviceScope.ServiceProvider.GetService(); + foreach (var item in entities) + { + db.Entry(item).State = existingData.Any(g => propertyToMatch(g).Equals(propertyToMatch(item))) + ? EntityState.Modified + : EntityState.Added; + } + + await db.SaveChangesAsync(); + } + } + + /// + /// Creates a store manager user who can manage the inventory. + /// + /// + /// + private static async Task CreateAdminUser(IServiceProvider serviceProvider) + { + var env = serviceProvider.GetService(); + + var builder = new ConfigurationBuilder() + .SetBasePath(env.ContentRootPath) + .AddJsonFile("config.json") + .AddEnvironmentVariables(); + var configuration = builder.Build(); + + //const string adminRole = "Administrator"; + + var userManager = serviceProvider.GetService>(); + // TODO: Identity SQL does not support roles yet + //var roleManager = serviceProvider.GetService(); + //if (!await roleManager.RoleExistsAsync(adminRole)) + //{ + // await roleManager.CreateAsync(new IdentityRole(adminRole)); + //} + + var user = await userManager.FindByNameAsync(configuration[defaultAdminUserName]); + if (user == null) + { + user = new ApplicationUser { UserName = configuration[defaultAdminUserName] }; + await userManager.CreateAsync(user, configuration[defaultAdminPassword]); + //await userManager.AddToRoleAsync(user, adminRole); + await userManager.AddClaimAsync(user, new Claim("ManageStore", "Allowed")); + } + + // NOTE: For end to end testing only + var envPerfLab = configuration["PERF_LAB"]; + if (envPerfLab == "true") + { + for (int i = 0; i < 100; ++i) + { + var email = string.Format("User{0:D3}@example.com", i); + var normalUser = await userManager.FindByEmailAsync(email); + if (normalUser == null) + { + await userManager.CreateAsync(new ApplicationUser { UserName = email, Email = email }, "Password~!1"); + } + } + } + } + + private static Album[] GetAlbums(string imgUrl, Dictionary genres, Dictionary artists) + { + var albums = new Album[] + { + new Album { Title = "The Best Of The Men At Work", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Men At Work"], AlbumArtUrl = imgUrl }, + new Album { Title = "...And Justice For All", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "עד גבול האור", Genre = genres["World"], Price = 8.99M, Artist = artists["אריק אינשטיין"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Light Syndrome", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Terry Bozzio, Tony Levin & Steve Stevens"], AlbumArtUrl = imgUrl }, + new Album { Title = "10,000 Days", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "11i", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Supreme Beings of Leisure"], AlbumArtUrl = imgUrl }, + new Album { Title = "1960", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Soul-Junk"], AlbumArtUrl = imgUrl }, + new Album { Title = "4x4=12 ", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["deadmau5"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Copland Celebration, Vol. I", Genre = genres["Classical"], Price = 8.99M, Artist = artists["London Symphony Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Lively Mind", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Paul Oakenfold"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Matter of Life and Death", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Real Dead One", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Real Live One", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Rush of Blood to the Head", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Coldplay"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Soprano Inspired", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Britten Sinfonia, Ivor Bolton & Lesley Garrett"], AlbumArtUrl = imgUrl }, + new Album { Title = "A Winter Symphony", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Abbey Road", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Beatles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ace Of Spades", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Motörhead"], AlbumArtUrl = imgUrl }, + new Album { Title = "Achtung Baby", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Acústico MTV", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Os Paralamas Do Sucesso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Adams, John: The Chairman Dances", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Edo de Waart & San Francisco Symphony"], AlbumArtUrl = imgUrl }, + new Album { Title = "Adrenaline", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deftones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ænima", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "Afrociberdelia", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Chico Science & Nação Zumbi"], AlbumArtUrl = imgUrl }, + new Album { Title = "After the Goldrush", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Neil Young"], AlbumArtUrl = imgUrl }, + new Album { Title = "Airdrawn Dagger", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Sasha"], AlbumArtUrl = imgUrl }, + new Album { Title = "Album Title Goes Here", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["deadmau5"], AlbumArtUrl = imgUrl }, + new Album { Title = "Alcohol Fueled Brewtality Live! [Disc 1]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Label Society"], AlbumArtUrl = imgUrl }, + new Album { Title = "Alcohol Fueled Brewtality Live! [Disc 2]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Label Society"], AlbumArtUrl = imgUrl }, + new Album { Title = "Alive 2007", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Daft Punk"], AlbumArtUrl = imgUrl }, + new Album { Title = "All I Ask of You", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Amen (So Be It)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Paddy Casey"], AlbumArtUrl = imgUrl }, + new Album { Title = "Animal Vehicle", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Axis of Awesome"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ao Vivo [IMPORT]", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Zeca Pagodinho"], AlbumArtUrl = imgUrl }, + new Album { Title = "Apocalyptic Love", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Slash"], AlbumArtUrl = imgUrl }, + new Album { Title = "Appetite for Destruction", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Are You Experienced?", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Jimi Hendrix"], AlbumArtUrl = imgUrl }, + new Album { Title = "Arquivo II", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Os Paralamas Do Sucesso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Arquivo Os Paralamas Do Sucesso", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Os Paralamas Do Sucesso"], AlbumArtUrl = imgUrl }, + new Album { Title = "A-Sides", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Soundgarden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Audioslave", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Audioslave"], AlbumArtUrl = imgUrl }, + new Album { Title = "Automatic for the People", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["R.E.M."], AlbumArtUrl = imgUrl }, + new Album { Title = "Axé Bahia 2001", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Various Artists"], AlbumArtUrl = imgUrl }, + new Album { Title = "Babel", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Mumford & Sons"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: Goldberg Variations", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Wilhelm Kempff"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: The Brandenburg Concertos", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Orchestra of The Age of Enlightenment"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: The Cello Suites", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Yo-Yo Ma"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bach: Toccata & Fugue in D Minor", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Ton Koopman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bad Motorfinger", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Soundgarden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Balls to the Wall", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Accept"], AlbumArtUrl = imgUrl }, + new Album { Title = "Banadeek Ta'ala", Genre = genres["World"], Price = 8.99M, Artist = artists["Amr Diab"], AlbumArtUrl = imgUrl }, + new Album { Title = "Barbie Girl", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Aqua"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bark at the Moon (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bartok: Violin & Viola Concertos", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Yehudi Menuhin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Barulhinho Bom", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Marisa Monte"], AlbumArtUrl = imgUrl }, + new Album { Title = "BBC Sessions [Disc 1] [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "BBC Sessions [Disc 2] [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Be Here Now", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Oasis"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bedrock 11 Compiled & Mixed", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["John Digweed"], AlbumArtUrl = imgUrl }, + new Album { Title = "Berlioz: Symphonie Fantastique", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Michael Tilson Thomas"], AlbumArtUrl = imgUrl }, + new Album { Title = "Beyond Good And Evil", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Cult"], AlbumArtUrl = imgUrl }, + new Album { Title = "Big Bad Wolf ", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Armand Van Helden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Big Ones", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Aerosmith"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Album", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Sabbath Vol. 4 (Remaster)", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Sabbath"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black Sabbath", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Black Sabbath"], AlbumArtUrl = imgUrl }, + new Album { Title = "Black", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blackwater Park", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Opeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blizzard of Ozz", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blood", Genre = genres["Rock"], Price = 8.99M, Artist = artists["In This Moment"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blue Moods", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Incognito"], AlbumArtUrl = imgUrl }, + new Album { Title = "Blue", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Weezer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bongo Fury", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Frank Zappa & Captain Beefheart"], AlbumArtUrl = imgUrl }, + new Album { Title = "Boys & Girls", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alabama Shakes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Brave New World", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "B-Sides 1980-1990", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Bunkka", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Paul Oakenfold"], AlbumArtUrl = imgUrl }, + new Album { Title = "By The Way", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Red Hot Chili Peppers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cake: B-Sides and Rarities", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Cake"], AlbumArtUrl = imgUrl }, + new Album { Title = "Californication", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Red Hot Chili Peppers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Carmina Burana", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Boston Symphony Orchestra & Seiji Ozawa"], AlbumArtUrl = imgUrl }, + new Album { Title = "Carried to Dust (Bonus Track Version)", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Calexico"], AlbumArtUrl = imgUrl }, + new Album { Title = "Carry On", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Chris Cornell"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cássia Eller - Sem Limite [Disc 1]", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Cássia Eller"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chemical Wedding", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Bruce Dickinson"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chill: Brazil (Disc 1)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Marcos Valle"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chill: Brazil (Disc 2)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Antônio Carlos Jobim"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chocolate Starfish And The Hot Dog Flavored Water", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Limp Bizkit"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chronicle, Vol. 1", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Creedence Clearwater Revival"], AlbumArtUrl = imgUrl }, + new Album { Title = "Chronicle, Vol. 2", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Creedence Clearwater Revival"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ciao, Baby", Genre = genres["Rock"], Price = 8.99M, Artist = artists["TheStart"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cidade Negra - Hits", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Cidade Negra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Classic Munkle: Turbo Edition", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Munkle"], AlbumArtUrl = imgUrl }, + new Album { Title = "Classics: The Best of Sarah Brightman", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Coda", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Come Away With Me", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Norah Jones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Come Taste The Band", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Comfort Eagle", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Cake"], AlbumArtUrl = imgUrl }, + new Album { Title = "Common Reaction", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Uh Huh Her "], AlbumArtUrl = imgUrl }, + new Album { Title = "Compositores", Genre = genres["Rock"], Price = 8.99M, Artist = artists["O Terço"], AlbumArtUrl = imgUrl }, + new Album { Title = "Contraband", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Velvet Revolver"], AlbumArtUrl = imgUrl }, + new Album { Title = "Core", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Stone Temple Pilots"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cornerstone", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Styx"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cosmicolor", Genre = genres["Rap"], Price = 8.99M, Artist = artists["M-Flo"], AlbumArtUrl = imgUrl }, + new Album { Title = "Cross", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Justice"], AlbumArtUrl = imgUrl }, + new Album { Title = "Culture of Fear", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Thievery Corporation"], AlbumArtUrl = imgUrl }, + new Album { Title = "Da Lama Ao Caos", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Chico Science & Nação Zumbi"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dakshina", Genre = genres["World"], Price = 8.99M, Artist = artists["Deva Premal"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dark Side of the Moon", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "Death Magnetic", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deep End of Down", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Above the Fold"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deep Purple In Rock", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deixa Entrar", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Falamansa"], AlbumArtUrl = imgUrl }, + new Album { Title = "Deja Vu", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Crosby, Stills, Nash, and Young"], AlbumArtUrl = imgUrl }, + new Album { Title = "Di Korpu Ku Alma", Genre = genres["World"], Price = 8.99M, Artist = artists["Lura"], AlbumArtUrl = imgUrl }, + new Album { Title = "Diary of a Madman (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Diary of a Madman", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dirt", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alice in Chains"], AlbumArtUrl = imgUrl }, + new Album { Title = "Diver Down", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Djavan Ao Vivo - Vol. 02", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Djavan"], AlbumArtUrl = imgUrl }, + new Album { Title = "Djavan Ao Vivo - Vol. 1", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Djavan"], AlbumArtUrl = imgUrl }, + new Album { Title = "Drum'n'bass for Papa", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Plug"], AlbumArtUrl = imgUrl }, + new Album { Title = "Duluth", Genre = genres["Country"], Price = 8.99M, Artist = artists["Trampled By Turtles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Dummy", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Portishead"], AlbumArtUrl = imgUrl }, + new Album { Title = "Duos II", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Luciana Souza/Romero Lubambo"], AlbumArtUrl = imgUrl }, + new Album { Title = "Earl Scruggs and Friends", Genre = genres["Country"], Price = 8.99M, Artist = artists["Earl Scruggs"], AlbumArtUrl = imgUrl }, + new Album { Title = "Eden", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "El Camino", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Black Keys"], AlbumArtUrl = imgUrl }, + new Album { Title = "Elegant Gypsy", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Al di Meola"], AlbumArtUrl = imgUrl }, + new Album { Title = "Elements Of Life", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Tiësto"], AlbumArtUrl = imgUrl }, + new Album { Title = "Elis Regina-Minha História", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Elis Regina"], AlbumArtUrl = imgUrl }, + new Album { Title = "Emergency On Planet Earth", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Jamiroquai"], AlbumArtUrl = imgUrl }, + new Album { Title = "Emotion", Genre = genres["World"], Price = 8.99M, Artist = artists["Papa Wemba"], AlbumArtUrl = imgUrl }, + new Album { Title = "English Renaissance", Genre = genres["Classical"], Price = 8.99M, Artist = artists["The King's Singers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Every Kind of Light", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Posies"], AlbumArtUrl = imgUrl }, + new Album { Title = "Faceless", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Godsmack"], AlbumArtUrl = imgUrl }, + new Album { Title = "Facelift", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alice in Chains"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fair Warning", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fear of a Black Planet", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Public Enemy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fear Of The Dark", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Feels Like Home", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Norah Jones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fireball", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Fly", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "For Those About To Rock We Salute You", Genre = genres["Rock"], Price = 8.99M, Artist = artists["AC/DC"], AlbumArtUrl = imgUrl }, + new Album { Title = "Four", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Blues Traveler"], AlbumArtUrl = imgUrl }, + new Album { Title = "Frank", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Amy Winehouse"], AlbumArtUrl = imgUrl }, + new Album { Title = "Further Down the Spiral", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Nine Inch Nails"], AlbumArtUrl = imgUrl }, + new Album { Title = "Garage Inc. (Disc 1)", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Garage Inc. (Disc 2)", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Garbage", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Garbage"], AlbumArtUrl = imgUrl }, + new Album { Title = "Good News For People Who Love Bad News", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Modest Mouse"], AlbumArtUrl = imgUrl }, + new Album { Title = "Gordon", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Barenaked Ladies"], AlbumArtUrl = imgUrl }, + new Album { Title = "Górecki: Symphony No. 3", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Adrian Leaper & Doreen de Feis"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits II", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Duck Sauce"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Lenny Kravitz"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Hits", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Lenny Kravitz"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greatest Kiss", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Kiss"], AlbumArtUrl = imgUrl }, + new Album { Title = "Greetings from Michigan", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Sufjan Stevens"], AlbumArtUrl = imgUrl }, + new Album { Title = "Group Therapy", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Above & Beyond"], AlbumArtUrl = imgUrl }, + new Album { Title = "Handel: The Messiah (Highlights)", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Scholars Baroque Ensemble"], AlbumArtUrl = imgUrl }, + new Album { Title = "Haydn: Symphonies 99 - 104", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Royal Philharmonic Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Heart of the Night", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Spyro Gyra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Heart On", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Eagles of Death Metal"], AlbumArtUrl = imgUrl }, + new Album { Title = "Holy Diver", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Dio"], AlbumArtUrl = imgUrl }, + new Album { Title = "Homework", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Daft Punk"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hot Rocks, 1964-1971 (Disc 1)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Rolling Stones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Houses Of The Holy", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "How To Dismantle An Atomic Bomb", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Human", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Projected"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hunky Dory", Genre = genres["Rock"], Price = 8.99M, Artist = artists["David Bowie"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hymns", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Projected"], AlbumArtUrl = imgUrl }, + new Album { Title = "Hysteria", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Def Leppard"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Absentia", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Porcupine Tree"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Between", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Paul Van Dyk"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Rainbows", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Radiohead"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Step", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Stevie Ray Vaughan & Double Trouble"], AlbumArtUrl = imgUrl }, + new Album { Title = "In the court of the Crimson King", Genre = genres["Rock"], Price = 8.99M, Artist = artists["King Crimson"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Through The Out Door", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Your Honor [Disc 1]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Foo Fighters"], AlbumArtUrl = imgUrl }, + new Album { Title = "In Your Honor [Disc 2]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Foo Fighters"], AlbumArtUrl = imgUrl }, + new Album { Title = "Indestructible", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Rancid"], AlbumArtUrl = imgUrl }, + new Album { Title = "Infinity", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Journey"], AlbumArtUrl = imgUrl }, + new Album { Title = "Into The Light", Genre = genres["Rock"], Price = 8.99M, Artist = artists["David Coverdale"], AlbumArtUrl = imgUrl }, + new Album { Title = "Introspective", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Pet Shop Boys"], AlbumArtUrl = imgUrl }, + new Album { Title = "Iron Maiden", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "ISAM", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Amon Tobin"], AlbumArtUrl = imgUrl }, + new Album { Title = "IV", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jagged Little Pill", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Alanis Morissette"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jagged Little Pill", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Alanis Morissette"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jorge Ben Jor 25 Anos", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Jorge Ben"], AlbumArtUrl = imgUrl }, + new Album { Title = "Jota Quest-1995", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Jota Quest"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kick", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["INXS"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kill 'Em All", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kind of Blue", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "King For A Day Fool For A Lifetime", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Faith No More"], AlbumArtUrl = imgUrl }, + new Album { Title = "Kiss", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Carly Rae Jepsen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Last Call", Genre = genres["Country"], Price = 8.99M, Artist = artists["Cayouche"], AlbumArtUrl = imgUrl }, + new Album { Title = "Le Freak", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Chic"], AlbumArtUrl = imgUrl }, + new Album { Title = "Le Tigre", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Le Tigre"], AlbumArtUrl = imgUrl }, + new Album { Title = "Led Zeppelin I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Led Zeppelin II", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Led Zeppelin III", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Let There Be Rock", Genre = genres["Rock"], Price = 8.99M, Artist = artists["AC/DC"], AlbumArtUrl = imgUrl }, + new Album { Title = "Little Earthquakes", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Tori Amos"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live [Disc 1]", Genre = genres["Blues"], Price = 8.99M, Artist = artists["The Black Crowes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live [Disc 2]", Genre = genres["Blues"], Price = 8.99M, Artist = artists["The Black Crowes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live After Death", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live At Donington 1992 (Disc 1)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live At Donington 1992 (Disc 2)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live on Earth", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["The Cat Empire"], AlbumArtUrl = imgUrl }, + new Album { Title = "Live On Two Legs [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Living After Midnight", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Judas Priest"], AlbumArtUrl = imgUrl }, + new Album { Title = "Living", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Paddy Casey"], AlbumArtUrl = imgUrl }, + new Album { Title = "Load", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Love Changes Everything", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "MacArthur Park Suite", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Donna Summer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Machine Head", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Magical Mystery Tour", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Beatles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mais Do Mesmo", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Legião Urbana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Maquinarama", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Skank"], AlbumArtUrl = imgUrl }, + new Album { Title = "Marasim", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Jagjit Singh"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mascagni: Cavalleria Rusticana", Genre = genres["Classical"], Price = 8.99M, Artist = artists["James Levine"], AlbumArtUrl = imgUrl }, + new Album { Title = "Master of Puppets", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mechanics & Mathematics", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Venus Hum"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mental Jewelry", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Live"], AlbumArtUrl = imgUrl }, + new Album { Title = "Metallics", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "meteora", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Linkin Park"], AlbumArtUrl = imgUrl }, + new Album { Title = "Meus Momentos", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Gonzaguinha"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mezmerize", Genre = genres["Metal"], Price = 8.99M, Artist = artists["System Of A Down"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mezzanine", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Massive Attack"], AlbumArtUrl = imgUrl }, + new Album { Title = "Miles Ahead", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "Milton Nascimento Ao Vivo", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Milton Nascimento"], AlbumArtUrl = imgUrl }, + new Album { Title = "Minas", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Milton Nascimento"], AlbumArtUrl = imgUrl }, + new Album { Title = "Minha Historia", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Chico Buarque"], AlbumArtUrl = imgUrl }, + new Album { Title = "Misplaced Childhood", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Marillion"], AlbumArtUrl = imgUrl }, + new Album { Title = "MK III The Final Concerts [Disc 1]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Morning Dance", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Spyro Gyra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Motley Crue Greatest Hits", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Mötley Crüe"], AlbumArtUrl = imgUrl }, + new Album { Title = "Moving Pictures", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Rush"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mozart: Chamber Music", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Nash Ensemble"], AlbumArtUrl = imgUrl }, + new Album { Title = "Mozart: Symphonies Nos. 40 & 41", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Berliner Philharmoniker"], AlbumArtUrl = imgUrl }, + new Album { Title = "Murder Ballads", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Nick Cave and the Bad Seeds"], AlbumArtUrl = imgUrl }, + new Album { Title = "Music For The Jilted Generation", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["The Prodigy"], AlbumArtUrl = imgUrl }, + new Album { Title = "My Generation - The Very Best Of The Who", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Who"], AlbumArtUrl = imgUrl }, + new Album { Title = "My Name is Skrillex", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Skrillex"], AlbumArtUrl = imgUrl }, + new Album { Title = "Na Pista", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Cláudio Zoli"], AlbumArtUrl = imgUrl }, + new Album { Title = "Nevermind", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Nirvana"], AlbumArtUrl = imgUrl }, + new Album { Title = "New Adventures In Hi-Fi", Genre = genres["Rock"], Price = 8.99M, Artist = artists["R.E.M."], AlbumArtUrl = imgUrl }, + new Album { Title = "New Divide", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Linkin Park"], AlbumArtUrl = imgUrl }, + new Album { Title = "New York Dolls", Genre = genres["Punk"], Price = 8.99M, Artist = artists["New York Dolls"], AlbumArtUrl = imgUrl }, + new Album { Title = "News Of The World", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Nielsen: The Six Symphonies", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Göteborgs Symfoniker & Neeme Järvi"], AlbumArtUrl = imgUrl }, + new Album { Title = "Night At The Opera", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Queen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Night Castle", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Trans-Siberian Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Nkolo", Genre = genres["World"], Price = 8.99M, Artist = artists["Lokua Kanza"], AlbumArtUrl = imgUrl }, + new Album { Title = "No More Tears (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "No Prayer For The Dying", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "No Security", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Rolling Stones"], AlbumArtUrl = imgUrl }, + new Album { Title = "O Brother, Where Art Thou?", Genre = genres["Country"], Price = 8.99M, Artist = artists["Alison Krauss"], AlbumArtUrl = imgUrl }, + new Album { Title = "O Samba Poconé", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Skank"], AlbumArtUrl = imgUrl }, + new Album { Title = "O(+>", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Prince"], AlbumArtUrl = imgUrl }, + new Album { Title = "Oceania", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Smashing Pumpkins"], AlbumArtUrl = imgUrl }, + new Album { Title = "Off the Deep End", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Weird Al"], AlbumArtUrl = imgUrl }, + new Album { Title = "OK Computer", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Radiohead"], AlbumArtUrl = imgUrl }, + new Album { Title = "Olodum", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Olodum"], AlbumArtUrl = imgUrl }, + new Album { Title = "One Love", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["David Guetta"], AlbumArtUrl = imgUrl }, + new Album { Title = "Operation: Mindcrime", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Queensrÿche"], AlbumArtUrl = imgUrl }, + new Album { Title = "Opiate", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "Outbreak", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Dennis Chambers"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pachelbel: Canon & Gigue", Genre = genres["Classical"], Price = 8.99M, Artist = artists["English Concert & Trevor Pinnock"], AlbumArtUrl = imgUrl }, + new Album { Title = "Paid in Full", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Eric B. and Rakim"], AlbumArtUrl = imgUrl }, + new Album { Title = "Para Siempre", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Vicente Fernandez"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pause", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Four Tet"], AlbumArtUrl = imgUrl }, + new Album { Title = "Peace Sells... but Who's Buying", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Megadeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Physical Graffiti [Disc 1]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Physical Graffiti [Disc 2]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Physical Graffiti", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Piece Of Mind", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pinkerton", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Weezer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Plays Metallica By Four Cellos", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Apocalyptica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pop", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Powerslave", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prenda Minha", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Caetano Veloso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Presence", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Pretty Hate Machine", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Nine Inch Nails"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prisoner", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Jezabels"], AlbumArtUrl = imgUrl }, + new Album { Title = "Privateering", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Mark Knopfler"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prokofiev: Romeo & Juliet", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Michael Tilson Thomas"], AlbumArtUrl = imgUrl }, + new Album { Title = "Prokofiev: Symphony No.1", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sergei Prokofiev & Yuri Temirkanov"], AlbumArtUrl = imgUrl }, + new Album { Title = "PSY's Best 6th Part 1", Genre = genres["Pop"], Price = 8.99M, Artist = artists["PSY"], AlbumArtUrl = imgUrl }, + new Album { Title = "Purcell: The Fairy Queen", Genre = genres["Classical"], Price = 8.99M, Artist = artists["London Classical Players"], AlbumArtUrl = imgUrl }, + new Album { Title = "Purpendicular", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Purple", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Stone Temple Pilots"], AlbumArtUrl = imgUrl }, + new Album { Title = "Quanta Gente Veio Ver (Live)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Gilberto Gil"], AlbumArtUrl = imgUrl }, + new Album { Title = "Quanta Gente Veio ver--Bônus De Carnaval", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Gilberto Gil"], AlbumArtUrl = imgUrl }, + new Album { Title = "Quiet Songs", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Aisha Duo"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raices", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Los Tigres del Norte"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raising Hell", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Run DMC"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raoul and the Kings of Spain ", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tears For Fears"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rattle And Hum", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Raul Seixas", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Raul Seixas"], AlbumArtUrl = imgUrl }, + new Album { Title = "Recovery [Explicit]", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Eminem"], AlbumArtUrl = imgUrl }, + new Album { Title = "Reign In Blood", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Slayer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Relayed", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Yes"], AlbumArtUrl = imgUrl }, + new Album { Title = "ReLoad", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Respighi:Pines of Rome", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Eugene Ormandy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Restless and Wild", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Accept"], AlbumArtUrl = imgUrl }, + new Album { Title = "Retrospective I (1974-1980)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Rush"], AlbumArtUrl = imgUrl }, + new Album { Title = "Revelations", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Audioslave"], AlbumArtUrl = imgUrl }, + new Album { Title = "Revolver", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Beatles"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ride the Lighting ", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ride The Lightning", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ring My Bell", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Anita Ward"], AlbumArtUrl = imgUrl }, + new Album { Title = "Riot Act", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rise of the Phoenix", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Before the Dawn"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rock In Rio [CD1]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rock In Rio [CD2]", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rock In Rio [CD2]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Roda De Funk", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Funk Como Le Gusta"], AlbumArtUrl = imgUrl }, + new Album { Title = "Room for Squares", Genre = genres["Pop"], Price = 8.99M, Artist = artists["John Mayer"], AlbumArtUrl = imgUrl }, + new Album { Title = "Root Down", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Jimmy Smith"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rounds", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Four Tet"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rubber Factory", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Black Keys"], AlbumArtUrl = imgUrl }, + new Album { Title = "Rust in Peace", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Megadeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sambas De Enredo 2001", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Various Artists"], AlbumArtUrl = imgUrl }, + new Album { Title = "Santana - As Years Go By", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Santana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Santana Live", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Santana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Saturday Night Fever", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Bee Gees"], AlbumArtUrl = imgUrl }, + new Album { Title = "Scary Monsters and Nice Sprites", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Skrillex"], AlbumArtUrl = imgUrl }, + new Album { Title = "Scheherazade", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Chicago Symphony Orchestra & Fritz Reiner"], AlbumArtUrl = imgUrl }, + new Album { Title = "SCRIABIN: Vers la flamme", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Christopher O'Riley"], AlbumArtUrl = imgUrl }, + new Album { Title = "Second Coming", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Stone Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Serie Sem Limite (Disc 1)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Tim Maia"], AlbumArtUrl = imgUrl }, + new Album { Title = "Serie Sem Limite (Disc 2)", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Tim Maia"], AlbumArtUrl = imgUrl }, + new Album { Title = "Serious About Men", Genre = genres["Rap"], Price = 8.99M, Artist = artists["The Rubberbandits"], AlbumArtUrl = imgUrl }, + new Album { Title = "Seventh Son of a Seventh Son", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Short Bus", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Filter"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sibelius: Finlandia", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Berliner Philharmoniker"], AlbumArtUrl = imgUrl }, + new Album { Title = "Singles Collection", Genre = genres["Rock"], Price = 8.99M, Artist = artists["David Bowie"], AlbumArtUrl = imgUrl }, + new Album { Title = "Six Degrees of Inner Turbulence", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Dream Theater"], AlbumArtUrl = imgUrl }, + new Album { Title = "Slave To The Empire", Genre = genres["Metal"], Price = 8.99M, Artist = artists["T&N"], AlbumArtUrl = imgUrl }, + new Album { Title = "Slaves And Masters", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Slouching Towards Bethlehem", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Robert James"], AlbumArtUrl = imgUrl }, + new Album { Title = "Smash", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Offspring"], AlbumArtUrl = imgUrl }, + new Album { Title = "Something Special", Genre = genres["Country"], Price = 8.99M, Artist = artists["Dolly Parton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Somewhere in Time", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Song(s) You Know By Heart", Genre = genres["Country"], Price = 8.99M, Artist = artists["Jimmy Buffett"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sound of Music", Genre = genres["Punk"], Price = 8.99M, Artist = artists["Adicts"], AlbumArtUrl = imgUrl }, + new Album { Title = "South American Getaway", Genre = genres["Classical"], Price = 8.99M, Artist = artists["The 12 Cellists of The Berlin Philharmonic"], AlbumArtUrl = imgUrl }, + new Album { Title = "Sozinho Remix Ao Vivo", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Caetano Veloso"], AlbumArtUrl = imgUrl }, + new Album { Title = "Speak of the Devil", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Spiritual State", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Nujabes"], AlbumArtUrl = imgUrl }, + new Album { Title = "St. Anger", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Metallica"], AlbumArtUrl = imgUrl }, + new Album { Title = "Still Life", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Opeth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Stop Making Sense", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Talking Heads"], AlbumArtUrl = imgUrl }, + new Album { Title = "Stormbringer", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "Stranger than Fiction", Genre = genres["Punk"], Price = 8.99M, Artist = artists["Bad Religion"], AlbumArtUrl = imgUrl }, + new Album { Title = "Strauss: Waltzes", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Eugene Ormandy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Supermodified", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Amon Tobin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Supernatural", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Santana"], AlbumArtUrl = imgUrl }, + new Album { Title = "Surfing with the Alien (Remastered)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Joe Satriani"], AlbumArtUrl = imgUrl }, + new Album { Title = "Switched-On Bach", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Wendy Carlos"], AlbumArtUrl = imgUrl }, + new Album { Title = "Symphony", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Szymanowski: Piano Works, Vol. 1", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Martin Roscoe"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tchaikovsky: The Nutcracker", Genre = genres["Classical"], Price = 8.99M, Artist = artists["London Symphony Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ted Nugent", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Ted Nugent"], AlbumArtUrl = imgUrl }, + new Album { Title = "Teflon Don", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Rick Ross"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tell Another Joke at the Ol' Choppin' Block", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Danielson Famile"], AlbumArtUrl = imgUrl }, + new Album { Title = "Temple of the Dog", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Temple of the Dog"], AlbumArtUrl = imgUrl }, + new Album { Title = "Ten", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Texas Flood", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Stevie Ray Vaughan"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Battle Rages On", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Beast Live", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Paul D'Ianno"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best Of 1980-1990", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best of 1990–2000", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best of Beethoven", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Nicolaus Esterhazy Sinfonia"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best Of Billy Cobham", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Billy Cobham"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best of Ed Motta", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Ed Motta"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Best Of Van Halen, Vol. I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Bridge", Genre = genres["R&B"], Price = 8.99M, Artist = artists["Melanie Fiona"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Cage", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tygers of Pan Tang"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Chicago Transit Authority", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Chicago "], AlbumArtUrl = imgUrl }, + new Album { Title = "The Chronic", Genre = genres["Rap"], Price = 8.99M, Artist = artists["Dr. Dre"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Colour And The Shape", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Foo Fighters"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Crane Wife", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["The Decemberists"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Cream Of Clapton", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Eric Clapton"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Cure", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Cure"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Dark Side Of The Moon", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Divine Conspiracy", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Epica"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Doors", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Doors"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Dream of the Blue Turtles", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Sting"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Essential Miles Davis [Disc 1]", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Essential Miles Davis [Disc 2]", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Miles Davis"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Final Concerts (Disc 2)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deep Purple"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Final Frontier", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Head and the Heart", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Head and the Heart"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Joshua Tree", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Last Night of the Proms", Genre = genres["Classical"], Price = 8.99M, Artist = artists["BBC Concert Orchestra"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Lumineers", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Lumineers"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Number of The Beast", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Number of The Beast", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Police Greatest Hits", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Police"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Song Remains The Same (Disc 1)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Song Remains The Same (Disc 2)", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Southern Harmony and Musical Companion", Genre = genres["Blues"], Price = 8.99M, Artist = artists["The Black Crowes"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Spade", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Butch Walker & The Black Widows"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Stone Roses", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Stone Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Suburbs", Genre = genres["Indie"], Price = 8.99M, Artist = artists["Arcade Fire"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Three Tenors Disc1/Disc2", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Carreras, Pavarotti, Domingo"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Trees They Grow So High", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "The Wall", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "The X Factor", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Them Crooked Vultures", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Them Crooked Vultures"], AlbumArtUrl = imgUrl }, + new Album { Title = "This Is Happening", Genre = genres["Rock"], Price = 8.99M, Artist = artists["LCD Soundsystem"], AlbumArtUrl = imgUrl }, + new Album { Title = "Thunder, Lightning, Strike", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Go! Team"], AlbumArtUrl = imgUrl }, + new Album { Title = "Time to Say Goodbye", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sarah Brightman"], AlbumArtUrl = imgUrl }, + new Album { Title = "Time, Love & Tenderness", Genre = genres["Pop"], Price = 8.99M, Artist = artists["Michael Bolton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tomorrow Starts Today", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Mobile"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tribute", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Ozzy Osbourne"], AlbumArtUrl = imgUrl }, + new Album { Title = "Tuesday Night Music Club", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Sheryl Crow"], AlbumArtUrl = imgUrl }, + new Album { Title = "Umoja", Genre = genres["Rock"], Price = 8.99M, Artist = artists["BLØF"], AlbumArtUrl = imgUrl }, + new Album { Title = "Under the Pink", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Tori Amos"], AlbumArtUrl = imgUrl }, + new Album { Title = "Undertow", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Tool"], AlbumArtUrl = imgUrl }, + new Album { Title = "Un-Led-Ed", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Dread Zeppelin"], AlbumArtUrl = imgUrl }, + new Album { Title = "Unplugged [Live]", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Kiss"], AlbumArtUrl = imgUrl }, + new Album { Title = "Unplugged", Genre = genres["Blues"], Price = 8.99M, Artist = artists["Eric Clapton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Unplugged", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Eric Clapton"], AlbumArtUrl = imgUrl }, + new Album { Title = "Untrue", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Burial"], AlbumArtUrl = imgUrl }, + new Album { Title = "Use Your Illusion I", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Use Your Illusion II", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Use Your Illusion II", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Guns N' Roses"], AlbumArtUrl = imgUrl }, + new Album { Title = "Van Halen III", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Van Halen", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Van Halen"], AlbumArtUrl = imgUrl }, + new Album { Title = "Version 2.0", Genre = genres["Alternative"], Price = 8.99M, Artist = artists["Garbage"], AlbumArtUrl = imgUrl }, + new Album { Title = "Vinicius De Moraes", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Vinícius De Moraes"], AlbumArtUrl = imgUrl }, + new Album { Title = "Virtual XI", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Iron Maiden"], AlbumArtUrl = imgUrl }, + new Album { Title = "Voodoo Lounge", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Rolling Stones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Vozes do MPB", Genre = genres["Latin"], Price = 8.99M, Artist = artists["Various Artists"], AlbumArtUrl = imgUrl }, + new Album { Title = "Vs.", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pearl Jam"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wagner: Favourite Overtures", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Sir Georg Solti & Wiener Philharmoniker"], AlbumArtUrl = imgUrl }, + new Album { Title = "Walking Into Clarksdale", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Page & Plant"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wapi Yo", Genre = genres["World"], Price = 8.99M, Artist = artists["Lokua Kanza"], AlbumArtUrl = imgUrl }, + new Album { Title = "War", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Warner 25 Anos", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Antônio Carlos Jobim"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wasteland R&Btheque", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Raunchy"], AlbumArtUrl = imgUrl }, + new Album { Title = "Watermark", Genre = genres["Electronic"], Price = 8.99M, Artist = artists["Enya"], AlbumArtUrl = imgUrl }, + new Album { Title = "We Were Exploding Anyway", Genre = genres["Rock"], Price = 8.99M, Artist = artists["65daysofstatic"], AlbumArtUrl = imgUrl }, + new Album { Title = "Weill: The Seven Deadly Sins", Genre = genres["Classical"], Price = 8.99M, Artist = artists["Orchestre de l'Opéra de Lyon"], AlbumArtUrl = imgUrl }, + new Album { Title = "White Pony", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Deftones"], AlbumArtUrl = imgUrl }, + new Album { Title = "Who's Next", Genre = genres["Rock"], Price = 8.99M, Artist = artists["The Who"], AlbumArtUrl = imgUrl }, + new Album { Title = "Wish You Were Here", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Pink Floyd"], AlbumArtUrl = imgUrl }, + new Album { Title = "With Oden on Our Side", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Amon Amarth"], AlbumArtUrl = imgUrl }, + new Album { Title = "Worlds", Genre = genres["Jazz"], Price = 8.99M, Artist = artists["Aaron Goldberg"], AlbumArtUrl = imgUrl }, + new Album { Title = "Worship Music", Genre = genres["Metal"], Price = 8.99M, Artist = artists["Anthrax"], AlbumArtUrl = imgUrl }, + new Album { Title = "X&Y", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Coldplay"], AlbumArtUrl = imgUrl }, + new Album { Title = "Xinti", Genre = genres["World"], Price = 8.99M, Artist = artists["Sara Tavares"], AlbumArtUrl = imgUrl }, + new Album { Title = "Yano", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Yano"], AlbumArtUrl = imgUrl }, + new Album { Title = "Yesterday Once More Disc 1/Disc 2", Genre = genres["Pop"], Price = 8.99M, Artist = artists["The Carpenters"], AlbumArtUrl = imgUrl }, + new Album { Title = "Zooropa", Genre = genres["Rock"], Price = 8.99M, Artist = artists["U2"], AlbumArtUrl = imgUrl }, + new Album { Title = "Zoso", Genre = genres["Rock"], Price = 8.99M, Artist = artists["Led Zeppelin"], AlbumArtUrl = imgUrl }, + }; + + foreach (var album in albums) + { + album.ArtistId = album.Artist.ArtistId; + album.GenreId = album.Genre.GenreId; + } + + return albums; + } + + private static Dictionary artists; + public static Dictionary Artists + { + get + { + if (artists == null) + { + var artistsList = new Artist[] + { + new Artist { Name = "65daysofstatic" }, + new Artist { Name = "Aaron Goldberg" }, + new Artist { Name = "Above & Beyond" }, + new Artist { Name = "Above the Fold" }, + new Artist { Name = "AC/DC" }, + new Artist { Name = "Accept" }, + new Artist { Name = "Adicts" }, + new Artist { Name = "Adrian Leaper & Doreen de Feis" }, + new Artist { Name = "Aerosmith" }, + new Artist { Name = "Aisha Duo" }, + new Artist { Name = "Al di Meola" }, + new Artist { Name = "Alabama Shakes" }, + new Artist { Name = "Alanis Morissette" }, + new Artist { Name = "Alberto Turco & Nova Schola Gregoriana" }, + new Artist { Name = "Alice in Chains" }, + new Artist { Name = "Alison Krauss" }, + new Artist { Name = "Amon Amarth" }, + new Artist { Name = "Amon Tobin" }, + new Artist { Name = "Amr Diab" }, + new Artist { Name = "Amy Winehouse" }, + new Artist { Name = "Anita Ward" }, + new Artist { Name = "Anthrax" }, + new Artist { Name = "Antônio Carlos Jobim" }, + new Artist { Name = "Apocalyptica" }, + new Artist { Name = "Aqua" }, + new Artist { Name = "Armand Van Helden" }, + new Artist { Name = "Arcade Fire" }, + new Artist { Name = "Audioslave" }, + new Artist { Name = "Bad Religion" }, + new Artist { Name = "Barenaked Ladies" }, + new Artist { Name = "BBC Concert Orchestra" }, + new Artist { Name = "Bee Gees" }, + new Artist { Name = "Before the Dawn" }, + new Artist { Name = "Berliner Philharmoniker" }, + new Artist { Name = "Billy Cobham" }, + new Artist { Name = "Black Label Society" }, + new Artist { Name = "Black Sabbath" }, + new Artist { Name = "BLØF" }, + new Artist { Name = "Blues Traveler" }, + new Artist { Name = "Boston Symphony Orchestra & Seiji Ozawa" }, + new Artist { Name = "Britten Sinfonia, Ivor Bolton & Lesley Garrett" }, + new Artist { Name = "Bruce Dickinson" }, + new Artist { Name = "Buddy Guy" }, + new Artist { Name = "Burial" }, + new Artist { Name = "Butch Walker & The Black Widows" }, + new Artist { Name = "Caetano Veloso" }, + new Artist { Name = "Cake" }, + new Artist { Name = "Calexico" }, + new Artist { Name = "Carly Rae Jepsen" }, + new Artist { Name = "Carreras, Pavarotti, Domingo" }, + new Artist { Name = "Cássia Eller" }, + new Artist { Name = "Cayouche" }, + new Artist { Name = "Chic" }, + new Artist { Name = "Chicago " }, + new Artist { Name = "Chicago Symphony Orchestra & Fritz Reiner" }, + new Artist { Name = "Chico Buarque" }, + new Artist { Name = "Chico Science & Nação Zumbi" }, + new Artist { Name = "Choir Of Westminster Abbey & Simon Preston" }, + new Artist { Name = "Chris Cornell" }, + new Artist { Name = "Christopher O'Riley" }, + new Artist { Name = "Cidade Negra" }, + new Artist { Name = "Cláudio Zoli" }, + new Artist { Name = "Coldplay" }, + new Artist { Name = "Creedence Clearwater Revival" }, + new Artist { Name = "Crosby, Stills, Nash, and Young" }, + new Artist { Name = "Daft Punk" }, + new Artist { Name = "Danielson Famile" }, + new Artist { Name = "David Bowie" }, + new Artist { Name = "David Coverdale" }, + new Artist { Name = "David Guetta" }, + new Artist { Name = "deadmau5" }, + new Artist { Name = "Deep Purple" }, + new Artist { Name = "Def Leppard" }, + new Artist { Name = "Deftones" }, + new Artist { Name = "Dennis Chambers" }, + new Artist { Name = "Deva Premal" }, + new Artist { Name = "Dio" }, + new Artist { Name = "Djavan" }, + new Artist { Name = "Dolly Parton" }, + new Artist { Name = "Donna Summer" }, + new Artist { Name = "Dr. Dre" }, + new Artist { Name = "Dread Zeppelin" }, + new Artist { Name = "Dream Theater" }, + new Artist { Name = "Duck Sauce" }, + new Artist { Name = "Earl Scruggs" }, + new Artist { Name = "Ed Motta" }, + new Artist { Name = "Edo de Waart & San Francisco Symphony" }, + new Artist { Name = "Elis Regina" }, + new Artist { Name = "Eminem" }, + new Artist { Name = "English Concert & Trevor Pinnock" }, + new Artist { Name = "Enya" }, + new Artist { Name = "Epica" }, + new Artist { Name = "Eric B. and Rakim" }, + new Artist { Name = "Eric Clapton" }, + new Artist { Name = "Eugene Ormandy" }, + new Artist { Name = "Faith No More" }, + new Artist { Name = "Falamansa" }, + new Artist { Name = "Filter" }, + new Artist { Name = "Foo Fighters" }, + new Artist { Name = "Four Tet" }, + new Artist { Name = "Frank Zappa & Captain Beefheart" }, + new Artist { Name = "Fretwork" }, + new Artist { Name = "Funk Como Le Gusta" }, + new Artist { Name = "Garbage" }, + new Artist { Name = "Gerald Moore" }, + new Artist { Name = "Gilberto Gil" }, + new Artist { Name = "Godsmack" }, + new Artist { Name = "Gonzaguinha" }, + new Artist { Name = "Göteborgs Symfoniker & Neeme Järvi" }, + new Artist { Name = "Guns N' Roses" }, + new Artist { Name = "Gustav Mahler" }, + new Artist { Name = "In This Moment" }, + new Artist { Name = "Incognito" }, + new Artist { Name = "INXS" }, + new Artist { Name = "Iron Maiden" }, + new Artist { Name = "Jagjit Singh" }, + new Artist { Name = "James Levine" }, + new Artist { Name = "Jamiroquai" }, + new Artist { Name = "Jimi Hendrix" }, + new Artist { Name = "Jimmy Buffett" }, + new Artist { Name = "Jimmy Smith" }, + new Artist { Name = "Joe Satriani" }, + new Artist { Name = "John Digweed" }, + new Artist { Name = "John Mayer" }, + new Artist { Name = "Jorge Ben" }, + new Artist { Name = "Jota Quest" }, + new Artist { Name = "Journey" }, + new Artist { Name = "Judas Priest" }, + new Artist { Name = "Julian Bream" }, + new Artist { Name = "Justice" }, + new Artist { Name = "Orchestre de l'Opéra de Lyon" }, + new Artist { Name = "King Crimson" }, + new Artist { Name = "Kiss" }, + new Artist { Name = "LCD Soundsystem" }, + new Artist { Name = "Le Tigre" }, + new Artist { Name = "Led Zeppelin" }, + new Artist { Name = "Legião Urbana" }, + new Artist { Name = "Lenny Kravitz" }, + new Artist { Name = "Les Arts Florissants & William Christie" }, + new Artist { Name = "Limp Bizkit" }, + new Artist { Name = "Linkin Park" }, + new Artist { Name = "Live" }, + new Artist { Name = "Lokua Kanza" }, + new Artist { Name = "London Symphony Orchestra" }, + new Artist { Name = "Los Tigres del Norte" }, + new Artist { Name = "Luciana Souza/Romero Lubambo" }, + new Artist { Name = "Lulu Santos" }, + new Artist { Name = "Lura" }, + new Artist { Name = "Marcos Valle" }, + new Artist { Name = "Marillion" }, + new Artist { Name = "Marisa Monte" }, + new Artist { Name = "Mark Knopfler" }, + new Artist { Name = "Martin Roscoe" }, + new Artist { Name = "Massive Attack" }, + new Artist { Name = "Maurizio Pollini" }, + new Artist { Name = "Megadeth" }, + new Artist { Name = "Mela Tenenbaum, Pro Musica Prague & Richard Kapp" }, + new Artist { Name = "Melanie Fiona" }, + new Artist { Name = "Men At Work" }, + new Artist { Name = "Metallica" }, + new Artist { Name = "M-Flo" }, + new Artist { Name = "Michael Bolton" }, + new Artist { Name = "Michael Tilson Thomas" }, + new Artist { Name = "Miles Davis" }, + new Artist { Name = "Milton Nascimento" }, + new Artist { Name = "Mobile" }, + new Artist { Name = "Modest Mouse" }, + new Artist { Name = "Mötley Crüe" }, + new Artist { Name = "Motörhead" }, + new Artist { Name = "Mumford & Sons" }, + new Artist { Name = "Munkle" }, + new Artist { Name = "Nash Ensemble" }, + new Artist { Name = "Neil Young" }, + new Artist { Name = "New York Dolls" }, + new Artist { Name = "Nick Cave and the Bad Seeds" }, + new Artist { Name = "Nicolaus Esterhazy Sinfonia" }, + new Artist { Name = "Nine Inch Nails" }, + new Artist { Name = "Nirvana" }, + new Artist { Name = "Norah Jones" }, + new Artist { Name = "Nujabes" }, + new Artist { Name = "O Terço" }, + new Artist { Name = "Oasis" }, + new Artist { Name = "Olodum" }, + new Artist { Name = "Opeth" }, + new Artist { Name = "Orchestra of The Age of Enlightenment" }, + new Artist { Name = "Os Paralamas Do Sucesso" }, + new Artist { Name = "Ozzy Osbourne" }, + new Artist { Name = "Paddy Casey" }, + new Artist { Name = "Page & Plant" }, + new Artist { Name = "Papa Wemba" }, + new Artist { Name = "Paul D'Ianno" }, + new Artist { Name = "Paul Oakenfold" }, + new Artist { Name = "Paul Van Dyk" }, + new Artist { Name = "Pearl Jam" }, + new Artist { Name = "Pet Shop Boys" }, + new Artist { Name = "Pink Floyd" }, + new Artist { Name = "Plug" }, + new Artist { Name = "Porcupine Tree" }, + new Artist { Name = "Portishead" }, + new Artist { Name = "Prince" }, + new Artist { Name = "Projected" }, + new Artist { Name = "PSY" }, + new Artist { Name = "Public Enemy" }, + new Artist { Name = "Queen" }, + new Artist { Name = "Queensrÿche" }, + new Artist { Name = "R.E.M." }, + new Artist { Name = "Radiohead" }, + new Artist { Name = "Rancid" }, + new Artist { Name = "Raul Seixas" }, + new Artist { Name = "Raunchy" }, + new Artist { Name = "Red Hot Chili Peppers" }, + new Artist { Name = "Rick Ross" }, + new Artist { Name = "Robert James" }, + new Artist { Name = "London Classical Players" }, + new Artist { Name = "Royal Philharmonic Orchestra" }, + new Artist { Name = "Run DMC" }, + new Artist { Name = "Rush" }, + new Artist { Name = "Santana" }, + new Artist { Name = "Sara Tavares" }, + new Artist { Name = "Sarah Brightman" }, + new Artist { Name = "Sasha" }, + new Artist { Name = "Scholars Baroque Ensemble" }, + new Artist { Name = "Scorpions" }, + new Artist { Name = "Sergei Prokofiev & Yuri Temirkanov" }, + new Artist { Name = "Sheryl Crow" }, + new Artist { Name = "Sir Georg Solti & Wiener Philharmoniker" }, + new Artist { Name = "Skank" }, + new Artist { Name = "Skrillex" }, + new Artist { Name = "Slash" }, + new Artist { Name = "Slayer" }, + new Artist { Name = "Soul-Junk" }, + new Artist { Name = "Soundgarden" }, + new Artist { Name = "Spyro Gyra" }, + new Artist { Name = "Stevie Ray Vaughan & Double Trouble" }, + new Artist { Name = "Stevie Ray Vaughan" }, + new Artist { Name = "Sting" }, + new Artist { Name = "Stone Temple Pilots" }, + new Artist { Name = "Styx" }, + new Artist { Name = "Sufjan Stevens" }, + new Artist { Name = "Supreme Beings of Leisure" }, + new Artist { Name = "System Of A Down" }, + new Artist { Name = "T&N" }, + new Artist { Name = "Talking Heads" }, + new Artist { Name = "Tears For Fears" }, + new Artist { Name = "Ted Nugent" }, + new Artist { Name = "Temple of the Dog" }, + new Artist { Name = "Terry Bozzio, Tony Levin & Steve Stevens" }, + new Artist { Name = "The 12 Cellists of The Berlin Philharmonic" }, + new Artist { Name = "The Axis of Awesome" }, + new Artist { Name = "The Beatles" }, + new Artist { Name = "The Black Crowes" }, + new Artist { Name = "The Black Keys" }, + new Artist { Name = "The Carpenters" }, + new Artist { Name = "The Cat Empire" }, + new Artist { Name = "The Cult" }, + new Artist { Name = "The Cure" }, + new Artist { Name = "The Decemberists" }, + new Artist { Name = "The Doors" }, + new Artist { Name = "The Eagles of Death Metal" }, + new Artist { Name = "The Go! Team" }, + new Artist { Name = "The Head and the Heart" }, + new Artist { Name = "The Jezabels" }, + new Artist { Name = "The King's Singers" }, + new Artist { Name = "The Lumineers" }, + new Artist { Name = "The Offspring" }, + new Artist { Name = "The Police" }, + new Artist { Name = "The Posies" }, + new Artist { Name = "The Prodigy" }, + new Artist { Name = "The Rolling Stones" }, + new Artist { Name = "The Rubberbandits" }, + new Artist { Name = "The Smashing Pumpkins" }, + new Artist { Name = "The Stone Roses" }, + new Artist { Name = "The Who" }, + new Artist { Name = "Them Crooked Vultures" }, + new Artist { Name = "TheStart" }, + new Artist { Name = "Thievery Corporation" }, + new Artist { Name = "Tiësto" }, + new Artist { Name = "Tim Maia" }, + new Artist { Name = "Ton Koopman" }, + new Artist { Name = "Tool" }, + new Artist { Name = "Tori Amos" }, + new Artist { Name = "Trampled By Turtles" }, + new Artist { Name = "Trans-Siberian Orchestra" }, + new Artist { Name = "Tygers of Pan Tang" }, + new Artist { Name = "U2" }, + new Artist { Name = "UB40" }, + new Artist { Name = "Uh Huh Her " }, + new Artist { Name = "Van Halen" }, + new Artist { Name = "Various Artists" }, + new Artist { Name = "Velvet Revolver" }, + new Artist { Name = "Venus Hum" }, + new Artist { Name = "Vicente Fernandez" }, + new Artist { Name = "Vinícius De Moraes" }, + new Artist { Name = "Weezer" }, + new Artist { Name = "Weird Al" }, + new Artist { Name = "Wendy Carlos" }, + new Artist { Name = "Wilhelm Kempff" }, + new Artist { Name = "Yano" }, + new Artist { Name = "Yehudi Menuhin" }, + new Artist { Name = "Yes" }, + new Artist { Name = "Yo-Yo Ma" }, + new Artist { Name = "Zeca Pagodinho" }, + new Artist { Name = "אריק אינשטיין"} + }; + + artists = new Dictionary(); + foreach (Artist artist in artistsList) + { + artists.Add(artist.Name, artist); + } + } + + return artists; + } + } + + private static Dictionary genres; + public static Dictionary Genres + { + get + { + if (genres == null) + { + var genresList = new Genre[] + { + new Genre { Name = "Pop" }, + new Genre { Name = "Rock" }, + new Genre { Name = "Jazz" }, + new Genre { Name = "Metal" }, + new Genre { Name = "Electronic" }, + new Genre { Name = "Blues" }, + new Genre { Name = "Latin" }, + new Genre { Name = "Rap" }, + new Genre { Name = "Classical" }, + new Genre { Name = "Alternative" }, + new Genre { Name = "Country" }, + new Genre { Name = "R&B" }, + new Genre { Name = "Indie" }, + new Genre { Name = "Punk" }, + new Genre { Name = "World" } + }; + + genres = new Dictionary(); + + foreach (Genre genre in genresList) + { + genres.Add(genre.Name, genre); + } + } + + return genres; + } + } + } +} diff --git a/samples/MusicStore/Models/ShoppingCart.cs b/samples/MusicStore/Models/ShoppingCart.cs new file mode 100644 index 0000000..5d05325 --- /dev/null +++ b/samples/MusicStore/Models/ShoppingCart.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace MusicStore.Models +{ + public class ShoppingCart + { + private readonly MusicStoreContext _dbContext; + private readonly string _shoppingCartId; + + private ShoppingCart(MusicStoreContext dbContext, string id) + { + _dbContext = dbContext; + _shoppingCartId = id; + } + + public static ShoppingCart GetCart(MusicStoreContext db, HttpContext context) + => GetCart(db, GetCartId(context)); + + public static ShoppingCart GetCart(MusicStoreContext db, string cartId) + => new ShoppingCart(db, cartId); + + public async Task AddToCart(Album album) + { + // Get the matching cart and album instances + var cartItem = await _dbContext.CartItems.SingleOrDefaultAsync( + c => c.CartId == _shoppingCartId + && c.AlbumId == album.AlbumId); + + if (cartItem == null) + { + // Create a new cart item if no cart item exists + cartItem = new CartItem + { + AlbumId = album.AlbumId, + CartId = _shoppingCartId, + Count = 1, + DateCreated = DateTime.Now + }; + + _dbContext.CartItems.Add(cartItem); + } + else + { + // If the item does exist in the cart, then add one to the quantity + cartItem.Count++; + } + } + + public int RemoveFromCart(int id) + { + // Get the cart + var cartItem = _dbContext.CartItems.SingleOrDefault( + cart => cart.CartId == _shoppingCartId + && cart.CartItemId == id); + + int itemCount = 0; + + if (cartItem != null) + { + if (cartItem.Count > 1) + { + cartItem.Count--; + itemCount = cartItem.Count; + } + else + { + _dbContext.CartItems.Remove(cartItem); + } + } + + return itemCount; + } + + public async Task EmptyCart() + { + var cartItems = await _dbContext + .CartItems + .Where(cart => cart.CartId == _shoppingCartId) + .ToArrayAsync(); + + _dbContext.CartItems.RemoveRange(cartItems); + } + + public Task> GetCartItems() + { + return _dbContext + .CartItems + .Where(cart => cart.CartId == _shoppingCartId) + .Include(c => c.Album) + .ToListAsync(); + } + + public Task> GetCartAlbumTitles() + { + return _dbContext + .CartItems + .Where(cart => cart.CartId == _shoppingCartId) + .Select(c => c.Album.Title) + .OrderBy(n => n) + .ToListAsync(); + } + + public Task GetCount() + { + // Get the count of each item in the cart and sum them up + return _dbContext + .CartItems + .Where(c => c.CartId == _shoppingCartId) + .Select(c => c.Count) + .SumAsync(); + } + + public Task GetTotal() + { + // Multiply album price by count of that album to get + // the current price for each of those albums in the cart + // sum all album price totals to get the cart total + + return _dbContext + .CartItems + .Where(c => c.CartId == _shoppingCartId) + .Select(c => c.Album.Price * c.Count) + .SumAsync(); + } + + public async Task CreateOrder(Order order) + { + decimal orderTotal = 0; + + var cartItems = await GetCartItems(); + + // Iterate over the items in the cart, adding the order details for each + foreach (var item in cartItems) + { + //var album = _db.Albums.Find(item.AlbumId); + var album = await _dbContext.Albums.SingleAsync(a => a.AlbumId == item.AlbumId); + + var orderDetail = new OrderDetail + { + AlbumId = item.AlbumId, + OrderId = order.OrderId, + UnitPrice = album.Price, + Quantity = item.Count, + }; + + // Set the order total of the shopping cart + orderTotal += (item.Count * album.Price); + + _dbContext.OrderDetails.Add(orderDetail); + } + + // Set the order's total to the orderTotal count + order.Total = orderTotal; + + // Empty the shopping cart + await EmptyCart(); + + // Return the OrderId as the confirmation number + return order.OrderId; + } + + // We're using HttpContextBase to allow access to sessions. + private static string GetCartId(HttpContext context) + { + var cartId = context.Session.GetString("Session"); + + if (cartId == null) + { + //A GUID to hold the cartId. + cartId = Guid.NewGuid().ToString(); + + // Send cart Id as a cookie to the client. + context.Session.SetString("Session", cartId); + } + + return cartId; + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/MusicStore.csproj b/samples/MusicStore/MusicStore.csproj new file mode 100644 index 0000000..07dc732 --- /dev/null +++ b/samples/MusicStore/MusicStore.csproj @@ -0,0 +1,55 @@ + + + + Music store application on ASP.NET Core + netcoreapp2.1;netcoreapp2.0;net461 + $(DefineConstants);DEMO + true + true + win7-x86;win7-x64;linux-x64;osx-x64 + Debug;Release;RuntimeStore + full + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(PublishDir)$(RazorTargetName).dll + + + + + diff --git a/samples/MusicStore/Pages/PageThatThrows.cshtml b/samples/MusicStore/Pages/PageThatThrows.cshtml new file mode 100644 index 0000000..b119553 --- /dev/null +++ b/samples/MusicStore/Pages/PageThatThrows.cshtml @@ -0,0 +1,2 @@ +@page +@{ throw new InvalidOperationException(); } diff --git a/samples/MusicStore/Platform.cs b/samples/MusicStore/Platform.cs new file mode 100644 index 0000000..4124e39 --- /dev/null +++ b/samples/MusicStore/Platform.cs @@ -0,0 +1,110 @@ +using System; +using System.Runtime.InteropServices; + +namespace MusicStore +{ + internal class Platform + { + // Defined in winnt.h + private const int PRODUCT_NANO_SERVER = 0x0000006D; + private const int PRODUCT_DATACENTER_NANO_SERVER = 0x0000008F; + private const int PRODUCT_STANDARD_NANO_SERVER = 0x00000090; + + [DllImport("api-ms-win-core-sysinfo-l1-2-1.dll", SetLastError = false)] + private static extern bool GetProductInfo( + int dwOSMajorVersion, + int dwOSMinorVersion, + int dwSpMajorVersion, + int dwSpMinorVersion, + out int pdwReturnedProductType); + + private bool? _isNano; + private bool? _isWindows; + + public bool IsRunningOnWindows + { + get + { + if (_isWindows == null) + { + _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + } + + return _isWindows.Value; + } + } + + public bool IsRunningOnNanoServer + { + get + { + if (_isNano == null) + { + var osVersion = new Version(RtlGetVersion() ?? string.Empty); + + try + { + int productType; + if (GetProductInfo(osVersion.Major, osVersion.Minor, 0, 0, out productType)) + { + _isNano = productType == PRODUCT_NANO_SERVER || + productType == PRODUCT_DATACENTER_NANO_SERVER || + productType == PRODUCT_STANDARD_NANO_SERVER; + } + else + { + _isNano = false; + } + } + catch + { + // If the API call fails, the API set is not there which means + // that we are definetely not running on Nano + _isNano = false; + } + } + + return _isNano.Value; + } + } + + // Sql client not available on mono, non-windows, or nano + public bool UseInMemoryStore + { + get + { + return !IsRunningOnWindows || IsRunningOnNanoServer; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct RTL_OSVERSIONINFOEX + { + internal uint dwOSVersionInfoSize; + internal uint dwMajorVersion; + internal uint dwMinorVersion; + internal uint dwBuildNumber; + internal uint dwPlatformId; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + internal string szCSDVersion; + } + + // This call avoids the shimming Windows does to report old versions + [DllImport("ntdll")] + private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation); + + internal static string RtlGetVersion() + { + RTL_OSVERSIONINFOEX osvi = new RTL_OSVERSIONINFOEX(); + osvi.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osvi); + if (RtlGetVersion(out osvi) == 0) + { + return $"{osvi.dwMajorVersion}.{osvi.dwMinorVersion}.{osvi.dwBuildNumber}"; + } + else + { + return null; + } + } + } +} diff --git a/samples/MusicStore/Program.cs b/samples/MusicStore/Program.cs new file mode 100644 index 0000000..07c8104 --- /dev/null +++ b/samples/MusicStore/Program.cs @@ -0,0 +1,67 @@ +using System; +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.HttpSys; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace MusicStore +{ + public static class Program + { + public static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .AddCommandLine(args) + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + var builder = new WebHostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseConfiguration(config) + .UseIISIntegration() + .UseStartup("MusicStore") + .UseDefaultServiceProvider((context, options) => { + options.ValidateScopes = true; + }); + + var environment = builder.GetSetting("environment") ?? + Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + + if (string.Equals(builder.GetSetting("server"), "Microsoft.AspNetCore.Server.HttpSys", System.StringComparison.Ordinal)) + { + if (string.Equals(environment, "NtlmAuthentication", System.StringComparison.Ordinal)) + { + // Set up NTLM authentication for WebListener like below. + // For IIS and IISExpress: Use inetmgr to setup NTLM authentication on the application vDir or + // modify the applicationHost.config to enable NTLM. + builder.UseHttpSys(options => + { + options.Authentication.Schemes = AuthenticationSchemes.NTLM; + options.Authentication.AllowAnonymous = false; + }); + } + else + { + builder.UseHttpSys(); + } + } + else + { + builder.UseKestrel(); + } + + builder.ConfigureLogging(factory => + { + factory.AddConsole(); + + var logLevel = string.Equals(environment, "Development", StringComparison.Ordinal) ? LogLevel.Information : LogLevel.Warning; + factory.SetMinimumLevel(logLevel); + }); + + var host = builder.Build(); + + host.Run(); + } + } +} diff --git a/samples/MusicStore/Properties/AppSettings.cs b/samples/MusicStore/Properties/AppSettings.cs new file mode 100644 index 0000000..6ba3600 --- /dev/null +++ b/samples/MusicStore/Properties/AppSettings.cs @@ -0,0 +1,9 @@ +namespace MusicStore +{ + public class AppSettings + { + public string SiteTitle { get; set; } + + public bool CacheDbResults { get; set; } = true; + } +} \ No newline at end of file diff --git a/samples/MusicStore/Properties/launchSettings.json b/samples/MusicStore/Properties/launchSettings.json new file mode 100644 index 0000000..a2f3822 --- /dev/null +++ b/samples/MusicStore/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:4088/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "MusicStore": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "http://localhost:5000/", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/Scripts/_references.js b/samples/MusicStore/Scripts/_references.js new file mode 100644 index 0000000..73c9e0d --- /dev/null +++ b/samples/MusicStore/Scripts/_references.js @@ -0,0 +1,8 @@ +/// +/// +/// +/// +/// +/// +/// +/// diff --git a/samples/MusicStore/Startup.cs b/samples/MusicStore/Startup.cs new file mode 100644 index 0000000..2ca1de0 --- /dev/null +++ b/samples/MusicStore/Startup.cs @@ -0,0 +1,216 @@ +using System.Globalization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.AspNetCore.Localization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MusicStore.Components; +using MusicStore.Models; + +namespace MusicStore +{ + public class Startup + { + private readonly Platform _platform; + + public Startup(IHostingEnvironment hostingEnvironment) + { + // Below code demonstrates usage of multiple configuration sources. For instance a setting say 'setting1' + // is found in both the registered sources, then the later source will win. By this way a Local config + // can be overridden by a different setting while deployed remotely. + var builder = new ConfigurationBuilder() + .SetBasePath(hostingEnvironment.ContentRootPath) + .AddJsonFile("config.json") + //All environment variables in the process's context flow in as configuration values. + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + _platform = new Platform(); + } + + public IConfiguration Configuration { get; private set; } + + public void ConfigureServices(IServiceCollection services) + { + services.Configure(Configuration.GetSection("AppSettings")); + + // Add EF services to the services container + if (_platform.UseInMemoryStore) + { + services.AddDbContext(options => + options.UseInMemoryDatabase("Scratch")); + } + else + { + services.AddDbContext(options => + options.UseSqlServer(Configuration[StoreConfig.ConnectionStringKey.Replace("__", ":")])); + } + + // Add Identity services to the services container + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.ConfigureApplicationCookie(options => options.AccessDeniedPath = "/Home/AccessDenied"); + + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", builder => + { + builder.WithOrigins("http://example.com"); + }); + }); + + services.AddLogging(); + + // Add MVC services to the services container + services.AddMvc(); + + // Add memory cache services + services.AddMemoryCache(); + services.AddDistributedMemoryCache(); + + // Add session related services. + services.AddSession(); + + // Add the system clock service + services.AddSingleton(); + + // Configure Auth + services.AddAuthorization(options => + { + options.AddPolicy( + "ManageStore", + authBuilder => + { + authBuilder.RequireClaim("ManageStore", "Allowed"); + }); + }); + + + services.AddAuthentication() + .AddFacebook(options => + { + options.AppId = "550624398330273"; + options.AppSecret = "10e56a291d6b618da61b1e0dae3a8954"; + }) + .AddGoogle(options => + { + options.ClientId = "995291875932-0rt7417v5baevqrno24kv332b7d6d30a.apps.googleusercontent.com"; + options.ClientSecret = "J_AT57H5KH_ItmMdu0r6PfXm"; + }) + .AddTwitter(options => + { + options.ConsumerKey = "lDSPIu480ocnXYZ9DumGCDw37"; + options.ConsumerSecret = "fpo0oWRNc3vsZKlZSq1PyOSoeXlJd7NnG4Rfc94xbFXsdcc3nH"; + }) + // The MicrosoftAccount service has restrictions that prevent the use of + // http://localhost:5001/ for test applications. + // As such, here is how to change this sample to uses http://ktesting.com:5001/ instead. + + // From an admin command console first enter: + // notepad C:\Windows\System32\drivers\etc\hosts + // and add this to the file, save, and exit (and reboot?): + // 127.0.0.1 ktesting.com + + // Then you can choose to run the app as admin (see below) or add the following ACL as admin: + // netsh http add urlacl url=http://ktesting:5001/ user=[domain\user] + + // The sample app can then be run via: + // dnx . web + .AddMicrosoftAccount(options => + { + // MicrosoftAccount requires project changes + options.ClientId = "000000004012C08A"; + options.ClientSecret = "GaMQ2hCnqAC6EcDLnXsAeBVIJOLmeutL"; + }); + } + + //This method is invoked when ASPNETCORE_ENVIRONMENT is 'Development' or is not defined + //The allowed values are Development,Staging and Production + public void ConfigureDevelopment(IApplicationBuilder app) + { + // StatusCode pages to gracefully handle status codes 400-599. + app.UseStatusCodePagesWithRedirects("~/Home/StatusCodePage"); + + // Display custom error page in production when error occurs + // During development use the ErrorPage middleware to display error information in the browser + app.UseDeveloperExceptionPage(); + + app.UseDatabaseErrorPage(); + + Configure(app); + } + + //This method is invoked when ASPNETCORE_ENVIRONMENT is 'Staging' + //The allowed values are Development,Staging and Production + public void ConfigureStaging(IApplicationBuilder app) + { + // StatusCode pages to gracefully handle status codes 400-599. + app.UseStatusCodePagesWithRedirects("~/Home/StatusCodePage"); + + app.UseExceptionHandler("/Home/Error"); + + Configure(app); + } + + //This method is invoked when ASPNETCORE_ENVIRONMENT is 'Production' + //The allowed values are Development,Staging and Production + public void ConfigureProduction(IApplicationBuilder app) + { + // StatusCode pages to gracefully handle status codes 400-599. + app.UseStatusCodePagesWithRedirects("~/Home/StatusCodePage"); + + app.UseExceptionHandler("/Home/Error"); + + Configure(app); + } + + public void Configure(IApplicationBuilder app) + { + // force the en-US culture, so that the app behaves the same even on machines with different default culture + var supportedCultures = new[] { new CultureInfo("en-US") }; + + app.UseRequestLocalization(new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture("en-US"), + SupportedCultures = supportedCultures, + SupportedUICultures = supportedCultures + }); + + // Configure Session. + app.UseSession(); + + // Add static files to the request pipeline + app.UseStaticFiles(); + + // Add cookie-based authentication to the request pipeline + app.UseAuthentication(); + + // Add MVC to the request pipeline + app.UseMvc(routes => + { + routes.MapRoute( + name: "areaRoute", + template: "{area:exists}/{controller}/{action}", + defaults: new { action = "Index" }); + + routes.MapRoute( + name: "default", + template: "{controller}/{action}/{id?}", + defaults: new { controller = "Home", action = "Index" }); + + routes.MapRoute( + name: "api", + template: "{controller}/{id?}"); + }); + + //Populates the MusicStore sample data + SampleData.InitializeMusicStoreDatabaseAsync(app.ApplicationServices).Wait(); + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/StartupNtlmAuthentication.cs b/samples/MusicStore/StartupNtlmAuthentication.cs new file mode 100644 index 0000000..136ea1a --- /dev/null +++ b/samples/MusicStore/StartupNtlmAuthentication.cs @@ -0,0 +1,160 @@ +using System; +using System.Globalization; +using System.Security.Claims; +using System.Security.Principal; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.AspNetCore.Localization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MusicStore.Components; +using MusicStore.Models; + +namespace MusicStore +{ + /// + /// To make runtime to load an environment based startup class, specify the environment by the following ways: + /// 1. Drop a Microsoft.AspNetCore.Hosting.ini file in the wwwroot folder + /// 2. Add a setting in the ini file named 'ASPNETCORE_ENVIRONMENT' with value of the format 'Startup[EnvironmentName]'. + /// For example: To load a Startup class named 'StartupNtlmAuthentication' the value of the env should be + /// 'NtlmAuthentication' (eg. ASPNETCORE_ENVIRONMENT=NtlmAuthentication). Runtime adds a 'Startup' prefix to this and + /// loads 'StartupNtlmAuthentication'. + /// If no environment name is specified the default startup class loaded is 'Startup'. + /// + /// Alternative ways to specify environment are: + /// 1. Set the environment variable named SET ASPNETCORE_ENVIRONMENT=NtlmAuthentication + /// 2. For selfhost based servers pass in a command line variable named --env with this value. Eg: + /// "commands": { + /// "web": "Microsoft.AspNetCore.Hosting --server Microsoft.AspNetCore.Server.WebListener + /// --server.urls http://localhost:5002 --ASPNETCORE_ENVIRONMENT NtlmAuthentication", + /// }, + /// + public class StartupNtlmAuthentication + { + public StartupNtlmAuthentication(IHostingEnvironment hostingEnvironment) + { + // Below code demonstrates usage of multiple configuration sources. For instance a setting say 'setting1' + // is found in both the registered sources, then the later source will win. By this way a Local config + // can be overridden by a different setting while deployed remotely. + var builder = new ConfigurationBuilder() + .SetBasePath(hostingEnvironment.ContentRootPath) + .AddJsonFile("config.json") + //All environment variables in the process's context flow in as configuration values. + .AddEnvironmentVariables(); + Configuration = builder.Build(); + } + + public IConfiguration Configuration { get; private set; } + + public void ConfigureServices(IServiceCollection services) + { + services.Configure(Configuration.GetSection("AppSettings")); + + // Add EF services to the services container + services.AddDbContext(options => + options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); + + // Add Identity services to the services container + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", builder => + { + builder.WithOrigins("http://example.com"); + }); + }); + + // Add MVC services to the services container + services.AddMvc(); + + // Add memory cache services + services.AddMemoryCache(); + services.AddDistributedMemoryCache(); + + // Add session related services. + services.AddSession(); + + // Add the system clock service + services.AddSingleton(); + + // Configure Auth + services.AddAuthorization(options => + { + options.AddPolicy( + "ManageStore", + authBuilder => { + authBuilder.RequireClaim("ManageStore", "Allowed"); + }); + }); + } + + public void Configure(IApplicationBuilder app) + { + // force the en-US culture, so that the app behaves the same even on machines with different default culture + var supportedCultures = new[] { new CultureInfo("en-US") }; + + app.UseRequestLocalization(new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture("en-US"), + SupportedCultures = supportedCultures, + SupportedUICultures = supportedCultures + }); + + app.UseStatusCodePagesWithRedirects("~/Home/StatusCodePage"); + + // Error page middleware displays a nice formatted HTML page for any unhandled exceptions in the + // request pipeline. + // Note: Not recommended for production. + app.UseDeveloperExceptionPage(); + app.UseDatabaseErrorPage(); + + app.Use(async (context, next) => + { + // Who will get admin access? For demo sake I'm listing the currently logged on user as the application + // administrator. But this can be changed to suit the needs. + var identity = (ClaimsIdentity)context.User.Identity; + + if (context.User.Identity.Name == WindowsIdentity.GetCurrent().Name) + { + identity.AddClaim(new Claim("ManageStore", "Allowed")); + } + + await next.Invoke(); + }); + + // Configure Session. + app.UseSession(); + + // Add static files to the request pipeline + app.UseStaticFiles(); + + // Add MVC to the request pipeline + app.UseMvc(routes => + { + routes.MapRoute( + name: "areaRoute", + template: "{area:exists}/{controller}/{action}", + defaults: new { action = "Index" }); + + routes.MapRoute( + name: "default", + template: "{controller}/{action}/{id?}", + defaults: new { controller = "Home", action = "Index" }); + + routes.MapRoute( + name: "api", + template: "{controller}/{id?}"); + }); + + //Populates the MusicStore sample data + SampleData.InitializeMusicStoreDatabaseAsync(app.ApplicationServices, false).Wait(); + } + } +} diff --git a/samples/MusicStore/StartupOpenIdConnect.cs b/samples/MusicStore/StartupOpenIdConnect.cs new file mode 100644 index 0000000..cb1a15a --- /dev/null +++ b/samples/MusicStore/StartupOpenIdConnect.cs @@ -0,0 +1,165 @@ +using System.Globalization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.AspNetCore.Localization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using MusicStore.Components; +using MusicStore.Models; + +namespace MusicStore +{ + /// + /// To make runtime to load an environment based startup class, specify the environment by the following ways: + /// 1. Drop a Microsoft.AspNetCore.Hosting.ini file in the wwwroot folder + /// 2. Add a setting in the ini file named 'ASPNETCORE_ENVIRONMENT' with value of the format 'Startup[EnvironmentName]'. + /// For example: To load a Startup class named 'StartupOpenIdConnect' the value of the env should be + /// 'OpenIdConnect' (eg. ASPNETCORE_ENVIRONMENT=OpenIdConnect). Runtime adds a 'Startup' prefix to this + /// and loads 'StartupOpenIdConnect'. + /// + /// If no environment name is specified the default startup class loaded is 'Startup'. + /// Alternative ways to specify environment are: + /// 1. Set the environment variable named SET ASPNETCORE_ENVIRONMENT=OpenIdConnect + /// 2. For selfhost based servers pass in a command line variable named --env with this value. Eg: + /// "commands": { + /// "web": "Microsoft.AspNetCore.Hosting --server Microsoft.AspNetCore.Server.WebListener + /// --server.urls http://localhost:5002 --ASPNET_ENV OpenIdConnect", + /// }, + /// + public class StartupOpenIdConnect + { + private readonly Platform _platform; + + public StartupOpenIdConnect(IHostingEnvironment hostingEnvironment) + { + // Below code demonstrates usage of multiple configuration sources. For instance a setting say 'setting1' + // is found in both the registered sources, then the later source will win. By this way a Local config can + // be overridden by a different setting while deployed remotely. + var builder = new ConfigurationBuilder() + .SetBasePath(hostingEnvironment.ContentRootPath) + .AddJsonFile("config.json") + //All environment variables in the process's context flow in as configuration values. + .AddEnvironmentVariables(); + + Configuration = builder.Build(); + _platform = new Platform(); + } + + public IConfiguration Configuration { get; private set; } + + public void ConfigureServices(IServiceCollection services) + { + services.Configure(Configuration.GetSection("AppSettings")); + + // Add EF services to the services container + if (_platform.UseInMemoryStore) + { + services.AddDbContext(options => + options.UseInMemoryDatabase("Scratch")); + } + else + { + services.AddDbContext(options => + options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); + } + + // Add Identity services to the services container + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", builder => + { + builder.WithOrigins("http://example.com"); + }); + }); + + // Add MVC services to the services container + services.AddMvc(); + + // Add memory cache services + services.AddMemoryCache(); + services.AddDistributedMemoryCache(); + + // Add session related services. + services.AddSession(); + + // Add the system clock service + services.AddSingleton(); + + // Configure Auth + services.AddAuthorization(options => + { + options.AddPolicy( + "ManageStore", + authBuilder => + { + authBuilder.RequireClaim("ManageStore", "Allowed"); + }); + }); + + // Create an Azure Active directory application and copy paste the following + services.AddAuthentication().AddOpenIdConnect(options => + { + options.Authority = "https://login.windows.net/[tenantName].onmicrosoft.com"; + options.ClientId = "[ClientId]"; + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + }); + } + + public void Configure(IApplicationBuilder app) + { + // force the en-US culture, so that the app behaves the same even on machines with different default culture + var supportedCultures = new[] { new CultureInfo("en-US") }; + + app.UseRequestLocalization(new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture("en-US"), + SupportedCultures = supportedCultures, + SupportedUICultures = supportedCultures + }); + + app.UseStatusCodePagesWithRedirects("~/Home/StatusCodePage"); + + // Display custom error page in production when error occurs + // During development use the ErrorPage middleware to display error information in the browser + app.UseDeveloperExceptionPage(); + + app.UseDatabaseErrorPage(); + + // Configure Session. + app.UseSession(); + + // Add static files to the request pipeline + app.UseStaticFiles(); + + // Add MVC to the request pipeline + app.UseMvc(routes => + { + routes.MapRoute( + name: "areaRoute", + template: "{area:exists}/{controller}/{action}", + defaults: new { action = "Index" }); + + routes.MapRoute( + name: "default", + template: "{controller}/{action}/{id?}", + defaults: new { controller = "Home", action = "Index" }); + + routes.MapRoute( + name: "api", + template: "{controller}/{id?}"); + }); + + //Populates the MusicStore sample data + SampleData.InitializeMusicStoreDatabaseAsync(app.ApplicationServices).Wait(); + } + } +} diff --git a/samples/MusicStore/ViewModels/AlbumData.cs b/samples/MusicStore/ViewModels/AlbumData.cs new file mode 100644 index 0000000..1f8ac24 --- /dev/null +++ b/samples/MusicStore/ViewModels/AlbumData.cs @@ -0,0 +1,9 @@ +namespace MusicStore.ViewModels +{ + public class AlbumData + { + public string Title { get; set; } + + public string Url { get; set; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/ViewModels/ShoppingCartRemoveViewModel.cs b/samples/MusicStore/ViewModels/ShoppingCartRemoveViewModel.cs new file mode 100644 index 0000000..5743b27 --- /dev/null +++ b/samples/MusicStore/ViewModels/ShoppingCartRemoveViewModel.cs @@ -0,0 +1,11 @@ +namespace MusicStore.ViewModels +{ + public class ShoppingCartRemoveViewModel + { + public string Message { get; set; } + public decimal CartTotal { get; set; } + public int CartCount { get; set; } + public int ItemCount { get; set; } + public int DeleteId { get; set; } + } +} \ No newline at end of file diff --git a/samples/MusicStore/ViewModels/ShoppingCartViewModel.cs b/samples/MusicStore/ViewModels/ShoppingCartViewModel.cs new file mode 100644 index 0000000..2aa641b --- /dev/null +++ b/samples/MusicStore/ViewModels/ShoppingCartViewModel.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using MusicStore.Models; + +namespace MusicStore.ViewModels +{ + public class ShoppingCartViewModel + { + public List CartItems { get; set; } + public decimal CartTotal { get; set; } + } +} diff --git a/samples/MusicStore/Views/Account/ConfirmEmail.cshtml b/samples/MusicStore/Views/Account/ConfirmEmail.cshtml new file mode 100644 index 0000000..d8822d5 --- /dev/null +++ b/samples/MusicStore/Views/Account/ConfirmEmail.cshtml @@ -0,0 +1,10 @@ +@{ + ViewBag.Title = "Confirm Email"; +} + +

@ViewBag.Title.

+
+

+ Thank you for confirming your email. Please Click here to Log in. +

+
\ No newline at end of file diff --git a/samples/MusicStore/Views/Account/ExternalLoginConfirmation.cshtml b/samples/MusicStore/Views/Account/ExternalLoginConfirmation.cshtml new file mode 100644 index 0000000..c3c1f85 --- /dev/null +++ b/samples/MusicStore/Views/Account/ExternalLoginConfirmation.cshtml @@ -0,0 +1,34 @@ +@model ExternalLoginConfirmationViewModel +@{ + ViewBag.Title = "Register"; +} +

@ViewBag.Title.

+

Associate your @ViewBag.LoginProvider account.

+ +
+

Association Form

+
+
+ +

+ You've successfully authenticated with @ViewBag.LoginProvider. + Please enter a user name for this site below and click the Register button to finish + logging in. +

+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Account/ExternalLoginFailure.cshtml b/samples/MusicStore/Views/Account/ExternalLoginFailure.cshtml new file mode 100644 index 0000000..cb96d08 --- /dev/null +++ b/samples/MusicStore/Views/Account/ExternalLoginFailure.cshtml @@ -0,0 +1,8 @@ +@{ + ViewBag.Title = "Login Failure"; +} + +
+

@ViewBag.Title.

+

Unsuccessful login with service.

+
\ No newline at end of file diff --git a/samples/MusicStore/Views/Account/ForgotPassword.cshtml b/samples/MusicStore/Views/Account/ForgotPassword.cshtml new file mode 100644 index 0000000..37a40e9 --- /dev/null +++ b/samples/MusicStore/Views/Account/ForgotPassword.cshtml @@ -0,0 +1,28 @@ +@model ForgotPasswordViewModel +@{ + ViewBag.Title = "Forgot your password?"; +} + +

@ViewBag.Title.

+ +
+

Enter your email.

+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/samples/MusicStore/Views/Account/ForgotPasswordConfirmation.cshtml b/samples/MusicStore/Views/Account/ForgotPasswordConfirmation.cshtml new file mode 100644 index 0000000..80bb848 --- /dev/null +++ b/samples/MusicStore/Views/Account/ForgotPasswordConfirmation.cshtml @@ -0,0 +1,15 @@ +@{ + ViewBag.Title = "Forgot Password Confirmation"; +} + +
+

@ViewBag.Title.

+
+
+

+ Please check your email to reset your password. +

+

+ For demo purpose only: Click here to reset the password +

+
\ No newline at end of file diff --git a/samples/MusicStore/Views/Account/Login.cshtml b/samples/MusicStore/Views/Account/Login.cshtml new file mode 100644 index 0000000..3e195b3 --- /dev/null +++ b/samples/MusicStore/Views/Account/Login.cshtml @@ -0,0 +1,60 @@ +@model LoginViewModel + +@{ + ViewBag.Title = "Log in"; +} + +

@ViewBag.Title.

+
+
+
+
+

Use a local account to log in.

+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
+ + +
+
+
+
+
+ +
+
+

+ Register as a new user? +

+

+ Forgot your password? +

+
+
+
+
+
+ @await Html.PartialAsync("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl }) +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Account/Register.cshtml b/samples/MusicStore/Views/Account/Register.cshtml new file mode 100644 index 0000000..9daa6d1 --- /dev/null +++ b/samples/MusicStore/Views/Account/Register.cshtml @@ -0,0 +1,42 @@ +@model RegisterViewModel +@{ + ViewBag.Title = "Register"; +} + +

@ViewBag.Title.

+ +
+

Create a new account.

+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Account/RegisterConfirmation.cshtml b/samples/MusicStore/Views/Account/RegisterConfirmation.cshtml new file mode 100644 index 0000000..1317049 --- /dev/null +++ b/samples/MusicStore/Views/Account/RegisterConfirmation.cshtml @@ -0,0 +1,15 @@ +@{ + ViewBag.Title = "Register Confirmation"; +} + +
+

@ViewBag.Title.

+
+
+

+ Please check your email to activate your account. +

+

+ Demo/testing purposes only: The sample displays the code and user id in the page: Click here to confirm your email: +

+
\ No newline at end of file diff --git a/samples/MusicStore/Views/Account/ResetPassword.cshtml b/samples/MusicStore/Views/Account/ResetPassword.cshtml new file mode 100644 index 0000000..17198fc --- /dev/null +++ b/samples/MusicStore/Views/Account/ResetPassword.cshtml @@ -0,0 +1,43 @@ +@model ResetPasswordViewModel +@{ + ViewBag.Title = "Reset password"; +} + +

@ViewBag.Title.

+ +
+

Reset your password.

+
+
+ +
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Account/ResetPasswordConfirmation.cshtml b/samples/MusicStore/Views/Account/ResetPasswordConfirmation.cshtml new file mode 100644 index 0000000..0af2ecf --- /dev/null +++ b/samples/MusicStore/Views/Account/ResetPasswordConfirmation.cshtml @@ -0,0 +1,12 @@ +@{ + ViewBag.Title = "Reset password confirmation"; +} + +
+

@ViewBag.Title.

+
+
+

+ Your password has been reset. Please Click here to log in. +

+
\ No newline at end of file diff --git a/samples/MusicStore/Views/Account/SendCode.cshtml b/samples/MusicStore/Views/Account/SendCode.cshtml new file mode 100644 index 0000000..b16de72 --- /dev/null +++ b/samples/MusicStore/Views/Account/SendCode.cshtml @@ -0,0 +1,21 @@ +@model SendCodeViewModel +@{ + ViewBag.Title = "Send Verification Code"; +} + +

@ViewBag.Title.

+ +
+ +
+
+ Select Two-Factor Authentication Provider: + + +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/samples/MusicStore/Views/Account/VerifyCode.cshtml b/samples/MusicStore/Views/Account/VerifyCode.cshtml new file mode 100644 index 0000000..00676c7 --- /dev/null +++ b/samples/MusicStore/Views/Account/VerifyCode.cshtml @@ -0,0 +1,43 @@ +@model VerifyCodeViewModel +@{ + ViewBag.Title = "Verify"; +} + +

@ViewBag.Title.

+ +
+
+ + +

Enter verification code

+

+ For DEMO only: You can type in this code in the below text box to proceed: [ @ViewBag.Code ] +
+ Please change this code to register an SMS/Email service in IdentityConfig to send a message. +

+
+
+ +
+ + +
+
+
+
+
+ + +
+
+
+
+
+ +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Account/_ExternalLoginsListPartial.cshtml b/samples/MusicStore/Views/Account/_ExternalLoginsListPartial.cshtml new file mode 100644 index 0000000..56ebf81 --- /dev/null +++ b/samples/MusicStore/Views/Account/_ExternalLoginsListPartial.cshtml @@ -0,0 +1,31 @@ +@using Microsoft.AspNetCore.Authentication +@model ExternalLoginListViewModel +@inject IAuthenticationSchemeProvider SchemeProvider +

Use another service to log in.

+
+@{ + var schemes = await SchemeProvider.GetAllSchemesAsync(); + var loginProviders = schemes.ToList(); + if (!loginProviders.Any()) + { +
+

+ There are no external authentication services configured. See this article + for details on setting up this ASP.NET application to support logging in via external services. +

+
+ } + else + { +
+
+

+ @foreach (var p in loginProviders) + { + + } +

+
+
+ } +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Checkout/AddressAndPayment.cshtml b/samples/MusicStore/Views/Checkout/AddressAndPayment.cshtml new file mode 100644 index 0000000..96c6fdf --- /dev/null +++ b/samples/MusicStore/Views/Checkout/AddressAndPayment.cshtml @@ -0,0 +1,33 @@ +@model Order + +@{ + ViewBag.Title = "Address And Payment"; +} + +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} + +
+

Address And Payment

+
+
+
+ Shipping Information + + @Html.EditorForModel() +
+
+ Payment +

We're running a promotion: all music is free with the promo code: "FREE"

+ +
+ +
+
+ @Html.TextBox("PromoCode") +
+
+ + +
\ No newline at end of file diff --git a/samples/MusicStore/Views/Checkout/Complete.cshtml b/samples/MusicStore/Views/Checkout/Complete.cshtml new file mode 100644 index 0000000..14b8db5 --- /dev/null +++ b/samples/MusicStore/Views/Checkout/Complete.cshtml @@ -0,0 +1,14 @@ +@model int + +@{ + ViewBag.Title = "Checkout Complete"; +} + +

Checkout Complete

+ +

Thanks for your order! Your order number is: @Model

+ +

+ How about shopping for some more music in our + Store +

\ No newline at end of file diff --git a/samples/MusicStore/Views/Home/Index.cshtml b/samples/MusicStore/Views/Home/Index.cshtml new file mode 100644 index 0000000..2216fbb --- /dev/null +++ b/samples/MusicStore/Views/Home/Index.cshtml @@ -0,0 +1,21 @@ +@inject IOptions AppSettings +@{ + ViewBag.Title = "Home Page"; +} + +
+

@AppSettings.Value.SiteTitle

+ +
+ + \ No newline at end of file diff --git a/samples/MusicStore/Views/Manage/AddPhoneNumber.cshtml b/samples/MusicStore/Views/Manage/AddPhoneNumber.cshtml new file mode 100644 index 0000000..b89b005 --- /dev/null +++ b/samples/MusicStore/Views/Manage/AddPhoneNumber.cshtml @@ -0,0 +1,27 @@ +@model AddPhoneNumberViewModel +@{ + ViewBag.Title = "Add Phone Number"; +} + +

@ViewBag.Title.

+
+

Add a phone number.

+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Manage/ChangePassword.cshtml b/samples/MusicStore/Views/Manage/ChangePassword.cshtml new file mode 100644 index 0000000..527e9be --- /dev/null +++ b/samples/MusicStore/Views/Manage/ChangePassword.cshtml @@ -0,0 +1,42 @@ +@model ChangePasswordViewModel +@{ + ViewBag.Title = "Change Password"; +} + +

@ViewBag.Title.

+ +
+

Change Password Form

+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} diff --git a/samples/MusicStore/Views/Manage/Index.cshtml b/samples/MusicStore/Views/Manage/Index.cshtml new file mode 100644 index 0000000..79a2d97 --- /dev/null +++ b/samples/MusicStore/Views/Manage/Index.cshtml @@ -0,0 +1,70 @@ +@model IndexViewModel +@{ + ViewData["Title"] = "Manage your account"; +} + +

@ViewData["Title"].

+

@ViewData["StatusMessage"]

+ +
+

Change your account settings

+
+
+
Password:
+
+ @if (Model.HasPassword) + { + [  Change  ] + } + else + { + [  Create  ] + } +
+
External Logins:
+
+ @Model.Logins.Count [  Manage  ] +
+
Phone Number:
+
+

+ Phone Numbers can used as a second factor of verification in two-factor authentication. + See this article + for details on setting up this ASP.NET application to support two-factor authentication using SMS. +

+ @*@(Model.PhoneNumber ?? "None") + @if (Model.PhoneNumber != null) + { +
+ [  Change  ] +
+ [] +
+ } + else + { + [  Add  ] + }*@ +
+ +
Two-Factor Authentication:
+
+

+ There are no two-factor authentication providers configured. See this article + for setting up this application to support two-factor authentication. +

+ @*@if (Model.TwoFactor) + { +
+ Enabled [] +
+ } + else + { +
+ [] Disabled +
+ }*@ +
+
+
\ No newline at end of file diff --git a/samples/MusicStore/Views/Manage/ManageLogins.cshtml b/samples/MusicStore/Views/Manage/ManageLogins.cshtml new file mode 100644 index 0000000..d4d2625 --- /dev/null +++ b/samples/MusicStore/Views/Manage/ManageLogins.cshtml @@ -0,0 +1,53 @@ +@model ManageLoginsViewModel +@{ + ViewBag.Title = "Manage your external logins"; +} + +

@ViewBag.Title.

+ +

@ViewBag.StatusMessage

+@if (Model.CurrentLogins.Count > 0) +{ +

Registered Logins

+ + + @foreach (var account in Model.CurrentLogins) + { + + + + + } + +
@account.LoginProvider + @if (ViewBag.ShowRemoveButton) + { +
+
+ + + +
+
+ } + else + { + @:   + } +
+} +@if (Model.OtherLogins.Any()) +{ +

Add another service to log in.

+
+
+
+

+ @foreach (var p in Model.OtherLogins) + { + + } +

+
+
+} \ No newline at end of file diff --git a/samples/MusicStore/Views/Manage/SetPassword.cshtml b/samples/MusicStore/Views/Manage/SetPassword.cshtml new file mode 100644 index 0000000..55face2 --- /dev/null +++ b/samples/MusicStore/Views/Manage/SetPassword.cshtml @@ -0,0 +1,38 @@ +@model SetPasswordViewModel +@{ + ViewBag.Title = "Set Password"; +} + +

+ You do not have a local username/password for this site. Add a local + account so you can log in without an external login. +

+ +
+

Set your password

+
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Manage/VerifyPhoneNumber.cshtml b/samples/MusicStore/Views/Manage/VerifyPhoneNumber.cshtml new file mode 100644 index 0000000..55d0625 --- /dev/null +++ b/samples/MusicStore/Views/Manage/VerifyPhoneNumber.cshtml @@ -0,0 +1,34 @@ +@model VerifyPhoneNumberViewModel +@{ + ViewBag.Title = "Verify Phone Number"; +} + +

@ViewBag.Title.

+ +
+ +

Enter verification code

+

+ For DEMO only: You can type in this code in the below text box to proceed: @ViewBag.Code +
+ Please change this code to register an SMS service in IdentityConfig to send a text message. +

+
+
+
+ +
+ + +
+
+
+
+ +
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Shared/AccessDenied.cshtml b/samples/MusicStore/Views/Shared/AccessDenied.cshtml new file mode 100644 index 0000000..27ca49c --- /dev/null +++ b/samples/MusicStore/Views/Shared/AccessDenied.cshtml @@ -0,0 +1,5 @@ +@{ + ViewBag.Title = "Access denied due to insufficient permissions"; +} + +

Access denied due to insufficient permissions.

\ No newline at end of file diff --git a/samples/MusicStore/Views/Shared/Components/Announcement/Default.cshtml b/samples/MusicStore/Views/Shared/Components/Announcement/Default.cshtml new file mode 100644 index 0000000..52e11e3 --- /dev/null +++ b/samples/MusicStore/Views/Shared/Components/Announcement/Default.cshtml @@ -0,0 +1,10 @@ +@model Album + +@if (Model != null) +{ +
  • +
    + + @Model.Title +
  • +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Shared/Components/CartSummary/Default.cshtml b/samples/MusicStore/Views/Shared/Components/CartSummary/Default.cshtml new file mode 100644 index 0000000..9e9dd03 --- /dev/null +++ b/samples/MusicStore/Views/Shared/Components/CartSummary/Default.cshtml @@ -0,0 +1,11 @@ +@if (ViewBag.CartCount > 0) +{ +
  • + + + + @ViewBag.CartCount + + +
  • +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Shared/Components/GenreMenu/Default.cshtml b/samples/MusicStore/Views/Shared/Components/GenreMenu/Default.cshtml new file mode 100644 index 0000000..2acd0ae --- /dev/null +++ b/samples/MusicStore/Views/Shared/Components/GenreMenu/Default.cshtml @@ -0,0 +1,17 @@ +@model IEnumerable + + diff --git a/samples/MusicStore/Views/Shared/DemoLinkDisplay.cshtml b/samples/MusicStore/Views/Shared/DemoLinkDisplay.cshtml new file mode 100644 index 0000000..d4ff51b --- /dev/null +++ b/samples/MusicStore/Views/Shared/DemoLinkDisplay.cshtml @@ -0,0 +1,21 @@ +@{ + ViewBag.Title = "Demo link display page - Not for production use"; +} + +
    +

    @ViewBag.Title.

    +
    +
    +

    + Demo link display page - Not for production use. +

    + + @if (ViewBag.Link != null) + { +

    + For DEMO only: You can click this link to confirm the email: [[link]] +
    + Please change this code to register an email service in IdentityConfig to send an email. +

    + } +
    \ No newline at end of file diff --git a/samples/MusicStore/Views/Shared/Error.cshtml b/samples/MusicStore/Views/Shared/Error.cshtml new file mode 100644 index 0000000..dd26209 --- /dev/null +++ b/samples/MusicStore/Views/Shared/Error.cshtml @@ -0,0 +1,6 @@ +@{ + ViewBag.Title = "Error"; +} + +

    Error.

    +

    An error occurred while processing your request.

    \ No newline at end of file diff --git a/samples/MusicStore/Views/Shared/Lockout.cshtml b/samples/MusicStore/Views/Shared/Lockout.cshtml new file mode 100644 index 0000000..aff107a --- /dev/null +++ b/samples/MusicStore/Views/Shared/Lockout.cshtml @@ -0,0 +1,8 @@ +@{ + ViewBag.Title = "Locked Out"; +} + +
    +

    Locked out.

    +

    This account has been locked out, please try again later.

    +
    \ No newline at end of file diff --git a/samples/MusicStore/Views/Shared/StatusCodePage.cshtml b/samples/MusicStore/Views/Shared/StatusCodePage.cshtml new file mode 100644 index 0000000..53dff87 --- /dev/null +++ b/samples/MusicStore/Views/Shared/StatusCodePage.cshtml @@ -0,0 +1,6 @@ +@{ + ViewBag.Title = "Item not found"; +} + +

    Item not found.

    +

    Unable to find the item you are searching for. Please try again.

    \ No newline at end of file diff --git a/samples/MusicStore/Views/Shared/_Layout.cshtml b/samples/MusicStore/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000..d2009c0 --- /dev/null +++ b/samples/MusicStore/Views/Shared/_Layout.cshtml @@ -0,0 +1,74 @@ +@inject IOptions AppSettings + + + + + + @ViewBag.Title – @AppSettings.Value.SiteTitle + + + + + + + + + + + + + + + + + + +
    + @RenderBody() +
    + +
    + + + + + + + + + + + @RenderSection("scripts", required: false) + + \ No newline at end of file diff --git a/samples/MusicStore/Views/Shared/_LoginPartial.cshtml b/samples/MusicStore/Views/Shared/_LoginPartial.cshtml new file mode 100644 index 0000000..2595492 --- /dev/null +++ b/samples/MusicStore/Views/Shared/_LoginPartial.cshtml @@ -0,0 +1,33 @@ +@using Microsoft.AspNetCore.Identity +@using MusicStore.Models + +@inject SignInManager SignInManager +@inject UserManager UserManager + +@if (SignInManager.IsSignedIn(User)) +{ + +} +else if (User.Identity.IsAuthenticated) +{ + //This code block necessary only for NTLM authentication + +} +else +{ + +} \ No newline at end of file diff --git a/samples/MusicStore/Views/Shared/_ValidationScriptsPartial.cshtml b/samples/MusicStore/Views/Shared/_ValidationScriptsPartial.cshtml new file mode 100644 index 0000000..abfa56e --- /dev/null +++ b/samples/MusicStore/Views/Shared/_ValidationScriptsPartial.cshtml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/MusicStore/Views/ShoppingCart/Index.cshtml b/samples/MusicStore/Views/ShoppingCart/Index.cshtml new file mode 100644 index 0000000..4928fff --- /dev/null +++ b/samples/MusicStore/Views/ShoppingCart/Index.cshtml @@ -0,0 +1,106 @@ +@model MusicStore.ViewModels.ShoppingCartViewModel +@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf +@{ + ViewBag.Title = "Shopping Cart"; +} + +@functions +{ + public string GetAntiXsrfRequestToken() + { + return Xsrf.GetAndStoreTokens(Context).RequestToken; + } +} + +@section Scripts { + +} + +

    + Review your cart: +

    +

    + Checkout >> +

    +
    +
    + + + + + + + + @foreach (var item in Model.CartItems) + { + + + + + + + } + + + + + + +
    + Album Name + + Price (each) + + Quantity +
    + @item.Album.Title + + @item.Album.Price + + @item.Count + + + Remove from cart + +
    + Total + + @Model.CartTotal +
    \ No newline at end of file diff --git a/samples/MusicStore/Views/Store/Browse.cshtml b/samples/MusicStore/Views/Store/Browse.cshtml new file mode 100644 index 0000000..9b015e7 --- /dev/null +++ b/samples/MusicStore/Views/Store/Browse.cshtml @@ -0,0 +1,24 @@ +@model Genre +@{ + ViewBag.Title = "Browse Albums"; +} +
    +

    + @Model.Name Albums +

    + + +
    \ No newline at end of file diff --git a/samples/MusicStore/Views/Store/Details.cshtml b/samples/MusicStore/Views/Store/Details.cshtml new file mode 100644 index 0000000..cdcab0c --- /dev/null +++ b/samples/MusicStore/Views/Store/Details.cshtml @@ -0,0 +1,29 @@ +@model Album + +@{ + ViewBag.Title = "Album - " + Model.Title; +} + +

    @Model.Title

    + +

    + @Model.Title +

    + +
    +

    + Genre: + @Model.Genre.Name +

    +

    + Artist: + @Model.Artist.Name +

    +

    + Price: + +

    +

    + Add to cart +

    +
    \ No newline at end of file diff --git a/samples/MusicStore/Views/Store/Index.cshtml b/samples/MusicStore/Views/Store/Index.cshtml new file mode 100644 index 0000000..3a40308 --- /dev/null +++ b/samples/MusicStore/Views/Store/Index.cshtml @@ -0,0 +1,15 @@ +@model IEnumerable +@{ + ViewBag.Title = "Store"; +} +

    Browse Genres

    + +

    + Select from @Model.Count() genres: +

    +
      + @foreach (var genre in Model) + { +
    • @genre.Name
    • + } +
    \ No newline at end of file diff --git a/samples/MusicStore/Views/_ViewImports.cshtml b/samples/MusicStore/Views/_ViewImports.cshtml new file mode 100644 index 0000000..fa2866b --- /dev/null +++ b/samples/MusicStore/Views/_ViewImports.cshtml @@ -0,0 +1,5 @@ +@using MusicStore +@using MusicStore.Models +@using Microsoft.Extensions.Options +@using Microsoft.AspNetCore.Identity +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/samples/MusicStore/Views/_ViewStart.cshtml b/samples/MusicStore/Views/_ViewStart.cshtml new file mode 100644 index 0000000..ab23e9a --- /dev/null +++ b/samples/MusicStore/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "/Views/Shared/_Layout.cshtml"; +} \ No newline at end of file diff --git a/samples/MusicStore/config.json b/samples/MusicStore/config.json new file mode 100644 index 0000000..6c33b8a --- /dev/null +++ b/samples/MusicStore/config.json @@ -0,0 +1,15 @@ +{ + "AppSettings": { + "SiteTitle": "ASP.NET MVC Music Store", + "CacheDbResults": true + }, + "DefaultAdminUsername": "Administrator@test.com", + "DefaultAdminPassword": "YouShouldChangeThisPassword1!", + "Data": { + "DefaultConnection": { + // Use a shared (and running) LocalDB database when executing in IIS e.g. + // "Server=(localdb)\\.\\IIS_DB;Database=MusicStore;Trusted_Connection=False;MultipleActiveResultSets=true;User ID=iis_login;Password=********" + "ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=MusicStore;Trusted_Connection=True;MultipleActiveResultSets=true;Connect Timeout=30;" + } + } +} \ No newline at end of file diff --git a/samples/MusicStore/wwwroot/Content/Site.css b/samples/MusicStore/wwwroot/Content/Site.css new file mode 100644 index 0000000..23c0d16 --- /dev/null +++ b/samples/MusicStore/wwwroot/Content/Site.css @@ -0,0 +1,70 @@ +body { + padding-top: 50px; + padding-bottom: 20px; +} + +/* Set padding to keep content from hitting the edges */ +.body-content { + padding-left: 15px; + padding-right: 15px; +} + +/* Set width on the form input elements since they're 100% wide by default */ +input, +select, +textarea { + max-width: 280px; +} + +/* styles for validation helpers */ +.field-validation-error { + color: #b94a48; +} + +.field-validation-valid { + display: none; +} + +input.input-validation-error { + border: 1px solid #b94a48; +} + +input[type="checkbox"].input-validation-error { + border: 0 none; +} + +.validation-summary-errors { + color: #b94a48; +} + +.validation-summary-valid { + display: none; +} + + +/* Music Store additions */ + +ul#album-list li { + height: 160px; +} + +ul#album-list li img:hover { + box-shadow: 1px 1px 7px #777; +} + +ul#album-list li img { + box-shadow: 1px 1px 5px #999; + border: none; + padding: 0; +} + +ul#album-list li a, ul#album-details li a { + text-decoration:none; +} + +ul#album-list li a:hover { + background: none; + -webkit-text-shadow: 1px 1px 2px #bbb; + text-shadow: 1px 1px 2px #bbb; + color: #363430; +} \ No newline at end of file diff --git a/samples/MusicStore/wwwroot/Content/bootstrap.css b/samples/MusicStore/wwwroot/Content/bootstrap.css new file mode 100644 index 0000000..6d6e682 --- /dev/null +++ b/samples/MusicStore/wwwroot/Content/bootstrap.css @@ -0,0 +1,6816 @@ +/* NUGET: BEGIN LICENSE TEXT + * + * Microsoft grants you the right to use these script files for the sole + * purpose of either: (i) interacting through your browser with the Microsoft + * website or online service, subject to the applicable licensing or use + * terms; or (ii) using the files as included with a Microsoft product subject + * to that product's license terms. Microsoft reserves all other rights to the + * files not expressly granted by Microsoft, whether by implication, estoppel + * or otherwise. The notices and licenses below are for informational purposes only. + * + * NUGET: END LICENSE TEXT */ +/*! + * Bootstrap v3.0.0 + * + * Copyright 2013 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + */ + +/*! normalize.css v2.1.0 | MIT License | git.io/normalize */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} + +audio, +canvas, +video { + display: inline-block; +} + +audio:not([controls]) { + display: none; + height: 0; +} + +[hidden] { + display: none; +} + +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +body { + margin: 0; +} + +a:focus { + outline: thin dotted; +} + +a:active, +a:hover { + outline: 0; +} + +h1 { + margin: 0.67em 0; + font-size: 2em; +} + +abbr[title] { + border-bottom: 1px dotted; +} + +b, +strong { + font-weight: bold; +} + +dfn { + font-style: italic; +} + +hr { + height: 0; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +mark { + color: #000; + background: #ff0; +} + +code, +kbd, +pre, +samp { + font-family: monospace, serif; + font-size: 1em; +} + +pre { + white-space: pre-wrap; +} + +q { + quotes: "\201C" "\201D" "\2018" "\2019"; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +img { + border: 0; +} + +svg:not(:root) { + overflow: hidden; +} + +figure { + margin: 0; +} + +fieldset { + padding: 0.35em 0.625em 0.75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} + +legend { + padding: 0; + border: 0; +} + +button, +input, +select, +textarea { + margin: 0; + font-family: inherit; + font-size: 100%; +} + +button, +input { + line-height: normal; +} + +button, +select { + text-transform: none; +} + +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; + -webkit-appearance: button; +} + +button[disabled], +html input[disabled] { + cursor: default; +} + +input[type="checkbox"], +input[type="radio"] { + padding: 0; + box-sizing: border-box; +} + +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} + +textarea { + overflow: auto; + vertical-align: top; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +@media print { + * { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + @page { + margin: 2cm .5cm; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .table td, + .table th { + background-color: #fff !important; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} + +*, +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-size: 62.5%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.428571429; + color: #333333; + background-color: #ffffff; +} + +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input, +select[multiple], +textarea { + background-image: none; +} + +a { + color: #428bca; + text-decoration: none; +} + +a:hover, +a:focus { + color: #2a6496; + text-decoration: underline; +} + +a:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +img { + vertical-align: middle; +} + +.img-responsive { + display: block; + height: auto; + max-width: 100%; +} + +.img-rounded { + border-radius: 6px; +} + +.img-thumbnail { + display: inline-block; + height: auto; + max-width: 100%; + padding: 4px; + line-height: 1.428571429; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} + +.img-circle { + border-radius: 50%; +} + +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eeeeee; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0 0 0 0); + border: 0; +} + +p { + margin: 0 0 10px; +} + +.lead { + margin-bottom: 20px; + font-size: 16.099999999999998px; + font-weight: 200; + line-height: 1.4; +} + +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} + +small { + font-size: 85%; +} + +cite { + font-style: normal; +} + +.text-muted { + color: #999999; +} + +.text-primary { + color: #428bca; +} + +.text-warning { + color: #c09853; +} + +.text-danger { + color: #b94a48; +} + +.text-success { + color: #468847; +} + +.text-info { + color: #3a87ad; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-center { + text-align: center; +} + +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 500; + line-height: 1.1; +} + +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small { + font-weight: normal; + line-height: 1; + color: #999999; +} + +h1, +h2, +h3 { + margin-top: 20px; + margin-bottom: 10px; +} + +h4, +h5, +h6 { + margin-top: 10px; + margin-bottom: 10px; +} + +h1, +.h1 { + font-size: 36px; +} + +h2, +.h2 { + font-size: 30px; +} + +h3, +.h3 { + font-size: 24px; +} + +h4, +.h4 { + font-size: 18px; +} + +h5, +.h5 { + font-size: 14px; +} + +h6, +.h6 { + font-size: 12px; +} + +h1 small, +.h1 small { + font-size: 24px; +} + +h2 small, +.h2 small { + font-size: 18px; +} + +h3 small, +.h3 small, +h4 small, +.h4 small { + font-size: 14px; +} + +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eeeeee; +} + +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} + +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + list-style: none; +} + +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} + +dl { + margin-bottom: 20px; +} + +dt, +dd { + line-height: 1.428571429; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 0; +} + +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } + .dl-horizontal dd:before, + .dl-horizontal dd:after { + display: table; + content: " "; + } + .dl-horizontal dd:after { + clear: both; + } + .dl-horizontal dd:before, + .dl-horizontal dd:after { + display: table; + content: " "; + } + .dl-horizontal dd:after { + clear: both; + } +} + +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #999999; +} + +abbr.initialism { + font-size: 90%; + text-transform: uppercase; +} + +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + border-left: 5px solid #eeeeee; +} + +blockquote p { + font-size: 17.5px; + font-weight: 300; + line-height: 1.25; +} + +blockquote p:last-child { + margin-bottom: 0; +} + +blockquote small { + display: block; + line-height: 1.428571429; + color: #999999; +} + +blockquote small:before { + content: '\2014 \00A0'; +} + +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #eeeeee; + border-left: 0; +} + +blockquote.pull-right p, +blockquote.pull-right small { + text-align: right; +} + +blockquote.pull-right small:before { + content: ''; +} + +blockquote.pull-right small:after { + content: '\00A0 \2014'; +} + +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} + +address { + display: block; + margin-bottom: 20px; + font-style: normal; + line-height: 1.428571429; +} + +code, +pre { + font-family: Monaco, Menlo, Consolas, "Courier New", monospace; +} + +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + white-space: nowrap; + background-color: #f9f2f4; + border-radius: 4px; +} + +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.428571429; + color: #333333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #cccccc; + border-radius: 4px; +} + +pre.prettyprint { + margin-bottom: 20px; +} + +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border: 0; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +.container:before, +.container:after { + display: table; + content: " "; +} + +.container:after { + clear: both; +} + +.container:before, +.container:after { + display: table; + content: " "; +} + +.container:after { + clear: both; +} + +.row { + margin-right: -15px; + margin-left: -15px; +} + +.row:before, +.row:after { + display: table; + content: " "; +} + +.row:after { + clear: both; +} + +.row:before, +.row:after { + display: table; + content: " "; +} + +.row:after { + clear: both; +} + +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11, +.col-xs-12, +.col-sm-1, +.col-sm-2, +.col-sm-3, +.col-sm-4, +.col-sm-5, +.col-sm-6, +.col-sm-7, +.col-sm-8, +.col-sm-9, +.col-sm-10, +.col-sm-11, +.col-sm-12, +.col-md-1, +.col-md-2, +.col-md-3, +.col-md-4, +.col-md-5, +.col-md-6, +.col-md-7, +.col-md-8, +.col-md-9, +.col-md-10, +.col-md-11, +.col-md-12, +.col-lg-1, +.col-lg-2, +.col-lg-3, +.col-lg-4, +.col-lg-5, +.col-lg-6, +.col-lg-7, +.col-lg-8, +.col-lg-9, +.col-lg-10, +.col-lg-11, +.col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} + +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11 { + float: left; +} + +.col-xs-1 { + width: 8.333333333333332%; +} + +.col-xs-2 { + width: 16.666666666666664%; +} + +.col-xs-3 { + width: 25%; +} + +.col-xs-4 { + width: 33.33333333333333%; +} + +.col-xs-5 { + width: 41.66666666666667%; +} + +.col-xs-6 { + width: 50%; +} + +.col-xs-7 { + width: 58.333333333333336%; +} + +.col-xs-8 { + width: 66.66666666666666%; +} + +.col-xs-9 { + width: 75%; +} + +.col-xs-10 { + width: 83.33333333333334%; +} + +.col-xs-11 { + width: 91.66666666666666%; +} + +.col-xs-12 { + width: 100%; +} + +@media (min-width: 768px) { + .container { + max-width: 750px; + } + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11 { + float: left; + } + .col-sm-1 { + width: 8.333333333333332%; + } + .col-sm-2 { + width: 16.666666666666664%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-4 { + width: 33.33333333333333%; + } + .col-sm-5 { + width: 41.66666666666667%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-7 { + width: 58.333333333333336%; + } + .col-sm-8 { + width: 66.66666666666666%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-10 { + width: 83.33333333333334%; + } + .col-sm-11 { + width: 91.66666666666666%; + } + .col-sm-12 { + width: 100%; + } + .col-sm-push-1 { + left: 8.333333333333332%; + } + .col-sm-push-2 { + left: 16.666666666666664%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-4 { + left: 33.33333333333333%; + } + .col-sm-push-5 { + left: 41.66666666666667%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-7 { + left: 58.333333333333336%; + } + .col-sm-push-8 { + left: 66.66666666666666%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-10 { + left: 83.33333333333334%; + } + .col-sm-push-11 { + left: 91.66666666666666%; + } + .col-sm-pull-1 { + right: 8.333333333333332%; + } + .col-sm-pull-2 { + right: 16.666666666666664%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-4 { + right: 33.33333333333333%; + } + .col-sm-pull-5 { + right: 41.66666666666667%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-7 { + right: 58.333333333333336%; + } + .col-sm-pull-8 { + right: 66.66666666666666%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-10 { + right: 83.33333333333334%; + } + .col-sm-pull-11 { + right: 91.66666666666666%; + } + .col-sm-offset-1 { + margin-left: 8.333333333333332%; + } + .col-sm-offset-2 { + margin-left: 16.666666666666664%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-4 { + margin-left: 33.33333333333333%; + } + .col-sm-offset-5 { + margin-left: 41.66666666666667%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-7 { + margin-left: 58.333333333333336%; + } + .col-sm-offset-8 { + margin-left: 66.66666666666666%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-10 { + margin-left: 83.33333333333334%; + } + .col-sm-offset-11 { + margin-left: 91.66666666666666%; + } +} + +@media (min-width: 992px) { + .container { + max-width: 970px; + } + .col-md-1, + .col-md-2, + .col-md-3, + .col-md-4, + .col-md-5, + .col-md-6, + .col-md-7, + .col-md-8, + .col-md-9, + .col-md-10, + .col-md-11 { + float: left; + } + .col-md-1 { + width: 8.333333333333332%; + } + .col-md-2 { + width: 16.666666666666664%; + } + .col-md-3 { + width: 25%; + } + .col-md-4 { + width: 33.33333333333333%; + } + .col-md-5 { + width: 41.66666666666667%; + } + .col-md-6 { + width: 50%; + } + .col-md-7 { + width: 58.333333333333336%; + } + .col-md-8 { + width: 66.66666666666666%; + } + .col-md-9 { + width: 75%; + } + .col-md-10 { + width: 83.33333333333334%; + } + .col-md-11 { + width: 91.66666666666666%; + } + .col-md-12 { + width: 100%; + } + .col-md-push-0 { + left: auto; + } + .col-md-push-1 { + left: 8.333333333333332%; + } + .col-md-push-2 { + left: 16.666666666666664%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-4 { + left: 33.33333333333333%; + } + .col-md-push-5 { + left: 41.66666666666667%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-7 { + left: 58.333333333333336%; + } + .col-md-push-8 { + left: 66.66666666666666%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-10 { + left: 83.33333333333334%; + } + .col-md-push-11 { + left: 91.66666666666666%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-pull-1 { + right: 8.333333333333332%; + } + .col-md-pull-2 { + right: 16.666666666666664%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-4 { + right: 33.33333333333333%; + } + .col-md-pull-5 { + right: 41.66666666666667%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-7 { + right: 58.333333333333336%; + } + .col-md-pull-8 { + right: 66.66666666666666%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-10 { + right: 83.33333333333334%; + } + .col-md-pull-11 { + right: 91.66666666666666%; + } + .col-md-offset-0 { + margin-left: 0; + } + .col-md-offset-1 { + margin-left: 8.333333333333332%; + } + .col-md-offset-2 { + margin-left: 16.666666666666664%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-4 { + margin-left: 33.33333333333333%; + } + .col-md-offset-5 { + margin-left: 41.66666666666667%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-7 { + margin-left: 58.333333333333336%; + } + .col-md-offset-8 { + margin-left: 66.66666666666666%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-10 { + margin-left: 83.33333333333334%; + } + .col-md-offset-11 { + margin-left: 91.66666666666666%; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1170px; + } + .col-lg-1, + .col-lg-2, + .col-lg-3, + .col-lg-4, + .col-lg-5, + .col-lg-6, + .col-lg-7, + .col-lg-8, + .col-lg-9, + .col-lg-10, + .col-lg-11 { + float: left; + } + .col-lg-1 { + width: 8.333333333333332%; + } + .col-lg-2 { + width: 16.666666666666664%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-4 { + width: 33.33333333333333%; + } + .col-lg-5 { + width: 41.66666666666667%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-7 { + width: 58.333333333333336%; + } + .col-lg-8 { + width: 66.66666666666666%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-10 { + width: 83.33333333333334%; + } + .col-lg-11 { + width: 91.66666666666666%; + } + .col-lg-12 { + width: 100%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-push-1 { + left: 8.333333333333332%; + } + .col-lg-push-2 { + left: 16.666666666666664%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-4 { + left: 33.33333333333333%; + } + .col-lg-push-5 { + left: 41.66666666666667%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-7 { + left: 58.333333333333336%; + } + .col-lg-push-8 { + left: 66.66666666666666%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-10 { + left: 83.33333333333334%; + } + .col-lg-push-11 { + left: 91.66666666666666%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-pull-1 { + right: 8.333333333333332%; + } + .col-lg-pull-2 { + right: 16.666666666666664%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-4 { + right: 33.33333333333333%; + } + .col-lg-pull-5 { + right: 41.66666666666667%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-7 { + right: 58.333333333333336%; + } + .col-lg-pull-8 { + right: 66.66666666666666%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-10 { + right: 83.33333333333334%; + } + .col-lg-pull-11 { + right: 91.66666666666666%; + } + .col-lg-offset-0 { + margin-left: 0; + } + .col-lg-offset-1 { + margin-left: 8.333333333333332%; + } + .col-lg-offset-2 { + margin-left: 16.666666666666664%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-4 { + margin-left: 33.33333333333333%; + } + .col-lg-offset-5 { + margin-left: 41.66666666666667%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-7 { + margin-left: 58.333333333333336%; + } + .col-lg-offset-8 { + margin-left: 66.66666666666666%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-10 { + margin-left: 83.33333333333334%; + } + .col-lg-offset-11 { + margin-left: 91.66666666666666%; + } +} + +table { + max-width: 100%; + background-color: transparent; +} + +th { + text-align: left; +} + +.table { + width: 100%; + margin-bottom: 20px; +} + +.table thead > tr > th, +.table tbody > tr > th, +.table tfoot > tr > th, +.table thead > tr > td, +.table tbody > tr > td, +.table tfoot > tr > td { + padding: 8px; + line-height: 1.428571429; + vertical-align: top; + border-top: 1px solid #dddddd; +} + +.table thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #dddddd; +} + +.table caption + thead tr:first-child th, +.table colgroup + thead tr:first-child th, +.table thead:first-child tr:first-child th, +.table caption + thead tr:first-child td, +.table colgroup + thead tr:first-child td, +.table thead:first-child tr:first-child td { + border-top: 0; +} + +.table tbody + tbody { + border-top: 2px solid #dddddd; +} + +.table .table { + background-color: #ffffff; +} + +.table-condensed thead > tr > th, +.table-condensed tbody > tr > th, +.table-condensed tfoot > tr > th, +.table-condensed thead > tr > td, +.table-condensed tbody > tr > td, +.table-condensed tfoot > tr > td { + padding: 5px; +} + +.table-bordered { + border: 1px solid #dddddd; +} + +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #dddddd; +} + +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} + +.table-striped > tbody > tr:nth-child(odd) > td, +.table-striped > tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; +} + +.table-hover > tbody > tr:hover > td, +.table-hover > tbody > tr:hover > th { + background-color: #f5f5f5; +} + +table col[class*="col-"] { + display: table-column; + float: none; +} + +table td[class*="col-"], +table th[class*="col-"] { + display: table-cell; + float: none; +} + +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} + +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td { + background-color: #d0e9c6; + border-color: #c9e2b3; +} + +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; + border-color: #eed3d7; +} + +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td { + background-color: #ebcccc; + border-color: #e6c1c7; +} + +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td { + background-color: #faf2cc; + border-color: #f8e5be; +} + +@media (max-width: 768px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-x: scroll; + overflow-y: hidden; + border: 1px solid #dddddd; + } + .table-responsive > .table { + margin-bottom: 0; + background-color: #fff; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > thead > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > thead > tr:last-child > td, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} + +fieldset { + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} + +label { + display: inline-block; + margin-bottom: 5px; + font-weight: bold; +} + +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + /* IE8-9 */ + + line-height: normal; +} + +input[type="file"] { + display: block; +} + +select[multiple], +select[size] { + height: auto; +} + +select optgroup { + font-family: inherit; + font-size: inherit; + font-style: inherit; +} + +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { + height: auto; +} + +.form-control:-moz-placeholder { + color: #999999; +} + +.form-control::-moz-placeholder { + color: #999999; +} + +.form-control:-ms-input-placeholder { + color: #999999; +} + +.form-control::-webkit-input-placeholder { + color: #999999; +} + +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.428571429; + color: #555555; + vertical-align: middle; + background-color: #ffffff; + border: 1px solid #cccccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; +} + +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eeeeee; +} + +textarea.form-control { + height: auto; +} + +.form-group { + margin-bottom: 15px; +} + +.radio, +.checkbox { + display: block; + min-height: 20px; + padding-left: 20px; + margin-top: 10px; + margin-bottom: 10px; + vertical-align: middle; +} + +.radio label, +.checkbox label { + display: inline; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} + +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + float: left; + margin-left: -20px; +} + +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} + +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} + +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} + +input[type="radio"][disabled], +input[type="checkbox"][disabled], +.radio[disabled], +.radio-inline[disabled], +.checkbox[disabled], +.checkbox-inline[disabled], +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"], +fieldset[disabled] .radio, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} + +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +select.input-sm { + height: 30px; + line-height: 30px; +} + +textarea.input-sm { + height: auto; +} + +.input-lg { + height: 45px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +select.input-lg { + height: 45px; + line-height: 45px; +} + +textarea.input-lg { + height: auto; +} + +.has-warning .help-block, +.has-warning .control-label { + color: #c09853; +} + +.has-warning .form-control { + border-color: #c09853; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.has-warning .form-control:focus { + border-color: #a47e3c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; +} + +.has-warning .input-group-addon { + color: #c09853; + background-color: #fcf8e3; + border-color: #c09853; +} + +.has-error .help-block, +.has-error .control-label { + color: #b94a48; +} + +.has-error .form-control { + border-color: #b94a48; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.has-error .form-control:focus { + border-color: #953b39; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; +} + +.has-error .input-group-addon { + color: #b94a48; + background-color: #f2dede; + border-color: #b94a48; +} + +.has-success .help-block, +.has-success .control-label { + color: #468847; +} + +.has-success .form-control { + border-color: #468847; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.has-success .form-control:focus { + border-color: #356635; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; +} + +.has-success .input-group-addon { + color: #468847; + background-color: #dff0d8; + border-color: #468847; +} + +.form-control-static { + padding-top: 7px; + margin-bottom: 0; +} + +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} + +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } +} + +.form-horizontal .control-label, +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} + +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} + +.form-horizontal .form-group:before, +.form-horizontal .form-group:after { + display: table; + content: " "; +} + +.form-horizontal .form-group:after { + clear: both; +} + +.form-horizontal .form-group:before, +.form-horizontal .form-group:after { + display: table; + content: " "; +} + +.form-horizontal .form-group:after { + clear: both; +} + +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + } +} + +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.428571429; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + border: 1px solid transparent; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +.btn:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +.btn:hover, +.btn:focus { + color: #333333; + text-decoration: none; +} + +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} + +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + pointer-events: none; + cursor: not-allowed; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn-default { + color: #333333; + background-color: #ffffff; + border-color: #cccccc; +} + +.btn-default:hover, +.btn-default:focus, +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + color: #333333; + background-color: #ebebeb; + border-color: #adadad; +} + +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + background-image: none; +} + +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #ffffff; + border-color: #cccccc; +} + +.btn-primary { + color: #ffffff; + background-color: #428bca; + border-color: #357ebd; +} + +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + color: #ffffff; + background-color: #3276b1; + border-color: #285e8e; +} + +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + background-image: none; +} + +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #428bca; + border-color: #357ebd; +} + +.btn-warning { + color: #ffffff; + background-color: #f0ad4e; + border-color: #eea236; +} + +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + color: #ffffff; + background-color: #ed9c28; + border-color: #d58512; +} + +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + background-image: none; +} + +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} + +.btn-danger { + color: #ffffff; + background-color: #d9534f; + border-color: #d43f3a; +} + +.btn-danger:hover, +.btn-danger:focus, +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + color: #ffffff; + background-color: #d2322d; + border-color: #ac2925; +} + +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + background-image: none; +} + +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} + +.btn-success { + color: #ffffff; + background-color: #5cb85c; + border-color: #4cae4c; +} + +.btn-success:hover, +.btn-success:focus, +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + color: #ffffff; + background-color: #47a447; + border-color: #398439; +} + +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + background-image: none; +} + +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} + +.btn-info { + color: #ffffff; + background-color: #5bc0de; + border-color: #46b8da; +} + +.btn-info:hover, +.btn-info:focus, +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + color: #ffffff; + background-color: #39b3d7; + border-color: #269abc; +} + +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + background-image: none; +} + +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} + +.btn-link { + font-weight: normal; + color: #428bca; + cursor: pointer; + border-radius: 0; +} + +.btn-link, +.btn-link:active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} + +.btn-link:hover, +.btn-link:focus { + color: #2a6496; + text-decoration: underline; + background-color: transparent; +} + +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #999999; + text-decoration: none; +} + +.btn-lg { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +.btn-sm, +.btn-xs { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-xs { + padding: 1px 5px; +} + +.btn-block { + display: block; + width: 100%; + padding-right: 0; + padding-left: 0; +} + +.btn-block + .btn-block { + margin-top: 5px; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} + +.fade.in { + opacity: 1; +} + +.collapse { + display: none; +} + +.collapse.in { + display: block; +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + transition: height 0.35s ease; +} + +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg'); +} + +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + -webkit-font-smoothing: antialiased; + font-style: normal; + font-weight: normal; + line-height: 1; +} + +.glyphicon-asterisk:before { + content: "\2a"; +} + +.glyphicon-plus:before { + content: "\2b"; +} + +.glyphicon-euro:before { + content: "\20ac"; +} + +.glyphicon-minus:before { + content: "\2212"; +} + +.glyphicon-cloud:before { + content: "\2601"; +} + +.glyphicon-envelope:before { + content: "\2709"; +} + +.glyphicon-pencil:before { + content: "\270f"; +} + +.glyphicon-glass:before { + content: "\e001"; +} + +.glyphicon-music:before { + content: "\e002"; +} + +.glyphicon-search:before { + content: "\e003"; +} + +.glyphicon-heart:before { + content: "\e005"; +} + +.glyphicon-star:before { + content: "\e006"; +} + +.glyphicon-star-empty:before { + content: "\e007"; +} + +.glyphicon-user:before { + content: "\e008"; +} + +.glyphicon-film:before { + content: "\e009"; +} + +.glyphicon-th-large:before { + content: "\e010"; +} + +.glyphicon-th:before { + content: "\e011"; +} + +.glyphicon-th-list:before { + content: "\e012"; +} + +.glyphicon-ok:before { + content: "\e013"; +} + +.glyphicon-remove:before { + content: "\e014"; +} + +.glyphicon-zoom-in:before { + content: "\e015"; +} + +.glyphicon-zoom-out:before { + content: "\e016"; +} + +.glyphicon-off:before { + content: "\e017"; +} + +.glyphicon-signal:before { + content: "\e018"; +} + +.glyphicon-cog:before { + content: "\e019"; +} + +.glyphicon-trash:before { + content: "\e020"; +} + +.glyphicon-home:before { + content: "\e021"; +} + +.glyphicon-file:before { + content: "\e022"; +} + +.glyphicon-time:before { + content: "\e023"; +} + +.glyphicon-road:before { + content: "\e024"; +} + +.glyphicon-download-alt:before { + content: "\e025"; +} + +.glyphicon-download:before { + content: "\e026"; +} + +.glyphicon-upload:before { + content: "\e027"; +} + +.glyphicon-inbox:before { + content: "\e028"; +} + +.glyphicon-play-circle:before { + content: "\e029"; +} + +.glyphicon-repeat:before { + content: "\e030"; +} + +.glyphicon-refresh:before { + content: "\e031"; +} + +.glyphicon-list-alt:before { + content: "\e032"; +} + +.glyphicon-flag:before { + content: "\e034"; +} + +.glyphicon-headphones:before { + content: "\e035"; +} + +.glyphicon-volume-off:before { + content: "\e036"; +} + +.glyphicon-volume-down:before { + content: "\e037"; +} + +.glyphicon-volume-up:before { + content: "\e038"; +} + +.glyphicon-qrcode:before { + content: "\e039"; +} + +.glyphicon-barcode:before { + content: "\e040"; +} + +.glyphicon-tag:before { + content: "\e041"; +} + +.glyphicon-tags:before { + content: "\e042"; +} + +.glyphicon-book:before { + content: "\e043"; +} + +.glyphicon-print:before { + content: "\e045"; +} + +.glyphicon-font:before { + content: "\e047"; +} + +.glyphicon-bold:before { + content: "\e048"; +} + +.glyphicon-italic:before { + content: "\e049"; +} + +.glyphicon-text-height:before { + content: "\e050"; +} + +.glyphicon-text-width:before { + content: "\e051"; +} + +.glyphicon-align-left:before { + content: "\e052"; +} + +.glyphicon-align-center:before { + content: "\e053"; +} + +.glyphicon-align-right:before { + content: "\e054"; +} + +.glyphicon-align-justify:before { + content: "\e055"; +} + +.glyphicon-list:before { + content: "\e056"; +} + +.glyphicon-indent-left:before { + content: "\e057"; +} + +.glyphicon-indent-right:before { + content: "\e058"; +} + +.glyphicon-facetime-video:before { + content: "\e059"; +} + +.glyphicon-picture:before { + content: "\e060"; +} + +.glyphicon-map-marker:before { + content: "\e062"; +} + +.glyphicon-adjust:before { + content: "\e063"; +} + +.glyphicon-tint:before { + content: "\e064"; +} + +.glyphicon-edit:before { + content: "\e065"; +} + +.glyphicon-share:before { + content: "\e066"; +} + +.glyphicon-check:before { + content: "\e067"; +} + +.glyphicon-move:before { + content: "\e068"; +} + +.glyphicon-step-backward:before { + content: "\e069"; +} + +.glyphicon-fast-backward:before { + content: "\e070"; +} + +.glyphicon-backward:before { + content: "\e071"; +} + +.glyphicon-play:before { + content: "\e072"; +} + +.glyphicon-pause:before { + content: "\e073"; +} + +.glyphicon-stop:before { + content: "\e074"; +} + +.glyphicon-forward:before { + content: "\e075"; +} + +.glyphicon-fast-forward:before { + content: "\e076"; +} + +.glyphicon-step-forward:before { + content: "\e077"; +} + +.glyphicon-eject:before { + content: "\e078"; +} + +.glyphicon-chevron-left:before { + content: "\e079"; +} + +.glyphicon-chevron-right:before { + content: "\e080"; +} + +.glyphicon-plus-sign:before { + content: "\e081"; +} + +.glyphicon-minus-sign:before { + content: "\e082"; +} + +.glyphicon-remove-sign:before { + content: "\e083"; +} + +.glyphicon-ok-sign:before { + content: "\e084"; +} + +.glyphicon-question-sign:before { + content: "\e085"; +} + +.glyphicon-info-sign:before { + content: "\e086"; +} + +.glyphicon-screenshot:before { + content: "\e087"; +} + +.glyphicon-remove-circle:before { + content: "\e088"; +} + +.glyphicon-ok-circle:before { + content: "\e089"; +} + +.glyphicon-ban-circle:before { + content: "\e090"; +} + +.glyphicon-arrow-left:before { + content: "\e091"; +} + +.glyphicon-arrow-right:before { + content: "\e092"; +} + +.glyphicon-arrow-up:before { + content: "\e093"; +} + +.glyphicon-arrow-down:before { + content: "\e094"; +} + +.glyphicon-share-alt:before { + content: "\e095"; +} + +.glyphicon-resize-full:before { + content: "\e096"; +} + +.glyphicon-resize-small:before { + content: "\e097"; +} + +.glyphicon-exclamation-sign:before { + content: "\e101"; +} + +.glyphicon-gift:before { + content: "\e102"; +} + +.glyphicon-leaf:before { + content: "\e103"; +} + +.glyphicon-eye-open:before { + content: "\e105"; +} + +.glyphicon-eye-close:before { + content: "\e106"; +} + +.glyphicon-warning-sign:before { + content: "\e107"; +} + +.glyphicon-plane:before { + content: "\e108"; +} + +.glyphicon-random:before { + content: "\e110"; +} + +.glyphicon-comment:before { + content: "\e111"; +} + +.glyphicon-magnet:before { + content: "\e112"; +} + +.glyphicon-chevron-up:before { + content: "\e113"; +} + +.glyphicon-chevron-down:before { + content: "\e114"; +} + +.glyphicon-retweet:before { + content: "\e115"; +} + +.glyphicon-shopping-cart:before { + content: "\e116"; +} + +.glyphicon-folder-close:before { + content: "\e117"; +} + +.glyphicon-folder-open:before { + content: "\e118"; +} + +.glyphicon-resize-vertical:before { + content: "\e119"; +} + +.glyphicon-resize-horizontal:before { + content: "\e120"; +} + +.glyphicon-hdd:before { + content: "\e121"; +} + +.glyphicon-bullhorn:before { + content: "\e122"; +} + +.glyphicon-certificate:before { + content: "\e124"; +} + +.glyphicon-thumbs-up:before { + content: "\e125"; +} + +.glyphicon-thumbs-down:before { + content: "\e126"; +} + +.glyphicon-hand-right:before { + content: "\e127"; +} + +.glyphicon-hand-left:before { + content: "\e128"; +} + +.glyphicon-hand-up:before { + content: "\e129"; +} + +.glyphicon-hand-down:before { + content: "\e130"; +} + +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} + +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} + +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} + +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} + +.glyphicon-globe:before { + content: "\e135"; +} + +.glyphicon-tasks:before { + content: "\e137"; +} + +.glyphicon-filter:before { + content: "\e138"; +} + +.glyphicon-fullscreen:before { + content: "\e140"; +} + +.glyphicon-dashboard:before { + content: "\e141"; +} + +.glyphicon-heart-empty:before { + content: "\e143"; +} + +.glyphicon-link:before { + content: "\e144"; +} + +.glyphicon-phone:before { + content: "\e145"; +} + +.glyphicon-usd:before { + content: "\e148"; +} + +.glyphicon-gbp:before { + content: "\e149"; +} + +.glyphicon-sort:before { + content: "\e150"; +} + +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} + +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} + +.glyphicon-sort-by-order:before { + content: "\e153"; +} + +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} + +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} + +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} + +.glyphicon-unchecked:before { + content: "\e157"; +} + +.glyphicon-expand:before { + content: "\e158"; +} + +.glyphicon-collapse-down:before { + content: "\e159"; +} + +.glyphicon-collapse-up:before { + content: "\e160"; +} + +.glyphicon-log-in:before { + content: "\e161"; +} + +.glyphicon-flash:before { + content: "\e162"; +} + +.glyphicon-log-out:before { + content: "\e163"; +} + +.glyphicon-new-window:before { + content: "\e164"; +} + +.glyphicon-record:before { + content: "\e165"; +} + +.glyphicon-save:before { + content: "\e166"; +} + +.glyphicon-open:before { + content: "\e167"; +} + +.glyphicon-saved:before { + content: "\e168"; +} + +.glyphicon-import:before { + content: "\e169"; +} + +.glyphicon-export:before { + content: "\e170"; +} + +.glyphicon-send:before { + content: "\e171"; +} + +.glyphicon-floppy-disk:before { + content: "\e172"; +} + +.glyphicon-floppy-saved:before { + content: "\e173"; +} + +.glyphicon-floppy-remove:before { + content: "\e174"; +} + +.glyphicon-floppy-save:before { + content: "\e175"; +} + +.glyphicon-floppy-open:before { + content: "\e176"; +} + +.glyphicon-credit-card:before { + content: "\e177"; +} + +.glyphicon-transfer:before { + content: "\e178"; +} + +.glyphicon-cutlery:before { + content: "\e179"; +} + +.glyphicon-header:before { + content: "\e180"; +} + +.glyphicon-compressed:before { + content: "\e181"; +} + +.glyphicon-earphone:before { + content: "\e182"; +} + +.glyphicon-phone-alt:before { + content: "\e183"; +} + +.glyphicon-tower:before { + content: "\e184"; +} + +.glyphicon-stats:before { + content: "\e185"; +} + +.glyphicon-sd-video:before { + content: "\e186"; +} + +.glyphicon-hd-video:before { + content: "\e187"; +} + +.glyphicon-subtitles:before { + content: "\e188"; +} + +.glyphicon-sound-stereo:before { + content: "\e189"; +} + +.glyphicon-sound-dolby:before { + content: "\e190"; +} + +.glyphicon-sound-5-1:before { + content: "\e191"; +} + +.glyphicon-sound-6-1:before { + content: "\e192"; +} + +.glyphicon-sound-7-1:before { + content: "\e193"; +} + +.glyphicon-copyright-mark:before { + content: "\e194"; +} + +.glyphicon-registration-mark:before { + content: "\e195"; +} + +.glyphicon-cloud-download:before { + content: "\e197"; +} + +.glyphicon-cloud-upload:before { + content: "\e198"; +} + +.glyphicon-tree-conifer:before { + content: "\e199"; +} + +.glyphicon-tree-deciduous:before { + content: "\e200"; +} + +.glyphicon-briefcase:before { + content: "\1f4bc"; +} + +.glyphicon-calendar:before { + content: "\1f4c5"; +} + +.glyphicon-pushpin:before { + content: "\1f4cc"; +} + +.glyphicon-paperclip:before { + content: "\1f4ce"; +} + +.glyphicon-camera:before { + content: "\1f4f7"; +} + +.glyphicon-lock:before { + content: "\1f512"; +} + +.glyphicon-bell:before { + content: "\1f514"; +} + +.glyphicon-bookmark:before { + content: "\1f516"; +} + +.glyphicon-fire:before { + content: "\1f525"; +} + +.glyphicon-wrench:before { + content: "\1f527"; +} + +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid #000000; + border-right: 4px solid transparent; + border-bottom: 0 dotted; + border-left: 4px solid transparent; + content: ""; +} + +.dropdown { + position: relative; +} + +.dropdown-toggle:focus { + outline: 0; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + list-style: none; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; +} + +.dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} + +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.428571429; + color: #333333; + white-space: nowrap; +} + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #ffffff; + text-decoration: none; + background-color: #428bca; +} + +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + background-color: #428bca; + outline: 0; +} + +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #999999; +} + +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + +.open > .dropdown-menu { + display: block; +} + +.open > a { + outline: 0; +} + +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.428571429; + color: #999999; +} + +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} + +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0 dotted; + border-bottom: 4px solid #000000; + content: ""; +} + +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; +} + +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } +} + +.btn-default .caret { + border-top-color: #333333; +} + +.btn-primary .caret, +.btn-success .caret, +.btn-warning .caret, +.btn-danger .caret, +.btn-info .caret { + border-top-color: #fff; +} + +.dropup .btn-default .caret { + border-bottom-color: #333333; +} + +.dropup .btn-primary .caret, +.dropup .btn-success .caret, +.dropup .btn-warning .caret, +.dropup .btn-danger .caret, +.dropup .btn-info .caret { + border-bottom-color: #fff; +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} + +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} + +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} + +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus { + outline: none; +} + +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} + +.btn-toolbar:before, +.btn-toolbar:after { + display: table; + content: " "; +} + +.btn-toolbar:after { + clear: both; +} + +.btn-toolbar:before, +.btn-toolbar:after { + display: table; + content: " "; +} + +.btn-toolbar:after { + clear: both; +} + +.btn-toolbar .btn-group { + float: left; +} + +.btn-toolbar > .btn + .btn, +.btn-toolbar > .btn-group + .btn, +.btn-toolbar > .btn + .btn-group, +.btn-toolbar > .btn-group + .btn-group { + margin-left: 5px; +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +.btn-group > .btn:first-child { + margin-left: 0; +} + +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.btn-group > .btn-group { + float: left; +} + +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group > .btn-group:first-child > .btn:last-child, +.btn-group > .btn-group:first-child > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn-group:last-child > .btn:first-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + +.btn-group-xs > .btn { + padding: 5px 10px; + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} + +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} + +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} + +.btn .caret { + margin-left: 0; +} + +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} + +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} + +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group { + display: block; + float: none; + width: 100%; + max-width: 100%; +} + +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after { + display: table; + content: " "; +} + +.btn-group-vertical > .btn-group:after { + clear: both; +} + +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after { + display: table; + content: " "; +} + +.btn-group-vertical > .btn-group:after { + clear: both; +} + +.btn-group-vertical > .btn-group > .btn { + float: none; +} + +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} + +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-right-radius: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 0; +} + +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} + +.btn-group-vertical > .btn-group:first-child > .btn:last-child, +.btn-group-vertical > .btn-group:first-child > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn-group:last-child > .btn:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.btn-group-justified { + display: table; + width: 100%; + border-collapse: separate; + table-layout: fixed; +} + +.btn-group-justified .btn { + display: table-cell; + float: none; + width: 1%; +} + +[data-toggle="buttons"] > .btn > input[type="radio"], +[data-toggle="buttons"] > .btn > input[type="checkbox"] { + display: none; +} + +.input-group { + position: relative; + display: table; + border-collapse: separate; +} + +.input-group.col { + float: none; + padding-right: 0; + padding-left: 0; +} + +.input-group .form-control { + width: 100%; + margin-bottom: 0; +} + +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 45px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} + +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 45px; + line-height: 45px; +} + +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn { + height: auto; +} + +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} + +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} + +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn { + height: auto; +} + +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} + +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} + +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + text-align: center; + background-color: #eeeeee; + border: 1px solid #cccccc; + border-radius: 4px; +} + +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} + +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} + +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} + +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group-addon:first-child { + border-right: 0; +} + +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.input-group-addon:last-child { + border-left: 0; +} + +.input-group-btn { + position: relative; + white-space: nowrap; +} + +.input-group-btn > .btn { + position: relative; +} + +.input-group-btn > .btn + .btn { + margin-left: -4px; +} + +.input-group-btn > .btn:hover, +.input-group-btn > .btn:active { + z-index: 2; +} + +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav:before, +.nav:after { + display: table; + content: " "; +} + +.nav:after { + clear: both; +} + +.nav:before, +.nav:after { + display: table; + content: " "; +} + +.nav:after { + clear: both; +} + +.nav > li { + position: relative; + display: block; +} + +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} + +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} + +.nav > li.disabled > a { + color: #999999; +} + +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #999999; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} + +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eeeeee; + border-color: #428bca; +} + +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} + +.nav > li > a > img { + max-width: none; +} + +.nav-tabs { + border-bottom: 1px solid #dddddd; +} + +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} + +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.428571429; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} + +.nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #dddddd; +} + +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555555; + cursor: default; + background-color: #ffffff; + border: 1px solid #dddddd; + border-bottom-color: transparent; +} + +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} + +.nav-tabs.nav-justified > li { + float: none; +} + +.nav-tabs.nav-justified > li > a { + text-align: center; +} + +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } +} + +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-bottom: 1px solid #dddddd; +} + +.nav-tabs.nav-justified > .active > a { + border-bottom-color: #ffffff; +} + +.nav-pills > li { + float: left; +} + +.nav-pills > li > a { + border-radius: 5px; +} + +.nav-pills > li + li { + margin-left: 2px; +} + +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #ffffff; + background-color: #428bca; +} + +.nav-stacked > li { + float: none; +} + +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} + +.nav-justified { + width: 100%; +} + +.nav-justified > li { + float: none; +} + +.nav-justified > li > a { + text-align: center; +} + +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } +} + +.nav-tabs-justified { + border-bottom: 0; +} + +.nav-tabs-justified > li > a { + margin-right: 0; + border-bottom: 1px solid #dddddd; +} + +.nav-tabs-justified > .active > a { + border-bottom-color: #ffffff; +} + +.tabbable:before, +.tabbable:after { + display: table; + content: " "; +} + +.tabbable:after { + clear: both; +} + +.tabbable:before, +.tabbable:after { + display: table; + content: " "; +} + +.tabbable:after { + clear: both; +} + +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} + +.tab-content > .active, +.pill-content > .active { + display: block; +} + +.nav .caret { + border-top-color: #428bca; + border-bottom-color: #428bca; +} + +.nav a:hover .caret { + border-top-color: #2a6496; + border-bottom-color: #2a6496; +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.navbar { + position: relative; + z-index: 1000; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} + +.navbar:before, +.navbar:after { + display: table; + content: " "; +} + +.navbar:after { + clear: both; +} + +.navbar:before, +.navbar:after { + display: table; + content: " "; +} + +.navbar:after { + clear: both; +} + +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} + +.navbar-header:before, +.navbar-header:after { + display: table; + content: " "; +} + +.navbar-header:after { + clear: both; +} + +.navbar-header:before, +.navbar-header:after { + display: table; + content: " "; +} + +.navbar-header:after { + clear: both; +} + +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} + +.navbar-collapse { + max-height: 340px; + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} + +.navbar-collapse:before, +.navbar-collapse:after { + display: table; + content: " "; +} + +.navbar-collapse:after { + clear: both; +} + +.navbar-collapse:before, +.navbar-collapse:after { + display: table; + content: " "; +} + +.navbar-collapse:after { + clear: both; +} + +.navbar-collapse.in { + overflow-y: auto; +} + +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-collapse .navbar-nav.navbar-left:first-child { + margin-left: -15px; + } + .navbar-collapse .navbar-nav.navbar-right:last-child { + margin-right: -15px; + } + .navbar-collapse .navbar-text:last-child { + margin-right: 0; + } +} + +.container > .navbar-header, +.container > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} + +@media (min-width: 768px) { + .container > .navbar-header, + .container > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} + +.navbar-static-top { + border-width: 0 0 1px; +} + +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + border-width: 0 0 1px; +} + +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} + +.navbar-fixed-top { + top: 0; + z-index: 1030; +} + +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; +} + +.navbar-brand { + float: left; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} + +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} + +@media (min-width: 768px) { + .navbar > .container .navbar-brand { + margin-left: -15px; + } +} + +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + border: 1px solid transparent; + border-radius: 4px; +} + +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} + +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} + +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} + +.navbar-nav { + margin: 7.5px -15px; +} + +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} + +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} + +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} + +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + } +} + +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); +} + +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } +} + +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } +} + +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} + +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.navbar-nav.pull-right > li > .dropdown-menu, +.navbar-nav > li > .dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} + +.navbar-text { + float: left; + margin-top: 15px; + margin-bottom: 15px; +} + +@media (min-width: 768px) { + .navbar-text { + margin-right: 15px; + margin-left: 15px; + } +} + +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} + +.navbar-default .navbar-brand { + color: #777777; +} + +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} + +.navbar-default .navbar-text { + color: #777777; +} + +.navbar-default .navbar-nav > li > a { + color: #777777; +} + +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333333; + background-color: transparent; +} + +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555555; + background-color: #e7e7e7; +} + +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #cccccc; + background-color: transparent; +} + +.navbar-default .navbar-toggle { + border-color: #dddddd; +} + +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #dddddd; +} + +.navbar-default .navbar-toggle .icon-bar { + background-color: #cccccc; +} + +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e6e6e6; +} + +.navbar-default .navbar-nav > .dropdown > a:hover .caret, +.navbar-default .navbar-nav > .dropdown > a:focus .caret { + border-top-color: #333333; + border-bottom-color: #333333; +} + +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555555; + background-color: #e7e7e7; +} + +.navbar-default .navbar-nav > .open > a .caret, +.navbar-default .navbar-nav > .open > a:hover .caret, +.navbar-default .navbar-nav > .open > a:focus .caret { + border-top-color: #555555; + border-bottom-color: #555555; +} + +.navbar-default .navbar-nav > .dropdown > a .caret { + border-top-color: #777777; + border-bottom-color: #777777; +} + +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #cccccc; + background-color: transparent; + } +} + +.navbar-default .navbar-link { + color: #777777; +} + +.navbar-default .navbar-link:hover { + color: #333333; +} + +.navbar-inverse { + background-color: #222222; + border-color: #080808; +} + +.navbar-inverse .navbar-brand { + color: #999999; +} + +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #ffffff; + background-color: transparent; +} + +.navbar-inverse .navbar-text { + color: #999999; +} + +.navbar-inverse .navbar-nav > li > a { + color: #999999; +} + +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #ffffff; + background-color: transparent; +} + +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #080808; +} + +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444444; + background-color: transparent; +} + +.navbar-inverse .navbar-toggle { + border-color: #333333; +} + +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333333; +} + +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #ffffff; +} + +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} + +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #ffffff; + background-color: #080808; +} + +.navbar-inverse .navbar-nav > .dropdown > a:hover .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +.navbar-inverse .navbar-nav > .dropdown > a .caret { + border-top-color: #999999; + border-bottom-color: #999999; +} + +.navbar-inverse .navbar-nav > .open > a .caret, +.navbar-inverse .navbar-nav > .open > a:hover .caret, +.navbar-inverse .navbar-nav > .open > a:focus .caret { + border-top-color: #ffffff; + border-bottom-color: #ffffff; +} + +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #999999; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444444; + background-color: transparent; + } +} + +.navbar-inverse .navbar-link { + color: #999999; +} + +.navbar-inverse .navbar-link:hover { + color: #ffffff; +} + +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} + +.breadcrumb > li { + display: inline-block; +} + +.breadcrumb > li + li:before { + padding: 0 5px; + color: #cccccc; + content: "/\00a0"; +} + +.breadcrumb > .active { + color: #999999; +} + +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} + +.pagination > li { + display: inline; +} + +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.428571429; + text-decoration: none; + background-color: #ffffff; + border: 1px solid #dddddd; +} + +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} + +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + background-color: #eeeeee; +} + +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #ffffff; + cursor: default; + background-color: #428bca; + border-color: #428bca; +} + +.pagination > .disabled > span, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #999999; + cursor: not-allowed; + background-color: #ffffff; + border-color: #dddddd; +} + +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; +} + +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-bottom-left-radius: 6px; + border-top-left-radius: 6px; +} + +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} + +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; +} + +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} + +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} + +.pager:before, +.pager:after { + display: table; + content: " "; +} + +.pager:after { + clear: both; +} + +.pager:before, +.pager:after { + display: table; + content: " "; +} + +.pager:after { + clear: both; +} + +.pager li { + display: inline; +} + +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 15px; +} + +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} + +.pager .next > a, +.pager .next > span { + float: right; +} + +.pager .previous > a, +.pager .previous > span { + float: left; +} + +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #999999; + cursor: not-allowed; + background-color: #ffffff; +} + +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} + +.label[href]:hover, +.label[href]:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} + +.label:empty { + display: none; +} + +.label-default { + background-color: #999999; +} + +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #808080; +} + +.label-primary { + background-color: #428bca; +} + +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #3071a9; +} + +.label-success { + background-color: #5cb85c; +} + +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} + +.label-info { + background-color: #5bc0de; +} + +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} + +.label-warning { + background-color: #f0ad4e; +} + +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} + +.label-danger { + background-color: #d9534f; +} + +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} + +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + background-color: #999999; + border-radius: 10px; +} + +.badge:empty { + display: none; +} + +a.badge:hover, +a.badge:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} + +.btn .badge { + position: relative; + top: -1px; +} + +a.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #428bca; + background-color: #ffffff; +} + +.nav-pills > li > a > .badge { + margin-left: 3px; +} + +.jumbotron { + padding: 30px; + margin-bottom: 30px; + font-size: 21px; + font-weight: 200; + line-height: 2.1428571435; + color: inherit; + background-color: #eeeeee; +} + +.jumbotron h1 { + line-height: 1; + color: inherit; +} + +.jumbotron p { + line-height: 1.4; +} + +.container .jumbotron { + border-radius: 6px; +} + +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1 { + font-size: 63px; + } +} + +.thumbnail { + display: inline-block; + display: block; + height: auto; + max-width: 100%; + padding: 4px; + line-height: 1.428571429; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} + +.thumbnail > img { + display: block; + height: auto; + max-width: 100%; +} + +a.thumbnail:hover, +a.thumbnail:focus { + border-color: #428bca; +} + +.thumbnail > img { + margin-right: auto; + margin-left: auto; +} + +.thumbnail .caption { + padding: 9px; + color: #333333; +} + +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} + +.alert h4 { + margin-top: 0; + color: inherit; +} + +.alert .alert-link { + font-weight: bold; +} + +.alert > p, +.alert > ul { + margin-bottom: 0; +} + +.alert > p + p { + margin-top: 5px; +} + +.alert-dismissable { + padding-right: 35px; +} + +.alert-dismissable .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} + +.alert-success { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.alert-success hr { + border-top-color: #c9e2b3; +} + +.alert-success .alert-link { + color: #356635; +} + +.alert-info { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.alert-info hr { + border-top-color: #a6e1ec; +} + +.alert-info .alert-link { + color: #2d6987; +} + +.alert-warning { + color: #c09853; + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.alert-warning hr { + border-top-color: #f8e5be; +} + +.alert-warning .alert-link { + color: #a47e3c; +} + +.alert-danger { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} + +.alert-danger hr { + border-top-color: #e6c1c7; +} + +.alert-danger .alert-link { + color: #953b39; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-moz-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +@-o-keyframes progress-bar-stripes { + from { + background-position: 0 0; + } + to { + background-position: 40px 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} + +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + color: #ffffff; + text-align: center; + background-color: #428bca; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-transition: width 0.6s ease; + transition: width 0.6s ease; +} + +.progress-striped .progress-bar { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 40px 40px; +} + +.progress.active .progress-bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + -ms-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} + +.progress-bar-success { + background-color: #5cb85c; +} + +.progress-striped .progress-bar-success { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-bar-info { + background-color: #5bc0de; +} + +.progress-striped .progress-bar-info { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-bar-warning { + background-color: #f0ad4e; +} + +.progress-striped .progress-bar-warning { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.progress-bar-danger { + background-color: #d9534f; +} + +.progress-striped .progress-bar-danger { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} + +.media, +.media-body { + overflow: hidden; + zoom: 1; +} + +.media, +.media .media { + margin-top: 15px; +} + +.media:first-child { + margin-top: 0; +} + +.media-object { + display: block; +} + +.media-heading { + margin: 0 0 5px; +} + +.media > .pull-left { + margin-right: 10px; +} + +.media > .pull-right { + margin-left: 10px; +} + +.media-list { + padding-left: 0; + list-style: none; +} + +.list-group { + padding-left: 0; + margin-bottom: 20px; +} + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #ffffff; + border: 1px solid #dddddd; +} + +.list-group-item:first-child { + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} + +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} + +.list-group-item > .badge { + float: right; +} + +.list-group-item > .badge + .badge { + margin-right: 5px; +} + +a.list-group-item { + color: #555555; +} + +a.list-group-item .list-group-item-heading { + color: #333333; +} + +a.list-group-item:hover, +a.list-group-item:focus { + text-decoration: none; + background-color: #f5f5f5; +} + +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #ffffff; + background-color: #428bca; + border-color: #428bca; +} + +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading { + color: inherit; +} + +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #e1edf7; +} + +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} + +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} + +.panel { + margin-bottom: 20px; + background-color: #ffffff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} + +.panel-body { + padding: 15px; +} + +.panel-body:before, +.panel-body:after { + display: table; + content: " "; +} + +.panel-body:after { + clear: both; +} + +.panel-body:before, +.panel-body:after { + display: table; + content: " "; +} + +.panel-body:after { + clear: both; +} + +.panel > .list-group { + margin-bottom: 0; +} + +.panel > .list-group .list-group-item { + border-width: 1px 0; +} + +.panel > .list-group .list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.panel > .list-group .list-group-item:last-child { + border-bottom: 0; +} + +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} + +.panel > .table { + margin-bottom: 0; +} + +.panel > .panel-body + .table { + border-top: 1px solid #dddddd; +} + +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} + +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; +} + +.panel-title > a { + color: inherit; +} + +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #dddddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} + +.panel-group .panel { + margin-bottom: 0; + overflow: hidden; + border-radius: 4px; +} + +.panel-group .panel + .panel { + margin-top: 5px; +} + +.panel-group .panel-heading { + border-bottom: 0; +} + +.panel-group .panel-heading + .panel-collapse .panel-body { + border-top: 1px solid #dddddd; +} + +.panel-group .panel-footer { + border-top: 0; +} + +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #dddddd; +} + +.panel-default { + border-color: #dddddd; +} + +.panel-default > .panel-heading { + color: #333333; + background-color: #f5f5f5; + border-color: #dddddd; +} + +.panel-default > .panel-heading + .panel-collapse .panel-body { + border-top-color: #dddddd; +} + +.panel-default > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #dddddd; +} + +.panel-primary { + border-color: #428bca; +} + +.panel-primary > .panel-heading { + color: #ffffff; + background-color: #428bca; + border-color: #428bca; +} + +.panel-primary > .panel-heading + .panel-collapse .panel-body { + border-top-color: #428bca; +} + +.panel-primary > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #428bca; +} + +.panel-success { + border-color: #d6e9c6; +} + +.panel-success > .panel-heading { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.panel-success > .panel-heading + .panel-collapse .panel-body { + border-top-color: #d6e9c6; +} + +.panel-success > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #d6e9c6; +} + +.panel-warning { + border-color: #fbeed5; +} + +.panel-warning > .panel-heading { + color: #c09853; + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.panel-warning > .panel-heading + .panel-collapse .panel-body { + border-top-color: #fbeed5; +} + +.panel-warning > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #fbeed5; +} + +.panel-danger { + border-color: #eed3d7; +} + +.panel-danger > .panel-heading { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} + +.panel-danger > .panel-heading + .panel-collapse .panel-body { + border-top-color: #eed3d7; +} + +.panel-danger > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #eed3d7; +} + +.panel-info { + border-color: #bce8f1; +} + +.panel-info > .panel-heading { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +.panel-info > .panel-heading + .panel-collapse .panel-body { + border-top-color: #bce8f1; +} + +.panel-info > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #bce8f1; +} + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} + +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} + +.well-lg { + padding: 24px; + border-radius: 6px; +} + +.well-sm { + padding: 9px; + border-radius: 3px; +} + +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} + +.close:hover, +.close:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); +} + +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} + +.modal-open { + overflow: hidden; +} + +body.modal-open, +.modal-open .navbar-fixed-top, +.modal-open .navbar-fixed-bottom { + margin-right: 15px; +} + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + display: none; + overflow: auto; + overflow-y: scroll; +} + +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + transform: translate(0, -25%); + -webkit-transition: -webkit-transform 0.3s ease-out; + -moz-transition: -moz-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + transition: transform 0.3s ease-out; +} + +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); +} + +.modal-dialog { + z-index: 1050; + width: auto; + padding: 10px; + margin-right: auto; + margin-left: auto; +} + +.modal-content { + position: relative; + background-color: #ffffff; + border: 1px solid #999999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + outline: none; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + background-clip: padding-box; +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; + background-color: #000000; +} + +.modal-backdrop.fade { + opacity: 0; + filter: alpha(opacity=0); +} + +.modal-backdrop.in { + opacity: 0.5; + filter: alpha(opacity=50); +} + +.modal-header { + min-height: 16.428571429px; + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} + +.modal-header .close { + margin-top: -2px; +} + +.modal-title { + margin: 0; + line-height: 1.428571429; +} + +.modal-body { + position: relative; + padding: 20px; +} + +.modal-footer { + padding: 19px 20px 20px; + margin-top: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} + +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} + +.modal-footer:after { + clear: both; +} + +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} + +.modal-footer:after { + clear: both; +} + +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} + +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} + +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} + +@media screen and (min-width: 768px) { + .modal-dialog { + right: auto; + left: 50%; + width: 600px; + padding-top: 30px; + padding-bottom: 30px; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } +} + +.tooltip { + position: absolute; + z-index: 1030; + display: block; + font-size: 12px; + line-height: 1.4; + opacity: 0; + filter: alpha(opacity=0); + visibility: visible; +} + +.tooltip.in { + opacity: 0.9; + filter: alpha(opacity=90); +} + +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} + +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} + +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} + +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} + +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #ffffff; + text-align: center; + text-decoration: none; + background-color: #000000; + border-radius: 4px; +} + +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.top-left .tooltip-arrow { + bottom: 0; + left: 5px; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.top-right .tooltip-arrow { + right: 5px; + bottom: 0; + border-top-color: #000000; + border-width: 5px 5px 0; +} + +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-right-color: #000000; + border-width: 5px 5px 5px 0; +} + +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-left-color: #000000; + border-width: 5px 0 5px 5px; +} + +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.tooltip.bottom-left .tooltip-arrow { + top: 0; + left: 5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.tooltip.bottom-right .tooltip-arrow { + top: 0; + right: 5px; + border-bottom-color: #000000; + border-width: 0 5px 5px; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1010; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + white-space: normal; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + background-clip: padding-box; +} + +.popover.top { + margin-top: -10px; +} + +.popover.right { + margin-left: 10px; +} + +.popover.bottom { + margin-top: 10px; +} + +.popover.left { + margin-left: -10px; +} + +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} + +.popover-content { + padding: 9px 14px; +} + +.popover .arrow, +.popover .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} + +.popover .arrow { + border-width: 11px; +} + +.popover .arrow:after { + border-width: 10px; + content: ""; +} + +.popover.top .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.25); + border-bottom-width: 0; +} + +.popover.top .arrow:after { + bottom: 1px; + margin-left: -10px; + border-top-color: #ffffff; + border-bottom-width: 0; + content: " "; +} + +.popover.right .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.25); + border-left-width: 0; +} + +.popover.right .arrow:after { + bottom: -10px; + left: 1px; + border-right-color: #ffffff; + border-left-width: 0; + content: " "; +} + +.popover.bottom .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.25); + border-top-width: 0; +} + +.popover.bottom .arrow:after { + top: 1px; + margin-left: -10px; + border-bottom-color: #ffffff; + border-top-width: 0; + content: " "; +} + +.popover.left .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.25); + border-right-width: 0; +} + +.popover.left .arrow:after { + right: 1px; + bottom: -10px; + border-left-color: #ffffff; + border-right-width: 0; + content: " "; +} + +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} + +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + height: auto; + max-width: 100%; + line-height: 1; +} + +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} + +.carousel-inner > .active { + left: 0; +} + +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} + +.carousel-inner > .next { + left: 100%; +} + +.carousel-inner > .prev { + left: -100%; +} + +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} + +.carousel-inner > .active.left { + left: -100%; +} + +.carousel-inner > .active.right { + left: 100%; +} + +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); + opacity: 0.5; + filter: alpha(opacity=50); +} + +.carousel-control.left { + background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%)); + background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); +} + +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%)); + background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); +} + +.carousel-control:hover, +.carousel-control:focus { + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} + +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + left: 50%; + z-index: 5; + display: inline-block; +} + +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + font-family: serif; +} + +.carousel-control .icon-prev:before { + content: '\2039'; +} + +.carousel-control .icon-next:before { + content: '\203a'; +} + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} + +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + border: 1px solid #ffffff; + border-radius: 10px; +} + +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #ffffff; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} + +.carousel-caption .btn { + text-shadow: none; +} + +@media screen and (min-width: 768px) { + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + margin-left: -15px; + font-size: 30px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} + +.clearfix:before, +.clearfix:after { + display: table; + content: " "; +} + +.clearfix:after { + clear: both; +} + +.pull-right { + float: right !important; +} + +.pull-left { + float: left !important; +} + +.hide { + display: none !important; +} + +.show { + display: block !important; +} + +.invisible { + visibility: hidden; +} + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.affix { + position: fixed; +} + +@-ms-viewport { + width: device-width; +} + +@media screen and (max-width: 400px) { + @-ms-viewport { + width: 320px; + } +} + +.hidden { + display: none !important; + visibility: hidden !important; +} + +.visible-xs { + display: none !important; +} + +tr.visible-xs { + display: none !important; +} + +th.visible-xs, +td.visible-xs { + display: none !important; +} + +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-xs.visible-sm { + display: block !important; + } + tr.visible-xs.visible-sm { + display: table-row !important; + } + th.visible-xs.visible-sm, + td.visible-xs.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-xs.visible-md { + display: block !important; + } + tr.visible-xs.visible-md { + display: table-row !important; + } + th.visible-xs.visible-md, + td.visible-xs.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-xs.visible-lg { + display: block !important; + } + tr.visible-xs.visible-lg { + display: table-row !important; + } + th.visible-xs.visible-lg, + td.visible-xs.visible-lg { + display: table-cell !important; + } +} + +.visible-sm { + display: none !important; +} + +tr.visible-sm { + display: none !important; +} + +th.visible-sm, +td.visible-sm { + display: none !important; +} + +@media (max-width: 767px) { + .visible-sm.visible-xs { + display: block !important; + } + tr.visible-sm.visible-xs { + display: table-row !important; + } + th.visible-sm.visible-xs, + td.visible-sm.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-sm.visible-md { + display: block !important; + } + tr.visible-sm.visible-md { + display: table-row !important; + } + th.visible-sm.visible-md, + td.visible-sm.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-sm.visible-lg { + display: block !important; + } + tr.visible-sm.visible-lg { + display: table-row !important; + } + th.visible-sm.visible-lg, + td.visible-sm.visible-lg { + display: table-cell !important; + } +} + +.visible-md { + display: none !important; +} + +tr.visible-md { + display: none !important; +} + +th.visible-md, +td.visible-md { + display: none !important; +} + +@media (max-width: 767px) { + .visible-md.visible-xs { + display: block !important; + } + tr.visible-md.visible-xs { + display: table-row !important; + } + th.visible-md.visible-xs, + td.visible-md.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-md.visible-sm { + display: block !important; + } + tr.visible-md.visible-sm { + display: table-row !important; + } + th.visible-md.visible-sm, + td.visible-md.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-md.visible-lg { + display: block !important; + } + tr.visible-md.visible-lg { + display: table-row !important; + } + th.visible-md.visible-lg, + td.visible-md.visible-lg { + display: table-cell !important; + } +} + +.visible-lg { + display: none !important; +} + +tr.visible-lg { + display: none !important; +} + +th.visible-lg, +td.visible-lg { + display: none !important; +} + +@media (max-width: 767px) { + .visible-lg.visible-xs { + display: block !important; + } + tr.visible-lg.visible-xs { + display: table-row !important; + } + th.visible-lg.visible-xs, + td.visible-lg.visible-xs { + display: table-cell !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .visible-lg.visible-sm { + display: block !important; + } + tr.visible-lg.visible-sm { + display: table-row !important; + } + th.visible-lg.visible-sm, + td.visible-lg.visible-sm { + display: table-cell !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-lg.visible-md { + display: block !important; + } + tr.visible-lg.visible-md { + display: table-row !important; + } + th.visible-lg.visible-md, + td.visible-lg.visible-md { + display: table-cell !important; + } +} + +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} + +.hidden-xs { + display: block !important; +} + +tr.hidden-xs { + display: table-row !important; +} + +th.hidden-xs, +td.hidden-xs { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } + tr.hidden-xs { + display: none !important; + } + th.hidden-xs, + td.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-xs.hidden-sm { + display: none !important; + } + tr.hidden-xs.hidden-sm { + display: none !important; + } + th.hidden-xs.hidden-sm, + td.hidden-xs.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-xs.hidden-md { + display: none !important; + } + tr.hidden-xs.hidden-md { + display: none !important; + } + th.hidden-xs.hidden-md, + td.hidden-xs.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-xs.hidden-lg { + display: none !important; + } + tr.hidden-xs.hidden-lg { + display: none !important; + } + th.hidden-xs.hidden-lg, + td.hidden-xs.hidden-lg { + display: none !important; + } +} + +.hidden-sm { + display: block !important; +} + +tr.hidden-sm { + display: table-row !important; +} + +th.hidden-sm, +td.hidden-sm { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-sm.hidden-xs { + display: none !important; + } + tr.hidden-sm.hidden-xs { + display: none !important; + } + th.hidden-sm.hidden-xs, + td.hidden-sm.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } + tr.hidden-sm { + display: none !important; + } + th.hidden-sm, + td.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-sm.hidden-md { + display: none !important; + } + tr.hidden-sm.hidden-md { + display: none !important; + } + th.hidden-sm.hidden-md, + td.hidden-sm.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-sm.hidden-lg { + display: none !important; + } + tr.hidden-sm.hidden-lg { + display: none !important; + } + th.hidden-sm.hidden-lg, + td.hidden-sm.hidden-lg { + display: none !important; + } +} + +.hidden-md { + display: block !important; +} + +tr.hidden-md { + display: table-row !important; +} + +th.hidden-md, +td.hidden-md { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-md.hidden-xs { + display: none !important; + } + tr.hidden-md.hidden-xs { + display: none !important; + } + th.hidden-md.hidden-xs, + td.hidden-md.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-md.hidden-sm { + display: none !important; + } + tr.hidden-md.hidden-sm { + display: none !important; + } + th.hidden-md.hidden-sm, + td.hidden-md.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } + tr.hidden-md { + display: none !important; + } + th.hidden-md, + td.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-md.hidden-lg { + display: none !important; + } + tr.hidden-md.hidden-lg { + display: none !important; + } + th.hidden-md.hidden-lg, + td.hidden-md.hidden-lg { + display: none !important; + } +} + +.hidden-lg { + display: block !important; +} + +tr.hidden-lg { + display: table-row !important; +} + +th.hidden-lg, +td.hidden-lg { + display: table-cell !important; +} + +@media (max-width: 767px) { + .hidden-lg.hidden-xs { + display: none !important; + } + tr.hidden-lg.hidden-xs { + display: none !important; + } + th.hidden-lg.hidden-xs, + td.hidden-lg.hidden-xs { + display: none !important; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-lg.hidden-sm { + display: none !important; + } + tr.hidden-lg.hidden-sm { + display: none !important; + } + th.hidden-lg.hidden-sm, + td.hidden-lg.hidden-sm { + display: none !important; + } +} + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-lg.hidden-md { + display: none !important; + } + tr.hidden-lg.hidden-md { + display: none !important; + } + th.hidden-lg.hidden-md, + td.hidden-lg.hidden-md { + display: none !important; + } +} + +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } + tr.hidden-lg { + display: none !important; + } + th.hidden-lg, + td.hidden-lg { + display: none !important; + } +} + +.visible-print { + display: none !important; +} + +tr.visible-print { + display: none !important; +} + +th.visible-print, +td.visible-print { + display: none !important; +} + +@media print { + .visible-print { + display: block !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } + .hidden-print { + display: none !important; + } + tr.hidden-print { + display: none !important; + } + th.hidden-print, + td.hidden-print { + display: none !important; + } +} \ No newline at end of file diff --git a/samples/MusicStore/wwwroot/Content/bootstrap.min.css b/samples/MusicStore/wwwroot/Content/bootstrap.min.css new file mode 100644 index 0000000..df89a50 --- /dev/null +++ b/samples/MusicStore/wwwroot/Content/bootstrap.min.css @@ -0,0 +1,20 @@ +/* NUGET: BEGIN LICENSE TEXT + * + * Microsoft grants you the right to use these script files for the sole + * purpose of either: (i) interacting through your browser with the Microsoft + * website or online service, subject to the applicable licensing or use + * terms; or (ii) using the files as included with a Microsoft product subject + * to that product's license terms. Microsoft reserves all other rights to the + * files not expressly granted by Microsoft, whether by implication, estoppel + * or otherwise. The notices and licenses below are for informational purposes only. + * + * NUGET: END LICENSE TEXT */ +/*! + * Bootstrap v3.0.0 + * + * Copyright 2013 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + *//*! normalize.css v2.1.0 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{margin:.67em 0;font-size:2em}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{height:0;-moz-box-sizing:content-box;box-sizing:content-box}mark{color:#000;background:#ff0}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid #c0c0c0}legend{padding:0;border:0}button,input,select,textarea{margin:0;font-family:inherit;font-size:100%}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{padding:0;box-sizing:border-box}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:2cm .5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}button,input,select[multiple],textarea{background-image:none}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}.img-responsive{display:block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16.099999999999998px;font-weight:200;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#428bca}.text-warning{color:#c09853}.text-danger{color:#b94a48}.text-success{color:#468847}.text-info{color:#3a87ad}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{margin-top:20px;margin-bottom:10px}h4,h5,h6{margin-top:10px;margin-bottom:10px}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}h1 small,.h1 small{font-size:24px}h2 small,.h2 small{font-size:18px}h3 small,.h3 small,h4 small,.h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt,dd{line-height:1.428571429}dt{font-weight:bold}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small{display:block;line-height:1.428571429;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:1.428571429}code,pre{font-family:Monaco,Menlo,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;white-space:nowrap;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.row{margin-right:-15px;margin-left:-15px}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11{float:left}.col-xs-1{width:8.333333333333332%}.col-xs-2{width:16.666666666666664%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333333333%}.col-xs-5{width:41.66666666666667%}.col-xs-6{width:50%}.col-xs-7{width:58.333333333333336%}.col-xs-8{width:66.66666666666666%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333333334%}.col-xs-11{width:91.66666666666666%}.col-xs-12{width:100%}@media(min-width:768px){.container{max-width:750px}.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11{float:left}.col-sm-1{width:8.333333333333332%}.col-sm-2{width:16.666666666666664%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333333333%}.col-sm-5{width:41.66666666666667%}.col-sm-6{width:50%}.col-sm-7{width:58.333333333333336%}.col-sm-8{width:66.66666666666666%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333333334%}.col-sm-11{width:91.66666666666666%}.col-sm-12{width:100%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-11{left:91.66666666666666%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-11{margin-left:91.66666666666666%}}@media(min-width:992px){.container{max-width:970px}.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11{float:left}.col-md-1{width:8.333333333333332%}.col-md-2{width:16.666666666666664%}.col-md-3{width:25%}.col-md-4{width:33.33333333333333%}.col-md-5{width:41.66666666666667%}.col-md-6{width:50%}.col-md-7{width:58.333333333333336%}.col-md-8{width:66.66666666666666%}.col-md-9{width:75%}.col-md-10{width:83.33333333333334%}.col-md-11{width:91.66666666666666%}.col-md-12{width:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.333333333333332%}.col-md-push-2{left:16.666666666666664%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333333333%}.col-md-push-5{left:41.66666666666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.333333333333336%}.col-md-push-8{left:66.66666666666666%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333333334%}.col-md-push-11{left:91.66666666666666%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-11{right:91.66666666666666%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-11{margin-left:91.66666666666666%}}@media(min-width:1200px){.container{max-width:1170px}.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11{float:left}.col-lg-1{width:8.333333333333332%}.col-lg-2{width:16.666666666666664%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333333333%}.col-lg-5{width:41.66666666666667%}.col-lg-6{width:50%}.col-lg-7{width:58.333333333333336%}.col-lg-8{width:66.66666666666666%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333333334%}.col-lg-11{width:91.66666666666666%}.col-lg-12{width:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-11{left:91.66666666666666%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-11{margin-left:91.66666666666666%}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{padding:8px;line-height:1.428571429;vertical-align:top;border-top:1px solid #ddd}.table thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table caption+thead tr:first-child th,.table colgroup+thead tr:first-child th,.table thead:first-child tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed thead>tr>th,.table-condensed tbody>tr>th,.table-condensed tfoot>tr>th,.table-condensed thead>tr>td,.table-condensed tbody>tr>td,.table-condensed tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*="col-"]{display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8;border-color:#d6e9c6}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td{background-color:#d0e9c6;border-color:#c9e2b3}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede;border-color:#eed3d7}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td{background-color:#ebcccc;border-color:#e6c1c7}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3;border-color:#fbeed5}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td{background-color:#faf2cc;border-color:#f8e5be}@media(max-width:768px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:scroll;overflow-y:hidden;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0;background-color:#fff}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>thead>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>thead>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}select[multiple],select[size]{height:auto}select optgroup{font-family:inherit;font-size:inherit;font-style:inherit}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{height:auto}.form-control:-moz-placeholder{color:#999}.form-control::-moz-placeholder{color:#999}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee}textarea.form-control{height:auto}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;padding-left:20px;margin-top:10px;margin-bottom:10px;vertical-align:middle}.radio label,.checkbox label{display:inline;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:normal;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm{height:auto}.input-lg{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:45px;line-height:45px}textarea.input-lg{height:auto}.has-warning .help-block,.has-warning .control-label{color:#c09853}.has-warning .form-control{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.has-warning .input-group-addon{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.has-error .help-block,.has-error .control-label{color:#b94a48}.has-error .form-control{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.has-error .input-group-addon{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.has-success .help-block,.has-success .control-label{color:#468847}.has-success .form-control{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.has-success .input-group-addon{color:#468847;background-color:#dff0d8;border-color:#468847}.form-control-static{padding-top:7px;margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block}.form-inline .radio,.form-inline .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}@media(min-width:768px){.form-horizontal .control-label{text-align:right}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;border:1px solid transparent;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-link{font-weight:normal;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-xs{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';-webkit-font-smoothing:antialiased;font-style:normal;font-weight:normal;line-height:1}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-print:before{content:"\e045"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-briefcase:before{content:"\1f4bc"}.glyphicon-calendar:before{content:"\1f4c5"}.glyphicon-pushpin:before{content:"\1f4cc"}.glyphicon-paperclip:before{content:"\1f4ce"}.glyphicon-camera:before{content:"\1f4f7"}.glyphicon-lock:before{content:"\1f512"}.glyphicon-bell:before{content:"\1f514"}.glyphicon-bookmark:before{content:"\1f516"}.glyphicon-fire:before{content:"\1f525"}.glyphicon-wrench:before{content:"\1f527"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid #000;border-right:4px solid transparent;border-bottom:0 dotted;border-left:4px solid transparent;content:""}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#fff;text-decoration:none;background-color:#428bca}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0 dotted;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}}.btn-default .caret{border-top-color:#333}.btn-primary .caret,.btn-success .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret{border-top-color:#fff}.dropup .btn-default .caret{border-bottom-color:#333}.dropup .btn-primary .caret,.dropup .btn-success .caret,.dropup .btn-warning .caret,.dropup .btn-danger .caret,.dropup .btn-info .caret{border-bottom-color:#fff}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group{float:left}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group,.btn-toolbar>.btn-group+.btn-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group-xs>.btn{padding:5px 10px;padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-bottom-left-radius:4px;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child>.btn:last-child,.btn-group-vertical>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;border-collapse:separate;table-layout:fixed}.btn-group-justified .btn{display:table-cell;float:none;width:1%}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group.col{float:none;padding-right:0;padding-left:0}.input-group .form-control{width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:45px;line-height:45px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:normal;line-height:1;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-4px}.input-group-btn>.btn:hover,.input-group-btn>.btn:active{z-index:2}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.428571429;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center}@media(min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}}.nav-tabs.nav-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs.nav-justified>.active>a{border-bottom-color:#fff}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:5px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center}@media(min-width:768px){.nav-justified>li{display:table-cell;width:1%}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs-justified>.active>a{border-bottom-color:#fff}.tabbable:before,.tabbable:after{display:table;content:" "}.tabbable:after{clear:both}.tabbable:before,.tabbable:after{display:table;content:" "}.tabbable:after{clear:both}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.nav .caret{border-top-color:#428bca;border-bottom-color:#428bca}.nav a:hover .caret{border-top-color:#2a6496;border-bottom-color:#2a6496}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;z-index:1000;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}@media(min-width:768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-collapse .navbar-nav.navbar-left:first-child{margin-left:-15px}.navbar-collapse .navbar-nav.navbar-right:last-child{margin-right:-15px}.navbar-collapse .navbar-text:last-child{margin-right:0}}.container>.navbar-header,.container>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.container>.navbar-header,.container>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{border-width:0 0 1px}@media(min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;border-width:0 0 1px}@media(min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;z-index:1030}.navbar-fixed-bottom{bottom:0;margin-bottom:0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media(min-width:768px){.navbar>.container .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media(min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media(max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media(min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}@media(min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media(min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-nav.pull-right>li>.dropdown-menu,.navbar-nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-text{float:left;margin-top:15px;margin-bottom:15px}@media(min-width:768px){.navbar-text{margin-right:15px;margin-left:15px}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#ccc}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e6e6e6}.navbar-default .navbar-nav>.dropdown>a:hover .caret,.navbar-default .navbar-nav>.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.open>a .caret,.navbar-default .navbar-nav>.open>a:hover .caret,.navbar-default .navbar-nav>.open>a:focus .caret{border-top-color:#555;border-bottom-color:#555}.navbar-default .navbar-nav>.dropdown>a .caret{border-top-color:#777;border-bottom-color:#777}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-nav>.dropdown>a .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .navbar-nav>.open>a .caret,.navbar-inverse .navbar-nav>.open>a:hover .caret,.navbar-inverse .navbar-nav>.open>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.428571429;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#eee}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#999;border-radius:10px}.badge:empty{display:none}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.btn .badge{position:relative;top:-1px}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;font-size:21px;font-weight:200;line-height:2.1428571435;color:inherit;background-color:#eee}.jumbotron h1{line-height:1;color:inherit}.jumbotron p{line-height:1.4}.container .jumbotron{border-radius:6px}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1{font-size:63px}}.thumbnail{display:inline-block;display:block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img{display:block;height:auto;max-width:100%}a.thumbnail:hover,a.thumbnail:focus{border-color:#428bca}.thumbnail>img{margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#356635}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#2d6987}.alert-warning{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.alert-warning hr{border-top-color:#f8e5be}.alert-warning .alert-link{color:#a47e3c}.alert-danger{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger hr{border-top-color:#e6c1c7}.alert-danger .alert-link{color:#953b39}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0}.panel>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.list-group .list-group-item:last-child{border-bottom:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table{margin-bottom:0}.panel>.panel-body+.table{border-top:1px solid #ddd}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-title{margin-top:0;margin-bottom:0;font-size:16px}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-group .panel{margin-bottom:0;overflow:hidden;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-warning{border-color:#fbeed5}.panel-warning>.panel-heading{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#fbeed5}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#fbeed5}.panel-danger{border-color:#eed3d7}.panel-danger>.panel-heading{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#eed3d7}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#eed3d7}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}body.modal-open,.modal-open .navbar-fixed-top,.modal-open .navbar-fixed-bottom{margin-right:15px}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:auto;overflow-y:scroll}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{z-index:1050;width:auto;padding:10px;margin-right:auto;margin-left:auto}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1030;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{min-height:16.428571429px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{padding:19px 20px 20px;margin-top:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media screen and (min-width:768px){.modal-dialog{right:auto;left:50%;width:600px;padding-top:30px;padding-bottom:30px}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}}.tooltip{position:absolute;z-index:1030;display:block;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0;content:" "}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0;content:" "}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0;content:" "}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0;content:" "}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;height:auto;max-width:100%;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);opacity:.5;filter:alpha(opacity=50)}.carousel-control.left{background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.5)),to(rgba(0,0,0,0.0001)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.5) 0),color-stop(rgba(0,0,0,0.0001) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000',endColorstr='#00000000',GradientType=1)}.carousel-control.right{right:0;left:auto;background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.0001)),to(rgba(0,0,0,0.5)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.0001) 0),color-stop(rgba(0,0,0,0.5) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000',endColorstr='#80000000',GradientType=1)}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;left:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix:after{clear:both}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.affix{position:fixed}@-ms-viewport{width:device-width}@media screen and (max-width:400px){@-ms-viewport{width:320px}}.hidden{display:none!important;visibility:hidden!important}.visible-xs{display:none!important}tr.visible-xs{display:none!important}th.visible-xs,td.visible-xs{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-xs.visible-sm{display:block!important}tr.visible-xs.visible-sm{display:table-row!important}th.visible-xs.visible-sm,td.visible-xs.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-xs.visible-md{display:block!important}tr.visible-xs.visible-md{display:table-row!important}th.visible-xs.visible-md,td.visible-xs.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-xs.visible-lg{display:block!important}tr.visible-xs.visible-lg{display:table-row!important}th.visible-xs.visible-lg,td.visible-xs.visible-lg{display:table-cell!important}}.visible-sm{display:none!important}tr.visible-sm{display:none!important}th.visible-sm,td.visible-sm{display:none!important}@media(max-width:767px){.visible-sm.visible-xs{display:block!important}tr.visible-sm.visible-xs{display:table-row!important}th.visible-sm.visible-xs,td.visible-sm.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-sm.visible-md{display:block!important}tr.visible-sm.visible-md{display:table-row!important}th.visible-sm.visible-md,td.visible-sm.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-sm.visible-lg{display:block!important}tr.visible-sm.visible-lg{display:table-row!important}th.visible-sm.visible-lg,td.visible-sm.visible-lg{display:table-cell!important}}.visible-md{display:none!important}tr.visible-md{display:none!important}th.visible-md,td.visible-md{display:none!important}@media(max-width:767px){.visible-md.visible-xs{display:block!important}tr.visible-md.visible-xs{display:table-row!important}th.visible-md.visible-xs,td.visible-md.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-md.visible-sm{display:block!important}tr.visible-md.visible-sm{display:table-row!important}th.visible-md.visible-sm,td.visible-md.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-md.visible-lg{display:block!important}tr.visible-md.visible-lg{display:table-row!important}th.visible-md.visible-lg,td.visible-md.visible-lg{display:table-cell!important}}.visible-lg{display:none!important}tr.visible-lg{display:none!important}th.visible-lg,td.visible-lg{display:none!important}@media(max-width:767px){.visible-lg.visible-xs{display:block!important}tr.visible-lg.visible-xs{display:table-row!important}th.visible-lg.visible-xs,td.visible-lg.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-lg.visible-sm{display:block!important}tr.visible-lg.visible-sm{display:table-row!important}th.visible-lg.visible-sm,td.visible-lg.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-lg.visible-md{display:block!important}tr.visible-lg.visible-md{display:table-row!important}th.visible-lg.visible-md,td.visible-lg.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-lg{display:block!important}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}.hidden-xs{display:block!important}tr.hidden-xs{display:table-row!important}th.hidden-xs,td.hidden-xs{display:table-cell!important}@media(max-width:767px){.hidden-xs{display:none!important}tr.hidden-xs{display:none!important}th.hidden-xs,td.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-xs.hidden-sm{display:none!important}tr.hidden-xs.hidden-sm{display:none!important}th.hidden-xs.hidden-sm,td.hidden-xs.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-xs.hidden-md{display:none!important}tr.hidden-xs.hidden-md{display:none!important}th.hidden-xs.hidden-md,td.hidden-xs.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-xs.hidden-lg{display:none!important}tr.hidden-xs.hidden-lg{display:none!important}th.hidden-xs.hidden-lg,td.hidden-xs.hidden-lg{display:none!important}}.hidden-sm{display:block!important}tr.hidden-sm{display:table-row!important}th.hidden-sm,td.hidden-sm{display:table-cell!important}@media(max-width:767px){.hidden-sm.hidden-xs{display:none!important}tr.hidden-sm.hidden-xs{display:none!important}th.hidden-sm.hidden-xs,td.hidden-sm.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}tr.hidden-sm{display:none!important}th.hidden-sm,td.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-sm.hidden-md{display:none!important}tr.hidden-sm.hidden-md{display:none!important}th.hidden-sm.hidden-md,td.hidden-sm.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-sm.hidden-lg{display:none!important}tr.hidden-sm.hidden-lg{display:none!important}th.hidden-sm.hidden-lg,td.hidden-sm.hidden-lg{display:none!important}}.hidden-md{display:block!important}tr.hidden-md{display:table-row!important}th.hidden-md,td.hidden-md{display:table-cell!important}@media(max-width:767px){.hidden-md.hidden-xs{display:none!important}tr.hidden-md.hidden-xs{display:none!important}th.hidden-md.hidden-xs,td.hidden-md.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-md.hidden-sm{display:none!important}tr.hidden-md.hidden-sm{display:none!important}th.hidden-md.hidden-sm,td.hidden-md.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}tr.hidden-md{display:none!important}th.hidden-md,td.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-md.hidden-lg{display:none!important}tr.hidden-md.hidden-lg{display:none!important}th.hidden-md.hidden-lg,td.hidden-md.hidden-lg{display:none!important}}.hidden-lg{display:block!important}tr.hidden-lg{display:table-row!important}th.hidden-lg,td.hidden-lg{display:table-cell!important}@media(max-width:767px){.hidden-lg.hidden-xs{display:none!important}tr.hidden-lg.hidden-xs{display:none!important}th.hidden-lg.hidden-xs,td.hidden-lg.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-lg.hidden-sm{display:none!important}tr.hidden-lg.hidden-sm{display:none!important}th.hidden-lg.hidden-sm,td.hidden-lg.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-lg.hidden-md{display:none!important}tr.hidden-lg.hidden-md{display:none!important}th.hidden-lg.hidden-md,td.hidden-lg.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg{display:none!important}tr.hidden-lg{display:none!important}th.hidden-lg,td.hidden-lg{display:none!important}}.visible-print{display:none!important}tr.visible-print{display:none!important}th.visible-print,td.visible-print{display:none!important}@media print{.visible-print{display:block!important}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}.hidden-print{display:none!important}tr.hidden-print{display:none!important}th.hidden-print,td.hidden-print{display:none!important}} \ No newline at end of file diff --git a/samples/MusicStore/wwwroot/Images/home-showcase.png b/samples/MusicStore/wwwroot/Images/home-showcase.png new file mode 100644 index 0000000..258c19d Binary files /dev/null and b/samples/MusicStore/wwwroot/Images/home-showcase.png differ diff --git a/samples/MusicStore/wwwroot/Images/logo.png b/samples/MusicStore/wwwroot/Images/logo.png new file mode 100644 index 0000000..d334c86 Binary files /dev/null and b/samples/MusicStore/wwwroot/Images/logo.png differ diff --git a/samples/MusicStore/wwwroot/Images/logo.svg b/samples/MusicStore/wwwroot/Images/logo.svg new file mode 100644 index 0000000..ec3cd6a --- /dev/null +++ b/samples/MusicStore/wwwroot/Images/logo.svg @@ -0,0 +1,303 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/MusicStore/wwwroot/Images/placeholder.png b/samples/MusicStore/wwwroot/Images/placeholder.png new file mode 100644 index 0000000..1f73dbb Binary files /dev/null and b/samples/MusicStore/wwwroot/Images/placeholder.png differ diff --git a/samples/MusicStore/wwwroot/Images/placeholder.svg b/samples/MusicStore/wwwroot/Images/placeholder.svg new file mode 100644 index 0000000..07d5820 --- /dev/null +++ b/samples/MusicStore/wwwroot/Images/placeholder.svg @@ -0,0 +1,112 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/MusicStore/wwwroot/Scripts/bootstrap.js b/samples/MusicStore/wwwroot/Scripts/bootstrap.js new file mode 100644 index 0000000..5aa9982 --- /dev/null +++ b/samples/MusicStore/wwwroot/Scripts/bootstrap.js @@ -0,0 +1,2014 @@ +/* NUGET: BEGIN LICENSE TEXT + * + * Microsoft grants you the right to use these script files for the sole + * purpose of either: (i) interacting through your browser with the Microsoft + * website or online service, subject to the applicable licensing or use + * terms; or (ii) using the files as included with a Microsoft product subject + * to that product's license terms. Microsoft reserves all other rights to the + * files not expressly granted by Microsoft, whether by implication, estoppel + * or otherwise. Insofar as a script file is dual licensed under GPL, + * Microsoft neither took the code under GPL nor distributes it thereunder but + * under the terms set out in this paragraph. All notices and licenses + * below are for informational purposes only. + * + * NUGET: END LICENSE TEXT */ + +/** +* bootstrap.js v3.0.0 by @fat and @mdo +* Copyright 2013 Twitter Inc. +* http://www.apache.org/licenses/LICENSE-2.0 +*/ +if (!jQuery) { throw new Error("Bootstrap requires jQuery") } + +/* ======================================================================== + * Bootstrap: transition.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#transitions + * ======================================================================== + * Copyright 2013 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + 'WebkitTransition' : 'webkitTransitionEnd' + , 'MozTransition' : 'transitionend' + , 'OTransition' : 'oTransitionEnd otransitionend' + , 'transition' : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false, $el = this + $(this).one($.support.transition.end, function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + }) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#alerts + * ======================================================================== + * Copyright 2013 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.hasClass('alert') ? $this : $this.parent() + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + $parent.trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one($.support.transition.end, removeElement) + .emulateTransitionEnd(150) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + var old = $.fn.alert + + $.fn.alert = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#buttons + * ======================================================================== + * Copyright 2013 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + } + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state = state + 'Text' + + if (!data.resetText) $el.data('resetText', $el[val]()) + + $el[val](data[state] || this.options[state]) + + // push to event loop to allow forms to submit + setTimeout(function () { + state == 'loadingText' ? + $el.addClass(d).attr(d, d) : + $el.removeClass(d).removeAttr(d); + }, 0) + } + + Button.prototype.toggle = function () { + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + .prop('checked', !this.$element.hasClass('active')) + .trigger('change') + if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active') + } + + this.$element.toggleClass('active') + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + var old = $.fn.button + + $.fn.button = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + $btn.button('toggle') + e.preventDefault() + }) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#carousel + * ======================================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = + this.sliding = + this.interval = + this.$active = + this.$items = null + + this.options.pause == 'hover' && this.$element + .on('mouseenter', $.proxy(this.pause, this)) + .on('mouseleave', $.proxy(this.cycle, this)) + } + + Carousel.DEFAULTS = { + interval: 5000 + , pause: 'hover' + , wrap: true + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getActiveIndex = function () { + this.$active = this.$element.find('.item.active') + this.$items = this.$active.parent().children() + + return this.$items.index(this.$active) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getActiveIndex() + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid', function () { that.to(pos) }) + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition.end) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || $active[type]() + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var fallback = type == 'next' ? 'first' : 'last' + var that = this + + if (!$next.length) { + if (!this.options.wrap) return + $next = this.$element.find('.item')[fallback]() + } + + this.sliding = true + + isCycling && this.pause() + + var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction }) + + if ($next.hasClass('active')) return + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + this.$element.one('slid', function () { + var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) + $nextIndicator && $nextIndicator.addClass('active') + }) + } + + if ($.support.transition && this.$element.hasClass('slide')) { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one($.support.transition.end, function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { that.$element.trigger('slid') }, 0) + }) + .emulateTransitionEnd(600) + } else { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger('slid') + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + var old = $.fn.carousel + + $.fn.carousel = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { + var $this = $(this), href + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + $target.carousel(options) + + if (slideIndex = $this.attr('data-slide-to')) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + }) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + $carousel.carousel($carousel.data()) + }) + }) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#collapse + * ======================================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.transitioning = null + + if (this.options.parent) this.$parent = $(this.options.parent) + if (this.options.toggle) this.toggle() + } + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var actives = this.$parent && this.$parent.find('> .panel > .in') + + if (actives && actives.length) { + var hasData = actives.data('bs.collapse') + if (hasData && hasData.transitioning) return + actives.collapse('hide') + hasData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing') + [dimension](0) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('in') + [dimension]('auto') + this.transitioning = 0 + this.$element.trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one($.support.transition.end, $.proxy(complete, this)) + .emulateTransitionEnd(350) + [dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element + [dimension](this.$element[dimension]()) + [0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse') + .removeClass('in') + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .trigger('hidden.bs.collapse') + .removeClass('collapsing') + .addClass('collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one($.support.transition.end, $.proxy(complete, this)) + .emulateTransitionEnd(350) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + var old = $.fn.collapse + + $.fn.collapse = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) { + var $this = $(this), href + var target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + var $target = $(target) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + var parent = $this.attr('data-parent') + var $parent = parent && $(parent) + + if (!data || !data.transitioning) { + if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed') + $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') + } + + $target.collapse(option) + }) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#dropdowns + * ======================================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle=dropdown]' + var Dropdown = function (element) { + var $el = $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we we use a backdrop because click events don't delegate + $('