The ASP.NET Core WishList Application is designed to allow users to create their own wishlists, and other users to mark that they are buying those items in such a way the owner of the wish list isn't able to see, while other users are able to see. This application is designed using the Model View Controller design pattern.
Note: This project is the second in a series of four projects, this project will cover taking an existing ASP.NET Core web application and changing it from only supporting one user to being able to support many users with authentication and basic security.
If you want to use Visual Studio (highly recommended) follow the following steps:
- If you already have Visual Studio installed make sure you have .Net Core installed by running the "Visual Studio Installer" and making sure ".NET Core cross-platform development" is checked
- If you need to install visual studio download it at https://www.microsoft.com/net/download/ (If you're using Windows you'll want to check "ASP.NET" and ".NET Core cross-platform development" on the workloads screen during installation.)
- Open the .sln file in visual studio
- To run the application simply press the Start Debug button (green arrow) or press F5
- If you're using Visual Studio on Windows, to run tests open the Test menu, click Run, then click on Run all tests (results will show up in the Test Explorer)
- If you're using Visual Studio on macOS, to run tests, select the WishListTests Project, then go to the Run menu, then click on Run Unit Tests (results will show up in the Unit Tests panel)
(Note: All tests should fail at this point, this is by design. As you progress through the projects more and more tests will pass. All tests should pass upon completion of the project.)
If you would rather use something other than Visual Studio
- Install the .Net Core SDK from https://www.microsoft.com/net/download/core once that installation completes you're ready to roll!
- To run the application go into the WishList project folder and type
dotnet run - To run the tests go into the WishListTests project folder and type
dotnet test
- Add support for user authentication
- Create functionality for creating and logging in
- Expand Wishlist functionality to support multiple users
- Implement several basic security practices (validation tokens, user verification, authentication, etc)
Note: this isn't the only way to accomplish this, however; this is what the project's tests are expecting. Implementing this in a different way will likely result in being marked as incomplete / incorrect.
- Adding Authentication to our existing ASP.NET Core wishlist app
- Configure Authentication
- Note : We created the model
ApplicationUserthat inheritsIdentityUserfor you! (This was done to allow us to more accurately test your code through out this project) - Replace
ApplicationDbContext's inheritance ofDbContexttoIdentityDbContext<ApplicationUser>(you will need to addusingdirectives forMicrosoft.AspNetCore.Identity.EntityFrameworkCoreandusing WishList.Models) - In
Startup.cs'sConfigureServicesmethod callAddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();onservices(you will need to addusingdirectives forMicrosoft.AspNetCore.Identityandusing WishList.Models) - In
Startup.cs'sConfiguremethod Beforeapp.UseMvcWithDefaultRoute();callUseAuthenticationonapp.
- Note : We created the model
- Create
AccountController- Create new controller
AccountControllerin theControllersfolder- The
AccountControllerclass should have theAuthorizeattribute (you will need ausingdirective forMicrosoft.AspNetCore.Authorization) - The
AccountControllershould inherit theControllerclass (you will need ausingdirective forMicrosoft.AspNetCore.Mvc)
- The
- Create private fields in the
AccountControllerclass- This should have a private readonly field of type
UserManager<ApplicationUser>named_userManager(you will need ausingdirectives forWishList.ModelsandMicrosoft.AspNetCore.Identity) - This should have a private readonly field of type
SignInManager<ApplicationUser>named_signInManager
- This should have a private readonly field of type
- Create a constructor in the
AccountControllerclass- This constructor should accept two parameters, the first of type
UserManager<ApplicationUser>, the second of typesignInManager<ApplicationUser> - This constructor should set each of the private readonly properties using the parameter of the same type
- This constructor should accept two parameters, the first of type
- Create new controller
- Create Register Functionality
- Create RegisterViewModel class
- Inside the
Models/AccountViewModelsfolder create a new modelRegisterViewModel(You will need to create the AccountViewModels folder) - Create a
StringProperty Email- Email should have the Required attribute (you will need to add a
usingdirective forSystem.ComponentModel.DataAnnotations) - Email should have the EmailAddress attribute
- Email should have the Required attribute (you will need to add a
- Create a String Property
PasswordPasswordshould have theRequiredattributePasswordshould have aStringLengthattribute of 100 with aMinimumLengthof 8 charactersPasswordshould have theDataType.Passwordattribute
- Create a
StringPropertyConfirmPasswordConfirmPasswordshould have theDataType.PasswordattributeConfirmPasswordshould have theCompareattribute with "Password"
- Inside the
- Create the Register View
- Inside the
Views/Accountfolder add a new viewRegister(you will need to create theAccountfolder) Register.cshtmlshould have a model ofRegisterViewModel(You will need to include the namespace,WishList.Models.AccountViewModels.RegisterViewModel)- Add the following HTML to the view (we're providing this to save you from needing to type it all yourself)
<h3>Register New User</h3> <form> <div class="text-danger"></div> <div> <label></label> <input /> </div> <div> <label></label> <input /> </div> <div> <label></label> <input /> </div> <div> <button type="submit">Register User</button> </div> </form> - Add an attribute
asp-actionwith a value of"Register"to theformtag - Add an attribute
asp-validation-summarywith a value of"All"for thedivtag with an attributeclassof value"text-danger" - Add an attribute
asp-forwith a value of"Email"to both the firstlabelandinputtag - Add an attribute
asp-forwith a value of"Password"to both the secondlabelandinputtag - Add an attribute
asp-forwith a value of"ConfirmPassword"to both the thirdlabelandinputtag
- Inside the
- Create an
HttpGetactionRegisterin theAccountControllerclass- This action should have the
HttpGetattribute - This action should have the
AllowAnonymousattribute - This action should have no parameters
- This action should return the
Registerview.
- This action should have the
- Create an
HttpPostactionRegisterin theAccountControllerclass- This action should have the
HttpPostattribute - This action should have the
AllowAnonymousattribute - This action should accept a parameter of type
RegisterViewModel(you will need to add ausingdirective forWishList.Models.AccountViewModels) - This action should return a
RedirectToActiontoHomeController.Index
- This action should have the
- Update the
HttpPostRegisteraction to check if theModelStateis valid - If not return theRegisterview with the model provided in the parameter as it's model - Update the
HttpPostRegisteraction to create the new user using the_userManager.CreateAsyncmethod providing it a validApplicationUser(you will need to set theUserandEmailproperties to theEmailfrom theRegisterViewModel) and astringwhich you'll set the thePasswordproperty from theRegisterViewModel - Check the
Resultproperty from theHttpPostRegister's theCreateAsynccall ifResult.Success- IfResult.Successisfalseforeach error inResult.ErrorsuseModelState.AddModelErrorto add an error with a the first parameter of"Password"and second with the value of the error'sDescriptionproperty. Then return theRegisterview with the model provided byRegister's the parameter.
- Create RegisterViewModel class
- Create Login / Logout Functionality
- Create
LoginViewModelclass in theModels\AccountViewModelsfolder- Create a
StringProperty Email- Email should have the Required attribute
- Email should have the EmailAddress attribute
- Create a String Property
PasswordPasswordshould have theRequiredattributePasswordshould have theDataType.Passwordattribute
- Create a
- Create a
Login.cshtmlview in theViews/Accountfolder- Add the following HTML to the
Loginview@model WishList.Models.AccountViewModels.LoginViewModel <h2>Log in</h2> <form asp-action="Login" method="post"> <div asp-validation-summary="All" class="text-danger"></div> <div> <label asp-for="Email"></label> <input asp-for="Email" /> <span asp-validation-for="Email"></span> </div> <div> <label asp-for="Password"></label> <input asp-for="Password" /> <span asp-validation-for="Password"></span> </div> <div> <button type="submit">Log in</button> </div> </form> <a asp-action="Register">Register new account</a>
- Add the following HTML to the
- Create an
HttpGetactionLoginin theAccountController- This action should have the
HttpGetattribute - This action should have the
AllowAnonymousattribute - This action should have no parameters
- This action should return the
Loginview.
- This action should have the
- Create an
HttpPostactionLoginin theAccountController- This action should have the
HttpPostattribute - This action should have the
AllowAnonymousattribute - This action should have the
ValidateAntiForgeryTokenattribute - This action should have a return type of
IActionResult - This action should accept a parameter of type
LoginViewModel - This action should return a
RedirectToActionto theHome.Indexaction.
- This action should have the
- Update
HttpPostLoginto check if theModelStateis valid- If not return the
Loginview with the model provided in the parameter as it's model
- If not return the
- Update
HttpPostLoginto useSignInManager'sPasswordSignInAsyncmethod with the(string,string,bool,bool)signature to attempt to login the user (Note: you will need to check theResultproperty to see the results, passfalsefor the 3rd and 4th parameters)- if the
SignInResultreturned byPasswordSignInAsync'sSucceededproperty isfalseuseModelState'sAddModelErrorwith a key ofstring.Emptyand anerrorMessageof"Invalid login attempt."
- if the
-
CreateanHttpPostactionLogoutin theAccountController- This action should have the
HttpPostattribute - This action should have the
ValidateAntiForgeryTokenattribute - This action should have a return type of
IActionResult - This action should use
SignInManager'sSignOutAsyncmethod - This should return a
RedirectToActionto theHome.Indexaction
- This action should have the
- Create
- Add Links to Index View
- Add
usingdirectives forMicrosoft.AspNetCore.IdentityandWishList.Modelsto the top ofIndex.cshtml - Add an
injectdirective forSignInManager<ApplicationUser>with the nameSignInManagerafter theusingdirectives - Check if the user is signed in using the injected
SignInManager'sIsSignedInmethod (provideUseras the arguement)- If
IsSignedInreturnstrueprovide the following HTML<div> <form asp-action="Logout" asp-controller="Account" method="post"> <button type="submit">Logout</button> </form> </div> - If
IsSignedInreturnsfalseprovide the following HTML<div> <a asp-action="Register" asp-controller="Account" >Register</a> </div> <div> <a asp-action="Login" asp-controller="Account" >Log in</a> </div>
- If
- Add
- Create Relationship Between Item and ApplicationUser Models
- Add a
virtualproperty of typeApplicationUsernamedUserto theItemmodel - Add a
virtualproperty of typeICollection<Item>namedItemsto theApplicationUsermodel (you will need to add ausingdirective forSystem.Collections.Generic)
- Add a
- Update ItemController actions to consider user
- Add the
Authorizeattribute to theItemControllerclass (you will need to add ausingdirective forMicrosoft.AspNetCore.Authorization) - Add a new
privatereadonlyfield of typeUserManager<ApplicationUser>named_userManager - Update the
ItemController's constructor to accept a second parameter of typeUserManager<ApplicationUser>and within the costructor set_userManagerto the providedUserManager<ApplicationUser>paramater. - Update the
ItemController.Indexaction to only return items with associated with the currently logged in user.- Change the
_context.Items.ToList();to include aWherecall that only gets items with the matchingUser.Id. (You can get the current logged in user usingUserManager.GetUserAsync(HttpContext.User))
- Change the
- Update the
HttpPostItemController.Createaction to add the logged in User to theItem- Before adding the Item to set the UserId to the logged in user's Id (You can get the current logged in user using
UserManager.GetUserAsync(HttpContext.User))
- Before adding the Item to set the UserId to the logged in user's Id (You can get the current logged in user using
- Update the
ItemController.Deleteaction to prevent deleting items by anyone but the user who owns that item.- Before removing the
Itemcheck that it is notnullif it isnullreturnNotFound - After checking if
Itemis null, but before removing theItemcheck te make sure the Item's User is the same as the logged in user, if not returnUnauthorized
- Before removing the
- Add the
- Configure Authentication
You've completed the tasks of this project, if you want to continue working on this project there will be additional projects added to the ASP.NET Core path that continue where this project left off adding more advanced views and models, as well as providing and consuming data as a web service.
Otherwise now is a good time to continue on the ASP.NET Core path to expand your understanding of the ASP.NET Core framework or take a look at the Microsoft Azure for Developers path as Azure is a common choice for hosting, scaling, and expanding the functionality of ASP.NET Core applications.