Introduction
Rather than being more theoretical, this article will focus mainly on the implementation part. Wherever required, I’ll throw some light on the conceptual part too.
Prerequisite
Good knowledge of C# and working knowledge of web designing and its concepts.
Machine Configuration
Machine configuration used for this walkthrough is Visual Studio 2017 with .NET Core development tools/SDK. Make sure that you have at least .NET Core 2.2 installed.
Expectation
By the end of this article, the reader will have a working model of an ASP.NET Core website for a Cookie Store.
Good to Start
Background is all set and we are good to go. Let's start by creating our application in a step-by-step fashion.
Creating a New ASP.NET Core 2.2 Application
Let’s go to the Visual Studio. Go to File menu, select New and then select Project. On selecting the Web as a category, you will land upon the below dialog,
Once you clicked on OK, you will witness the below dialog wherein you can re-verify the selected
ASP.NET Core version. Being a learner, it’s good to go with an Empty template as we can take care of each and everything by ourselves rather than relying on auto generated code. Select Empty and proceed.
Once the project is successfully created, you can see the dependency node is having required references added as shown below,
Here, let’s remove appsettings.json file as we will be creating our own at a later point of time and add a new folder named wwwroot under the project. Once the wwwroot folder is created, you can notice that the folder icon is changed. This is a special folder which stores all the static files (css, image files, JavaScript files, etc.) and is mapped directly to the web site URL.
Plugging Middleware and Services
Let’s start with Startup class. This class holds two very important methods named ConfigureServices and Configure. Both these methods are automatically called by ASP.NET Core. Using ConfigureServices, we can add services to dependency injection container using IServiceCollection. So, let’s go and add MVC service as shown below,
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc();
- }
Next is ConfigureServices method, which is used to configure middleware pipeline with the components which will serve our requests.
Here is the initial code to plug the required middleware,
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- app.UseDeveloperExceptionPage();
- app.UseStatusCodePages();
- app.UseStaticFiles();
- app.UseMvcWithDefaultRoute();
- }
Please note that the sequence of adding middleware matters a lot. Now we have done the ground work for setting up the foundation for our application, let’s go ahead and setup other useful components.
Creating Model and Repository Classes
Let’s start by creating a Models folder under solution with a class named Cookie underneath it.
- public class Cookie
- {
- public int Id {get;set; }
- public string Name { get; set; }
- public string TinyDescription { get; set; }
- public string FullDescription { get; set; }
- public decimal Price { get; set; }
- public string ImageUrl { get; set; }
- public bool IsCookieOfTheDay { get; set; }
- }
Now quickly create an interface with name IRepository under the same folder with two initial methods as shown below,
- public interface IRepository
- {
- IEnumerable<cookie> GetAllCookies();
- Cookie GetCookieById(int id);
- }
The next task would be to create some dummy data, which we can use to verify whether our application is working as expected or not.
Here is the implementation of IRepository interface,
- public class MockRepository : IRepository
- {
- private List<cookie> _cookies;
- public MockRepository()
- {
- if (_cookies == null)
- {
- InitializeRepository();
- }
- }
-
- private void InitializeRepository()
- {
- _cookies = new List<cookie>
- {
- new Cookie
- { Id=1,Name="Raspberry", Price=110,
- TinyDescription="Dark chocolate overloaded",
- FullDescription ="This is one of the best ever
- soft and chewy cookie and also been awarded as the
- best cookie several times.", IsCookieOfTheDay=false,
- ImageUrl ="\\Images\\1.png"},
- new Cookie{ Id=2, Name="Nuts Overloaded",
- Price=100, TinyDescription="Truely healthy",
- FullDescription ="This cookie is fully loaded
- with nuts of various types and contains
- nice amount of peanut butter.", IsCookieOfTheDay=true,
- ImageUrl ="\\Images\\2.png"},
- new Cookie{Id=3, Name="Chocolate",
- Price=70,TinyDescription="Amazingly fruity",
- FullDescription ="This cookie is best suited
- for the chocolate lovers. It's less sweet and gives
- very elegant taste.", IsCookieOfTheDay=false,
- ImageUrl ="\\Images\\3.png"},
- new Cookie{Id=4, Name="Delicious Oatmeal",Price=50,
- TinyDescription="Truely healthy",
- FullDescription ="This is one of the most moist and
- flavourful cookie, which can make anyone's mood happy.",
- IsCookieOfTheDay=false,
- ImageUrl ="\\Images\\4.png"},
- };
- }
-
- public Cookie GetCookie(int id)
- {
- return _cookies.FirstOrDefault(x => x.Id == id);
- }
-
- public IEnumerable<cookie> GetCookies()
- {
- return _cookies;
- }
- }
Let’s go and register this IRepository with dependency injection container inside ConfigureServicesmethod as shown below:
services.AddTransient<irepository, mockrepository="">(); // get me new instance every time
Adding Controller
Let’s create a new folder named Controllers under project and add an empty controller with the name Home inside it. Briefly, controller is responsible for creating a response based on user’s request with the help of a model by calling a method. Such methods are generally known as action methods in terms of MVC. Below is the code to use IRepository inside controller to get the data from model,
- public class HomeController : Controller
- {
- private readonly IRepository _repository;
- public HomeController(IRepository repository)
- {
- _repository = repository;
- }
-
- public IActionResult Index()
- {
- return View();
- }
- }
Adding View
So far, we are already done with basic Model and Controller. So, the only thing pending is View. In ASP.NET Core 2.2, a View can be of two types – Regular/Plain view and Strongly-typed view. But in most of the scenarios, strongly-typed view is required. Here, we would be using Razor.
So, let’s create a new folder named Views under project along with a subfolder named as Home. Right-click on the Home folder and add new item Razor View. On successful addition of View, you will notice that a new file with name Index.cshtml is added under Home folder.
Now it’s time to verify whether view and controller are tied properly and able to communicate. For this verification purpose, let’s add some title to the page and we will pass the value of this title from controller class using ViewBag. Similarly, we will also show some information about the cookies. Below is the updated method of HomeController,
- public IActionResult Index()
- {
- ViewBag.Title = "Cookies and only Cookies";
- var cookies = _repository.GetAllCookies().OrderByDescending(x=>x.Price);
- return View(cookies);
- }
Next is to update the view to read this title value. Here is the code for this,
Now, if you will run the application, you would be able to see the below output,
Improvising on Including HTML Markup
In the above snippet, we have seen that the complete HTML markup is written to display a page. Now what if we have lots of views? Are we going to repeat this HTML code for each and every page?
Certainly not. Here comes the templating. We can create a template and refer to that template in all the views. Doing this will also reduce the amount of code in individual views. It’s obvious that, if something is to be shared among many components, then it has to be kept in a shared location and that’s where shared folder is introduced as part of the MVC design.
Introducing Layout Template
Add a new folder under Views folder and name it as Shared. Right click on the Shared folder and add a new item Razor Layout. Default name of this newly added item is _Layout.cshtml and it looks like below,
If you will look closely, you may notice that _Layout.cshtml and our view contains most of the common code. So, it’s time to integrate _Layout.cshtml into our view and after integration, our view will contain the below code only,
Refining the View Even Further Using _ViewStart
We did a really good job by reducing the line of codes in our view file, but there is still a need to do more. Again, the layout, which we integrated in our view is going to be duplicated for each and every view. Can we get rid of this duplication also?
Of course, Yes. Let’s add another new item Razor View Start under Views folder with a default name as _ViewStart.cshtml. This file comes with the default code in it as shown below and is invoked automatically,
Now you can see that this file has already plugged Layout for us, which means the line of code in our view is further reduced to just a few lines as shown below,
You can re-run your application and verify that it is working as expected.
Introducing ViewModel
You must have noticed that our view is getting data from multiple paths. So, why can’t we get rid of this and create a single entity which would be the source for our view. Let’s try this.
We will add a new folder named ViewModels under project with a new class named HomeViewModel under it. This ViewModel class will be the data source for our Home view. As of now, we will take the bare minimum fields as shown below,
- public class HomeViewModel
- {
- public string Title { get; set; }
- public List<cookie> Cookies { get; set; }
- }
Accordingly, we must update our Index method inside HomeController as shown below,
- public IActionResult Index()
- {
- HomeViewModel viewModel = new HomeViewModel
- {
- Title = "Cookies and only Cookies",
- Cookies = _repository.GetAllCookies().OrderByDescending(x => x.Price).ToList()
- };
-
- return View(viewModel);
- }
Lastly, let’s update the View with reference of HomeViewModel as shown below,
Re-verify the output, it should still be the same.
Improving Look and Feel of the View using Bootstrap
This can be done using a client-side package known as Bootstrap. Depending on the version and updates of Visual Studio, one can choose any of the package manager like Bower, Library Manager (LibMan), etc. Here, I’ll go with the Library Manager as my Visual Studio version is 15.8.5. Let’s go and add new item ‘Add Client-Side Library’ at project level and furnish the details as shown below,
Next is to add images to our application. For that, we have to create another folder named Images under wwwroot.
Then we have to add a style sheet to our app and to do that, a new folder named content has to be added under wwwroot and inside that, we have to create a css named site.css. Once done, we can add the below basic style,
- body {
- padding-top:50px;
- padding-bottom:20px;
- background-image: url();
- background-repeat: repeat;
- }
-
- .body-content{
- padding-left: 15px;
- padding-right: 15px;
- }
Finally, we have to associate css and bootstrap to our _Layout page using <Link> as shown below,
Next is to update view file to accommodate bootstrap. Below is the complete code,
If everything was done well, you will see the below web page on running the application.
Next is to associate the real database with our application.
Getting Real Data using EF Core
EF core is an ORM which supports cross-platform capabilities like ASP.NET Core. It’s worth mentioning that as of now, EF Core only supports Code-First approach. Below are the steps which need to be taken care to fully integrate the EF with our application,
- Create Entity Classes
- Create Database Context
- Setup Connection string
- Changes to Application Configuration
Let’s create a new class named DatabaseContext under Models folder and place the below code in it,
- public class DatabaseContext:DbContext
- {
- public DatabaseContext(DbContextOptions<databasecontext> options):base(options)
- {
-
- }
- public DbSet<cookie> Cookies { get; set; }
- }
DataContext plays a vital role of connecting our application to the actual database. So far, we were using mocked values using MockRepository class. So, it’s time to create an actual class named Repository under Models folder having the below code,
- public class Repository:IRepository
- {
- private readonly DatabaseContext _dbContext;
- public Repository(DatabaseContext databaseContext)
- {
- _dbContext = databaseContext;
- }
-
- public Cookie GetCookie(int id)
- {
- return _dbContext.Cookies.FirstOrDefault(x => x.Id == id);
- }
-
- public IEnumerable<cookie> GetCookies()
- {
- return _dbContext.Cookies;
- }
- }
Next is to set up the connection string. I hope most of you are aware that ASP.NET Core no longer uses Web.Configfile, rather it uses appsettings.json. So, add a new item ‘App Settings File’ named appsettings.json and update the connection string in it. My code looks something like this,
- {
- "ConnectionStrings": {
- "DefaultConnection":
- "Server=(localdb)\\MSSQLLocalDB;Database=CookiesDataStore;
- Trusted_Connection=True;MultipleActiveResultSets=true"
- }
- }
As a final step, we have to register EF core with our application and that can be done by updating ConfigureServices() in Startup class as shown below,
- public class Startup
- {
- public IConfiguration Configuration { get; set; }
-
- public Startup(IConfiguration configuration)
- {
- Configuration = configuration;
- }
-
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddDbContext<DatabaseContext>(options => options.UseSqlServer
- (Configuration.GetConnectionString("DefaultConnection")));
- services.AddTransient<IRepository, Repository>();
- services.AddMvc();
- }
-
-
-
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- app.UseDeveloperExceptionPage();
- app.UseStatusCodePages();
- app.UseStaticFiles();
- app.UseMvcWithDefaultRoute();
- }
- }
It’s time to build the code and verify that there are no more compilation errors.
Creating a Database
In order to view something on a web page, we need data in our database. Here, we will use Package Manager console with the below command:
PM> add-migration CookiesDatabaseMigration
PM> update-database
To add some initial data to database, we will create a new class called DbInitializer under Models folder with the below code,
- public static class DbInitializer
- {
- public static void Seed(DatabaseContext dbContext)
- {
- if (!dbContext.Cookies.Any())
- {
- dbContext.AddRange(
- new Cookie
- {
- Name = "Choco Chips",
- Price = 80,
- TinyDescription = "Dark chocolate overloaded",
- FullDescription = "This is one of the most moist
- and flavourful cookie,
- which can make anyone's mood happy.",
- IsCookieOfTheDay = false,
- ImageUrl = "\\Images\\Chocochip.png"
- },
- new Cookie
- {
- Name = "Nuts & Peanuts",
- Price = 75,
- TinyDescription = "Truely healthy",
- FullDescription = "This cookie is fully loaded
- with nuts of various types and
- contain nice amount of peanut butter.",
- IsCookieOfTheDay = true,
- ImageUrl = "\\Images\\ChocolateChipWalnut.png"
- },
- new Cookie
- {
- Name = "Berries & Rasins",
- Price = 50,
- TinyDescription = "Amazingly fruity",
- FullDescription = "This is one of the best ever soft
- and chewy cookie and also been awarded
- as the best cookie several times.",
- IsCookieOfTheDay = false,
- ImageUrl = "\\Images\\Nuts.png"
- },
- new Cookie
- {
- Name = "Coconut",
- Price = 100,
- TinyDescription = "Truely healthy",
- FullDescription = "This cookie is best suited
- for the nut lovers. It's less sweet and
- gives very elegant taste.",
- IsCookieOfTheDay = false,
- ImageUrl = "\\Images\\Coconut.png"
- }
- );
- }
- dbContext.SaveChanges();
- }
- }
Next is to call this DbInitializer from Program class and here is the updated code for the same,
- public static void Main(string[] args)
- {
- var host = CreateWebHostBuilder(args).Build();
- using (var scope = host.Services.CreateScope())
- {
- var services = scope.ServiceProvider;
- try
- {
- var context = services.GetRequiredService<databasecontext>();
- DbInitializer.Seed(context);
- }
- catch (Exception ex)
- {
-
- }
- }
-
- host.Run();
- }
Now quickly run the application and you will see the same output as before.
Adding Navigation
As part of navigation, we will create a details page which will hold the details of the selected cookie. To get the details of a selected cookie, a new method has to be added in HomeController class as shown below,
- public IActionResult Details(int id)
- {
- var cookie = _repository.GetCookie(id);
- return View(cookie);
- }
And of course, we have to add the view for this method with the name Details.cshtml under HomeController,
Now, we want to navigate to Details view from Index view. So, we will use tag helpers as shown below,
One last thing is to add navigation on our front page and that can be done using nav element. Below is the updated code of _Layout.cshtml,
Now quickly run the application and you will be able to see the links on top of the page as,
Adding Form to Request Comments
Now, let’s add a form by which the user can give feedback or comments for our lovely cookies. In order to do that, we have to add a new model entity named Feedback as shown below,
- public class Feedback
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Content { get; set; }
- public string Email { get; set; }
- }
Next is to update database with this new entity and that can be done by running add-migration command as
PM> Add-Migration Feedback
Let’s quickly add the interface and a method in that to save feedback and code for that is as shown below,
- public class FeedbackRepository: IFeedbackRepository
- {
- private readonly DatabaseContext _dbContext;
- public FeedbackRepository(DatabaseContext context)
- {
- _dbContext = context;
- }
- public void AddFeedback(Feedback feedback)
- {
- _dbContext.Feedbacks.Add(feedback);
- _dbContext.SaveChanges();
- }
- }
Next is to register this new interface with dependency injection container under ConfigureServices method,
- public void ConfigureServices(IServiceCollection services)
- {
- ...
- services.AddTransient<IFeedbackRepository, FeedbackRepository>();
- services.AddMvc();
- }
Now it’s time to make UI changes for feedback functionality. Below is the code for FeedbackController and its view,
- public class FeedbackController : Controller
- {
- private readonly IFeedbackRepository _feedbackRepository;
- public FeedbackController(IFeedbackRepository feedbackRepository)
- {
- _feedbackRepository = feedbackRepository;
- }
-
- public IActionResult Index()
- {
- return View();
- }
-
- [HttpPost]
- public IActionResult Index(Feedback feedback)
- {
- _feedbackRepository.AddFeedback(feedback);
- return View();
- }
- }
Next is to add the Feedback link on our application front page and that can be done by updating Layout page as shown below,
On click of Feedback, navigation will occur on the below page,
Securing Application Configuring Identity
As part of this section, we will explore the ASP.NET Core identity API and to accommodate this, we have to update a bit of our existing code. Let’s start with DatabaseContext class. Now, instead of inheriting with DbContext, we will inherit DatabaseContext class with IdentityDbContext<IdentityUser> and at the same time, we have to update our middleware pipeline by adding app.UseAuthentication().
Next, we have to update our database with user information. So, we have to run add and update migration as shown below:
PM> add-migration AuthenticationAdded
PM> update-database
On successful execution, you will see that the below tables are created in the database,
Adding Authentication Support
To add authentication capabilities to our application, we will use out of box functionality provided by the Razor Class Library which comes with ASP.NET Core. To do this, right click on the project and select Add and then New Scaffolded Item…, select Identity.
After the above action, we will get the below dialog which mentions all the views that are readily available for us to use. Here, I will be choosing three views with required data context as shown below,
- public class IdentityHostingStartup : IHostingStartup
- {
- public void Configure(IWebHostBuilder builder)
- {
- builder.ConfigureServices((context, services) => {
- services.AddDefaultIdentity<identityuser>(IdentityUser)
- .AddEntityFrameworkStores<databasecontext>(DatabaseContext);
- });
- }
- }
By the above code, we are using our database context to save identity information. Finally, we have to provide a link on our navigation bar, so just plug the login stuff to _Layout page by going with <Partial> as shown below,
Now run the application and you will see two additional links added to the navigation bar. Of course, you can go ahead and play with Register and Login functionality now. Isn’t it cool?
Adding Authorization Support
Up to now, we have provided login functionality but what about restricting any user from using some functionality of a website, for example, only the logged in users should be allowed to provide feedback. Here comes in the concept of Authorization. Let’s quickly do it by decorating our Feedback controller with Authorize attribute as shown below,
- [Authorize]
- public class FeedbackController : Controller
- {
- ……
- }
We are done and it’s time to run the application and verify all the things.
Hope you enjoyed creating your own ASP.NET Core application with all the basic concepts covered.
References