
In this video, I describe the inspiration behind this course, and the purpose of why the course is building the "Contact Web" CRUD operations.
Additionally, this first video details the overall goal for what you will learn and build in this course.
In this video, I introduce myself and welcome you to the course. I invite you to connect with me on LinkedIn or Twitter or you can just message me from within this platform.
The video also details a bit about what you will build in the course, which is mostly a review of the previous video.
In this video I talk through how this course originally was formatted and how much has changed through the years. Yes, this course has been around for some time, but it is still incredibly relevant (100,000+ jobs available in the US today). This update takes the course to a new level, since the majority of you are in a business developer role. For that reason, this course has become much more robust and valuable through the years, and is a bit more technically difficult now than it was at inception.
This video details the resources for the course at GitHub, as well as talks about how to utilize the repository releases to find versions of the code relevant to the section of the course you are working through.
In this video, we will get Visual Studio Code installed on our development machine. If you already have VSCode, you can mark this as complete. If you do not have VSCode, I highly recommend getting this as it is a great tool for all development work, not just for this course.
VSCode is free, and allows you to add extensions for everything from HTML to python to Java to Angular/Typescript to Git extensions to Azure tools. Although the overall use of this program will be somewhat limited for this course, I think you will agree that this tool is a must have for developers.
In this lecture, you will get Visual Studio 2022 installed on your machine. Along with Visual Studio 2022, you will get the .NET 6 framework. You may also desire to get previous frameworks if you have legacy projects. In Addition to the tool, we'll look at how you can install features along with the installation for things like web development (of course) and game development. The options you select will mostly be up to you and you can always come back and install features in the future if something you need is not installed.
In this video, we'll install a couple of visual studio extensions that I cannot live without while developing and recommend that you also get. These are optional, but if nothing else, you should get the Add New File extension because it will make your development life 100% easier when it comes to creating new files (something we will do a lot of in this course).
In this video, we'll cover how to get a local SQL Server installed on our development machine. With the release of SQL Server development edition a few years ago as a free tool for developers, it is the tool of choice in most development shops. With Developer edition you get a full working copy of SQL Server to use locally as a developer. Using SQL Server developer also helps to standardize the connection string for all team members.
If you have limited space or power on your development machine, you will have an option to use the SQL Server Express edition locally.
If you are going to do containerized development, you might also want to just install SQL Server on a container instead of on your machine (this is also an option if you are on a mac).
In this video, we install the SQL Server Management Studio - which is the tool of choice to interact with SQL Server databases in order to check database state/schema, run ad hoc queries, troubleshoot issues, and use the SQL Profiler to evaluate query efficiency.
If you are doing any development with SQL Server, you will definitely want to utilize SSMS as an additional tool.
Along with this installation, you now also get the Azure Data Studio, which allows you to work against Azure databases (including Azure Postgre SQL). You can do most of your work with SSMS, but the Azure Data Tool is a handy and lightweight tool available for you to leverage.
In the past, we had to install SQL Server Data Tools (SSDT) as a standalone installation. From VS2019 on (including VS2022 of course), SSDT is part of the data tools installation.
Data tools gives you the ability to create traditional database projects as well as SQL Server Integration Services (SSIS) projects.
Although we will not be building any SSIS projects or anything at Azure Data Factory for this course, having this tool installed will be an important part of your toolkit as a .Net and SQL developer.
In this video we make sure our machine has GIT on it. This is something I would recommend for everyone on Windows. If you are using Mac or Linux, you may already have GIT (may need updates).
In addition to getting GIT, we'll also cover setting our default credentials and the default editor for our GIT interaction on our development machine.
In this video, you will see how to easily create a new MVC project for web development with the Model-View-Controller pattern using .Net 6 in visual studio 2022.
During the video, I'll call out the required settings, including the use of the Individual user accounts. I'll also recommend that even if you are going to use containers you don't wire up the containers in VS2022. This is a personal preference and you should feel free to use the Docker tools if you want to.
After completion, you'll have a default project that is ready to use that contains the ability to connect to a database with EFCore6 and individual user accounts.
Right now everything is new (at the time of this recording). However, as time goes on, NuGet packages will get updated, so it will be highly likely you will want to update your NuGet packages to the latest version. This video briefly talks about how to update NuGet packages when desired.
One of the most important things to understand when working with the database from our MVC project will be how to set the connection string correctly. In order to work together well as a team, it's likely a good idea to have the developer edition of SQL server installed and then just set your connection string to "localhost". This will allow all developers to easily interact.
Additionally, this video shows how to change the name of the database.
With the integration of individual user accounts, the entire ASP.Net identity ecosystem is built in to the solution. In order to be able to register users and work with them, the first thing you need to do is run the database migrations to create the basic data schema for identity in your database.
This video shows how to run an 'update-database' command and then review the database to see that the tables are created as expected.
With the migrations applied, you can easily register users and log in as the registered user.
Before establishing a local GIT repository, it's a very good idea to put a .gitignore file in place. Using a .gitignore file allows you to block specific files from getting committed into the repository.
The main reason to do this is because the files that are generated with builds should never be checked in. If they were checked in, then every time any developer runs a build, there would be new files to check in and the files would be in conflict with each other quite frequently.
In order to make this work, folders such as 'bin' and 'obj' should be prevented from being checked in.
In this video, we'll see how to easily get a version of a .gitignore file for C# .Net projects, and we'll put it in place on our machine in the root directory for our project.
With the .gitignore file in place, it's time to create a new local repository.
There are a number of ways to do this, but the easiest way is just to run the 'git init' command on your local directory and then create the first commit using the commands 'git add .' and then 'git commit -m "initial commit"' or something similar.
This video will show how to quickly create a new local repository. Once the repository is created locally, any GIT tool will work just fine (including the tools inside of Visual Studio) to work directly with your repository locally.
Now that you have a local git repository, you should ensure that you can push the changes to a remote repository. This will ensure that even if your computer drive fails you will always be able to quickly and easily get your code back.
GitHub will be the repository of choice for this course going forward, but you are welcome to use any other remote repository, such as BitBucket or GitLab if you so desire.
In this video, we'll discuss creating an account at GitHub. If you do not have one, you should create one for use in the rest of the course.
Once you have an account at GitHub, you should create a new repository to store your project code. Eventually you will also be able to create CI/CD Automation against this repository.
For now this video covers getting your code out to a remote repository at GitHub.
Just to prove a point, and to see how you can easily get code from a remote repository at GitHub, this video takes you through deleting the code on the local drive and then using the remote repository link to clone the code back to your machine.
This should give you the peace of mind that your code is going to be easily restored from the remote, even in the event of a disaster.
In this video, we talk about how you could potentially use the default models folder within the MVC project. I've seen this done on many projects, and have even utilized this strategy in the previous runs of this course. For this reason, you could just opt to put your models in the Models folder and you can do everything in one database context. This is an easier solution to implement, but is not as robust.
For reasons of reusability, testability, and maintainability, we'll use a separate project for our models in this solution. We will therefore not put the Contact and State models in the default project within the models folder. We will utilize a class library for the models, and just reference them from within our web project.
In this video, we create the new project to house the models for the solution.
The new project is a class library and it will provide the ability to easily reuse the code in future projects. Using this library will also make it easier to utilize unit and integration tests in future sections of the course.
The first data model that we will create is the State model. This model will contain an Id, the name of the state, and an abbreviation. Typical states might be "Alabama", "AL", with and Id.
In this video, we'll walk through the creation of the State model. We'll also get a chance to learn about Data Annotations, which are used to set specific properties that will be enforced for validation and database schema configurations.
In this video, which is part 2 of 2, we'll complete the creation of the Contact model.
With the new Models project in place and the two models for State and Contact in place, it's time to commit and push changes.
It is a great idea to commit and push often. In future units we will continue to do this, however, soon we'll learn about using a feature branch. For now, we're just working on the main branch.
As with the models, the database changes could all be accomplished with a single database context and all within the default web project. If you choose to do this, you wouldn't need a new database project.
However, to continue with our effort to architect a very robust solution, we're going to continue on that path by creating a database project to house our main database code. This library could be reused in other projects so we will not be limited to just this web solution to build applications. Utilizing a new library will also make it easier to test the data solution in both unit testing and integration testing.
By moving the data out of the web, we will add a second database context, which can be very useful in single sign-on environments (SSO). For our solution we'll target just one database, but it would be very easy to point the identity at one database and the business data at another if desired.
The one drawback of this approach is that using multiple database contexts can add some unnecessary complexity to the solution.
With the new project for data in place, we'll utilize the new database context to create a new migration.
To affect changes in the database, we need to add references to each of the data models, and then create a new migration.
In order to run the database commands, with the addition of a second database context, the migration commands will have to specify which context to apply migrations against.
In this video, we will see how to apply the migration for the changes using the new database project.
Once the database changes are applied, we'll look at a couple of ways that we can review the data in the database.
Sometimes it will be necessary for us to roll our changes back via migrations. In this video we'll see how to roll the database back to a previous migration.
Once a migration is no longer applied, you can remove the migration. Of course you should only do this if you have never applied the migration on a public database. In other words, use a roll-forward approach if migrations have been applied on other developer and/or production releases. Use a remove operation only when the migration has only been applied to just your own local copy of the database.
There are a few approaches that could be taken to apply a seed to the database.
In this video, we'll take an approach that allows the seed to be handled by the database project. The seed will ensure that any new database will have the default states automatically placed into the database.
To seed the data in the database project, we'll override the OnModelBuilding method in the database context class, and then we'll utilize the Fluent API to set the data to be generated in a migration.
Once again, at the end of a unit we'll commit locally and then push our changes out to the remote.
In this video, we create the states controller and then utilize the built-in tools to scaffold the views for our site to easily interact with the States data.
In this video we make sure that our second database context is injected into the solution so that we can interact with the data without getting an error due to the use of a second database context.
In this video, we continue our work on the initial project to create the Contacts controller with the appropriate scaffolded views.
One issue that we will run into with this solution is the fact that sometimes data gets disconnected from the context or is not hydrated correctly. Sometimes this is due to the default code and sometimes it's just a simple oversight. Either way, we often need to make sure our data is hydrated correctly (i.e. the State object is not null on the Contact object) before saving to the database.
This video is just a simple walk-through to ensure that all of the pages are working as expected at the base level. In other words, we want to test as much as we can quickly, and expect that the majority of functionality will be proven to work as expected.
In this video, we're going to start making changes to try to limit database calls. In the initial job post, one of the requirements was to talk about performance of your site. Here we get to discover how to trace database calls and find a repeated call to get states information on the contacts page. This leads us to the conclusion that even consolidation to one place for the call won't be enough, so we'll implement caching in the next unit.
At this point, we've created the basic functionality, so it's a good time to check our changes in and push them to the remote repository. Truly we should be doing this more often, but at the end of a unit is a great checkpoint to make sure we keep our progress and changes from getting lost.
Starting from this point, we're going to utilize feature branches (or our own development branch) to create changes. As a team member, you will not want to be committing changes directly to the main branch. For this reason, you should create a feature branch and then use a pull request to get your changes approved and merged into the main branch.
In this video, we're going to implement caching using in memory caching. We'll see how easy it is to inject this into our controller and then we'll use a constant value to store the variable name for our cache object. By implementing caching, we'll get to see how we can eliminate multiple calls to the database to get information that rarely changes.
After we've created caching, the issue exists that changes to the database are not immediately available when the default data is cached. As such, we need to invalidate the existing cache when the data for states is updated (either created, edited, or deleted).
By invalidating the cache, the next time the data is requested it will be updated and set back into cache so that future calls would leverage the most recent version of the data.
Now that the states data has been cached on the states controller, the next step is to also leverage cache from the contacts controller as well. By using the same name for the cache, both controllers can leverage the same object, so this will maximize our efficiency on utilization of the states data.
A couple of issues are prevalent now that the data is cached and disconnected. When we separate concerns later in the course, this will not be as much of an issue because we'll have a more robust data solution. For now, due to how this is set up and how I've created the solution, the easiest solution was to just make sure that data is rehydrated so the object will save correctly. A better solution would be to make the State object nullable in the contact entity - which would require a migration. By doing that, the model should be able to be saved as long as the StateId is correctly hydrated. You could implement that change for practice if you wanted to try it out.
Once again, at the end of a unit it's a good idea to test everything and make sure it's working, then commit and push changes to the remote repository. Now that we're using a feature branch, we can also get our first look at creating and using a pull request to merge the changes to the main branch.
In this section, we will begin the work to make our solution more robust by creating a repository project. We'll start off with a new feature branch and then quickly create the new library project that will ultimately be making calls to the database context.
For purposes of simplicity, we'll mostly be making simple calls to model the same data that we needed on our web pages that were called from the controller actions. That being said, you would be able to utilize the repository project later for more intense database operations against the context that are not simple "get all the data" calls. Additionally, leveraging this project as a repository layer will make it much easier to build integration testing later in the course.
In this video, we will create the services project. This layer will serve as the business logic layer. Here, you will create methods that call to the repository project to get data and then the service project (business layer) can further manipulate the data. For simplicity in our project, we'll mostly be making pass-trough calls. However, in the future you could easily add more business logic to manipulate or configure the data as needed. Additionally, using this project layer for business logic (service layer) will allow for easily implementing unit tests later in the course.
With the repository and services projects in place, it's time to start moving the operations into the layers and out of the controllers. To get started, the methods to get the data need to be implemented in the repository layer. While it may seem a bit like over-engineering for the simplicity of this system, taking these steps will ensure that we can completely disconnect the web solution from the database solution. This would make it extremely painless to change the database for the web solution. Additionally, putting all of this code into the layers will give us the ability to reuse our database repository and services projects in other solutions. With testing implemented and the disconnection of the layers, our application is much more flexible for changes and maintenance in the future.
In this video, we continue moving calls from the controller into the service layer which leverages the data layer. With these changes, we can also remove references from the controller to the database context. This is yet another benefit of abstracting the database calls to their own layer. Now we are only loosely coupled to the implementation, and could change this layer out if necessary in the future. Additionally, using the layered approach allows us to have the database calls we need in just one place (easily testable and reusable). This is a much better solution with these changes in place.
Now that we've added the new layers for the business logic (services) and the data layer (repository), we need to make sure that the application can utilize the code. We'll start off by seeing that we have an error for the missing services, and then we'll get them injected into the application the same way we got the application db context to be added to the project.
Now that we've completed the work to disconnect the data, we can add unit tests to the service layer. In this video, we'll implement the unit tests and utilize an XUnit project with Moq and Shouldly to test our service layer. Again, we're keeping things fairly trivial here, but we will get to see the ability to run these tests with data results mocked from the repository layer. In the future, we could create more robust unit tests.
In this video, we complete the work for the unit testing on the states service.
At the data layer, we might want to actually see what the database calls will do, and we can make that happen with integration tests. For this solution, we'll utilize an in-memory database to create an actual database that is called during the integration testing process. In this video, we'll get set up to do the integration testing against the states data from the states repository.
In this video, we'll impelement the integration tests and validate that our states repository is working as expected via the integration tests using an XUnit project with an in-memory database and shouldly.
In this video, we'll complete the work to get the integration testing working against the States repository.
Now that the States data and service layers are tested, we need to repeat the work to get the calls for Contact data into the repository layer, then leverage those calls from the service layer and finally call only to the service layer from the Contacts controller. In this video, we'll get started moving the calls to the data and service layers.
In this video, we'll complete the updates to move database calls out of the Contacts controller and leverage calls to the contacts service (business layer) to get data from the repository (data) layer. We'll also ensure all of the unit and integration testing is completed for the Contacts data and services layers.
In this video, we once again make sure that everything is still working as expected and push the changes to the remote repository and create a pull request.
It's important to note that if you are working along with me, at this point you should be able to commit your changes via a pull request.
However, I screwed up the videos on the first run and had to redo some work. Unfortunately, I had already pushed changes to the remote repository main branch. For this reason, I created another video to show how to do some history manipulation. I'll take care of committing my changes in the next video.
Ordinarily, the correct solution when you have commits on the main branch and you need to "undo" them would be to revert those changes - however reverting a merge commit is not entirely trivial. Since I'm the only developer, I decided to to a history manipulation operation. I decided to show this in a video because I thought it might be interesting to you.
To make this happen, I did a full reset to a previous commit that I wanted to leverage as the starting point. I then made the new changes I desired and did a force push to the remote. By doing this, I orphaned the original three commits. I also showed how to create a "just in case" branch in the off chance that I might need those orphaned commits in the future.
In this section, we're going to lock down the controllers so that only users that are logged in can get to the states and contacts controllers. This first video will talk about the differences between authentication and authorization, and then we'll use the authorization attribute to lock down our controllers appropriately.
In this video we'll create a further restriction on the States controller to lock down the controller to only be able to be leveraged by users that are in a role called "Admin"
Now that we have our States controller locked down to require a user that is in the Admin role, we need to implement a new Admin role and a user to be in that role. We could script this or potentially create a migration, however it may not be as trivial as it sounds to ensure that your identity context is leveraged for the database. For this reason, it's incredibly easy to just inject user and role manager objects into any controller. In this video, we'll therefore create a new service for creating a user and role and we'll wire up a secret call from the Home controller to allow the operation to be executed via a simple web call.
In this video, we complete the ability to make a call to create the admin user, and then we test it to ensure that the new admin user is the only user that is allowed to access and modify the states data directly. Of course the service layer and data layer calls can still get to the states data as necessary, so there is no concern that users won't be able to get states data while working on their contacts.
Now that our data is protected to authenticated/authorized users, there is still one more problem to correct. Even though data is locked down to the logged-in users, we haven't yet associated the data to specific users by user id. In this video, we see how this is a problem when one user can see another users' data. Another issue that was not shown in the video but needs to be corrected is that even if the user didn't have a list of the other users' contacts, a savvy user might understand how to hijack the url by putting any valid contact ID in the query string, which would also get them unauthorized access to another users' contacts.
In this video, we will start the work to update the solution so that contacts are directly associated to the logged-in user. We will also be making this so that the user won't have any ability to hijack their user id or try to impersonate another user. Through this process, the lock-down will also ensure that even a hijacked URL would fail to allow the users to affect the data of another user. Finally, we'll make sure that users don't even see their user id directly on any form, and they will never need to enter their user id (they wouldn't know it and we don't want them to know it).
In this video, we will update the repository and service layers to require user id on all of the calls. This will prevent a user from being able to access any data that is not directly associated to their user id.
In this video, we'll replace the code in the front-end UI to ensure that the user id for the logged-in user is sent to the service layer without needing any interaction from the user directly.
In this video, we begin the process of obscuring the UserId from the view of the user. While it's true that a savvy user could inspect the page source and likely see their user id in a hidden field, even if they see their id they won't be able to change it to hijack another user id, unless they somehow know the guid of another user.
In this video, we complete the work to obsure the user id from the view of the user, and they will no longer need to enter anything on the creation of a contact, as the user id will be pulled in on the server-side, not from the UI.
In this video, we complete the work for unit ten, and then commit and push the changes to the remote repository, and create and merge the changes via a pull request.
This course is a very quick workshop to take you through the basic actions that will be required for you to create an awesome, real-world CRUD web application in just a few hours (you could do it over a weekend very easily) using ASP .Net MVC, the Entity Framework (code-first development approach), and the built-in scaffolding capabilities of ASP .Net MVC. In the end, you'll have your own public-facing website to enhance your resume and show off your skills in your own e-portfolio for job interviews and applications!
MVC = Model, View, Controller, and is a well-known and established design pattern. Microsoft has made it extremely easy and convenient to create your web solutions using this pattern. In Microsoft's MVC framework, we find that convention is favored over configuration, so as long as we follow a few simple guidelines, the system will do a lot of the work for us.
We'll also be taking a quick look at how we can use the Entity Framework to easily model and create a database for us. By using the code-first approach, we'll simply create our models and then look at writing a DBContext to store the models. We'll also look at some of the more critical operations, such as building relationships and seeding our data.
After we've learned about the different aspects of our application, we'll quickly create a couple of controllers and use ASP .Net MVC's built-in view and scaffolding generators to easily build our CRUD operations against our database. While working on controllers we'll also look at Authentication, Authorization, and a couple of other quick security concerns.
To make the solution more robust, we'll learn about using repository and service layers to separate concerns, while also keeping our models and data in their own projects. We'll also implement integration and unit testing around these layers using XUnit, Shouldly, Moq, and in-memory databases.
After creating the backend portion of the site, we'll put DataTables.js on our Index view so that we can easily show the data, as well as provide built-in JavaScript tools that will filter and sort the data for us. This will make your application "pop" when people look at it because it will be fast and easy to sort through the data and filter for results.
Other things we will learn throughout the course involve the use of GIT for source control, pushing our repository to GitHub, and utilizing CI/CD through GitHub Actions to automatically deploy your solution to Azure. With Azure being free now, you can easily utilize the robust platform solutions available at Azure without spending any money. You'll gain experience setting up an Azure app service, configuring the connection string to connect to your Azure SQL server and database, and you'll learn about utilizing Azure Application Insights to monitor your application, including writing your own custom events and exception handling.
By the end of the course, you'll be familiar with the major working parts of creating an ASP .Net MVC CRUD application and publishing to a public-facing website with a fairly nice and responsive UI. You'll have working knowledge of Models, Views, and Controllers, as well as how they interact to create a functional web application. You'll also be exposed to the Entity Framework and Code First Migrations with use of a SQL Server backend. The best part is that although this sounds pretty daunting, it's actually quite easy with a majority of the work done for us by convention and tools.