├── .deployment ├── samples └── MusicStore │ ├── ForTesting │ ├── Readme.md │ ├── MusicStoreConfig.cs │ └── Mocks │ │ ├── Common │ │ ├── Helpers.cs │ │ └── CustomStateDataFormat.cs │ │ ├── OpenIdConnect │ │ ├── CustomStringDataFormat.cs │ │ ├── openid-configuration.json │ │ ├── OpenIdConnectBackChannelHttpHandler.cs │ │ ├── keys.json │ │ └── TestOpenIdConnectEvents.cs │ │ ├── Twitter │ │ ├── CustomTwitterStateDataFormat.cs │ │ ├── TestTwitterEvents.cs │ │ └── TwitterMockBackChannelHttpHandler.cs │ │ ├── Facebook │ │ ├── TestFacebookEvents.cs │ │ └── FacebookMockBackChannelHttpHandler.cs │ │ ├── Google │ │ ├── TestGoogleEvents.cs │ │ └── GoogleMockBackChannelHttpHandler.cs │ │ └── MicrosoftAccount │ │ ├── MicrosoftAccountMockBackChannelHandler.cs │ │ └── TestMicrosoftAccountEvents.cs │ ├── Views │ ├── _ViewStart.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── AccessDenied.cshtml │ │ ├── StatusCodePage.cshtml │ │ ├── Lockout.cshtml │ │ ├── Components │ │ │ ├── Announcement │ │ │ │ └── Default.cshtml │ │ │ ├── CartSummary │ │ │ │ └── Default.cshtml │ │ │ └── GenreMenu │ │ │ │ └── Default.cshtml │ │ ├── DemoLinkDisplay.cshtml │ │ ├── _ValidationScriptsPartial.cshtml │ │ ├── _LoginPartial.cshtml │ │ └── _Layout.cshtml │ ├── _ViewImports.cshtml │ ├── Account │ │ ├── ExternalLoginFailure.cshtml │ │ ├── ConfirmEmail.cshtml │ │ ├── ResetPasswordConfirmation.cshtml │ │ ├── ForgotPasswordConfirmation.cshtml │ │ ├── RegisterConfirmation.cshtml │ │ ├── SendCode.cshtml │ │ ├── ForgotPassword.cshtml │ │ ├── _ExternalLoginsListPartial.cshtml │ │ ├── ExternalLoginConfirmation.cshtml │ │ ├── Register.cshtml │ │ ├── VerifyCode.cshtml │ │ ├── ResetPassword.cshtml │ │ └── Login.cshtml │ ├── Checkout │ │ ├── Complete.cshtml │ │ └── AddressAndPayment.cshtml │ ├── Store │ │ ├── Index.cshtml │ │ ├── Details.cshtml │ │ └── Browse.cshtml │ ├── Home │ │ └── Index.cshtml │ ├── Manage │ │ ├── AddPhoneNumber.cshtml │ │ ├── VerifyPhoneNumber.cshtml │ │ ├── SetPassword.cshtml │ │ ├── ChangePassword.cshtml │ │ ├── ManageLogins.cshtml │ │ └── Index.cshtml │ └── ShoppingCart │ │ └── Index.cshtml │ ├── Areas │ └── Admin │ │ └── Views │ │ ├── _ViewStart.cshtml │ │ └── StoreManager │ │ ├── RemoveAlbum.cshtml │ │ ├── Details.cshtml │ │ ├── Index.cshtml │ │ ├── Create.cshtml │ │ └── Edit.cshtml │ ├── wwwroot │ ├── favicon.ico │ ├── Images │ │ ├── logo.png │ │ ├── placeholder.png │ │ └── home-showcase.png │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── Content │ │ └── Site.css │ └── Scripts │ │ ├── respond.min.js │ │ └── jquery.validate.unobtrusive.min.js │ ├── ViewModels │ ├── AlbumData.cs │ ├── ShoppingCartViewModel.cs │ └── ShoppingCartRemoveViewModel.cs │ ├── Properties │ ├── AppSettings.cs │ └── launchSettings.json │ ├── Models │ ├── Artist.cs │ ├── Genre.cs │ ├── OrderDetail.cs │ ├── CartItem.cs │ ├── MusicStoreContext.cs │ ├── Album.cs │ ├── Order.cs │ ├── ManageViewModels.cs │ └── AccountViewModels.cs │ ├── Components │ ├── ISystemClock.cs │ ├── SystemClock.cs │ ├── CartSummaryComponent.cs │ └── GenreMenuComponent.cs │ ├── Scripts │ └── _references.js │ ├── MessageServices.cs │ ├── config.json │ ├── MusicStore.csproj │ ├── Program.cs │ ├── Controllers │ ├── HomeController.cs │ ├── StoreController.cs │ ├── CheckoutController.cs │ └── ShoppingCartController.cs │ └── Platform.cs ├── test ├── MusicStore.E2ETests │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── MusicStoreConfig.cs │ ├── remoteDeploymentConfig.json │ ├── Common │ │ ├── Extensions.cs │ │ ├── XunitLogger.cs │ │ ├── DbUtils.cs │ │ ├── HtmlDOMHelper.cs │ │ └── Helpers.cs │ ├── RemoteDeploymentConfig.cs │ ├── MusicStore.E2ETests.csproj │ ├── NtlmAuthentationTest.cs │ └── OpenIdConnectTests.cs ├── RemoteTest.cmd ├── MusicStore.Test │ ├── TestAppSettings.cs │ ├── MusicStore.Test.csproj │ ├── TestSession.cs │ ├── GenreMenuComponentTest.cs │ ├── Models │ │ └── ShoppingCartTest.cs │ ├── CartSummaryComponentTest.cs │ ├── HomeControllerTest.cs │ └── ManageControllerTest.cs └── RemoteTest.ps1 ├── CONTRIBUTING.md ├── tools ├── BundleAndDeploy.cmd └── BundleAndDeploy.ps1 ├── appveyor.yml ├── NuGet.config ├── LICENSE.txt ├── .travis.yml ├── .dockerignore ├── .gitignore ├── docker-compose.windows.yml ├── Dockerfile.windows ├── .gitattributes ├── README.md └── MusicStore.sln /.deployment: -------------------------------------------------------------------------------- 1 | +[config] 2 | +project = src/MusicStore/MusicStore.csproj 3 | -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Readme.md: -------------------------------------------------------------------------------- 1 | The contents of this folder are used for end to end testing. -------------------------------------------------------------------------------- /samples/MusicStore/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Views/Shared/_Layout.cshtml"; 3 | } -------------------------------------------------------------------------------- /samples/MusicStore/Areas/Admin/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Views/Shared/_Layout.cshtml"; 3 | } -------------------------------------------------------------------------------- /samples/MusicStore/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/friism/MusicStore/HEAD/samples/MusicStore/wwwroot/favicon.ico -------------------------------------------------------------------------------- /samples/MusicStore/wwwroot/Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/friism/MusicStore/HEAD/samples/MusicStore/wwwroot/Images/logo.png -------------------------------------------------------------------------------- /samples/MusicStore/wwwroot/Images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/friism/MusicStore/HEAD/samples/MusicStore/wwwroot/Images/placeholder.png -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] 4 | -------------------------------------------------------------------------------- /samples/MusicStore/wwwroot/Images/home-showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/friism/MusicStore/HEAD/samples/MusicStore/wwwroot/Images/home-showcase.png -------------------------------------------------------------------------------- /samples/MusicStore/wwwroot/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/friism/MusicStore/HEAD/samples/MusicStore/wwwroot/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /samples/MusicStore/wwwroot/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/friism/MusicStore/HEAD/samples/MusicStore/wwwroot/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /samples/MusicStore/wwwroot/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/friism/MusicStore/HEAD/samples/MusicStore/wwwroot/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ====== 3 | 4 | Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/dev/CONTRIBUTING.md) in the Home repo. 5 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

-------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Access denied due to insufficient permissions"; 3 | } 4 | 5 |

Access denied due to insufficient permissions.

