ASP.NET Core WishList Application
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.
Setup the Application
If you want to use Visual Studio
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 don't plan to use Visual studio
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
Features you will implement
- 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)
Tasks necessary to complete implementation:
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
ApplicationUser
that inheritsIdentityUser
for you! (This was done to allow us to more accurately test your code through out this project) - Replace
ApplicationDbContext
's inheritance ofDbContext
toIdentityDbContext<ApplicationUser>
(you will need to addusing
directives forMicrosoft.AspNetCore.Identity.EntityFrameworkCore
andusing WishList.Models
) - In
Startup.cs
'sConfigureServices
method callAddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
onservices
(you will need to addusing
directives forMicrosoft.AspNetCore.Identity
andusing WishList.Models
) - In
Startup.cs
'sConfigure
method Beforeapp.UseMvcWithDefaultRoute();
callUseAuthentication
onapp
.
- Note : We created the model
- Create
AccountController
- Create new controller
AccountController
in theControllers
folder- The
AccountController
class should have theAuthorize
attribute (you will need ausing
directive forMicrosoft.AspNetCore.Authorization
) - The
AccountController
should inherit theController
class (you will need ausing
directive forMicrosoft.AspNetCore.Mvc
)
- The
- Create private fields in the
AccountController
class- This should have a private readonly field of type
UserManager<ApplicationUser>
named_userManager
(you will need ausing
directives forWishList.Models
andMicrosoft.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
AccountController
class- 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/AccountViewModels
folder create a new modelRegisterViewModel
(You will need to create the AccountViewModels folder) - Create a
String
Property Email- Email should have the Required attribute (you will need to add a
using
directive 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
Password
Password
should have theRequired
attributePassword
should have aStringLength
attribute of 100 with aMinimumLength
of 8 charactersPassword
should have theDataType.Password
attribute
- Create a
String
PropertyConfirmPassword
ConfirmPassword
should have theDataType.Password
attributeConfirmPassword
should have theCompare
attribute with "Password"
- Inside the
- Create the Register View
- Inside the
Views/Account
folder add a new viewRegister
(you will need to create theAccount
folder) Register.cshtml
should 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-action
with a value of"Register"
to theform
tag - Add an attribute
asp-validation-summary
with a value of"All"
for thediv
tag with an attributeclass
of value"text-danger"
- Add an attribute
asp-for
with a value of"Email"
to both the firstlabel
andinput
tag - Add an attribute
asp-for
with a value of"Password"
to both the secondlabel
andinput
tag - Add an attribute
asp-for
with a value of"ConfirmPassword"
to both the thirdlabel
andinput
tag
- Inside the
- Create an
HttpGet
actionRegister
in theAccountController
class- This action should have the
HttpGet
attribute - This action should have the
AllowAnonymous
attribute - This action should have no parameters
- This action should return the
Register
view.
- This action should have the
- Create an
HttpPost
actionRegister
in theAccountController
class- This action should have the
HttpPost
attribute - This action should have the
AllowAnonymous
attribute - This action should accept a parameter of type
RegisterViewModel
(you will need to add ausing
directive forWishList.Models.AccountViewModels
) - This action should return a
RedirectToAction
toHomeController.Index
- This action should have the
- Update the
HttpPost
Register
action to check if theModelState
is valid - If not return theRegister
view with the model provided in the parameter as it's model - Update the
HttpPost
Register
action to create the new user using the_userManager.CreateAsync
method providing it a validApplicationUser
(you will need to set theUser
andEmail
properties to theEmail
from theRegisterViewModel
) and astring
which you'll set the thePassword
property from theRegisterViewModel
- Check the
Result
property from theHttpPost
Register
's theCreateAsync
call ifResult.Success
- IfResult.Success
isfalse
foreach error inResult.Errors
useModelState.AddModelError
to add an error with a the first parameter of"Password"
and second with the value of the error'sDescription
property. Then return theRegister
view with the model provided byRegister
's the parameter.
- Create RegisterViewModel class
- Create Login / Logout Functionality
- Create
LoginViewModel
class in theModels\AccountViewModels
folder- Create a
String
Property Email- Email should have the Required attribute
- Email should have the EmailAddress attribute
- Create a String Property
Password
Password
should have theRequired
attributePassword
should have theDataType.Password
attribute
- Create a
- Create a
Login.cshtml
view in theViews/Account
folder- Add the following HTML to the
Login
view@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
HttpGet
actionLogin
in theAccountController
- This action should have the
HttpGet
attribute - This action should have the
AllowAnonymous
attribute - This action should have no parameters
- This action should return the
Login
view.
- This action should have the
- Create an
HttpPost
actionLogin
in theAccountController
- This action should have the
HttpPost
attribute - This action should have the
AllowAnonymous
attribute - This action should have the
ValidateAntiForgeryToken
attribute - This action should have a return type of
IActionResult
- This action should accept a parameter of type
LoginViewModel
- This action should return a
RedirectToAction
to theHome.Index
action.
- This action should have the
- Update
HttpPost
Login
to check if theModelState
is valid- If not return the
Login
view with the model provided in the parameter as it's model
- If not return the
- Update
HttpPost
Login
to useSignInManager
'sPasswordSignInAsync
method with the(string,string,bool,bool)
signature to attempt to login the user (Note: you will need to check theResult
property to see the results, passfalse
for the 3rd and 4th parameters)- if the
SignInResult
returned byPasswordSignInAsync
'sSucceeded
property isfalse
useModelState
'sAddModelError
with a key ofstring.Empty
and anerrorMessage
of"Invalid login attempt."
- if the
-
Create
anHttpPost
actionLogout
in theAccountController
- This action should have the
HttpPost
attribute - This action should have the
ValidateAntiForgeryToken
attribute - This action should have a return type of
IActionResult
- This action should use
SignInManager
'sSignOutAsync
method - This should return a
RedirectToAction
to theHome.Index
action
- This action should have the
- Create
- Add Links to Index View
- Add
using
directives forMicrosoft.AspNetCore.Identity
andWishList.Models
to the top ofIndex.cshtml
- Add an
inject
directive forSignInManager<ApplicationUser>
with the nameSignInManager
after theusing
directives - Check if the user is signed in using the injected
SignInManager
'sIsSignedIn
method (provideUser
as the arguement)- If
IsSignedIn
returnstrue
provide the following HTML<div> <form asp-action="Logout" asp-controller="Account" method="post"> <button type="submit">Logout</button> </form> </div>
- If
IsSignedIn
returnsfalse
provide 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
virtual
property of typeApplicationUser
namedUser
to theItem
model - Add a
virtual
property of typeICollection<Item>
namedItems
to theApplicationUser
model (you will need to add ausing
directive forSystem.Collections.Generic
)
- Add a
- Update ItemController actions to consider user
- Add the
Authorize
attribute to theItemController
class (you will need to add ausing
directive forMicrosoft.AspNetCore.Authorization
) - Add a new
private
readonly
field of typeUserManager<ApplicationUser>
named_userManager
- Update the
ItemController
's constructor to accept a second parameter of typeUserManager<ApplicationUser>
and within the costructor set_userManager
to the providedUserManager<ApplicationUser>
paramater. - Update the
ItemController.Index
action to only return items with associated with the currently logged in user.- Change the
_context.Items.ToList();
to include aWhere
call that only gets items with the matchingUser.Id
. (You can get the current logged in user usingUserManager.GetUserAsync(HttpContext.User)
)
- Change the
- Update the
HttpPost
ItemController.Create
action 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.Delete
action to prevent deleting items by anyone but the user who owns that item.- Before removing the
Item
check that it is notnull
if it isnull
returnNotFound
- After checking if
Item
is null, but before removing theItem
check 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
What Now?
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.