-------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/MusicStoreConfig.cs: -------------------------------------------------------------------------------- 1 | namespace MusicStore 2 | { 3 | public class StoreConfig 4 | { 5 | public const string ConnectionStringKey = "Data__DefaultConnection__ConnectionString"; 6 | } 7 | } -------------------------------------------------------------------------------- /samples/MusicStore/ViewModels/AlbumData.cs: -------------------------------------------------------------------------------- 1 | namespace MusicStore.ViewModels 2 | { 3 | public class AlbumData 4 | { 5 | public string Title { get; set; } 6 | 7 | public string Url { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using MusicStore 2 | @using MusicStore.Models 3 | @using Microsoft.Extensions.Options 4 | @using Microsoft.AspNetCore.Identity 5 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 | -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/MusicStoreConfig.cs: -------------------------------------------------------------------------------- 1 | namespace E2ETests 2 | { 3 | public class MusicStoreConfig 4 | { 5 | public const string ConnectionStringKey = "Data__DefaultConnection__ConnectionString"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /samples/MusicStore/Properties/AppSettings.cs: -------------------------------------------------------------------------------- 1 | namespace MusicStore 2 | { 3 | public class AppSettings 4 | { 5 | public string SiteTitle { get; set; } 6 | 7 | public bool CacheDbResults { get; set; } = true; 8 | } 9 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/ExternalLoginFailure.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Login Failure"; 3 | } 4 | 5 |
6 |

@ViewBag.Title.

7 |

Unsuccessful login with service.

8 |
-------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/StatusCodePage.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Item not found"; 3 | } 4 | 5 |

Item not found.

6 |

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

-------------------------------------------------------------------------------- /test/RemoteTest.cmd: -------------------------------------------------------------------------------- 1 | @Echo off 2 | 3 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0RemoteTest.ps1' %*" -------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/Lockout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Locked Out"; 3 | } 4 | 5 |
6 |

Locked out.

7 |

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

8 |
-------------------------------------------------------------------------------- /tools/BundleAndDeploy.cmd: -------------------------------------------------------------------------------- 1 | @Echo off 2 | 3 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0BundleAndDeploy.ps1' %*" -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/remoteDeploymentConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "NanoServer": { 3 | "ServerName": "", 4 | "AccountName": "", 5 | "AccountPassword": "", 6 | "FileSharePath": "", 7 | "DotnetRuntimeFolderPath": "", 8 | "DotnetRuntimeZipFilePath": "" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - git config --global core.autocrlf true 3 | branches: 4 | only: 5 | - master 6 | - release 7 | - dev 8 | - /^(.*\/)?ci-.*$/ 9 | build_script: 10 | - ps: .\build.ps1 11 | clone_depth: 1 12 | test: off 13 | deploy: off 14 | os: Visual Studio 2017 15 | -------------------------------------------------------------------------------- /samples/MusicStore/Models/Artist.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace MusicStore.Models 4 | { 5 | public class Artist 6 | { 7 | public int ArtistId { get; set; } 8 | 9 | [Required] 10 | public string Name { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/ConfirmEmail.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Confirm Email"; 3 | } 4 | 5 |

@ViewBag.Title.

6 |
7 |

8 | Thank you for confirming your email. Please Click here to Log in. 9 |

10 |
-------------------------------------------------------------------------------- /samples/MusicStore/ViewModels/ShoppingCartViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using MusicStore.Models; 3 | 4 | namespace MusicStore.ViewModels 5 | { 6 | public class ShoppingCartViewModel 7 | { 8 | public List CartItems { get; set; } 9 | public decimal CartTotal { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Checkout/Complete.cshtml: -------------------------------------------------------------------------------- 1 | @model int 2 | 3 | @{ 4 | ViewBag.Title = "Checkout Complete"; 5 | } 6 | 7 |

Checkout Complete

8 | 9 |

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

10 | 11 |

12 | How about shopping for some more music in our 13 | Store 14 |

-------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/ResetPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Reset password confirmation"; 3 | } 4 | 5 |
6 |

@ViewBag.Title.

7 |
8 |
9 |

10 | Your password has been reset. Please Click here to log in. 11 |

12 |
-------------------------------------------------------------------------------- /samples/MusicStore/ViewModels/ShoppingCartRemoveViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace MusicStore.ViewModels 2 | { 3 | public class ShoppingCartRemoveViewModel 4 | { 5 | public string Message { get; set; } 6 | public decimal CartTotal { get; set; } 7 | public int CartCount { get; set; } 8 | public int ItemCount { get; set; } 9 | public int DeleteId { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/Components/Announcement/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model Album 2 | 3 | @if (Model != null) 4 | { 5 |
  • 6 |
    7 | 8 | @Model.Title 9 |
  • 10 | } -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/Common/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MusicStore.Mocks.Common 4 | { 5 | internal class Helpers 6 | { 7 | internal static void ThrowIfConditionFailed(Func condition, string errorMessage) 8 | { 9 | if (!condition()) 10 | { 11 | throw new Exception(errorMessage); 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/Components/CartSummary/Default.cshtml: -------------------------------------------------------------------------------- 1 | @if (ViewBag.CartCount > 0) 2 | { 3 |
  • 4 | 5 | 6 | 7 | @ViewBag.CartCount 8 | 9 | 10 |
  • 11 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Store/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | @{ 3 | ViewBag.Title = "Store"; 4 | } 5 |

    Browse Genres

    6 | 7 |

    8 | Select from @Model.Count() genres: 9 |

    10 |
      11 | @foreach (var genre in Model) 12 | { 13 |
    • @genre.Name
    • 14 | } 15 |
    -------------------------------------------------------------------------------- /samples/MusicStore/Models/Genre.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace MusicStore.Models 5 | { 6 | public class Genre 7 | { 8 | public int GenreId { get; set; } 9 | 10 | [Required] 11 | public string Name { get; set; } 12 | 13 | public string Description { get; set; } 14 | 15 | public List Albums { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/ForgotPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Forgot Password Confirmation"; 3 | } 4 | 5 |
    6 |

    @ViewBag.Title.

    7 |
    8 |
    9 |

    10 | Please check your email to reset your password. 11 |

    12 |

    13 | For demo purpose only: Click here to reset the password 14 |

    15 |
    -------------------------------------------------------------------------------- /samples/MusicStore/Models/OrderDetail.cs: -------------------------------------------------------------------------------- 1 | namespace MusicStore.Models 2 | { 3 | public class OrderDetail 4 | { 5 | public int OrderDetailId { get; set; } 6 | 7 | public int OrderId { get; set; } 8 | 9 | public int AlbumId { get; set; } 10 | 11 | public int Quantity { get; set; } 12 | 13 | public decimal UnitPrice { get; set; } 14 | 15 | public virtual Album Album { get; set; } 16 | 17 | public virtual Order Order { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /samples/MusicStore/Components/ISystemClock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MusicStore.Components 4 | { 5 | /// 6 | /// Abstracts the system clock to facilitate testing. 7 | /// 8 | public interface ISystemClock 9 | { 10 | /// 11 | /// Gets a DateTime object that is set to the current date and time on this computer, 12 | /// expressed as the Coordinated Universal Time(UTC) 13 | /// 14 | DateTime UtcNow { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /samples/MusicStore/Scripts/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | /// 9 | -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/Common/Extensions.cs: -------------------------------------------------------------------------------- 1 | namespace System.Net 2 | { 3 | public static class Extensions 4 | { 5 | public static Cookie GetCookieWithName(this CookieCollection cookieCollection, string cookieName) 6 | { 7 | foreach (Cookie cookie in cookieCollection) 8 | { 9 | if (cookie.Name == cookieName) 10 | { 11 | return cookie; 12 | } 13 | } 14 | 15 | return null; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/RegisterConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Register Confirmation"; 3 | } 4 | 5 |
    6 |

    @ViewBag.Title.

    7 |
    8 |
    9 |

    10 | Please check your email to activate your account. 11 |

    12 |

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

    15 |
    -------------------------------------------------------------------------------- /samples/MusicStore/MessageServices.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace MusicStore 4 | { 5 | public static class MessageServices 6 | { 7 | public static Task SendEmailAsync(string email, string subject, string message) 8 | { 9 | // Plug in your email service 10 | return Task.FromResult(0); 11 | } 12 | 13 | public static Task SendSmsAsync(string number, string message) 14 | { 15 | // Plug in your sms service 16 | return Task.FromResult(0); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /samples/MusicStore/Models/CartItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace MusicStore.Models 5 | { 6 | public class CartItem 7 | { 8 | [Key] 9 | public int CartItemId { get; set; } 10 | 11 | [Required] 12 | public string CartId { get; set; } 13 | public int AlbumId { get; set; } 14 | public int Count { get; set; } 15 | 16 | [DataType(DataType.DateTime)] 17 | public DateTime DateCreated { get; set; } 18 | 19 | public virtual Album Album { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | sudo: false 3 | dist: trusty 4 | env: 5 | global: 6 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 7 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1 8 | mono: none 9 | os: 10 | - linux 11 | - osx 12 | osx_image: xcode8.2 13 | branches: 14 | only: 15 | - master 16 | - release 17 | - dev 18 | - /^(.*\/)?ci-.*$/ 19 | before_install: 20 | - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi 21 | script: 22 | - ./build.sh 23 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | **/Obj/ 4 | **/obj/ 5 | **/bin/ 6 | **/Bin/ 7 | .vs/ 8 | *.xap 9 | *.user 10 | /TestResults 11 | *.vspscc 12 | *.vssscc 13 | *.suo 14 | *.cache 15 | *.docstates 16 | _ReSharper.* 17 | *.csproj.user 18 | *[Rr]e[Ss]harper.user 19 | _ReSharper.*/ 20 | packages/* 21 | artifacts/* 22 | msbuild.log 23 | PublishProfiles/ 24 | *.psess 25 | *.vsp 26 | *.pidb 27 | *.userprefs 28 | *DS_Store 29 | *.ncrunchsolution 30 | *.log 31 | *.vspx 32 | /.symbols 33 | nuget.exe 34 | *net45.csproj 35 | *k10.csproj 36 | App_Data/ 37 | bower_components 38 | node_modules 39 | *.sln.ide 40 | *.ng.ts 41 | *.sln.ide 42 | .build/ 43 | .testpublish/ 44 | launchSettings.json 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | .vs/ 4 | *.xap 5 | *.user 6 | /TestResults 7 | *.vspscc 8 | *.vssscc 9 | *.suo 10 | *.cache 11 | *.docstates 12 | *.log 13 | _ReSharper.* 14 | *.csproj.user 15 | *[Rr]e[Ss]harper.user 16 | _ReSharper.*/ 17 | packages/* 18 | artifacts/* 19 | PublishProfiles/ 20 | *.psess 21 | *.vsp 22 | *.pidb 23 | *.userprefs 24 | *DS_Store 25 | *.ncrunchsolution 26 | *.log 27 | *.vspx 28 | /.symbols 29 | nuget.exe 30 | *net45.csproj 31 | *k10.csproj 32 | App_Data/ 33 | bower_components 34 | node_modules 35 | *.sln.ide 36 | *.ng.ts 37 | *.sln.ide 38 | .build/ 39 | .testpublish/ 40 | launchSettings.json 41 | .vscode/ 42 | TestResults/ 43 | global.json 44 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/DemoLinkDisplay.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Demo link display page - Not for production use"; 3 | } 4 | 5 |
    6 |

    @ViewBag.Title.

    7 |
    8 |
    9 |

    10 | Demo link display page - Not for production use. 11 |

    12 | 13 | @if (ViewBag.Link != null) 14 | { 15 |

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

    20 | } 21 |
    -------------------------------------------------------------------------------- /samples/MusicStore/Areas/Admin/Views/StoreManager/RemoveAlbum.cshtml: -------------------------------------------------------------------------------- 1 | @model MusicStore.Models.Album 2 | 3 | @{ 4 | ViewBag.Title = "Delete"; 5 | } 6 | 7 | @if (Model != null) 8 | { 9 |

    Delete Confirmation

    10 | 11 |

    12 | Are you sure you want to delete the album titled 13 | @Model.Title? 14 |

    15 | 16 | @using (Html.BeginForm()) 17 | { 18 |

    19 | 20 |

    21 |

    22 | @Html.ActionLink("Back to List", "Index") 23 |

    24 | } 25 | } 26 | else 27 | { 28 | @Html.Label(null, "Unable to locate the album") 29 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @inject IOptions AppSettings 2 | @{ 3 | ViewBag.Title = "Home Page"; 4 | } 5 | 6 |
    7 |

    @AppSettings.Value.SiteTitle

    8 | 9 |
    10 | 11 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/Components/GenreMenu/Default.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Store/Details.cshtml: -------------------------------------------------------------------------------- 1 | @model Album 2 | 3 | @{ 4 | ViewBag.Title = "Album - " + Model.Title; 5 | } 6 | 7 |

    @Model.Title

    8 | 9 |

    10 | @Model.Title 11 |

    12 | 13 |
    14 |

    15 | Genre: 16 | @Model.Genre.Name 17 |

    18 |

    19 | Artist: 20 | @Model.Artist.Name 21 |

    22 |

    23 | Price: 24 | 25 |

    26 |

    27 | Add to cart 28 |

    29 |
    -------------------------------------------------------------------------------- /samples/MusicStore/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings": { 3 | "SiteTitle": "ASP.NET MVC Music Store", 4 | "CacheDbResults": true 5 | }, 6 | "DefaultAdminUsername": "Administrator@test.com", 7 | "DefaultAdminPassword": "YouShouldChangeThisPassword1!", 8 | "Data": { 9 | "DefaultConnection": { 10 | // Use a shared (and running) LocalDB database when executing in IIS e.g. 11 | // "Server=(localdb)\\.\\IIS_DB;Database=MusicStore;Trusted_Connection=False;MultipleActiveResultSets=true;User ID=iis_login;Password=********" 12 | "ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=MusicStore;Trusted_Connection=True;MultipleActiveResultSets=true;Connect Timeout=30;" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /test/MusicStore.Test/TestAppSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | 3 | namespace MusicStore.Test 4 | { 5 | public class TestAppSettings : IOptions 6 | { 7 | private readonly AppSettings _appSettings; 8 | 9 | public TestAppSettings(bool storeInCache = true) 10 | { 11 | _appSettings = new AppSettings() 12 | { 13 | SiteTitle = "ASP.NET MVC Music Store", 14 | CacheDbResults = storeInCache 15 | }; 16 | } 17 | 18 | public AppSettings Value 19 | { 20 | get 21 | { 22 | return _appSettings; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/MusicStore/Components/SystemClock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MusicStore.Components 4 | { 5 | /// 6 | /// Provides access to the normal system clock. 7 | /// 8 | public class SystemClock : ISystemClock 9 | { 10 | /// 11 | public DateTime UtcNow 12 | { 13 | get 14 | { 15 | // The clock measures whole seconds only, and truncates the milliseconds, 16 | // because millisecond resolution is inconsistent among various underlying systems. 17 | DateTime utcNow = DateTime.UtcNow; 18 | return utcNow.AddMilliseconds(-utcNow.Millisecond); 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/MusicStore/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:4088/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "MusicStore": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000/", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /docker-compose.windows.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | image: microsoft/mssql-server-windows-developer 5 | environment: 6 | sa_password: "Password1" 7 | ACCEPT_EULA: "Y" 8 | ports: 9 | - "1433:1433" # REMARK: This is currently required, needs investigation 10 | healthcheck: 11 | test: [ "CMD", "sqlcmd", "-U", "sa", "-P", "Password1", "-Q", "select 1" ] 12 | interval: 1s 13 | retries: 30 14 | 15 | web: 16 | build: 17 | context: . 18 | dockerfile: Dockerfile.windows 19 | environment: 20 | - "Data:DefaultConnection:ConnectionString=Server=db,1433;Database=MusicStore;User Id=sa;Password=Password1;MultipleActiveResultSets=True" 21 | depends_on: 22 | - db 23 | ports: 24 | - "5000:5000" 25 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/SendCode.cshtml: -------------------------------------------------------------------------------- 1 | @model SendCodeViewModel 2 | @{ 3 | ViewBag.Title = "Send Verification Code"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 |
    9 | 10 |
    11 |
    12 | Select Two-Factor Authentication Provider: 13 | 14 | 15 |
    16 |
    17 |
    18 | 19 | @section Scripts { 20 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 21 | } 22 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Store/Browse.cshtml: -------------------------------------------------------------------------------- 1 | @model Genre 2 | @{ 3 | ViewBag.Title = "Browse Albums"; 4 | } 5 |
    6 |

    7 | @Model.Name Albums 8 |

    9 | 10 | 24 |
    -------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 14 | -------------------------------------------------------------------------------- /samples/MusicStore/Models/MusicStoreContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace MusicStore.Models 5 | { 6 | public class ApplicationUser : IdentityUser { } 7 | 8 | public class MusicStoreContext : IdentityDbContext 9 | { 10 | public MusicStoreContext(DbContextOptions options) 11 | : base(options) 12 | { 13 | // TODO: #639 14 | //ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; 15 | } 16 | 17 | public DbSet Albums { get; set; } 18 | public DbSet Artists { get; set; } 19 | public DbSet Orders { get; set; } 20 | public DbSet Genres { get; set; } 21 | public DbSet CartItems { get; set; } 22 | public DbSet OrderDetails { get; set; } 23 | } 24 | } -------------------------------------------------------------------------------- /Dockerfile.windows: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet-nightly:2.0-sdk-nanoserver 2 | 3 | SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] 4 | 5 | ENV NUGET_XMLDOC_MODE skip 6 | ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE 1 7 | 8 | RUN New-Item -Path \MusicStore\samples\MusicStore -Type Directory 9 | WORKDIR app 10 | 11 | ADD samples/MusicStore/MusicStore.csproj samples/MusicStore/MusicStore.csproj 12 | ADD build/dependencies.props build/dependencies.props 13 | ADD NuGet.config . 14 | RUN dotnet restore --runtime win10-x64 .\samples\MusicStore 15 | 16 | ADD samples samples 17 | 18 | RUN dotnet publish --output /out --configuration Release --framework netcoreapp2.0 --runtime win10-x64 .\samples\MusicStore 19 | 20 | FROM microsoft/dotnet-nightly:2.0-runtime-nanoserver 21 | 22 | WORKDIR /app 23 | COPY --from=0 /out . 24 | 25 | EXPOSE 5000 26 | ENV ASPNETCORE_URLS http://0.0.0.0:5000 27 | 28 | CMD dotnet musicstore.dll 29 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Checkout/AddressAndPayment.cshtml: -------------------------------------------------------------------------------- 1 | @model Order 2 | 3 | @{ 4 | ViewBag.Title = "Address And Payment"; 5 | } 6 | 7 | @section Scripts { 8 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 9 | } 10 | 11 |
    12 |

    Address And Payment

    13 |
    14 |
    15 |
    16 | Shipping Information 17 | 18 | @Html.EditorForModel() 19 |
    20 |
    21 | Payment 22 |

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

    23 | 24 |
    25 | 26 |
    27 |
    28 | @Html.TextBox("PromoCode") 29 |
    30 |
    31 | 32 | 33 |
    -------------------------------------------------------------------------------- /samples/MusicStore/Components/CartSummaryComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using MusicStore.Models; 6 | 7 | namespace MusicStore.Components 8 | { 9 | [ViewComponent(Name = "CartSummary")] 10 | public class CartSummaryComponent : ViewComponent 11 | { 12 | public CartSummaryComponent(MusicStoreContext dbContext) 13 | { 14 | DbContext = dbContext; 15 | } 16 | 17 | private MusicStoreContext DbContext { get; } 18 | 19 | public async Task InvokeAsync() 20 | { 21 | var cart = ShoppingCart.GetCart(DbContext, HttpContext); 22 | 23 | var cartItems = await cart.GetCartAlbumTitles(); 24 | 25 | ViewBag.CartCount = cartItems.Count; 26 | ViewBag.CartSummary = string.Join("\n", cartItems.Distinct()); 27 | 28 | return View(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /test/MusicStore.Test/MusicStore.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | netcoreapp2.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Manage/AddPhoneNumber.cshtml: -------------------------------------------------------------------------------- 1 | @model AddPhoneNumberViewModel 2 | @{ 3 | ViewBag.Title = "Add Phone Number"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 |
    8 |

    Add a phone number.

    9 |
    10 |
    11 |
    12 | 13 |
    14 | 15 | 16 |
    17 |
    18 |
    19 |
    20 | 21 |
    22 |
    23 |
    24 | 25 | @section Scripts { 26 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 27 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/ForgotPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model ForgotPasswordViewModel 2 | @{ 3 | ViewBag.Title = "Forgot your password?"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 |
    9 |

    Enter your email.

    10 |
    11 |
    12 |
    13 | 14 |
    15 | 16 | 17 |
    18 |
    19 |
    20 |
    21 | 22 |
    23 |
    24 |
    25 | 26 | @section Scripts { 27 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 28 | } 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | *.cs text=auto diff=csharp 17 | *.vb text=auto 18 | *.resx text=auto 19 | *.c text=auto 20 | *.cpp text=auto 21 | *.cxx text=auto 22 | *.h text=auto 23 | *.hxx text=auto 24 | *.py text=auto 25 | *.rb text=auto 26 | *.java text=auto 27 | *.html text=auto 28 | *.htm text=auto 29 | *.css text=auto 30 | *.scss text=auto 31 | *.sass text=auto 32 | *.less text=auto 33 | *.js text=auto 34 | *.lisp text=auto 35 | *.clj text=auto 36 | *.sql text=auto 37 | *.php text=auto 38 | *.lua text=auto 39 | *.m text=auto 40 | *.asm text=auto 41 | *.erl text=auto 42 | *.fs text=auto 43 | *.fsx text=auto 44 | *.hs text=auto 45 | 46 | *.csproj text=auto 47 | *.vbproj text=auto 48 | *.fsproj text=auto 49 | *.dbproj text=auto 50 | *.sln text=auto eol=crlf 51 | -------------------------------------------------------------------------------- /samples/MusicStore/Components/GenreMenuComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.EntityFrameworkCore; 6 | using MusicStore.Models; 7 | 8 | namespace MusicStore.Components 9 | { 10 | [ViewComponent(Name = "GenreMenu")] 11 | public class GenreMenuComponent : ViewComponent 12 | { 13 | public GenreMenuComponent(MusicStoreContext dbContext) 14 | { 15 | DbContext = dbContext; 16 | } 17 | 18 | private MusicStoreContext DbContext { get; } 19 | 20 | public async Task InvokeAsync() 21 | { 22 | // TODO use nested sum https://github.com/aspnet/EntityFramework/issues/3792 23 | //.OrderByDescending( 24 | // g => g.Albums.Sum(a => a.OrderDetails.Sum(od => od.Quantity))) 25 | 26 | var genres = await DbContext.Genres.Select(g => g.Name).Take(9).ToListAsync(); 27 | 28 | return View(genres); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/OpenIdConnect/CustomStringDataFormat.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | 3 | namespace MusicStore.Mocks.OpenIdConnect 4 | { 5 | internal class CustomStringDataFormat : ISecureDataFormat 6 | { 7 | private const string _capturedNonce = "635579928639517715.OTRjOTVkM2EtMDRmYS00ZDE3LThhZGUtZWZmZGM4ODkzZGZkMDRlNDhkN2MtOWIwMC00ZmVkLWI5MTItMTUwYmQ4MzdmOWI0"; 8 | 9 | public string Protect(string data) 10 | { 11 | return "protectedString"; 12 | } 13 | 14 | public string Protect(string data, string purpose) 15 | { 16 | return purpose + "protectedString"; 17 | } 18 | 19 | public string Unprotect(string protectedText) 20 | { 21 | return protectedText == "protectedString" ? _capturedNonce : null; 22 | } 23 | 24 | public string Unprotect(string protectedText, string purpose) 25 | { 26 | return protectedText == (purpose + "protectedString") ? _capturedNonce : null; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/MusicStore.Test/TestSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace MusicStore.Controllers 7 | { 8 | internal class TestSession : ISession 9 | { 10 | private Dictionary _store 11 | = new Dictionary(StringComparer.OrdinalIgnoreCase); 12 | 13 | public IEnumerable Keys { get { return _store.Keys; } } 14 | 15 | public string Id { get; set; } 16 | 17 | public bool IsAvailable { get; } = true; 18 | 19 | public void Clear() 20 | { 21 | _store.Clear(); 22 | } 23 | 24 | public Task CommitAsync() 25 | { 26 | return Task.FromResult(0); 27 | } 28 | 29 | public Task LoadAsync() 30 | { 31 | return Task.FromResult(0); 32 | } 33 | 34 | public void Remove(string key) 35 | { 36 | _store.Remove(key); 37 | } 38 | 39 | public void Set(string key, byte[] value) 40 | { 41 | _store[key] = value; 42 | } 43 | 44 | public bool TryGetValue(string key, out byte[] value) 45 | { 46 | return _store.TryGetValue(key, out value); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Manage/VerifyPhoneNumber.cshtml: -------------------------------------------------------------------------------- 1 | @model VerifyPhoneNumberViewModel 2 | @{ 3 | ViewBag.Title = "Verify Phone Number"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 |
    9 | 10 |

    Enter verification code

    11 |

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

    16 |
    17 |
    18 |
    19 | 20 |
    21 | 22 | 23 |
    24 |
    25 |
    26 |
    27 | 28 |
    29 |
    30 |
    31 | 32 | @section Scripts { 33 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 34 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using MusicStore.Models 3 | 4 | @inject SignInManager SignInManager 5 | @inject UserManager UserManager 6 | 7 | @if (SignInManager.IsSignedIn(User)) 8 | { 9 | 17 | } 18 | else if (User.Identity.IsAuthenticated) 19 | { 20 | //This code block necessary only for NTLM authentication 21 | 26 | } 27 | else 28 | { 29 | 33 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/_ExternalLoginsListPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Authentication 2 | @model ExternalLoginListViewModel 3 | @inject IAuthenticationSchemeProvider SchemeProvider 4 |

    Use another service to log in.

    5 |
    6 | @{ 7 | var schemes = await SchemeProvider.GetAllSchemesAsync(); 8 | var loginProviders = schemes.ToList(); 9 | if (!loginProviders.Any()) 10 | { 11 |
    12 |

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

    16 |
    17 | } 18 | else 19 | { 20 |
    21 |
    22 |

    23 | @foreach (var p in loginProviders) 24 | { 25 | 26 | } 27 |

    28 |
    29 |
    30 | } 31 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/ExternalLoginConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @model ExternalLoginConfirmationViewModel 2 | @{ 3 | ViewBag.Title = "Register"; 4 | } 5 |

    @ViewBag.Title.

    6 |

    Associate your @ViewBag.LoginProvider account.

    7 | 8 |
    9 |

    Association Form

    10 |
    11 |
    12 | 13 |

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

    18 |
    19 | 20 |
    21 | 22 | 23 |
    24 |
    25 |
    26 |
    27 | 28 |
    29 |
    30 |
    31 | 32 | @section Scripts { 33 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 34 | } -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/OpenIdConnect/openid-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "issuer": "https://sts.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/", 3 | "authorization_endpoint": "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/oauth2/authorize", 4 | "token_endpoint": "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/oauth2/token", 5 | "token_endpoint_auth_methods_supported": [ 6 | "client_secret_post", 7 | "private_key_jwt" 8 | ], 9 | "jwks_uri": "https://login.windows.net/common/discovery/keys", 10 | "response_types_supported": [ 11 | "code", 12 | "id_token", 13 | "code id_token", 14 | "token" 15 | ], 16 | "response_modes_supported": [ 17 | "query", 18 | "fragment", 19 | "form_post" 20 | ], 21 | "subject_types_supported": [ 22 | "pairwise" 23 | ], 24 | "scopes_supported": [ 25 | "openid" 26 | ], 27 | "id_token_signing_alg_values_supported": [ 28 | "RS256" 29 | ], 30 | "microsoft_multi_refresh_token": true, 31 | "check_session_iframe": "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/oauth2/checksession", 32 | "end_session_endpoint": "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/oauth2/logout", 33 | "userinfo_endpoint": "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/openid/userinfo" 34 | } -------------------------------------------------------------------------------- /samples/MusicStore/Areas/Admin/Views/StoreManager/Details.cshtml: -------------------------------------------------------------------------------- 1 | @model MusicStore.Models.Album 2 | 3 | @{ 4 | ViewBag.Title = "Details"; 5 | } 6 | 7 |

    Details

    8 | 9 |
    10 |

    Album

    11 |
    12 |
    13 |
    14 | @Html.DisplayNameFor(model => model.Artist.Name) 15 |
    16 | 17 |
    18 | @Html.DisplayFor(model => model.Artist.Name) 19 |
    20 | 21 |
    22 | @Html.DisplayNameFor(model => model.Genre.Name) 23 |
    24 | 25 |
    26 | @Html.DisplayFor(model => model.Genre.Name) 27 |
    28 | 29 |
    30 | @Html.DisplayNameFor(model => model.Title) 31 |
    32 | 33 |
    34 | @Html.DisplayFor(model => model.Title) 35 |
    36 | 37 |
    38 | @Html.DisplayNameFor(model => model.Price) 39 |
    40 | 41 |
    42 | @Html.DisplayFor(model => model.Price) 43 |
    44 | 45 |
    46 | @Html.DisplayNameFor(model => model.AlbumArtUrl) 47 |
    48 | 49 |
    50 | @Html.DisplayFor(model => model.AlbumArtUrl) 51 |
    52 | 53 |
    54 |
    55 |

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

    -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/Common/CustomStateDataFormat.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | using Newtonsoft.Json; 3 | 4 | namespace MusicStore.Mocks.Common 5 | { 6 | public class CustomStateDataFormat : ISecureDataFormat 7 | { 8 | private static string _lastSavedAuthenticationProperties; 9 | 10 | public string Protect(AuthenticationProperties data, string purose) 11 | { 12 | return Protect(data); 13 | } 14 | 15 | public string Protect(AuthenticationProperties data) 16 | { 17 | _lastSavedAuthenticationProperties = Serialize(data); 18 | return "ValidStateData"; 19 | } 20 | 21 | public AuthenticationProperties Unprotect(string state, string purpose) 22 | { 23 | return Unprotect(state); 24 | } 25 | 26 | public AuthenticationProperties Unprotect(string state) 27 | { 28 | return state == "ValidStateData" ? DeSerialize(_lastSavedAuthenticationProperties) : null; 29 | } 30 | 31 | private string Serialize(AuthenticationProperties data) 32 | { 33 | return JsonConvert.SerializeObject(data, Formatting.Indented); 34 | } 35 | 36 | private AuthenticationProperties DeSerialize(string state) 37 | { 38 | return JsonConvert.DeserializeObject(state); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/MusicStore/wwwroot/Content/Site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Set padding to keep content from hitting the edges */ 7 | .body-content { 8 | padding-left: 15px; 9 | padding-right: 15px; 10 | } 11 | 12 | /* Set width on the form input elements since they're 100% wide by default */ 13 | input, 14 | select, 15 | textarea { 16 | max-width: 280px; 17 | } 18 | 19 | /* styles for validation helpers */ 20 | .field-validation-error { 21 | color: #b94a48; 22 | } 23 | 24 | .field-validation-valid { 25 | display: none; 26 | } 27 | 28 | input.input-validation-error { 29 | border: 1px solid #b94a48; 30 | } 31 | 32 | input[type="checkbox"].input-validation-error { 33 | border: 0 none; 34 | } 35 | 36 | .validation-summary-errors { 37 | color: #b94a48; 38 | } 39 | 40 | .validation-summary-valid { 41 | display: none; 42 | } 43 | 44 | 45 | /* Music Store additions */ 46 | 47 | ul#album-list li { 48 | height: 160px; 49 | } 50 | 51 | ul#album-list li img:hover { 52 | box-shadow: 1px 1px 7px #777; 53 | } 54 | 55 | ul#album-list li img { 56 | box-shadow: 1px 1px 5px #999; 57 | border: none; 58 | padding: 0; 59 | } 60 | 61 | ul#album-list li a, ul#album-details li a { 62 | text-decoration:none; 63 | } 64 | 65 | ul#album-list li a:hover { 66 | background: none; 67 | -webkit-text-shadow: 1px 1px 2px #bbb; 68 | text-shadow: 1px 1px 2px #bbb; 69 | color: #363430; 70 | } -------------------------------------------------------------------------------- /samples/MusicStore/Models/Album.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using Microsoft.AspNetCore.Mvc.ModelBinding; 5 | 6 | namespace MusicStore.Models 7 | { 8 | public class Album 9 | { 10 | [ScaffoldColumn(false)] 11 | public int AlbumId { get; set; } 12 | 13 | public int GenreId { get; set; } 14 | 15 | public int ArtistId { get; set; } 16 | 17 | [Required] 18 | [StringLength(160, MinimumLength = 2)] 19 | public string Title { get; set; } 20 | 21 | [Required] 22 | [Range(0.01, 100.00)] 23 | 24 | [DataType(DataType.Currency)] 25 | public decimal Price { get; set; } 26 | 27 | [Display(Name = "Album Art URL")] 28 | [StringLength(1024)] 29 | public string AlbumArtUrl { get; set; } 30 | 31 | public virtual Genre Genre { get; set; } 32 | public virtual Artist Artist { get; set; } 33 | public virtual List OrderDetails { get; set; } 34 | 35 | [ScaffoldColumn(false)] 36 | [BindNever] 37 | [Required] 38 | public DateTime Created { get; set; } 39 | 40 | /// 41 | /// TODO: Temporary hack to populate the orderdetails until EF does this automatically. 42 | /// 43 | public Album() 44 | { 45 | OrderDetails = new List(); 46 | Created = DateTime.UtcNow; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model SetPasswordViewModel 2 | @{ 3 | ViewBag.Title = "Set Password"; 4 | } 5 | 6 |

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

    10 | 11 |
    12 |

    Set your password

    13 |
    14 |
    15 |
    16 | 17 |
    18 | 19 | 20 |
    21 |
    22 |
    23 | 24 |
    25 | 26 | 27 |
    28 |
    29 |
    30 |
    31 | 32 |
    33 |
    34 |
    35 | 36 | @section Scripts { 37 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 38 | } -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/Twitter/CustomTwitterStateDataFormat.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | using Microsoft.AspNetCore.Authentication.Twitter; 3 | using Newtonsoft.Json; 4 | 5 | namespace MusicStore.Mocks.Twitter 6 | { 7 | /// 8 | /// Summary description for CustomTwitterStateDataFormat 9 | /// 10 | public class CustomTwitterStateDataFormat : ISecureDataFormat 11 | { 12 | private static string _lastSavedRequestToken; 13 | 14 | public string Protect(RequestToken data) 15 | { 16 | data.Token = "valid_oauth_token"; 17 | _lastSavedRequestToken = Serialize(data); 18 | return "valid_oauth_token"; 19 | } 20 | 21 | public string Protect(RequestToken data, string purpose) 22 | { 23 | return Protect(data); 24 | } 25 | 26 | public RequestToken Unprotect(string state) 27 | { 28 | return state == "valid_oauth_token" ? DeSerialize(_lastSavedRequestToken) : null; 29 | } 30 | 31 | public RequestToken Unprotect(string state, string purpose) 32 | { 33 | return Unprotect(state); 34 | } 35 | 36 | private string Serialize(RequestToken data) 37 | { 38 | return JsonConvert.SerializeObject(data, Formatting.Indented); 39 | } 40 | 41 | private RequestToken DeSerialize(string state) 42 | { 43 | return JsonConvert.DeserializeObject(state); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @model RegisterViewModel 2 | @{ 3 | ViewBag.Title = "Register"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 |
    9 |

    Create a new account.

    10 |
    11 |
    12 |
    13 | 14 |
    15 | 16 | 17 |
    18 |
    19 |
    20 | 21 |
    22 | 23 | 24 |
    25 |
    26 |
    27 | 28 |
    29 | 30 | 31 |
    32 |
    33 |
    34 |
    35 | 36 |
    37 |
    38 |
    39 | 40 | @section Scripts { 41 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 42 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/VerifyCode.cshtml: -------------------------------------------------------------------------------- 1 | @model VerifyCodeViewModel 2 | @{ 3 | ViewBag.Title = "Verify"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 |
    9 |
    10 | 11 | 12 |

    Enter verification code

    13 |

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

    18 |
    19 |
    20 | 21 |
    22 | 23 | 24 |
    25 |
    26 |
    27 |
    28 |
    29 | 30 | 31 |
    32 |
    33 |
    34 |
    35 |
    36 | 37 |
    38 |
    39 |
    40 | 41 | @section Scripts { 42 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 43 | } -------------------------------------------------------------------------------- /test/RemoteTest.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string] $server = $env:TEST_SERVER, 3 | [string] $userName = $env:TEST_SERVER_USER, 4 | [string] $password = $env:TEST_SERVER_PASS, 5 | [string] $serverFolder = "dev" 6 | ) 7 | $ErrorActionPreference = "Stop" 8 | 9 | $projectFile = "MusicStore.Test\MusicStore.Test.csproj" 10 | 11 | Write-Host "Test server: $server" 12 | Write-Host "Test folder: $serverFolder" 13 | 14 | $projectName = (get-item $projectFile).Directory.Name 15 | Write-Host "Test project: $projectName" 16 | 17 | Invoke-Expression "..\tools\BundleAndDeploy.ps1 -projectFile $projectFile -server $server -serverFolder $serverFolder -userName $userName -password `"$password`"" 18 | 19 | $pass = ConvertTo-SecureString $password -AsPlainText -Force 20 | $cred = New-Object System.Management.Automation.PSCredential ($userName, $pass); 21 | 22 | Set-Item WSMan:\localhost\Client\TrustedHosts "$server" -Force 23 | 24 | #This block of code will be executed remotely 25 | $remoteScript = { 26 | $ErrorActionPreference = "Continue" 27 | cd C:\$using:serverFolder\$using:projectName 28 | dir 29 | $env:DNX_TRACE=1 30 | 31 | $output = & .\approot\test.cmd 2>&1 32 | $output 33 | $lastexitcode 34 | } 35 | 36 | Write-Host ">>>> Remote code execution started <<<<" 37 | $remoteJob = Invoke-Command -ComputerName $server -Credential $cred -ScriptBlock $remoteScript -AsJob 38 | Wait-Job $remoteJob 39 | Write-Host "<<<< Remote execution code completed >>>>" 40 | 41 | Write-Host ">>>> Remote execution output <<<<" 42 | $remoteResult = Receive-Job $remoteJob 43 | $remoteResult 44 | Write-Host "<<<< End of remote execution output >>>>" 45 | 46 | $finalExitCode = $remoteResult[$remoteResult.Length-1] 47 | exit $finalExitCode 48 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @model ChangePasswordViewModel 2 | @{ 3 | ViewBag.Title = "Change Password"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 |
    9 |

    Change Password Form

    10 |
    11 |
    12 |
    13 | 14 |
    15 | 16 | 17 |
    18 |
    19 |
    20 | 21 |
    22 | 23 | 24 |
    25 |
    26 |
    27 | 28 |
    29 | 30 | 31 |
    32 |
    33 |
    34 |
    35 | 36 |
    37 |
    38 |
    39 | 40 | @section Scripts { 41 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 42 | } 43 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/ResetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model ResetPasswordViewModel 2 | @{ 3 | ViewBag.Title = "Reset password"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 |
    9 |

    Reset your password.

    10 |
    11 |
    12 | 13 |
    14 | 15 |
    16 | 17 | 18 |
    19 |
    20 |
    21 | 22 |
    23 | 24 | 25 |
    26 |
    27 |
    28 | 29 |
    30 | 31 | 32 |
    33 |
    34 |
    35 |
    36 | 37 |
    38 |
    39 |
    40 | 41 | @section Scripts { 42 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 43 | } -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/RemoteDeploymentConfig.cs: -------------------------------------------------------------------------------- 1 | namespace E2ETests 2 | { 3 | public class RemoteDeploymentConfig 4 | { 5 | /// 6 | /// Name or IP address of the server to deploy to 7 | /// 8 | public string ServerName { get; set; } 9 | 10 | /// 11 | /// Account name for the credentials required to setup a powershell session to the target server 12 | /// 13 | public string AccountName { get; set; } 14 | 15 | /// 16 | /// Account password for the credentials required to setup a powershell session to the target server 17 | /// 18 | public string AccountPassword { get; set; } 19 | 20 | /// 21 | /// The file share on the target server to copy the files to 22 | /// 23 | public string FileSharePath { get; set; } 24 | 25 | /// 26 | /// Location of the dotnet runtime zip file which is required for testing portable apps. 27 | /// When both and properties 28 | /// are provided, the property is preferred. 29 | /// 30 | public string DotnetRuntimeZipFilePath { get; set; } 31 | 32 | /// 33 | /// Location of the dotnet runtime folder which is required for testing portable apps. 34 | /// This property is probably more useful for users as they can point their local 'dotnet' runtime folder. 35 | /// 36 | public string DotnetRuntimeFolderPath { get; set; } 37 | 38 | /// 39 | /// Path to the parent folder containing 'dotnet.exe' on remote server's file share 40 | /// 41 | public string DotnetRuntimePathOnShare { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/Common/XunitLogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // See License.txt in the project root for license information 3 | 4 | using System; 5 | using Microsoft.Extensions.Logging; 6 | using Xunit.Abstractions; 7 | using System.Linq; 8 | 9 | namespace E2ETests.Common 10 | { 11 | public class XunitLogger : ILogger, IDisposable 12 | { 13 | private readonly LogLevel _minLogLevel; 14 | private readonly ITestOutputHelper _output; 15 | private bool _disposed; 16 | 17 | public XunitLogger(ITestOutputHelper output, LogLevel minLogLevel) 18 | { 19 | _minLogLevel = minLogLevel; 20 | _output = output; 21 | } 22 | 23 | public void Log( 24 | LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 25 | { 26 | if (!IsEnabled(logLevel)) 27 | { 28 | return; 29 | } 30 | var firstLinePrefix = "| " + logLevel + ": "; 31 | var lines = formatter(state, exception).Split('\n'); 32 | _output.WriteLine(firstLinePrefix + lines.First()); 33 | 34 | var additionalLinePrefix = "|" + new string(' ', firstLinePrefix.Length - 1); 35 | foreach (var line in lines.Skip(1)) 36 | { 37 | _output.WriteLine(additionalLinePrefix + line.Trim('\r')); 38 | } 39 | } 40 | 41 | public bool IsEnabled(LogLevel logLevel) 42 | => logLevel >= _minLogLevel && !_disposed; 43 | 44 | public IDisposable BeginScope(TState state) 45 | => new NullScope(); 46 | 47 | private class NullScope : IDisposable 48 | { 49 | public void Dispose() 50 | { 51 | } 52 | } 53 | 54 | public void Dispose() 55 | { 56 | _disposed = true; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MusicStore (Sample ASP.NET Core application) 2 | ============================================ 3 | 4 | AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/ja8a7j6jscj7k3xa/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/MusicStore/branch/dev) 5 | 6 | Travis: [![Travis](https://travis-ci.org/aspnet/MusicStore.svg?branch=dev)](https://travis-ci.org/aspnet/MusicStore) 7 | 8 | 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. 9 | 10 | ## Run the application: 11 | * If you have Visual Studio 2017 12 | 1. Open MusicStore.sln in Visual Studio 2017 and run the individual applications on `IIS Express`. 13 | 14 | * If you don't have Visual Studio 2017 15 | 1. Open a command prompt and execute `cd \src\MusicStore\`. 16 | 2. Execute `dotnet restore`. 17 | 18 | **NOTE:** App and tests require Visual Studio 2017 LocalDB on the machine to run. 19 | **NOTE:** Since SQL Server is not generlly available on Mac, the InMemoryStore is used to run the application. So the changes that you make will not be persisted. 20 | 21 | ## Run on Docker Windows Containers 22 | 23 | * [Install Docker for Windows](https://docs.docker.com/docker-for-windows/) or [setup up Docker Windows containers](https://msdn.microsoft.com/en-us/virtualization/windowscontainers/containers_welcome) 24 | * `docker-compose -f .\docker-compose.windows.yml build` 25 | * `docker-compose -f .\docker-compose.windows.yml up` 26 | * Access MusicStore on either the Windows VM IP or (if container is running locally) on the container IP: `docker inspect -f "{{ .NetworkSettings.Networks.nat.IPAddress }}" musicstore_web_1` 27 | 28 | ## NTLM authentication 29 | More information at [src/MusicStore/StartupNtlmAuthentication.cs](src/MusicStore/StartupNtlmAuthentication.cs). 30 | 31 | ## OpenIdConnect authentication 32 | More information at [src/MusicStore/StartupOpenIdConnect.cs](src/MusicStore/StartupOpenIdConnect.cs). 33 | -------------------------------------------------------------------------------- /samples/MusicStore/Areas/Admin/Views/StoreManager/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 | @{ 4 | ViewBag.Title = "Index"; 5 | } 6 | 7 |

    Index

    8 | 9 |

    10 | @Html.ActionLink("Create New", "Create") 11 |

    12 | 13 | 14 | 17 | 20 | 23 | 26 | 27 | 28 | 29 | @foreach (var item in Model) 30 | { 31 | 32 | 35 | 45 | 55 | 58 | 63 | 64 | } 65 | 66 |
    15 | @Html.DisplayNameFor(model => model.Genre.Name) 16 | 18 | @Html.DisplayNameFor(model => model.FirstOrDefault().Artist.Name) 19 | 21 | @Html.DisplayNameFor(model => model.FirstOrDefault().Title) 22 | 24 | @Html.DisplayNameFor(model => model.FirstOrDefault().Price) 25 |
    33 | @Html.DisplayFor(modelItem => item.Genre.Name) 34 | 36 | @if (item.Artist.Name.Length <= 25) 37 | { 38 | @item.Artist.Name 39 | } 40 | else 41 | { 42 | @item.Artist.Name.Substring(0, 25)... 43 | } 44 | 46 | @if (item.Title.Length <= 25) 47 | { 48 | @item.Title 49 | } 50 | else 51 | { 52 | @item.Title.Substring(0, 25)... 53 | } 54 | 56 | @Html.DisplayFor(modelItem => item.Price) 57 | 59 | @Html.ActionLink("Edit", "Edit", new { id = item.AlbumId }) | 60 | @Html.ActionLink("Details", "Details", new { id = item.AlbumId }) | 61 | @Html.ActionLink("Delete", "RemoveAlbum", new { id = item.AlbumId }) 62 |
    -------------------------------------------------------------------------------- /test/MusicStore.Test/GenreMenuComponentTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.ViewComponents; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using MusicStore.Models; 9 | using Xunit; 10 | 11 | namespace MusicStore.Components 12 | { 13 | public class GenreMenuComponentTest 14 | { 15 | private readonly IServiceProvider _serviceProvider; 16 | 17 | public GenreMenuComponentTest() 18 | { 19 | var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); 20 | 21 | var services = new ServiceCollection(); 22 | 23 | services.AddDbContext(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider)); 24 | 25 | _serviceProvider = services.BuildServiceProvider(); 26 | } 27 | 28 | [Fact] 29 | public async Task GenreMenuComponent_Returns_NineGenres() 30 | { 31 | // Arrange 32 | var dbContext = _serviceProvider.GetRequiredService(); 33 | var genreMenuComponent = new GenreMenuComponent(dbContext); 34 | 35 | PopulateData(dbContext); 36 | 37 | // Act 38 | var result = await genreMenuComponent.InvokeAsync(); 39 | 40 | // Assert 41 | Assert.NotNull(result); 42 | var viewResult = Assert.IsType(result); 43 | Assert.Null(viewResult.ViewName); 44 | var genreResult = Assert.IsType>(viewResult.ViewData.Model); 45 | Assert.Equal(9, genreResult.Count); 46 | } 47 | 48 | private static void PopulateData(MusicStoreContext context) 49 | { 50 | var genres = Enumerable.Range(1, 10).Select(n => new Genre { GenreId = n }); 51 | 52 | context.AddRange(genres); 53 | context.SaveChanges(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/MusicStore.Test/Models/ShoppingCartTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // See License.txt in the project root for license information 3 | 4 | using System; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using MusicStore.Models; 8 | using Xunit; 9 | 10 | namespace MusicStore.Test 11 | { 12 | public class ShoppingCartTest : IClassFixture 13 | { 14 | private readonly ShoppingCartFixture _fixture; 15 | 16 | public ShoppingCartTest(ShoppingCartFixture fixture) 17 | { 18 | _fixture = fixture; 19 | } 20 | 21 | [Fact] 22 | public async void ComputesTotal() 23 | { 24 | var cartId = Guid.NewGuid().ToString(); 25 | using (var db = _fixture.CreateContext()) 26 | { 27 | var a = db.Albums.Add( 28 | new Album 29 | { 30 | Price = 15.99m 31 | }).Entity; 32 | 33 | db.CartItems.Add(new CartItem { Album = a, Count = 2, CartId = cartId }); 34 | 35 | db.SaveChanges(); 36 | 37 | Assert.Equal(31.98m, await ShoppingCart.GetCart(db, cartId).GetTotal()); 38 | } 39 | } 40 | } 41 | 42 | public class ShoppingCartFixture 43 | { 44 | private readonly IServiceProvider _serviceProvider; 45 | 46 | public ShoppingCartFixture() 47 | { 48 | var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); 49 | 50 | var services = new ServiceCollection(); 51 | 52 | services.AddDbContext(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider)); 53 | 54 | _serviceProvider = services.BuildServiceProvider(); 55 | } 56 | 57 | public virtual MusicStoreContext CreateContext() 58 | => _serviceProvider.GetRequiredService(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Manage/ManageLogins.cshtml: -------------------------------------------------------------------------------- 1 | @model ManageLoginsViewModel 2 | @{ 3 | ViewBag.Title = "Manage your external logins"; 4 | } 5 | 6 |

    @ViewBag.Title.

    7 | 8 |

    @ViewBag.StatusMessage

    9 | @if (Model.CurrentLogins.Count > 0) 10 | { 11 |

    Registered Logins

    12 | 13 | 14 | @foreach (var account in Model.CurrentLogins) 15 | { 16 | 17 | 18 | 34 | 35 | } 36 | 37 |
    @account.LoginProvider 19 | @if (ViewBag.ShowRemoveButton) 20 | { 21 |
    22 |
    23 | 24 | 25 | 26 |
    27 |
    28 | } 29 | else 30 | { 31 | @:   32 | } 33 |
    38 | } 39 | @if (Model.OtherLogins.Any()) 40 | { 41 |

    Add another service to log in.

    42 |
    43 |
    44 |
    45 |

    46 | @foreach (var p in Model.OtherLogins) 47 | { 48 | 49 | } 50 |

    51 |
    52 |
    53 | } -------------------------------------------------------------------------------- /samples/MusicStore/Models/Order.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using Microsoft.AspNetCore.Mvc.ModelBinding; 4 | 5 | namespace MusicStore.Models 6 | { 7 | //[Bind(Include = "FirstName,LastName,Address,City,State,PostalCode,Country,Phone,Email")] 8 | public class Order 9 | { 10 | [BindNever] 11 | [ScaffoldColumn(false)] 12 | public int OrderId { get; set; } 13 | 14 | [BindNever] 15 | [ScaffoldColumn(false)] 16 | public System.DateTime OrderDate { get; set; } 17 | 18 | [BindNever] 19 | [ScaffoldColumn(false)] 20 | public string Username { get; set; } 21 | 22 | [Required] 23 | [Display(Name = "First Name")] 24 | [StringLength(160)] 25 | public string FirstName { get; set; } 26 | 27 | [Required] 28 | [Display(Name = "Last Name")] 29 | [StringLength(160)] 30 | public string LastName { get; set; } 31 | 32 | [Required] 33 | [StringLength(70, MinimumLength = 3)] 34 | public string Address { get; set; } 35 | 36 | [Required] 37 | [StringLength(40)] 38 | public string City { get; set; } 39 | 40 | [Required] 41 | [StringLength(40)] 42 | public string State { get; set; } 43 | 44 | [Required] 45 | [Display(Name = "Postal Code")] 46 | [StringLength(10, MinimumLength = 5)] 47 | public string PostalCode { get; set; } 48 | 49 | [Required] 50 | [StringLength(40)] 51 | public string Country { get; set; } 52 | 53 | [Required] 54 | [StringLength(24)] 55 | [DataType(DataType.PhoneNumber)] 56 | public string Phone { get; set; } 57 | 58 | [Required] 59 | [Display(Name = "Email Address")] 60 | [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", 61 | ErrorMessage = "Email is not valid.")] 62 | [DataType(DataType.EmailAddress)] 63 | public string Email { get; set; } 64 | 65 | [BindNever] 66 | [ScaffoldColumn(false)] 67 | public decimal Total { get; set; } 68 | 69 | [BindNever] 70 | public List OrderDetails { get; set; } 71 | } 72 | } -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/Common/DbUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using Microsoft.AspNetCore.Testing; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace E2ETests 7 | { 8 | /// 9 | /// Summary description for DtUtils 10 | /// 11 | public class DbUtils 12 | { 13 | private const string BaseConnString = @"Server=(localdb)\MSSQLLocalDB;Trusted_Connection=True;MultipleActiveResultSets=true;Connect Timeout=30;"; 14 | 15 | public static string CreateConnectionString(string dbName) 16 | => new SqlConnectionStringBuilder(BaseConnString) 17 | { 18 | InitialCatalog = dbName 19 | }.ToString(); 20 | 21 | public static string GetUniqueName() 22 | => "MusicStore_Test_" + Guid.NewGuid().ToString().Replace("-", string.Empty); 23 | 24 | public static void DropDatabase(string databaseName, ILogger logger) 25 | { 26 | if (!TestPlatformHelper.IsWindows) 27 | { 28 | return; 29 | } 30 | try 31 | { 32 | logger.LogInformation("Trying to drop database '{0}'", databaseName); 33 | using (var conn = new SqlConnection(CreateConnectionString("master"))) 34 | { 35 | conn.Open(); 36 | 37 | var cmd = conn.CreateCommand(); 38 | cmd.CommandText = string.Format(@"IF EXISTS (SELECT * FROM sys.databases WHERE name = N'{0}') 39 | BEGIN 40 | ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; 41 | DROP DATABASE [{0}]; 42 | END", databaseName); 43 | cmd.ExecuteNonQuery(); 44 | 45 | logger.LogInformation("Successfully dropped database {0}", databaseName); 46 | } 47 | } 48 | catch (Exception exception) 49 | { 50 | //Ignore if there is failure in cleanup. 51 | logger.LogWarning("Error occurred while dropping database {0}. Exception : {1}", databaseName, exception.ToString()); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/Twitter/TestTwitterEvents.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Security.Claims; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.AspNetCore.Authentication.Twitter; 6 | using Microsoft.AspNetCore.Identity; 7 | using MusicStore.Mocks.Common; 8 | 9 | namespace MusicStore.Mocks.Twitter 10 | { 11 | internal class TestTwitterEvents 12 | { 13 | internal static Task OnCreatingTicket(TwitterCreatingTicketContext context) 14 | { 15 | if (context.Principal != null) 16 | { 17 | Helpers.ThrowIfConditionFailed(() => context.UserId == "valid_user_id", "UserId is not valid"); 18 | Helpers.ThrowIfConditionFailed(() => context.ScreenName == "valid_screen_name", "ScreenName is not valid"); 19 | Helpers.ThrowIfConditionFailed(() => context.AccessToken == "valid_oauth_token", "AccessToken is not valid"); 20 | Helpers.ThrowIfConditionFailed(() => context.AccessTokenSecret == "valid_oauth_token_secret", "AccessTokenSecret is not valid"); 21 | context.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); 22 | } 23 | 24 | return Task.FromResult(0); 25 | } 26 | 27 | internal static Task OnTicketReceived(TicketReceivedContext context) 28 | { 29 | if (context.Principal != null && context.Options.SignInScheme == new IdentityCookieOptions().ExternalCookieAuthenticationScheme) 30 | { 31 | //This way we will know all Events were fired. 32 | var identity = context.Principal.Identities.First(); 33 | var manageStoreClaim = identity?.Claims.Where(c => c.Type == "ManageStore" && c.Value == "false").FirstOrDefault(); 34 | if (manageStoreClaim != null) 35 | { 36 | identity.RemoveClaim(manageStoreClaim); 37 | identity.AddClaim(new Claim("ManageStore", "Allowed")); 38 | } 39 | } 40 | 41 | return Task.FromResult(0); 42 | } 43 | 44 | internal static Task RedirectToAuthorizationEndpoint(TwitterRedirectToAuthorizationEndpointContext context) 45 | { 46 | context.Response.Redirect(context.RedirectUri + "&custom_redirect_uri=custom"); 47 | return Task.FromResult(0); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/Common/HtmlDOMHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml; 3 | 4 | namespace E2ETests 5 | { 6 | public class HtmlDOMHelper 7 | { 8 | public static string RetrieveAntiForgeryToken(string htmlContent, string actionUrl) 9 | { 10 | int startSearchIndex = 0; 11 | 12 | while (startSearchIndex < htmlContent.Length) 13 | { 14 | var antiForgeryToken = RetrieveAntiForgeryToken(htmlContent, actionUrl, ref startSearchIndex); 15 | 16 | if (antiForgeryToken != null) 17 | { 18 | return antiForgeryToken; 19 | } 20 | } 21 | 22 | return string.Empty; 23 | } 24 | 25 | private static string RetrieveAntiForgeryToken(string htmlContent, string actionLocation, ref int startIndex) 26 | { 27 | var formStartIndex = htmlContent.IndexOf("", startIndex, StringComparison.OrdinalIgnoreCase); 29 | 30 | if (formStartIndex == -1 || formEndIndex == -1) 31 | { 32 | //Unable to find the form start or end - finish the search 33 | startIndex = htmlContent.Length; 34 | return null; 35 | } 36 | 37 | formEndIndex = formEndIndex + "".Length; 38 | startIndex = formEndIndex + 1; 39 | 40 | var htmlDocument = new XmlDocument(); 41 | htmlDocument.LoadXml(htmlContent.Substring(formStartIndex, formEndIndex - formStartIndex)); 42 | 43 | foreach (XmlAttribute attribute in htmlDocument.DocumentElement.Attributes) 44 | { 45 | if (string.Compare(attribute.Name, "action", true) == 0 && attribute.Value.EndsWith(actionLocation, StringComparison.OrdinalIgnoreCase)) 46 | { 47 | foreach (XmlNode input in htmlDocument.GetElementsByTagName("input")) 48 | { 49 | if (input.Attributes["name"]?.Value == "__RequestVerificationToken" && input.Attributes["type"].Value == "hidden") 50 | { 51 | return input.Attributes["value"].Value; 52 | } 53 | } 54 | } 55 | } 56 | 57 | return null; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @model LoginViewModel 2 | 3 | @{ 4 | ViewBag.Title = "Log in"; 5 | } 6 | 7 |

    @ViewBag.Title.

    8 |
    9 |
    10 |
    11 |
    12 |

    Use a local account to log in.

    13 |
    14 |
    15 |
    16 | 17 |
    18 | 19 | 20 |
    21 |
    22 |
    23 | 24 |
    25 | 26 | 27 |
    28 |
    29 |
    30 |
    31 |
    32 | 33 | 34 |
    35 |
    36 |
    37 |
    38 |
    39 | 40 |
    41 |
    42 |

    43 | Register as a new user? 44 |

    45 |

    46 | Forgot your password? 47 |

    48 |
    49 |
    50 |
    51 |
    52 |
    53 | @await Html.PartialAsync("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl }) 54 |
    55 |
    56 |
    57 | 58 | @section Scripts { 59 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 60 | } -------------------------------------------------------------------------------- /samples/MusicStore/MusicStore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Music store application on ASP.NET Core 7 | netcoreapp2.0 8 | $(DefineConstants);DEMO 9 | true 10 | win7-x86;win7-x64;linux-x64;osx-x64 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /samples/MusicStore/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.Server.HttpSys; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace MusicStore 9 | { 10 | public static class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var config = new ConfigurationBuilder() 15 | .AddCommandLine(args) 16 | .AddEnvironmentVariables(prefix: "ASPNETCORE_") 17 | .Build(); 18 | 19 | var builder = new WebHostBuilder() 20 | .UseContentRoot(Directory.GetCurrentDirectory()) 21 | .UseConfiguration(config) 22 | .UseIISIntegration() 23 | .UseStartup("MusicStore") 24 | .UseDefaultServiceProvider((context, options) => { 25 | options.ValidateScopes = true; 26 | }); 27 | 28 | var environment = builder.GetSetting("environment") ?? 29 | Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); 30 | 31 | if (string.Equals(builder.GetSetting("server"), "Microsoft.AspNetCore.Server.HttpSys", System.StringComparison.Ordinal)) 32 | { 33 | if (string.Equals(environment, "NtlmAuthentication", System.StringComparison.Ordinal)) 34 | { 35 | // Set up NTLM authentication for WebListener like below. 36 | // For IIS and IISExpress: Use inetmgr to setup NTLM authentication on the application vDir or 37 | // modify the applicationHost.config to enable NTLM. 38 | builder.UseHttpSys(options => 39 | { 40 | options.Authentication.Schemes = AuthenticationSchemes.NTLM; 41 | options.Authentication.AllowAnonymous = false; 42 | }); 43 | } 44 | else 45 | { 46 | builder.UseHttpSys(); 47 | } 48 | } 49 | else 50 | { 51 | builder.UseKestrel(); 52 | } 53 | 54 | builder.ConfigureLogging(factory => 55 | { 56 | factory.AddConsole(); 57 | 58 | var logLevel = string.Equals(environment, "Development", StringComparison.Ordinal) ? LogLevel.Information : LogLevel.Warning; 59 | 60 | factory.AddFilter("Console", level => level >= logLevel); 61 | }); 62 | 63 | var host = builder.Build(); 64 | 65 | host.Run(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /samples/MusicStore/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.Extensions.Caching.Memory; 8 | using Microsoft.Extensions.Options; 9 | using MusicStore.Models; 10 | 11 | namespace MusicStore.Controllers 12 | { 13 | public class HomeController : Controller 14 | { 15 | private readonly AppSettings _appSettings; 16 | 17 | public HomeController(IOptions options) 18 | { 19 | _appSettings = options.Value; 20 | } 21 | // 22 | // GET: /Home/ 23 | public async Task Index( 24 | [FromServices] MusicStoreContext dbContext, 25 | [FromServices] IMemoryCache cache) 26 | { 27 | // Get most popular albums 28 | var cacheKey = "topselling"; 29 | List albums; 30 | if (!cache.TryGetValue(cacheKey, out albums)) 31 | { 32 | albums = await GetTopSellingAlbumsAsync(dbContext, 6); 33 | 34 | if (albums != null && albums.Count > 0) 35 | { 36 | if (_appSettings.CacheDbResults) 37 | { 38 | // Refresh it every 10 minutes. 39 | // Let this be the last item to be removed by cache if cache GC kicks in. 40 | cache.Set( 41 | cacheKey, 42 | albums, 43 | new MemoryCacheEntryOptions() 44 | .SetAbsoluteExpiration(TimeSpan.FromMinutes(10)) 45 | .SetPriority(CacheItemPriority.High)); 46 | } 47 | } 48 | } 49 | 50 | return View(albums); 51 | } 52 | 53 | public IActionResult Error() 54 | { 55 | return View("~/Views/Shared/Error.cshtml"); 56 | } 57 | 58 | public IActionResult StatusCodePage() 59 | { 60 | return View("~/Views/Shared/StatusCodePage.cshtml"); 61 | } 62 | 63 | public IActionResult AccessDenied() 64 | { 65 | return View("~/Views/Shared/AccessDenied.cshtml"); 66 | } 67 | 68 | private Task> GetTopSellingAlbumsAsync(MusicStoreContext dbContext, int count) 69 | { 70 | // Group the order details by album and return 71 | // the albums with the highest count 72 | 73 | return dbContext.Albums 74 | .OrderByDescending(a => a.OrderDetails.Count) 75 | .Take(count) 76 | .ToListAsync(); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /samples/MusicStore/Areas/Admin/Views/StoreManager/Create.cshtml: -------------------------------------------------------------------------------- 1 | @model MusicStore.Models.Album 2 | 3 | @{ 4 | ViewBag.Title = "Create"; 5 | } 6 | 7 |

    Create

    8 | 9 | @using (Html.BeginForm()) 10 | { 11 | @Html.AntiForgeryToken() 12 | 13 |
    14 |

    Album

    15 |
    16 | @Html.ValidationSummary(true) 17 | 18 |
    19 | @Html.LabelFor(model => model.GenreId, "GenreId", new { @class = "control-label col-md-2" }) 20 |
    21 | @Html.DropDownList("GenreId", String.Empty) 22 | @Html.ValidationMessageFor(model => model.GenreId) 23 |
    24 |
    25 | 26 |
    27 | @Html.LabelFor(model => model.ArtistId, "ArtistId", new { @class = "control-label col-md-2" }) 28 |
    29 | @Html.DropDownList("ArtistId", String.Empty) 30 | @Html.ValidationMessageFor(model => model.ArtistId) 31 |
    32 |
    33 | 34 |
    35 | @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" }) 36 |
    37 | @Html.EditorFor(model => model.Title) 38 | @Html.ValidationMessageFor(model => model.Title) 39 |
    40 |
    41 | 42 |
    43 | @Html.LabelFor(model => model.Price, new { @class = "control-label col-md-2" }) 44 |
    45 | @Html.EditorFor(model => model.Price) 46 | @Html.ValidationMessageFor(model => model.Price) 47 |
    48 |
    49 | 50 |
    51 | @Html.LabelFor(model => model.AlbumArtUrl, new { @class = "control-label col-md-2" }) 52 |
    53 | @Html.EditorFor(model => model.AlbumArtUrl) 54 | @Html.ValidationMessageFor(model => model.AlbumArtUrl) 55 |
    56 |
    57 | 58 |
    59 |
    60 | 61 |
    62 |
    63 |
    64 | } 65 | 66 |
    67 | @Html.ActionLink("Back to List", "Index") 68 |
    69 | 70 | @section Scripts { 71 | @*TODO : Until script helpers are available, adding script references manually*@ 72 | @*@Scripts.Render("~/bundles/jqueryval")*@ 73 | 74 | 75 | } -------------------------------------------------------------------------------- /tools/BundleAndDeploy.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Mandatory=$true)] 3 | [string] $projectFile, 4 | [Parameter(Mandatory=$true)] 5 | [string] $server, 6 | [Parameter(Mandatory=$true)] 7 | [string] $serverFolder, 8 | [Parameter(Mandatory=$true)] 9 | [string] $userName, 10 | [Parameter(Mandatory=$true)] 11 | [string] $password, 12 | [bool] $remoteInvoke = $false 13 | ) 14 | $ErrorActionPreference = "Stop" 15 | 16 | function UpdateHostInProjectJson($projectFile, $newhost) 17 | { 18 | (Get-Content $projectFile) | 19 | Foreach-Object { 20 | $_ -replace "http://localhost", "http://$newhost" 21 | } | 22 | Set-Content $projectFile 23 | } 24 | 25 | if (-not (Test-Path $projectFile)) { 26 | Write-Error "Couldn't find $projectFile" 27 | exit 1 28 | } 29 | 30 | $projectName = (get-item $projectFile).Directory.Name 31 | $workDir = (get-item $projectFile).Directory.FullName 32 | $remoteRoot = "\\" + $server 33 | $remoteDir = Join-Path $remoteRoot -ChildPath $serverFolder 34 | 35 | try 36 | { 37 | if ($userName) { 38 | net use $remoteRoot $password /USER:$userName 39 | if ($lastexitcode -ne 0) { 40 | exit 1 41 | } 42 | } 43 | 44 | if (-not (Test-Path $remoteDir)) { 45 | Write-Error "Remote directory $remoteDir does not exist or it is not accessible" 46 | exit 1 47 | } 48 | 49 | $packDir = Join-Path $workDir -ChildPath "bin\output" 50 | if (Test-Path $packDir) { 51 | Write-Host "$packDir already exists. Removing it..." 52 | rmdir -Recurse "$packDir" 53 | } 54 | 55 | Write-Host "Bundling the application..." 56 | cd "$workDir" 57 | dnvm use default -r CoreCLR -arch x64 58 | dnu publish --runtime active --no-source 59 | if ($lastexitcode -ne 0) { 60 | Write-Error "Failed to bundle the application" 61 | exit 1 62 | } 63 | 64 | if ($remoteInvoke) { 65 | $packedProjectJsonFile = Join-Path $packDir -ChildPath "approot\src\$projectName\$projectName.csproj" 66 | Write-Host "Setting host to $server in $packedProjectJsonFile" 67 | if (-not (Test-Path $packedProjectJsonFile)) { 68 | Write-Error "Couldn't find $packedProjectJsonFile" 69 | exit 1 70 | } 71 | 72 | UpdateHostInProjectJson $packedProjectJsonFile $server 73 | } 74 | 75 | $destDir = Join-Path $remoteDir -ChildPath $projectName 76 | if (Test-Path $destDir) { 77 | Write-Host "$destDir already exists. Removing it..." 78 | cmd /c rmdir /S /Q "$destDir" 79 | } 80 | 81 | Write-Host "Copying bundled application to $destDir ..." 82 | Copy-Item "$packDir" -Destination "$destDir" -Recurse 83 | } 84 | finally 85 | { 86 | if ($userName -and $remoteRoot) { 87 | net use $remoteRoot /delete 88 | } 89 | } -------------------------------------------------------------------------------- /samples/MusicStore/Areas/Admin/Views/StoreManager/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @model MusicStore.Models.Album 2 | 3 | @{ 4 | ViewBag.Title = "Edit"; 5 | } 6 | 7 |

    Edit

    8 | 9 | @using (Html.BeginForm()) 10 | { 11 | @Html.AntiForgeryToken() 12 | 13 |
    14 |

    Album

    15 |
    16 | @Html.ValidationSummary(true) 17 | @Html.HiddenFor(model => model.AlbumId) 18 | 19 |
    20 | @Html.LabelFor(model => model.GenreId, "GenreId", new { @class = "control-label col-md-2" }) 21 |
    22 | @Html.DropDownList("GenreId", String.Empty) 23 | @Html.ValidationMessageFor(model => model.GenreId) 24 |
    25 |
    26 | 27 |
    28 | @Html.LabelFor(model => model.ArtistId, "ArtistId", new { @class = "control-label col-md-2" }) 29 |
    30 | @Html.DropDownList("ArtistId", String.Empty) 31 | @Html.ValidationMessageFor(model => model.ArtistId) 32 |
    33 |
    34 | 35 |
    36 | @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" }) 37 |
    38 | @Html.EditorFor(model => model.Title) 39 | @Html.ValidationMessageFor(model => model.Title) 40 |
    41 |
    42 | 43 |
    44 | @Html.LabelFor(model => model.Price, new { @class = "control-label col-md-2" }) 45 |
    46 | @Html.EditorFor(model => model.Price) 47 | @Html.ValidationMessageFor(model => model.Price) 48 |
    49 |
    50 | 51 |
    52 | @Html.LabelFor(model => model.AlbumArtUrl, new { @class = "control-label col-md-2" }) 53 |
    54 | @Html.EditorFor(model => model.AlbumArtUrl) 55 | @Html.ValidationMessageFor(model => model.AlbumArtUrl) 56 |
    57 |
    58 | 59 |
    60 |
    61 | 62 |
    63 |
    64 |
    65 | } 66 | 67 |
    68 | @Html.ActionLink("Back to List", "Index") 69 |
    70 | 71 | @section Scripts { 72 | @*TODO : Until script helpers are available, adding script references manually*@ 73 | @*@Scripts.Render("~/bundles/jqueryval")*@ 74 | 75 | 76 | } -------------------------------------------------------------------------------- /samples/MusicStore/Controllers/StoreController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.Caching.Memory; 7 | using Microsoft.Extensions.Options; 8 | using MusicStore.Models; 9 | 10 | namespace MusicStore.Controllers 11 | { 12 | public class StoreController : Controller 13 | { 14 | private readonly AppSettings _appSettings; 15 | 16 | public StoreController(MusicStoreContext dbContext, IOptions options) 17 | { 18 | DbContext = dbContext; 19 | _appSettings = options.Value; 20 | } 21 | 22 | public MusicStoreContext DbContext { get; } 23 | 24 | // 25 | // GET: /Store/ 26 | public async Task Index() 27 | { 28 | var genres = await DbContext.Genres.ToListAsync(); 29 | 30 | return View(genres); 31 | } 32 | 33 | // 34 | // GET: /Store/Browse?genre=Disco 35 | public async Task Browse(string genre) 36 | { 37 | // Retrieve Genre genre and its Associated associated Albums albums from database 38 | var genreModel = await DbContext.Genres 39 | .Include(g => g.Albums) 40 | .Where(g => g.Name == genre) 41 | .FirstOrDefaultAsync(); 42 | 43 | if (genreModel == null) 44 | { 45 | return NotFound(); 46 | } 47 | 48 | return View(genreModel); 49 | } 50 | 51 | public async Task Details( 52 | [FromServices] IMemoryCache cache, 53 | int id) 54 | { 55 | var cacheKey = string.Format("album_{0}", id); 56 | Album album; 57 | if (!cache.TryGetValue(cacheKey, out album)) 58 | { 59 | album = await DbContext.Albums 60 | .Where(a => a.AlbumId == id) 61 | .Include(a => a.Artist) 62 | .Include(a => a.Genre) 63 | .FirstOrDefaultAsync(); 64 | 65 | if (album != null) 66 | { 67 | if (_appSettings.CacheDbResults) 68 | { 69 | //Remove it from cache if not retrieved in last 10 minutes 70 | cache.Set( 71 | cacheKey, 72 | album, 73 | new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(10))); 74 | } 75 | } 76 | } 77 | 78 | if (album == null) 79 | { 80 | return NotFound(); 81 | } 82 | 83 | return View(album); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/MusicStore.E2ETests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | netcoreapp2.0 7 | MusicStore.E2ETests 8 | true 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/OpenIdConnect/OpenIdConnectBackChannelHttpHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace MusicStore.Mocks.OpenIdConnect 8 | { 9 | internal class OpenIdConnectBackChannelHttpHandler : HttpMessageHandler 10 | { 11 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 12 | { 13 | var response = new HttpResponseMessage(); 14 | 15 | var basePath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "ForTesting", "Mocks", "OpenIdConnect")); 16 | 17 | if (request.RequestUri.AbsoluteUri == "https://login.windows.net/[tenantName].onmicrosoft.com/.well-known/openid-configuration") 18 | { 19 | response.Content = new StringContent(File.ReadAllText(Path.Combine(basePath, "openid-configuration.json"))); 20 | } 21 | else if (request.RequestUri.AbsoluteUri == "https://login.windows.net/common/discovery/keys") 22 | { 23 | response.Content = new StringContent(File.ReadAllText(Path.Combine(basePath, "keys.json"))); 24 | } 25 | else if (request.RequestUri.AbsoluteUri == "https://login.windows.net/4afbc689-805b-48cf-a24c-d4aa3248a248/oauth2/token") 26 | { 27 | 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\"}"); 28 | } 29 | 30 | return Task.FromResult(response); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/Facebook/TestFacebookEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Authentication.Facebook; 7 | using Microsoft.AspNetCore.Authentication.OAuth; 8 | using Microsoft.AspNetCore.Identity; 9 | using MusicStore.Mocks.Common; 10 | 11 | namespace MusicStore.Mocks.Facebook 12 | { 13 | internal class TestFacebookEvents 14 | { 15 | internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) 16 | { 17 | if (context.Ticket.Principal != null) 18 | { 19 | Helpers.ThrowIfConditionFailed(() => context.AccessToken == "ValidAccessToken", ""); 20 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Email)?.Value == "AspnetvnextTest@test.com", ""); 21 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == "Id", ""); 22 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst("urn:facebook:link")?.Value == "https://www.facebook.com/myLink", ""); 23 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", ""); 24 | Helpers.ThrowIfConditionFailed(() => context.User.SelectToken("id").ToString() == context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value, ""); 25 | Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(100), ""); 26 | Helpers.ThrowIfConditionFailed(() => context.AccessToken == "ValidAccessToken", ""); 27 | context.Ticket.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); 28 | } 29 | 30 | return Task.FromResult(0); 31 | } 32 | 33 | internal static Task OnTicketReceived(TicketReceivedContext context) 34 | { 35 | if (context.Principal != null && context.Options.SignInScheme == new IdentityCookieOptions().ExternalCookieAuthenticationScheme) 36 | { 37 | //This way we will know all events were fired. 38 | var identity = context.Principal.Identities.First(); 39 | var manageStoreClaim = identity?.Claims.Where(c => c.Type == "ManageStore" && c.Value == "false").FirstOrDefault(); 40 | if (manageStoreClaim != null) 41 | { 42 | identity.RemoveClaim(manageStoreClaim); 43 | identity.AddClaim(new Claim("ManageStore", "Allowed")); 44 | } 45 | } 46 | 47 | return Task.FromResult(0); 48 | } 49 | 50 | internal static Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context) 51 | { 52 | context.Response.Redirect(context.RedirectUri + "&custom_redirect_uri=custom"); 53 | return Task.FromResult(0); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /samples/MusicStore/Models/ManageViewModels.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using Microsoft.AspNetCore.Authentication; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | 7 | namespace MusicStore.Models 8 | { 9 | public class IndexViewModel 10 | { 11 | public bool HasPassword { get; set; } 12 | public IList Logins { get; set; } 13 | public string PhoneNumber { get; set; } 14 | public bool TwoFactor { get; set; } 15 | public bool BrowserRemembered { get; set; } 16 | } 17 | 18 | public class ManageLoginsViewModel 19 | { 20 | public IList CurrentLogins { get; set; } 21 | public IList OtherLogins { get; set; } 22 | } 23 | 24 | public class FactorViewModel 25 | { 26 | public string Purpose { get; set; } 27 | } 28 | 29 | public class SetPasswordViewModel 30 | { 31 | [Required] 32 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 33 | [DataType(DataType.Password)] 34 | [Display(Name = "New password")] 35 | public string NewPassword { get; set; } 36 | 37 | [DataType(DataType.Password)] 38 | [Display(Name = "Confirm new password")] 39 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 40 | public string ConfirmPassword { get; set; } 41 | } 42 | 43 | public class ChangePasswordViewModel 44 | { 45 | [Required] 46 | [DataType(DataType.Password)] 47 | [Display(Name = "Current password")] 48 | public string OldPassword { get; set; } 49 | 50 | [Required] 51 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 52 | [DataType(DataType.Password)] 53 | [Display(Name = "New password")] 54 | public string NewPassword { get; set; } 55 | 56 | [DataType(DataType.Password)] 57 | [Display(Name = "Confirm new password")] 58 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 59 | public string ConfirmPassword { get; set; } 60 | } 61 | 62 | public class AddPhoneNumberViewModel 63 | { 64 | [Required] 65 | [Phone] 66 | [Display(Name = "Phone Number")] 67 | public string Number { get; set; } 68 | } 69 | 70 | public class VerifyPhoneNumberViewModel 71 | { 72 | [Required] 73 | [Display(Name = "Code")] 74 | public string Code { get; set; } 75 | 76 | [Required] 77 | [Phone] 78 | [Display(Name = "Phone Number")] 79 | public string PhoneNumber { get; set; } 80 | } 81 | 82 | public class ConfigureTwoFactorViewModel 83 | { 84 | public string SelectedProvider { get; set; } 85 | public ICollection Providers { get; set; } 86 | } 87 | } -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/Google/TestGoogleEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Authentication.Google; 7 | using Microsoft.AspNetCore.Authentication.OAuth; 8 | using Microsoft.AspNetCore.Identity; 9 | using MusicStore.Mocks.Common; 10 | 11 | namespace MusicStore.Mocks.Google 12 | { 13 | internal class TestGoogleEvents 14 | { 15 | internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) 16 | { 17 | if (context.Ticket.Principal != null) 18 | { 19 | Helpers.ThrowIfConditionFailed(() => context.AccessToken == "ValidAccessToken", "Access token is not valid"); 20 | Helpers.ThrowIfConditionFailed(() => context.RefreshToken == "ValidRefreshToken", "Refresh token is not valid"); 21 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Email)?.Value == "AspnetvnextTest@gmail.com", "Email is not valid"); 22 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == "106790274378320830963", "Id is not valid"); 23 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Surname)?.Value == "AspnetvnextTest", "FamilyName is not valid"); 24 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", "Name is not valid"); 25 | Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(1200), "ExpiresIn is not valid"); 26 | Helpers.ThrowIfConditionFailed(() => context.User != null, "User object is not valid"); 27 | context.Ticket.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); 28 | } 29 | 30 | return Task.FromResult(0); 31 | } 32 | 33 | internal static Task OnTicketReceived(TicketReceivedContext context) 34 | { 35 | if (context.Principal != null && context.Options.SignInScheme == new IdentityCookieOptions().ExternalCookieAuthenticationScheme) 36 | { 37 | //This way we will know all events were fired. 38 | var identity = context.Principal.Identities.First(); 39 | var manageStoreClaim = identity?.Claims.Where(c => c.Type == "ManageStore" && c.Value == "false").FirstOrDefault(); 40 | if (manageStoreClaim != null) 41 | { 42 | identity.RemoveClaim(manageStoreClaim); 43 | identity.AddClaim(new Claim("ManageStore", "Allowed")); 44 | } 45 | } 46 | 47 | return Task.FromResult(0); 48 | } 49 | 50 | internal static Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context) 51 | { 52 | context.Response.Redirect(context.RedirectUri + "&custom_redirect_uri=custom"); 53 | return Task.FromResult(0); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/MicrosoftAccountMockBackChannelHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.WebUtilities; 9 | 10 | namespace MusicStore.Mocks.MicrosoftAccount 11 | { 12 | /// 13 | /// Summary description for MicrosoftAccountMockBackChannelHandler 14 | /// 15 | public class MicrosoftAccountMockBackChannelHandler : HttpMessageHandler 16 | { 17 | protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 18 | { 19 | var response = new HttpResponseMessage(); 20 | 21 | if (request.RequestUri.AbsoluteUri.StartsWith("https://login.microsoftonline.com/common/oauth2/v2.0/token")) 22 | { 23 | var formData = new FormCollection(await new FormReader(await request.Content.ReadAsStreamAsync()).ReadFormAsync()); 24 | if (formData["grant_type"] == "authorization_code") 25 | { 26 | if (formData["code"] == "ValidCode") 27 | { 28 | if (formData["redirect_uri"].Count > 0 && ((string)formData["redirect_uri"]).EndsWith("signin-microsoft") && 29 | formData["client_id"] == "[ClientId]" && formData["client_secret"] == "[ClientSecret]") 30 | { 31 | 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\"}"); 32 | return response; 33 | } 34 | } 35 | } 36 | 37 | response.StatusCode = (HttpStatusCode)400; 38 | return response; 39 | } 40 | else if (request.RequestUri.AbsoluteUri.StartsWith("https://graph.microsoft.com/v1.0/me")) 41 | { 42 | if (request.Headers.Authorization.Parameter == "ValidAccessToken") 43 | { 44 | 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}"); 45 | } 46 | else 47 | { 48 | 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"); 49 | } 50 | return response; 51 | } 52 | 53 | throw new NotImplementedException(request.RequestUri.AbsoluteUri); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/Common/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Server.IntegrationTesting; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace E2ETests 8 | { 9 | public class Helpers 10 | { 11 | public static string GetApplicationPath(ApplicationType applicationType) 12 | { 13 | var current = new DirectoryInfo(AppContext.BaseDirectory); 14 | while (current != null) 15 | { 16 | if (File.Exists(Path.Combine(current.FullName, "MusicStore.sln"))) 17 | { 18 | break; 19 | } 20 | current = current.Parent; 21 | } 22 | 23 | if (current == null) 24 | { 25 | throw new InvalidOperationException("Could not find the solution directory"); 26 | } 27 | 28 | return Path.GetFullPath(Path.Combine(current.FullName, "samples", "MusicStore")); 29 | } 30 | 31 | public static void SetInMemoryStoreForIIS(DeploymentParameters deploymentParameters, ILogger logger) 32 | { 33 | if (deploymentParameters.ServerType == ServerType.IIS) 34 | { 35 | // Can't use localdb with IIS. Setting an override to use InMemoryStore. 36 | logger.LogInformation("Creating configoverride.json file to override default config."); 37 | 38 | var compileRoot = Path.GetFullPath( 39 | Path.Combine( 40 | deploymentParameters.ApplicationPath, 41 | "..", "approot", "packages", "MusicStore")); 42 | 43 | // We don't know the exact version number with which sources are built. 44 | string overrideConfig = Path.Combine(Directory.GetDirectories(compileRoot).First(), "root", "configoverride.json"); 45 | 46 | 47 | File.WriteAllText(overrideConfig, "{\"UseInMemoryDatabase\": \"true\"}"); 48 | } 49 | } 50 | 51 | public static string GetCurrentBuildConfiguration() 52 | { 53 | var configuration = "Debug"; 54 | if (string.Equals(Environment.GetEnvironmentVariable("Configuration"), "Release", StringComparison.OrdinalIgnoreCase)) 55 | { 56 | configuration = "Release"; 57 | } 58 | 59 | return configuration; 60 | } 61 | 62 | public static bool PreservePublishedApplicationForDebugging 63 | { 64 | get 65 | { 66 | var deletePublishedFolder = Environment.GetEnvironmentVariable("ASPNETCORE_DELETEPUBLISHEDFOLDER"); 67 | 68 | if (string.Equals("false", deletePublishedFolder, StringComparison.OrdinalIgnoreCase) 69 | || string.Equals("0", deletePublishedFolder, StringComparison.OrdinalIgnoreCase)) 70 | { 71 | // preserve the published folder and do not delete it 72 | return true; 73 | } 74 | 75 | // do not preserve the published folder and delete it 76 | return false; 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /samples/MusicStore/Views/Manage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IndexViewModel 2 | @{ 3 | ViewData["Title"] = "Manage your account"; 4 | } 5 | 6 |

    @ViewData["Title"].

    7 |

    @ViewData["StatusMessage"]

    8 | 9 |
    10 |

    Change your account settings

    11 |
    12 |
    13 |
    Password:
    14 |
    15 | @if (Model.HasPassword) 16 | { 17 | [  Change  ] 18 | } 19 | else 20 | { 21 | [  Create  ] 22 | } 23 |
    24 |
    External Logins:
    25 |
    26 | @Model.Logins.Count [  Manage  ] 27 |
    28 |
    Phone Number:
    29 |
    30 |

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

    35 | @*@(Model.PhoneNumber ?? "None") 36 | @if (Model.PhoneNumber != null) 37 | { 38 |
    39 | [  Change  ] 40 |
    41 | [] 42 |
    43 | } 44 | else 45 | { 46 | [  Add  ] 47 | }*@ 48 |
    49 | 50 |
    Two-Factor Authentication:
    51 |
    52 |

    53 | There are no two-factor authentication providers configured. See this article 54 | for setting up this application to support two-factor authentication. 55 |

    56 | @*@if (Model.TwoFactor) 57 | { 58 |
    59 | Enabled [] 60 |
    61 | } 62 | else 63 | { 64 |
    65 | [] Disabled 66 |
    67 | }*@ 68 |
    69 |
    70 |
    -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/Twitter/TwitterMockBackChannelHttpHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.WebUtilities; 9 | 10 | namespace MusicStore.Mocks.Twitter 11 | { 12 | /// 13 | /// Summary description for TwitterMockBackChannelHttpHandler 14 | /// 15 | public class TwitterMockBackChannelHttpHandler : HttpMessageHandler 16 | { 17 | private static bool _requestTokenEndpointInvoked = false; 18 | 19 | protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 20 | { 21 | var response = new HttpResponseMessage(); 22 | 23 | if (request.RequestUri.AbsoluteUri.StartsWith("https://api.twitter.com/oauth/access_token")) 24 | { 25 | var formData = new FormCollection(await new FormReader(await request.Content.ReadAsStreamAsync()).ReadFormAsync()); 26 | if (formData["oauth_verifier"] == "valid_oauth_verifier") 27 | { 28 | if (_requestTokenEndpointInvoked) 29 | { 30 | var response_Form_data = new List>() 31 | { 32 | new KeyValuePair("oauth_token", "valid_oauth_token"), 33 | new KeyValuePair("oauth_token_secret", "valid_oauth_token_secret"), 34 | new KeyValuePair("user_id", "valid_user_id"), 35 | new KeyValuePair("screen_name", "valid_screen_name"), 36 | }; 37 | 38 | response.Content = new FormUrlEncodedContent(response_Form_data); 39 | } 40 | else 41 | { 42 | response.StatusCode = HttpStatusCode.InternalServerError; 43 | response.Content = new StringContent("RequestTokenEndpoint is not invoked"); 44 | } 45 | return response; 46 | } 47 | response.StatusCode = (HttpStatusCode)400; 48 | return response; 49 | } 50 | else if (request.RequestUri.AbsoluteUri.StartsWith("https://api.twitter.com/oauth/request_token")) 51 | { 52 | var response_Form_data = new List>() 53 | { 54 | new KeyValuePair("oauth_callback_confirmed", "true"), 55 | new KeyValuePair("oauth_token", "valid_oauth_token"), 56 | new KeyValuePair("oauth_token_secret", "valid_oauth_token_secret") 57 | }; 58 | 59 | _requestTokenEndpointInvoked = true; 60 | response.Content = new FormUrlEncodedContent(response_Form_data); 61 | return response; 62 | } 63 | 64 | throw new NotImplementedException(request.RequestUri.AbsoluteUri); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/MusicStore.Test/CartSummaryComponentTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | using Microsoft.AspNetCore.Mvc.ViewComponents; 7 | using Microsoft.EntityFrameworkCore; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using MusicStore.Controllers; 10 | using MusicStore.Models; 11 | using Xunit; 12 | 13 | namespace MusicStore.Components 14 | { 15 | public class CartSummaryComponentTest 16 | { 17 | private readonly IServiceProvider _serviceProvider; 18 | 19 | public CartSummaryComponentTest() 20 | { 21 | var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); 22 | 23 | var services = new ServiceCollection(); 24 | 25 | services.AddDbContext(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider)); 26 | 27 | _serviceProvider = services.BuildServiceProvider(); 28 | } 29 | 30 | [Fact] 31 | public async Task CartSummaryComponent_Returns_CartedItems() 32 | { 33 | // Arrange 34 | var viewContext = new ViewContext() 35 | { 36 | HttpContext = new DefaultHttpContext() 37 | }; 38 | 39 | // Session initialization 40 | var cartId = "CartId_A"; 41 | viewContext.HttpContext.Session = new TestSession(); 42 | viewContext.HttpContext.Session.SetString("Session", cartId); 43 | 44 | // DbContext initialization 45 | var dbContext = _serviceProvider.GetRequiredService(); 46 | PopulateData(dbContext, cartId, albumTitle: "AlbumA", itemCount: 10); 47 | 48 | // CartSummaryComponent initialization 49 | var cartSummaryComponent = new CartSummaryComponent(dbContext) 50 | { 51 | ViewComponentContext = new ViewComponentContext() { ViewContext = viewContext } 52 | }; 53 | 54 | // Act 55 | var result = await cartSummaryComponent.InvokeAsync(); 56 | 57 | // Assert 58 | Assert.NotNull(result); 59 | var viewResult = Assert.IsType(result); 60 | Assert.Null(viewResult.ViewName); 61 | Assert.Null(viewResult.ViewData.Model); 62 | Assert.Equal(10, cartSummaryComponent.ViewBag.CartCount); 63 | Assert.Equal("AlbumA", cartSummaryComponent.ViewBag.CartSummary); 64 | } 65 | 66 | private static void PopulateData(MusicStoreContext context, string cartId, string albumTitle, int itemCount) 67 | { 68 | var album = new Album() 69 | { 70 | AlbumId = 1, 71 | Title = albumTitle, 72 | }; 73 | 74 | var cartItems = Enumerable.Range(1, itemCount).Select(n => 75 | new CartItem() 76 | { 77 | AlbumId = 1, 78 | Album = album, 79 | Count = 1, 80 | CartId = cartId, 81 | }).ToArray(); 82 | 83 | context.AddRange(cartItems); 84 | context.SaveChanges(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/MicrosoftAccount/TestMicrosoftAccountEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Authentication.MicrosoftAccount; 7 | using Microsoft.AspNetCore.Authentication.OAuth; 8 | using Microsoft.AspNetCore.Identity; 9 | using MusicStore.Mocks.Common; 10 | 11 | namespace MusicStore.Mocks.MicrosoftAccount 12 | { 13 | internal class TestMicrosoftAccountEvents 14 | { 15 | internal static Task OnCreatingTicket(OAuthCreatingTicketContext context) 16 | { 17 | if (context.Ticket.Principal != null) 18 | { 19 | Helpers.ThrowIfConditionFailed(() => context.AccessToken == "ValidAccessToken", "Access token is not valid"); 20 | Helpers.ThrowIfConditionFailed(() => context.RefreshToken == "ValidRefreshToken", "Refresh token is not valid"); 21 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.GivenName)?.Value == "AspnetvnextTest", "Given name is not valid"); 22 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Surname)?.Value == "AspnetvnextTest", "Surname is not valid"); 23 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == "fccf9a24999f4f4f", "Id is not valid"); 24 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.Name)?.Value == "AspnetvnextTest AspnetvnextTest", "Name is not valid"); 25 | Helpers.ThrowIfConditionFailed(() => context.ExpiresIn.Value == TimeSpan.FromSeconds(3600), "ExpiresIn is not valid"); 26 | Helpers.ThrowIfConditionFailed(() => context.User != null, "User object is not valid"); 27 | Helpers.ThrowIfConditionFailed(() => context.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value == context.User.SelectToken("id").ToString(), "User id is not valid"); 28 | context.Ticket.Principal.Identities.First().AddClaim(new Claim("ManageStore", "false")); 29 | } 30 | 31 | return Task.FromResult(0); 32 | } 33 | 34 | internal static Task OnTicketReceived(TicketReceivedContext context) 35 | { 36 | if (context.Principal != null && context.Options.SignInScheme == new IdentityCookieOptions().ExternalCookieAuthenticationScheme) 37 | { 38 | //This way we will know all events were fired. 39 | var identity = context.Principal.Identities.First(); 40 | var manageStoreClaim = identity?.Claims.Where(c => c.Type == "ManageStore" && c.Value == "false").FirstOrDefault(); 41 | if (manageStoreClaim != null) 42 | { 43 | identity.RemoveClaim(manageStoreClaim); 44 | identity.AddClaim(new Claim("ManageStore", "Allowed")); 45 | } 46 | } 47 | 48 | return Task.FromResult(0); 49 | } 50 | 51 | internal static Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context) 52 | { 53 | context.Response.Redirect(context.RedirectUri + "&custom_redirect_uri=custom"); 54 | return Task.FromResult(0); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/OpenIdConnect/keys.json: -------------------------------------------------------------------------------- 1 | { 2 | "keys": [ 3 | { 4 | "kty": "RSA", 5 | "use": "sig", 6 | "kid": "kriMPdmBvx68skT8-mPAB3BseeA", 7 | "x5t": "kriMPdmBvx68skT8-mPAB3BseeA", 8 | "n": "kSCWg6q9iYxvJE2NIhSyOiKvqoWCO2GFipgH0sTSAs5FalHQosk9ZNTztX0ywS/AHsBeQPqYygfYVJL6/EgzVuwRk5txr9e3n1uml94fLyq/AXbwo9yAduf4dCHTP8CWR1dnDR+Qnz/4PYlWVEuuHHONOw/blbfdMjhY+C/BYM2E3pRxbohBb3x//CfueV7ddz2LYiH3wjz0QS/7kjPiNCsXcNyKQEOTkbHFi3mu0u13SQwNddhcynd/GTgWN8A+6SN1r4hzpjFKFLbZnBt77ACSiYx+IHK4Mp+NaVEi5wQtSsjQtI++XsokxRDqYLwus1I1SihgbV/STTg5enufuw==", 9 | "e": "AQAB", 10 | "x5c": [ 11 | "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" 12 | ] 13 | }, 14 | { 15 | "kty": "RSA", 16 | "use": "sig", 17 | "kid": "MnC_VZcATfM5pOYiJHMba9goEKY", 18 | "x5t": "MnC_VZcATfM5pOYiJHMba9goEKY", 19 | "n": "vIqz+4+ER/vNWLON9yv8hIYV737JQ6rCl6XfzOC628seYUPf0TaGk91CFxefhzh23V9Tkq+RtwN1Vs/z57hO82kkzL+cQHZX3bMJD+GEGOKXCEXURN7VMyZWMAuzQoW9vFb1k3cR1RW/EW/P+C8bb2dCGXhBYqPfHyimvz2WarXhntPSbM5XyS5v5yCw5T/Vuwqqsio3V8wooWGMpp61y12NhN8bNVDQAkDPNu2DT9DXB1g0CeFINp/KAS/qQ2Kq6TSvRHJqxRR68RezYtje9KAqwqx4jxlmVAQy0T3+T+IAbsk1wRtWDndhO6s1Os+dck5TzyZ/dNOhfXgelixLUQ==", 20 | "e": "AQAB", 21 | "x5c": [ 22 | "MIIC4jCCAcqgAwIBAgIQQNXrmzhLN4VGlUXDYCRT3zANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE0MTAyODAwMDAwMFoXDTE2MTAyNzAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALyKs/uPhEf7zVizjfcr/ISGFe9+yUOqwpel38zgutvLHmFD39E2hpPdQhcXn4c4dt1fU5KvkbcDdVbP8+e4TvNpJMy/nEB2V92zCQ/hhBjilwhF1ETe1TMmVjALs0KFvbxW9ZN3EdUVvxFvz/gvG29nQhl4QWKj3x8opr89lmq14Z7T0mzOV8kub+cgsOU/1bsKqrIqN1fMKKFhjKaetctdjYTfGzVQ0AJAzzbtg0/Q1wdYNAnhSDafygEv6kNiquk0r0RyasUUevEXs2LY3vSgKsKseI8ZZlQEMtE9/k/iAG7JNcEbVg53YTurNTrPnXJOU88mf3TToX14HpYsS1ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfolx45w0i8CdAUjjeAaYdhG9+NDHxop0UvNOqlGqYJexqPLuvX8iyUaYxNGzZxFgGI3GpKfmQP2JQWQ1E5JtY/n8iNLOKRMwqkuxSCKJxZJq4Sl/m/Yv7TS1P5LNgAj8QLCypxsWrTAmq2HSpkeSk4JBtsYxX6uhbGM/K1sEktKybVTHu22/7TmRqWTmOUy9wQvMjJb2IXdMGLG3hVntN/WWcs5w8vbt1i8Kk6o19W2MjZ95JaECKjBDYRlhG1KmSBtrsKsCBQoBzwH/rXfksTO9JoUYLXiW0IppB7DhNH4PJ5hZI91R8rR0H3/bKkLSuDaKLWSqMhozdhXsIIKvJQ==" 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/Facebook/FacebookMockBackChannelHttpHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Http.Internal; 8 | using Microsoft.AspNetCore.WebUtilities; 9 | using MusicStore.Mocks.Common; 10 | 11 | namespace MusicStore.Mocks.Facebook 12 | { 13 | /// 14 | /// Summary description for FacebookMockBackChannelHttpHandler 15 | /// 16 | public class FacebookMockBackChannelHttpHandler : HttpMessageHandler 17 | { 18 | protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 19 | { 20 | var response = new HttpResponseMessage(); 21 | 22 | if (request.RequestUri.AbsoluteUri.StartsWith("https://graph.facebook.com/v2.6/oauth/access_token")) 23 | { 24 | var formData = new FormCollection(await new FormReader(await request.Content.ReadAsStreamAsync()).ReadFormAsync()); 25 | if (formData["grant_type"] == "authorization_code") 26 | { 27 | if (formData["code"] == "ValidCode") 28 | { 29 | Helpers.ThrowIfConditionFailed(() => ((string)formData["redirect_uri"]).EndsWith("signin-facebook"), "Redirect URI is not ending with /signin-facebook"); 30 | Helpers.ThrowIfConditionFailed(() => formData["client_id"] == "[AppId]", "Invalid client Id received"); 31 | Helpers.ThrowIfConditionFailed(() => formData["client_secret"] == "[AppSecret]", "Invalid client secret received"); 32 | response.Content = new StringContent("{ \"access_token\": \"ValidAccessToken\", \"expires_in\": \"100\" }"); 33 | return response; 34 | } 35 | response.StatusCode = (HttpStatusCode)400; 36 | return response; 37 | } 38 | } 39 | else if (request.RequestUri.AbsoluteUri.StartsWith("https://graph.facebook.com/v2.6/me")) 40 | { 41 | var queryParameters = new QueryCollection(QueryHelpers.ParseQuery(request.RequestUri.Query)); 42 | Helpers.ThrowIfConditionFailed(() => queryParameters["appsecret_proof"].Count > 0, "appsecret_proof is empty"); 43 | if (queryParameters["access_token"] == "ValidAccessToken") 44 | { 45 | 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\"}"); 46 | } 47 | else 48 | { 49 | response.Content = new StringContent("{\"error\":{\"message\":\"Invalid OAuth access token.\",\"type\":\"OAuthException\",\"code\":190}}"); 50 | } 51 | return response; 52 | } 53 | 54 | throw new NotImplementedException(request.RequestUri.AbsoluteUri); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/Google/GoogleMockBackChannelHttpHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.WebUtilities; 9 | 10 | namespace MusicStore.Mocks.Google 11 | { 12 | /// 13 | /// Summary description for GoogleMockBackChannelHttpHandler 14 | /// 15 | public class GoogleMockBackChannelHttpHandler : HttpMessageHandler 16 | { 17 | protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 18 | { 19 | var response = new HttpResponseMessage(); 20 | 21 | if (request.RequestUri.AbsoluteUri.StartsWith("https://www.googleapis.com/oauth2/v4/token")) 22 | { 23 | var formData = new FormCollection(await new FormReader(await request.Content.ReadAsStreamAsync()).ReadFormAsync()); 24 | if (formData["grant_type"] == "authorization_code") 25 | { 26 | if (formData["code"] == "ValidCode") 27 | { 28 | if (formData["redirect_uri"].Count > 0 && ((string)formData["redirect_uri"]).EndsWith("signin-google") && 29 | formData["client_id"] == "[ClientId]" && formData["client_secret"] == "[ClientSecret]") 30 | { 31 | response.Content = new StringContent("{\"access_token\":\"ValidAccessToken\",\"refresh_token\":\"ValidRefreshToken\",\"token_type\":\"Bearer\",\"expires_in\":\"1200\",\"id_token\":\"Token\"}", Encoding.UTF8, "application/json"); 32 | return response; 33 | } 34 | } 35 | } 36 | response.StatusCode = (HttpStatusCode)400; 37 | return response; 38 | } 39 | else if (request.RequestUri.AbsoluteUri.StartsWith("https://www.googleapis.com/plus/v1/people/me")) 40 | { 41 | if (request.Headers.Authorization.Parameter == "ValidAccessToken") 42 | { 43 | 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"); 44 | } 45 | else 46 | { 47 | response.Content = new StringContent("{\"error\":{\"message\":\"Invalid OAuth access token.\",\"type\":\"OAuthException\",\"code\":190}}"); 48 | } 49 | return response; 50 | } 51 | 52 | throw new NotImplementedException(request.RequestUri.AbsoluteUri); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @inject IOptions AppSettings 2 | 3 | 4 | 5 | 6 | 7 | @ViewBag.Title – @AppSettings.Value.SiteTitle 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 48 |
    49 | @RenderBody() 50 |
    51 | 55 |
    56 | 57 | 58 | 59 | 60 | 61 | 62 | 66 | 70 | 71 | 72 | @RenderSection("scripts", required: false) 73 | 74 | -------------------------------------------------------------------------------- /samples/MusicStore/Views/ShoppingCart/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model MusicStore.ViewModels.ShoppingCartViewModel 2 | @inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf 3 | @{ 4 | ViewBag.Title = "Shopping Cart"; 5 | } 6 | 7 | @functions 8 | { 9 | public string GetAntiXsrfRequestToken() 10 | { 11 | return Xsrf.GetAndStoreTokens(Context).RequestToken; 12 | } 13 | } 14 | 15 | @section Scripts { 16 | 53 | } 54 | 55 |

    56 | Review your cart: 57 |

    58 |

    59 | Checkout >> 60 |

    61 |
    62 |
    63 | 64 | 65 | 68 | 71 | 74 | 75 | 76 | @foreach (var item in Model.CartItems) 77 | { 78 | 79 | 82 | 85 | 88 | 94 | 95 | } 96 | 97 | 100 | 101 | 102 | 105 | 106 |
    66 | Album Name 67 | 69 | Price (each) 70 | 72 | Quantity 73 |
    80 | @item.Album.Title 81 | 83 | @item.Album.Price 84 | 86 | @item.Count 87 | 89 | 91 | Remove from cart 92 | 93 |
    98 | Total 99 | 103 | @Model.CartTotal 104 |
    -------------------------------------------------------------------------------- /samples/MusicStore/Models/AccountViewModels.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using Microsoft.AspNetCore.Mvc.Rendering; 4 | 5 | namespace MusicStore.Models 6 | { 7 | public class ExternalLoginConfirmationViewModel 8 | { 9 | [Required] 10 | [Display(Name = "Email")] 11 | public string Email { get; set; } 12 | } 13 | 14 | public class ExternalLoginListViewModel 15 | { 16 | public string ReturnUrl { get; set; } 17 | } 18 | 19 | public class SendCodeViewModel 20 | { 21 | public string SelectedProvider { get; set; } 22 | public ICollection Providers { get; set; } 23 | public string ReturnUrl { get; set; } 24 | public bool RememberMe { get; set; } 25 | } 26 | 27 | public class VerifyCodeViewModel 28 | { 29 | [Required] 30 | public string Provider { get; set; } 31 | 32 | [Required] 33 | [Display(Name = "Code")] 34 | public string Code { get; set; } 35 | public string ReturnUrl { get; set; } 36 | 37 | [Display(Name = "Remember this browser?")] 38 | public bool RememberBrowser { get; set; } 39 | 40 | public bool RememberMe { get; set; } 41 | } 42 | 43 | public class ForgotViewModel 44 | { 45 | [Required] 46 | [Display(Name = "Email")] 47 | public string Email { get; set; } 48 | } 49 | 50 | public class LoginViewModel 51 | { 52 | [Required] 53 | [Display(Name = "Email")] 54 | [EmailAddress] 55 | public string Email { get; set; } 56 | 57 | [Required] 58 | [DataType(DataType.Password)] 59 | [Display(Name = "Password")] 60 | public string Password { get; set; } 61 | 62 | [Display(Name = "Remember me?")] 63 | public bool RememberMe { get; set; } 64 | } 65 | 66 | public class RegisterViewModel 67 | { 68 | [Required] 69 | [EmailAddress] 70 | [Display(Name = "Email")] 71 | public string Email { get; set; } 72 | 73 | [Required] 74 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 75 | [DataType(DataType.Password)] 76 | [Display(Name = "Password")] 77 | public string Password { get; set; } 78 | 79 | [DataType(DataType.Password)] 80 | [Display(Name = "Confirm password")] 81 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 82 | public string ConfirmPassword { get; set; } 83 | } 84 | 85 | public class ResetPasswordViewModel 86 | { 87 | [Required] 88 | [EmailAddress] 89 | [Display(Name = "Email")] 90 | public string Email { get; set; } 91 | 92 | [Required] 93 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 94 | [DataType(DataType.Password)] 95 | [Display(Name = "Password")] 96 | public string Password { get; set; } 97 | 98 | [DataType(DataType.Password)] 99 | [Display(Name = "Confirm password")] 100 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 101 | public string ConfirmPassword { get; set; } 102 | 103 | public string Code { get; set; } 104 | } 105 | 106 | public class ForgotPasswordViewModel 107 | { 108 | [Required] 109 | [EmailAddress] 110 | [Display(Name = "Email")] 111 | public string Email { get; set; } 112 | } 113 | } -------------------------------------------------------------------------------- /samples/MusicStore/ForTesting/Mocks/OpenIdConnect/TestOpenIdConnectEvents.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Security.Claims; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Authentication.OpenIdConnect; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 7 | using MusicStore.Mocks.Common; 8 | 9 | namespace MusicStore.Mocks.OpenIdConnect 10 | { 11 | internal class TestOpenIdConnectEvents 12 | { 13 | private static List eventsFired = new List(); 14 | 15 | internal static Task MessageReceived(MessageReceivedContext context) 16 | { 17 | Helpers.ThrowIfConditionFailed(() => context.ProtocolMessage != null, "ProtocolMessage is null."); 18 | eventsFired.Add(nameof(MessageReceived)); 19 | return Task.FromResult(0); 20 | } 21 | 22 | internal static Task TokenValidated(TokenValidatedContext context) 23 | { 24 | Helpers.ThrowIfConditionFailed(() => context.Ticket != null, "context.Ticket is null."); 25 | Helpers.ThrowIfConditionFailed(() => context.Ticket.Principal != null, "context.Ticket.Principal is null."); 26 | Helpers.ThrowIfConditionFailed(() => context.Ticket.Principal.Identity != null, "context.Ticket.Principal.Identity is null."); 27 | Helpers.ThrowIfConditionFailed(() => !string.IsNullOrWhiteSpace(context.Ticket.Principal.Identity.Name), "context.Ticket.Principal.Identity.Name is null."); 28 | eventsFired.Add(nameof(TokenValidated)); 29 | return Task.FromResult(0); 30 | } 31 | 32 | internal static Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context) 33 | { 34 | 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", 35 | "context.TokenEndpointRequest.Code is invalid."); 36 | eventsFired.Add(nameof(AuthorizationCodeReceived)); 37 | 38 | // Verify all events are fired. 39 | if (eventsFired.Contains(nameof(RedirectToIdentityProvider)) && 40 | eventsFired.Contains(nameof(MessageReceived)) && 41 | eventsFired.Contains(nameof(TokenValidated)) && 42 | eventsFired.Contains(nameof(AuthorizationCodeReceived))) 43 | { 44 | ((ClaimsIdentity)context.Ticket.Principal.Identity).AddClaim(new Claim("ManageStore", "Allowed")); 45 | } 46 | 47 | return Task.FromResult(0); 48 | } 49 | 50 | internal static Task RedirectToIdentityProvider(RedirectContext context) 51 | { 52 | eventsFired.Add(nameof(RedirectToIdentityProvider)); 53 | 54 | if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout) 55 | { 56 | context.ProtocolMessage.PostLogoutRedirectUri = 57 | context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase + new PathString("/Account/Login"); 58 | } 59 | 60 | return Task.FromResult(0); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /samples/MusicStore/Controllers/CheckoutController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authorization; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.EntityFrameworkCore; 9 | using Microsoft.Extensions.Logging; 10 | using MusicStore.Models; 11 | 12 | namespace MusicStore.Controllers 13 | { 14 | [Authorize] 15 | public class CheckoutController : Controller 16 | { 17 | private const string PromoCode = "FREE"; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public CheckoutController(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | // 27 | // GET: /Checkout/ 28 | public IActionResult AddressAndPayment() 29 | { 30 | return View(); 31 | } 32 | 33 | // 34 | // POST: /Checkout/AddressAndPayment 35 | 36 | [HttpPost] 37 | [ValidateAntiForgeryToken] 38 | public async Task AddressAndPayment( 39 | [FromServices] MusicStoreContext dbContext, 40 | [FromForm] Order order, 41 | CancellationToken requestAborted) 42 | { 43 | if (!ModelState.IsValid) 44 | { 45 | return View(order); 46 | } 47 | 48 | var formCollection = await HttpContext.Request.ReadFormAsync(); 49 | 50 | try 51 | { 52 | if (string.Equals(formCollection["PromoCode"].FirstOrDefault(), PromoCode, 53 | StringComparison.OrdinalIgnoreCase) == false) 54 | { 55 | return View(order); 56 | } 57 | else 58 | { 59 | order.Username = HttpContext.User.Identity.Name; 60 | order.OrderDate = DateTime.Now; 61 | 62 | //Add the Order 63 | dbContext.Orders.Add(order); 64 | 65 | //Process the order 66 | var cart = ShoppingCart.GetCart(dbContext, HttpContext); 67 | await cart.CreateOrder(order); 68 | 69 | _logger.LogInformation("User {userName} started checkout of {orderId}.", order.Username, order.OrderId); 70 | 71 | // Save all changes 72 | await dbContext.SaveChangesAsync(requestAborted); 73 | 74 | return RedirectToAction("Complete", new { id = order.OrderId }); 75 | } 76 | } 77 | catch 78 | { 79 | //Invalid - redisplay with errors 80 | return View(order); 81 | } 82 | } 83 | 84 | // 85 | // GET: /Checkout/Complete 86 | 87 | public async Task Complete( 88 | [FromServices] MusicStoreContext dbContext, 89 | int id) 90 | { 91 | var userName = HttpContext.User.Identity.Name; 92 | 93 | // Validate customer owns this order 94 | bool isValid = await dbContext.Orders.AnyAsync( 95 | o => o.OrderId == id && 96 | o.Username == userName); 97 | 98 | if (isValid) 99 | { 100 | _logger.LogInformation("User {userName} completed checkout on order {orderId}.", userName, id); 101 | return View(id); 102 | } 103 | else 104 | { 105 | _logger.LogError( 106 | "User {userName} tried to checkout with an order ({orderId}) that doesn't belong to them.", 107 | userName, 108 | id); 109 | return View("Error"); 110 | } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /samples/MusicStore/Platform.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace MusicStore 5 | { 6 | internal class Platform 7 | { 8 | // Defined in winnt.h 9 | private const int PRODUCT_NANO_SERVER = 0x0000006D; 10 | private const int PRODUCT_DATACENTER_NANO_SERVER = 0x0000008F; 11 | private const int PRODUCT_STANDARD_NANO_SERVER = 0x00000090; 12 | 13 | [DllImport("api-ms-win-core-sysinfo-l1-2-1.dll", SetLastError = false)] 14 | private static extern bool GetProductInfo( 15 | int dwOSMajorVersion, 16 | int dwOSMinorVersion, 17 | int dwSpMajorVersion, 18 | int dwSpMinorVersion, 19 | out int pdwReturnedProductType); 20 | 21 | private bool? _isNano; 22 | private bool? _isWindows; 23 | 24 | public bool IsRunningOnWindows 25 | { 26 | get 27 | { 28 | if (_isWindows == null) 29 | { 30 | _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 31 | } 32 | 33 | return _isWindows.Value; 34 | } 35 | } 36 | 37 | public bool IsRunningOnNanoServer 38 | { 39 | get 40 | { 41 | if (_isNano == null) 42 | { 43 | var osVersion = new Version(RtlGetVersion() ?? string.Empty); 44 | 45 | try 46 | { 47 | int productType; 48 | if (GetProductInfo(osVersion.Major, osVersion.Minor, 0, 0, out productType)) 49 | { 50 | _isNano = productType == PRODUCT_NANO_SERVER || 51 | productType == PRODUCT_DATACENTER_NANO_SERVER || 52 | productType == PRODUCT_STANDARD_NANO_SERVER; 53 | } 54 | else 55 | { 56 | _isNano = false; 57 | } 58 | } 59 | catch 60 | { 61 | // If the API call fails, the API set is not there which means 62 | // that we are definetely not running on Nano 63 | _isNano = false; 64 | } 65 | } 66 | 67 | return _isNano.Value; 68 | } 69 | } 70 | 71 | // Sql client not available on mono, non-windows, or nano 72 | public bool UseInMemoryStore 73 | { 74 | get 75 | { 76 | return !IsRunningOnWindows || IsRunningOnNanoServer; 77 | } 78 | } 79 | 80 | [StructLayout(LayoutKind.Sequential)] 81 | internal struct RTL_OSVERSIONINFOEX 82 | { 83 | internal uint dwOSVersionInfoSize; 84 | internal uint dwMajorVersion; 85 | internal uint dwMinorVersion; 86 | internal uint dwBuildNumber; 87 | internal uint dwPlatformId; 88 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 89 | internal string szCSDVersion; 90 | } 91 | 92 | // This call avoids the shimming Windows does to report old versions 93 | [DllImport("ntdll")] 94 | private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation); 95 | 96 | internal static string RtlGetVersion() 97 | { 98 | RTL_OSVERSIONINFOEX osvi = new RTL_OSVERSIONINFOEX(); 99 | osvi.dwOSVersionInfoSize = (uint)Marshal.SizeOf(osvi); 100 | if (RtlGetVersion(out osvi) == 0) 101 | { 102 | return $"{osvi.dwMajorVersion}.{osvi.dwMinorVersion}.{osvi.dwBuildNumber}"; 103 | } 104 | else 105 | { 106 | return null; 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /samples/MusicStore/Controllers/ShoppingCartController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.Logging; 7 | using MusicStore.Models; 8 | using MusicStore.ViewModels; 9 | 10 | namespace MusicStore.Controllers 11 | { 12 | public class ShoppingCartController : Controller 13 | { 14 | private readonly ILogger _logger; 15 | 16 | public ShoppingCartController(MusicStoreContext dbContext, ILogger logger) 17 | { 18 | DbContext = dbContext; 19 | _logger = logger; 20 | } 21 | 22 | public MusicStoreContext DbContext { get; } 23 | 24 | // 25 | // GET: /ShoppingCart/ 26 | public async Task Index() 27 | { 28 | var cart = ShoppingCart.GetCart(DbContext, HttpContext); 29 | 30 | // Set up our ViewModel 31 | var viewModel = new ShoppingCartViewModel 32 | { 33 | CartItems = await cart.GetCartItems(), 34 | CartTotal = await cart.GetTotal() 35 | }; 36 | 37 | // Return the view 38 | return View(viewModel); 39 | } 40 | 41 | // 42 | // GET: /ShoppingCart/AddToCart/5 43 | 44 | public async Task AddToCart(int id, CancellationToken requestAborted) 45 | { 46 | // Retrieve the album from the database 47 | var addedAlbum = await DbContext.Albums 48 | .SingleAsync(album => album.AlbumId == id); 49 | 50 | // Add it to the shopping cart 51 | var cart = ShoppingCart.GetCart(DbContext, HttpContext); 52 | 53 | await cart.AddToCart(addedAlbum); 54 | 55 | await DbContext.SaveChangesAsync(requestAborted); 56 | _logger.LogInformation("Album {albumId} was added to the cart.", addedAlbum.AlbumId); 57 | 58 | // Go back to the main store page for more shopping 59 | return RedirectToAction("Index"); 60 | } 61 | 62 | // 63 | // AJAX: /ShoppingCart/RemoveFromCart/5 64 | [HttpPost] 65 | [ValidateAntiForgeryToken] 66 | public async Task RemoveFromCart( 67 | int id, 68 | CancellationToken requestAborted) 69 | { 70 | // Retrieve the current user's shopping cart 71 | var cart = ShoppingCart.GetCart(DbContext, HttpContext); 72 | 73 | // Get the name of the album to display confirmation 74 | var cartItem = await DbContext.CartItems 75 | .Where(item => item.CartItemId == id) 76 | .Include(c => c.Album) 77 | .SingleOrDefaultAsync(); 78 | 79 | string message; 80 | int itemCount; 81 | if (cartItem != null) 82 | { 83 | // Remove from cart 84 | itemCount = cart.RemoveFromCart(id); 85 | 86 | await DbContext.SaveChangesAsync(requestAborted); 87 | 88 | string removed = (itemCount > 0) ? " 1 copy of " : string.Empty; 89 | message = removed + cartItem.Album.Title + " has been removed from your shopping cart."; 90 | } 91 | else 92 | { 93 | itemCount = 0; 94 | message = "Could not find this item, nothing has been removed from your shopping cart."; 95 | } 96 | 97 | // Display the confirmation message 98 | 99 | var results = new ShoppingCartRemoveViewModel 100 | { 101 | Message = message, 102 | CartTotal = await cart.GetTotal(), 103 | CartCount = await cart.GetCount(), 104 | ItemCount = itemCount, 105 | DeleteId = id 106 | }; 107 | 108 | _logger.LogInformation("Album {id} was removed from a cart.", id); 109 | 110 | return Json(results); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /test/MusicStore.Test/HomeControllerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.Extensions.Caching.Memory; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using MusicStore.Models; 10 | using MusicStore.Test; 11 | using Xunit; 12 | 13 | namespace MusicStore.Controllers 14 | { 15 | public class HomeControllerTest 16 | { 17 | private readonly IServiceProvider _serviceProvider; 18 | 19 | public HomeControllerTest() 20 | { 21 | var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); 22 | 23 | var services = new ServiceCollection(); 24 | 25 | services.AddDbContext(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider)); 26 | 27 | _serviceProvider = services.BuildServiceProvider(); 28 | } 29 | 30 | [Fact] 31 | public void Error_ReturnsErrorView() 32 | { 33 | // Arrange 34 | var controller = new HomeController(new TestAppSettings()); 35 | var errorView = "~/Views/Shared/Error.cshtml"; 36 | 37 | // Act 38 | var result = controller.Error(); 39 | 40 | // Assert 41 | var viewResult = Assert.IsType(result); 42 | 43 | Assert.Equal(errorView, viewResult.ViewName); 44 | } 45 | 46 | [Fact] 47 | public async Task Index_GetsSixTopAlbums() 48 | { 49 | // Arrange 50 | var dbContext = _serviceProvider.GetRequiredService(); 51 | var cache = _serviceProvider.GetRequiredService(); 52 | var controller = new HomeController(new TestAppSettings()); 53 | PopulateData(dbContext); 54 | 55 | // Action 56 | var result = await controller.Index(dbContext, cache); 57 | 58 | // Assert 59 | var viewResult = Assert.IsType(result); 60 | Assert.Null(viewResult.ViewName); 61 | 62 | Assert.NotNull(viewResult.ViewData); 63 | Assert.NotNull(viewResult.ViewData.Model); 64 | 65 | var albums = Assert.IsType>(viewResult.ViewData.Model); 66 | Assert.Equal(6, albums.Count); 67 | } 68 | 69 | [Fact] 70 | public void StatusCodePage_ReturnsStatusCodePage() 71 | { 72 | // Arrange 73 | var controller = new HomeController(new TestAppSettings()); 74 | var statusCodeView = "~/Views/Shared/StatusCodePage.cshtml"; 75 | 76 | // Action 77 | var result = controller.StatusCodePage(); 78 | 79 | // Assert 80 | var viewResult = Assert.IsType(result); 81 | 82 | Assert.Equal(statusCodeView, viewResult.ViewName); 83 | } 84 | 85 | private void PopulateData(DbContext context) 86 | { 87 | var albums = TestAlbumDataProvider.GetAlbums(); 88 | 89 | foreach (var album in albums) 90 | { 91 | context.Add(album); 92 | } 93 | 94 | context.SaveChanges(); 95 | } 96 | 97 | private class TestAlbumDataProvider 98 | { 99 | public static Album[] GetAlbums() 100 | { 101 | var generes = Enumerable.Range(1, 10).Select(n => 102 | new Genre() 103 | { 104 | GenreId = n, 105 | Name = "Genre Name " + n, 106 | }).ToArray(); 107 | 108 | var artists = Enumerable.Range(1, 10).Select(n => 109 | new Artist() 110 | { 111 | ArtistId = n + 1, 112 | Name = "Artist Name " + n, 113 | }).ToArray(); 114 | 115 | var albums = Enumerable.Range(1, 10).Select(n => 116 | new Album() 117 | { 118 | Artist = artists[n - 1], 119 | ArtistId = n, 120 | Genre = generes[n - 1], 121 | GenreId = n, 122 | }).ToArray(); 123 | 124 | return albums; 125 | } 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /samples/MusicStore/wwwroot/Scripts/respond.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 16 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 17 | window.matchMedia=window.matchMedia||(function(e,f){var c,a=e.documentElement,b=a.firstElementChild||a.firstChild,d=e.createElement("body"),g=e.createElement("div");g.id="mq-test-1";g.style.cssText="position:absolute;top:-100em";d.style.background="none";d.appendChild(g);return function(h){g.innerHTML='­';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document); 18 | 19 | /*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 20 | (function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var w=e.document,s=w.documentElement,i=[],k=[],q=[],o={},h=30,f=w.getElementsByTagName("head")[0]||s,g=w.getElementsByTagName("base")[0],b=f.getElementsByTagName("link"),d=[],a=function(){var D=b,y=D.length,B=0,A,z,C,x;for(;B-1,minw:F.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:F.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}j()},l,r,v=function(){var z,A=w.createElement("div"),x=w.body,y=false;A.style.cssText="position:absolute;font-size:1em;width:1em";if(!x){x=y=w.createElement("body");x.style.background="none"}x.appendChild(A);s.insertBefore(x,s.firstChild);z=A.offsetWidth;if(y){s.removeChild(x)}else{x.removeChild(A)}z=p=parseFloat(z);return z},p,j=function(I){var x="clientWidth",B=s[x],H=w.compatMode==="CSS1Compat"&&B||w.body[x]||B,D={},G=b[b.length-1],z=(new Date()).getTime();if(I&&l&&z-l-1?(p||v()):1)}if(!!J){J=parseFloat(J)*(J.indexOf(y)>-1?(p||v()):1)}if(!K.hasquery||(!A||!L)&&(A||H>=C)&&(L||H<=J)){if(!D[K.media]){D[K.media]=[]}D[K.media].push(k[K.rules])}}for(var E in q){if(q[E]&&q[E].parentNode===f){f.removeChild(q[E])}}for(var E in D){var M=w.createElement("style"),F=D[E].join("\n");M.type="text/css";M.media=E;f.insertBefore(M,G.nextSibling);if(M.styleSheet){M.styleSheet.cssText=F}else{M.appendChild(w.createTextNode(F))}q.push(M)}},n=function(x,z){var y=c();if(!y){return}y.open("GET",x,true);y.onreadystatechange=function(){if(y.readyState!=4||y.status!=200&&y.status!=304){return}z(y.responseText)};if(y.readyState==4){return}y.send(null)},c=(function(){var x=false;try{x=new XMLHttpRequest()}catch(y){x=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return x}})();a();respond.update=a;function t(){j(true)}if(e.addEventListener){e.addEventListener("resize",t,false)}else{if(e.attachEvent){e.attachEvent("onresize",t)}}})(this); -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/NtlmAuthentationTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Server.IntegrationTesting; 7 | using Microsoft.AspNetCore.Testing.xunit; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.Logging.Testing; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | 13 | namespace E2ETests 14 | { 15 | public class NtlmAuthenticationTests : LoggedTest 16 | { 17 | public NtlmAuthenticationTests(ITestOutputHelper output) : base(output) 18 | { 19 | } 20 | 21 | [ConditionalTheory, Trait("E2Etests", "E2Etests")] 22 | [OSSkipCondition(OperatingSystems.Linux)] 23 | [OSSkipCondition(OperatingSystems.MacOSX)] 24 | [InlineData(ServerType.WebListener, RuntimeArchitecture.x64, ApplicationType.Portable)] 25 | [InlineData(ServerType.WebListener, RuntimeArchitecture.x64, ApplicationType.Standalone)] 26 | [InlineData(ServerType.IISExpress, RuntimeArchitecture.x64, ApplicationType.Portable)] 27 | [InlineData(ServerType.IISExpress, RuntimeArchitecture.x64, ApplicationType.Standalone)] 28 | public async Task NtlmAuthenticationTest(ServerType serverType, RuntimeArchitecture architecture, ApplicationType applicationType) 29 | { 30 | var testName = $"NtlmAuthentication_{serverType}_{architecture}_{applicationType}"; 31 | using (StartLog(out var loggerFactory, testName)) 32 | { 33 | var logger = loggerFactory.CreateLogger("NtlmAuthenticationTest"); 34 | var musicStoreDbName = DbUtils.GetUniqueName(); 35 | 36 | var deploymentParameters = new DeploymentParameters(Helpers.GetApplicationPath(applicationType), serverType, RuntimeFlavor.CoreClr, architecture) 37 | { 38 | PublishApplicationBeforeDeployment = true, 39 | PreservePublishedApplicationForDebugging = Helpers.PreservePublishedApplicationForDebugging, 40 | TargetFramework = "netcoreapp2.0", 41 | Configuration = Helpers.GetCurrentBuildConfiguration(), 42 | ApplicationType = applicationType, 43 | EnvironmentName = "NtlmAuthentication", //Will pick the Start class named 'StartupNtlmAuthentication' 44 | ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "NtlmAuthentation.config")) : null, 45 | SiteName = "MusicStoreNtlmAuthentication", //This is configured in the NtlmAuthentication.config 46 | UserAdditionalCleanup = parameters => 47 | { 48 | DbUtils.DropDatabase(musicStoreDbName, logger); 49 | } 50 | }; 51 | 52 | // Override the connection strings using environment based configuration 53 | deploymentParameters.EnvironmentVariables 54 | .Add(new KeyValuePair( 55 | MusicStoreConfig.ConnectionStringKey, 56 | DbUtils.CreateConnectionString(musicStoreDbName))); 57 | 58 | using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) 59 | { 60 | var deploymentResult = await deployer.DeployAsync(); 61 | var httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true }; 62 | var httpClient = deploymentResult.CreateHttpClient(httpClientHandler); 63 | 64 | // Request to base address and check if various parts of the body are rendered & measure the cold startup time. 65 | var response = await RetryHelper.RetryRequest(async () => 66 | { 67 | return await httpClient.GetAsync(string.Empty); 68 | }, logger: logger, cancellationToken: deploymentResult.HostShutdownToken); 69 | 70 | Assert.False(response == null, "Response object is null because the client could not " + 71 | "connect to the server after multiple retries"); 72 | 73 | var validator = new Validator(httpClient, httpClientHandler, logger, deploymentResult); 74 | 75 | logger.LogInformation("Verifying home page"); 76 | await validator.VerifyNtlmHomePage(response); 77 | 78 | logger.LogInformation("Verifying access to store with permissions"); 79 | await validator.AccessStoreWithPermissions(); 80 | 81 | logger.LogInformation("Variation completed successfully."); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /MusicStore.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.26405.2 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7D749BDA-4638-4517-B66A-D40DEDEEB141}" 6 | ProjectSection(SolutionItems) = preProject 7 | NuGet.config = NuGet.config 8 | EndProjectSection 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{B7B176B6-8D4D-4EF1-BBD2-DDA650C78FFF}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{363D2681-31A6-48C9-90BB-9ACFF4A41F06}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MusicStore", "samples\MusicStore\MusicStore.csproj", "{3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MusicStore.Test", "test\MusicStore.Test\MusicStore.Test.csproj", "{CA663205-77DE-4E55-B300-85594181B5A9}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MusicStore.E2ETests", "test\MusicStore.E2ETests\MusicStore.E2ETests.csproj", "{72A5F455-121F-4954-BF28-D712C6BE88EA}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Debug|x64 = Debug|x64 24 | Debug|x86 = Debug|x86 25 | Release|Any CPU = Release|Any CPU 26 | Release|x64 = Release|x64 27 | Release|x86 = Release|x86 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Debug|x64.ActiveCfg = Debug|Any CPU 33 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Debug|x64.Build.0 = Debug|Any CPU 34 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Debug|x86.ActiveCfg = Debug|Any CPU 35 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Debug|x86.Build.0 = Debug|Any CPU 36 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Release|x64.ActiveCfg = Release|Any CPU 39 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Release|x64.Build.0 = Release|Any CPU 40 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Release|x86.ActiveCfg = Release|Any CPU 41 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0}.Release|x86.Build.0 = Release|Any CPU 42 | {CA663205-77DE-4E55-B300-85594181B5A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {CA663205-77DE-4E55-B300-85594181B5A9}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {CA663205-77DE-4E55-B300-85594181B5A9}.Debug|x64.ActiveCfg = Debug|Any CPU 45 | {CA663205-77DE-4E55-B300-85594181B5A9}.Debug|x64.Build.0 = Debug|Any CPU 46 | {CA663205-77DE-4E55-B300-85594181B5A9}.Debug|x86.ActiveCfg = Debug|Any CPU 47 | {CA663205-77DE-4E55-B300-85594181B5A9}.Debug|x86.Build.0 = Debug|Any CPU 48 | {CA663205-77DE-4E55-B300-85594181B5A9}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {CA663205-77DE-4E55-B300-85594181B5A9}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {CA663205-77DE-4E55-B300-85594181B5A9}.Release|x64.ActiveCfg = Release|Any CPU 51 | {CA663205-77DE-4E55-B300-85594181B5A9}.Release|x64.Build.0 = Release|Any CPU 52 | {CA663205-77DE-4E55-B300-85594181B5A9}.Release|x86.ActiveCfg = Release|Any CPU 53 | {CA663205-77DE-4E55-B300-85594181B5A9}.Release|x86.Build.0 = Release|Any CPU 54 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Debug|x64.ActiveCfg = Debug|Any CPU 57 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Debug|x64.Build.0 = Debug|Any CPU 58 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Debug|x86.ActiveCfg = Debug|Any CPU 59 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Debug|x86.Build.0 = Debug|Any CPU 60 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Release|x64.ActiveCfg = Release|Any CPU 63 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Release|x64.Build.0 = Release|Any CPU 64 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Release|x86.ActiveCfg = Release|Any CPU 65 | {72A5F455-121F-4954-BF28-D712C6BE88EA}.Release|x86.Build.0 = Release|Any CPU 66 | EndGlobalSection 67 | GlobalSection(SolutionProperties) = preSolution 68 | HideSolutionNode = FALSE 69 | EndGlobalSection 70 | GlobalSection(NestedProjects) = preSolution 71 | {3CFBED5D-2ED8-49DB-96FB-BDAA748DC5A0} = {B7B176B6-8D4D-4EF1-BBD2-DDA650C78FFF} 72 | {CA663205-77DE-4E55-B300-85594181B5A9} = {363D2681-31A6-48C9-90BB-9ACFF4A41F06} 73 | {72A5F455-121F-4954-BF28-D712C6BE88EA} = {363D2681-31A6-48C9-90BB-9ACFF4A41F06} 74 | EndGlobalSection 75 | EndGlobal 76 | -------------------------------------------------------------------------------- /test/MusicStore.Test/ManageControllerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.EntityFrameworkCore; 11 | using Microsoft.Extensions.Configuration; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using MusicStore.Models; 14 | using Xunit; 15 | 16 | namespace MusicStore.Controllers 17 | { 18 | public class ManageControllerTest 19 | { 20 | private readonly IServiceProvider _serviceProvider; 21 | 22 | public ManageControllerTest() 23 | { 24 | var efServiceProvider = new ServiceCollection().AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); 25 | 26 | var services = new ServiceCollection(); 27 | services.AddSingleton(new ConfigurationBuilder().Build()); 28 | services.AddOptions(); 29 | services 30 | .AddDbContext(b => b.UseInMemoryDatabase("Scratch").UseInternalServiceProvider(efServiceProvider)); 31 | 32 | services.AddIdentity() 33 | .AddEntityFrameworkStores(); 34 | 35 | services.AddMvc(); 36 | services.AddSingleton(); 37 | services.AddLogging(); 38 | 39 | // IHttpContextAccessor is required for SignInManager, and UserManager 40 | var context = new DefaultHttpContext(); 41 | services.AddSingleton( 42 | new HttpContextAccessor() 43 | { 44 | HttpContext = context, 45 | }); 46 | 47 | _serviceProvider = services.BuildServiceProvider(); 48 | } 49 | 50 | [Fact] 51 | public async Task Index_ReturnsViewBagMessagesExpected() 52 | { 53 | // Arrange 54 | var userId = "TestUserA"; 55 | var phone = "abcdefg"; 56 | var claims = new List { new Claim(ClaimTypes.NameIdentifier, userId) }; 57 | 58 | var userManager = _serviceProvider.GetRequiredService>(); 59 | var userManagerResult = await userManager.CreateAsync( 60 | new ApplicationUser { Id = userId, UserName = "Test", TwoFactorEnabled = true, PhoneNumber = phone }, 61 | "Pass@word1"); 62 | Assert.True(userManagerResult.Succeeded); 63 | 64 | var signInManager = _serviceProvider.GetRequiredService>(); 65 | 66 | var httpContext = _serviceProvider.GetRequiredService().HttpContext; 67 | httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(claims)); 68 | httpContext.RequestServices = _serviceProvider; 69 | 70 | var schemeProvider = _serviceProvider.GetRequiredService(); 71 | 72 | var controller = new ManageController(userManager, signInManager, schemeProvider); 73 | controller.ControllerContext.HttpContext = httpContext; 74 | 75 | // Act 76 | var result = await controller.Index(); 77 | 78 | // Assert 79 | var viewResult = Assert.IsType(result); 80 | Assert.Null(viewResult.ViewName); 81 | 82 | Assert.Empty(controller.ViewBag.StatusMessage); 83 | 84 | Assert.NotNull(viewResult.ViewData); 85 | var model = Assert.IsType(viewResult.ViewData.Model); 86 | Assert.True(model.TwoFactor); 87 | Assert.Equal(phone, model.PhoneNumber); 88 | Assert.True(model.HasPassword); 89 | } 90 | 91 | public class NoOpAuth : IAuthenticationService 92 | { 93 | public Task AuthenticateAsync(HttpContext context, string scheme) 94 | { 95 | return Task.FromResult(AuthenticateResult.None()); 96 | } 97 | 98 | public Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties, ChallengeBehavior behavior) 99 | { 100 | return Task.FromResult(0); 101 | } 102 | 103 | public Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) 104 | { 105 | throw new NotImplementedException(); 106 | } 107 | 108 | public Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties) 109 | { 110 | throw new NotImplementedException(); 111 | } 112 | } 113 | 114 | } 115 | } -------------------------------------------------------------------------------- /test/MusicStore.E2ETests/OpenIdConnectTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Server.IntegrationTesting; 5 | using Microsoft.AspNetCore.Testing.xunit; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Logging.Testing; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace E2ETests 12 | { 13 | public class OpenIdConnectTests : LoggedTest 14 | { 15 | public OpenIdConnectTests(ITestOutputHelper output) : base(output) 16 | { 17 | } 18 | 19 | [ConditionalTheory, Trait("E2Etests", "E2Etests")] 20 | [OSSkipCondition(OperatingSystems.Linux)] 21 | [OSSkipCondition(OperatingSystems.MacOSX)] 22 | [InlineData(ServerType.Kestrel, RuntimeArchitecture.x64, ApplicationType.Portable)] 23 | [InlineData(ServerType.Kestrel, RuntimeArchitecture.x64, ApplicationType.Standalone)] 24 | public async Task OpenIdConnect_OnWindowsOS( 25 | ServerType serverType, 26 | RuntimeArchitecture architecture, 27 | ApplicationType applicationType) 28 | { 29 | await OpenIdConnectTestSuite(serverType, architecture, applicationType); 30 | } 31 | 32 | [ConditionalTheory, Trait("E2Etests", "E2Etests")] 33 | [OSSkipCondition(OperatingSystems.Windows)] 34 | [InlineData(ServerType.Kestrel, RuntimeArchitecture.x64, ApplicationType.Portable)] 35 | [InlineData(ServerType.Kestrel, RuntimeArchitecture.x64, ApplicationType.Standalone)] 36 | public async Task OpenIdConnect_OnNonWindows(ServerType serverType, RuntimeArchitecture architecture, ApplicationType applicationType) 37 | { 38 | await OpenIdConnectTestSuite(serverType, architecture, applicationType); 39 | } 40 | 41 | private async Task OpenIdConnectTestSuite(ServerType serverType, RuntimeArchitecture architecture, ApplicationType applicationType) 42 | { 43 | var testName = $"OpenIdConnectTestSuite_{serverType}_{architecture}_{applicationType}"; 44 | using (StartLog(out var loggerFactory, testName)) 45 | { 46 | var logger = loggerFactory.CreateLogger("OpenIdConnectTestSuite"); 47 | var musicStoreDbName = DbUtils.GetUniqueName(); 48 | 49 | var deploymentParameters = new DeploymentParameters(Helpers.GetApplicationPath(applicationType), serverType, RuntimeFlavor.CoreClr, architecture) 50 | { 51 | PublishApplicationBeforeDeployment = true, 52 | PreservePublishedApplicationForDebugging = Helpers.PreservePublishedApplicationForDebugging, 53 | TargetFramework = "netcoreapp2.0", 54 | Configuration = Helpers.GetCurrentBuildConfiguration(), 55 | ApplicationType = applicationType, 56 | EnvironmentName = "OpenIdConnectTesting", 57 | UserAdditionalCleanup = parameters => 58 | { 59 | DbUtils.DropDatabase(musicStoreDbName, logger); 60 | }, 61 | AdditionalPublishParameters = " /p:PublishForTesting=true" 62 | }; 63 | 64 | // Override the connection strings using environment based configuration 65 | deploymentParameters.EnvironmentVariables 66 | .Add(new KeyValuePair( 67 | MusicStoreConfig.ConnectionStringKey, 68 | DbUtils.CreateConnectionString(musicStoreDbName))); 69 | 70 | using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) 71 | { 72 | var deploymentResult = await deployer.DeployAsync(); 73 | var httpClientHandler = new HttpClientHandler(); 74 | var httpClient = deploymentResult.CreateHttpClient(httpClientHandler); 75 | 76 | // Request to base address and check if various parts of the body are rendered & measure the cold startup time. 77 | var response = await RetryHelper.RetryRequest(async () => 78 | { 79 | return await httpClient.GetAsync(string.Empty); 80 | }, logger: logger, cancellationToken: deploymentResult.HostShutdownToken); 81 | 82 | Assert.False(response == null, "Response object is null because the client could not " + 83 | "connect to the server after multiple retries"); 84 | 85 | var validator = new Validator(httpClient, httpClientHandler, logger, deploymentResult); 86 | 87 | logger.LogInformation("Verifying home page"); 88 | await validator.VerifyHomePage(response); 89 | 90 | logger.LogInformation("Verifying login by OpenIdConnect"); 91 | await validator.LoginWithOpenIdConnect(); 92 | 93 | logger.LogInformation("Variation completed successfully."); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /samples/MusicStore/wwwroot/Scripts/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /* 16 | ** Unobtrusive validation support library for jQuery and jQuery Validate 17 | ** Copyright (C) Microsoft Corporation. All rights reserved. 18 | */ 19 | (function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("
  • ").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this);b.data("validator").resetForm();b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(c){var b=a(c),d=b.data(e),f=a.proxy(n,c);if(!d){d={options:{errorClass:"input-validation-error",errorElement:"span",errorPlacement:a.proxy(m,c),invalidHandler:a.proxy(l,c),messages:{},rules:{},success:a.proxy(k,c)},attachValidation:function(){b.unbind("reset."+e,f).bind("reset."+e,f).validate(this.options)},validate:function(){b.validate();return b.valid()}};b.data(e,d)}return d}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(b){var c=a(b).parents("form").andSelf().add(a(b).find("form")).filter("form");a(b).find(":input").filter("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});c.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){return a(b.form).find(":input").filter("[name='"+f(c)+"']").val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery); 20 | --------------------------------------------------------------------------------