Download Attachments in Single Page App and Asp.Net Core

Introduction

If you have a SPA built with any JavaScript framework and it has an attachment feature, you must hit the part where you need to allow the user to download an attachment and the App is authenticating users using Tokens.

The problem

With normal forms authentication based on cookies the browser will simply send the authentication cookie with each request to your web server without you doing anything. If you have a link that will allow the user to download a file from your server, the browse will automatically send the authentication cookie when the user clicks the link. This makes it so easy for you. But if you are using token based authentication, it is your responsibility to send a token for each request sent to the server via Ajax by using the Authorization header.

Unfortunately, you cannot control the headers sent to the server if the user is opening a link in a new browser window and the user will end up with unauthorized request.

The solution

Download the file using Ajax Request

In this solution, you have to request the endpoint that downloads the file using Ajax Request which will include the authorization header and then get all the file content in a variable and push the content to the user. This works fine if the file size is very small. But imagine the case when you are downloading a 500MB file. This is not going to work since the file is stored in a JavaScript variable before the download takes place.

Make the API that download the attachment anonymous

If the endpoint that downloads the file doesn’t require authentication then we are good. But now the file will be available for every one to download. So, we have to find a way to secure the file even when the endpoint is anonymous.

If you have some experience with Azure Storage, you may have heard of Azure Storage Shared Access Signature. The idea is simple. When the user requests a file, generate a token, save it to a temporary storage  and append it to the URL of the download file endpoint. When the user clicks the link, the endpoint will be called and the token will be validated against the temporary storage and if it matches then send the file contents. This way we will be sure that the link was generated by the application to that user. Still, if the link was shared to another user, he will be able to download the file. But this is another issue that we can worry about later.

Implementation

We will create a new asp.net core site with an endpoint to download files but I will not create a SPA in this article. That will be left for the reader. I will test the idea though using Postman.

Open Visual Studio, Create a new project of type “Asp.NET Core Web Application” then Choose “API” in the next dialog. You can still choose “Web Application (Model-View-Controller)”. I will leave authentication to the default “No Authentication”.

Right Click on the Controllers folder and choose “New Controller”, choose “API Controlller – Empty” and name it AttachmentsController. You should end up with the following

[Produces("application/json")]
[Route("api/Attachments")]
 //[Authorize]
public class AttachmentsController : Controller
{
}

Notice that I have commented the [Authorize] attribute since I didn’t setup authentication in this demo. In real life scenario, you will setup authentication and authorization using Token based Authentication.

Create a folder named Services and then create a new interface called ISecureUrlGenerator. The content should look like the following:

   public interface ISecureUrlGenerator
    {
        string GenerateSecureAttachmentUrl(string id, string url);
        bool ValidateUrl(string url, string id);
        bool ValidateToken(string id, string token);
    }

Now, add class to implement the previous interface

using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;

namespace SecureAttachmentsDownload.Services
{
    public class SecureUrlGenerator : ISecureUrlGenerator
    {
        private readonly IMemoryCache memoryCache;

        public SecureUrlGenerator(IMemoryCache memoryCache)
        {
            this.memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache));
        }

        public string GenerateSecureAttachmentUrl(string id, string url)
        {
            var token = Guid.NewGuid().ToString().ToLower();
            StoreToken(id, token);
            var separator = url.Contains("?") ? "&" : "?";
            return $"{url}{separator}token={token}";
        }

        public bool ValidateToken(string id, string token)
        {
            var tokens = memoryCache.Get(id);
            if (tokens != null && tokens.Contains(token))
                return true;

            return false;
        }

        public bool ValidateUrl(string url, string id)
        {
            var uri = new Uri(url);
            var queryStringParams = uri.Query.Split("&");
            foreach (var param in queryStringParams)
            {
                var values = param.Split("=");
                if (values[0].ToLower() == "token")
                {
                    return ValidateToken(id, values[1]);
                }
            }

            return false;
        }

        private bool IsTokenValid(string id, string token)
        {
            var tokens = memoryCache.Get(id);
            if (tokens != null && tokens.Contains(token))
                return true;

            return false;
        }

        private void StoreToken(string id, string token)
        {
            var tokens = memoryCache.Get(id);
            if (tokens == null)
                tokens = new List();
            tokens.Add(token);

            memoryCache.Set(id, tokens);
        }
    }
}

In this implementation, I am storing the tokens in asp.net core memory cache. To enable this feature, you have to add the caching service in Starup.cs file

public void ConfigureServices(IServiceCollection services)
        {
            services.AddMemoryCache();
            services.AddMvc();
        }

You can replace the memory cache with a database if you want the tokens to be permanent and in this case you have to add an expiration date.

Before we utilize the secure URL genrator, we need a class to hold the attachments metadata since the user will request the list of attachments first and then download it.
Create a folder called Models and put the following class in it.

namespace SecureAttachmentsDownload.Models
{
    public class AttachmentMetadata
    {
        public int Id { get; set; }

        public string DownloadUrl { get; set; }

        public string Name { get; set; }

        public int FileSize { get; set; }
    }
}

Now, lets get to the part where we utilise our secure URL generator.
The flow will be as below:

  1. The user requests endpoint to return a list of attachments to be displayed to the user. Here, the DownloadUrl will have the token already. This will be secured by tokens
  2. The SPA will display this list to the user as links or buttons that the user can click to download the file. The href for the anchor tag will be the DownloadURl property
  3. The user will click the link to download the attachment
  4. The AttachmentController will be called and the endpoint will validate the token and return the file or else a 401

Open the AttachmentsController file and add the following 2 action methods

  using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using SecureAttachmentsDownload.Models;
using SecureAttachmentsDownload.Services;

namespace SecureAttachmentsDownload.Controllers
{
    [Produces("application/json")]
    [Route("api/Attachments")]
    //[Authorize]
    public class AttachmentsController : Controller
    {
        private readonly ISecureUrlGenerator _secureUrlGenerator;
        private readonly IHostingEnvironment _hostingEnvironment;

        private readonly List Attachments = new List()
            {
                new AttachmentMetadata
                {
                    Id = 1,
                    Name = "bitcoin.pdf",
                    ContentType = "application/pdf",
                    FileSize = 1024
                },
                  new AttachmentMetadata
                {
                    Id = 2,
                    Name = "report 1.pdf",
                    FileSize = 3024
                },
                  new AttachmentMetadata
                {
                    Id = 3,
                    Name = "report 2.pdf",
                    FileSize = 2024
                }
            };

        public AttachmentsController(ISecureUrlGenerator secureUrlGenerator, IHostingEnvironment hostingEnvironment)
        {
            _secureUrlGenerator = secureUrlGenerator;
            _hostingEnvironment = hostingEnvironment;
        }

        [HttpGet]
        [Route("")]
        public IActionResult Get()
        {
            foreach (var attachment in Attachments)
            {
                var url = Url.Action(nameof(AttachmentsController.Get), "Attachments", new { attachment.Id }, Url.ActionContext.HttpContext.Request.Scheme);
                attachment.DownloadUrl = _secureUrlGenerator.GenerateSecureAttachmentUrl(attachment.Id.ToString(), url);
            }

            return Ok(Attachments);
        }

        [HttpGet]
        [Route("{id}")]
        [AllowAnonymous]
        public IActionResult Get(int id, string token)
        {
            if (!_secureUrlGenerator.ValidateToken(id.ToString(), token))
                return Forbid();

            var attachment = Attachments.FirstOrDefault(a => a.Id == id);
            if (attachment == null)
                return NotFound();

            var stream = new FileStream($"{_hostingEnvironment.WebRootPath}\\Files\\{attachment.Name}", FileMode.Open);

            return File(stream, attachment.ContentType);
        }
    }
}

Now run the application and open the URL /api/Attachments. You will get the following excepttion:

InvalidOperationException: Unable to resolve service for type ‘SecureAttachmentsDownload.Services.ISecureUrlGenerator’ while attempting to activate ‘SecureAttachmentsDownload.Controllers.AttachmentsController’.

To fix it, open the startup.cs file and add the following line to the ConfigureServices method

  public void ConfigureServices(IServiceCollection services)
        {
            services.AddMemoryCache();
            services.AddScoped ();
            services.AddMvc();
        }

Now open the URL api/attachments again and you should see the following JSON response

[
{
"id": 1,
"downloadUrl": "http://localhost:53098/api/Attachments/1?token=b78763c2-0109-4c12-b771-5f5cc5d19017",
"name": "bitcoin.pdf",
"fileSize": 1024,
"contentType": "application/pdf"
},
{
"id": 2,
"downloadUrl": "http://localhost:53098/api/Attachments/2?token=12497a4a-8f08-44ba-b9f6-914c4b484cc5",
"name": "report 1.pdf",
"fileSize": 3024,
"contentType": null
},
{
"id": 3,
"downloadUrl": "http://localhost:53098/api/Attachments/3?token=8647bb52-e47f-4580-8149-0b1d238ab0e2",
"name": "report 2.pdf",
"fileSize": 2024,
"contentType": null
}
]

As you can see, the downloadUrl property has the absolute URL for the file and the `token` query string parameter is appended. If you open the first link in a new browser window, the Action Get(id) will be called and the token will be bound to the parameter token.
In my implementation, I have put some files in a folder called Files under the wwwroot folder. But in actual projects, you may retrieve the files from a Database, FTP or any Document Management System.

If you want to make sure that it is really working, just try to change any character in the token query string and you should get a forbid response from the server. In this example you will get an exception: InvalidOperationException: No authenticationScheme was specified, and there was no DefaultForbidScheme found.
This is because I didn’t configure the authentication middleware.

You can find the source code for this article on GitHub.

This implementation has as flaw. The list of attachments are returned with the download URL and the tokens are saved in memory. If the user didn’t click the link but after sometime, the tokens may have been already expired. So, either you save the tokens in a DB or you before clicking the link, fire an Ajax request to an endpoint that gets the metadata for a single attachment. This way, the downloadUrl will be always fresh and working.

If you have any questions or suggestions, please leave a comment below.

Advertisements

Web API with windows authentication on asp.net Core 2

Most REST services that are being built using asp.net core now are using token based authentication either using asp.net core authentication middleware or third party products such as Identity Server. But, sometimes you only need to build your APIs for intrenal use within your organization who happens to be using Windows Authentication.

In this point, I will explain how to build a web API that utilizes AD for authentication and AD groups for authorization and how to integrate it with authorization policies.

Creating the project

Open Visual Studio 2017, Create new asp.net core Web Application and name it AspnetCoreWindowsAuth, then press Ok. Choose Web API as a project Template and Change the authentication method to Windows then press Ok to create the project.

If you select the project in the solution explorer and press F4, you will find nothing to set the authentication mode to Windows and enable/disable anonmous access just like you used to do in normal MVC web application. This is because it is moved to the launchsettings.json file under the properties folder. If you want to change it, you have to open the file and edit the value of the json property iisSettings which looks like below:

IIS SettingsYou can also modify the URL and SSL settings.

Now, if you run the project, it will run just fine and you can call the default Values controller and see the output and even windows authentication will be working as well and you can get the name of the logged in user using the User.Identity.Name property and it will return the Domain\\username although we didn’t add any authentication code yet in the pipeline

Add windows authentication middleware

Now, lets add the authentication middleware into the request processing pipeline. Add the line  app.UseAuthentication(); in the Configure method just before the  app.UseMvc(); . Remeber that the middlewares run in the same order they were added in the Configure method.

Add the following code in the ConfigureServices method before the services.AddMvc();

services.Configure(options =>
{
options.AutomaticAuthentication = true;
});

services.AddAuthentication(IISDefaults.AuthenticationScheme);

To make sure this is working fine, you can edit the Authorize attribute on the ValuesController and add the role name which should be an AD group name, ex: Employees

[Authorize(Roles ="Employees")]

Now you have asp.net core working fine with Active Directory and you can can authenticate the users according to the AD groups they belong to.

Using Authorization Policies

If you need more fine grained control over your controllers and you need to add more authorizastion logc, then you can go for authorization policies and it is really easy to configure as you can see below. Just add the following lines in the ConfigureServices method before the AddMvc statement

services.AddAuthorization(options =>
{
options.AddPolicy("OnlyEmployees", policy =>
{
policy.AddAuthenticationSchemes(IISDefaults.AuthenticationScheme);
policy.RequireRole(""S-1-5-4");
});
});

Here we defined a policy called OnlyEmployees and it requires the users to be windows authenticated and in the Role named Employees which is eventually mapped to AD group named employees. Notice that I didn’t write the name Employees in the RequireRole method. Instead, the value “S-15-4” was used, which is the SID for the AD Group named Employees. I found that this is how the group names are mapped to Roles in asp.net core and even if you tried to retrive the list of claims that the user have, it will translate to all SIDs of the groups that the user belongs to in AD.

To utilize this policy you have to annotate the controller or method with it as below

[Authorize(Policy = "OnlyEmployees")]
[Route("api/[controller]")]
public class ValuesController : Controller
{

}

By now you should have a working solution that depends on windows authentication and AD groups. Notice that this will only work with windows and most probably IIS.

You can find the code on GitHub if you want to use it or add to it.

https://github.com/haitham-shaddad/aspnetcore-windows-auth

MVC 5 – OWIN and Katana

OWIN was the first step towards elegant design for the whole asp.net stack. By separating the hosting concerns from the framework, Microsoft was able to build modular services and add it to the stack such as Web API, SignalR and now the whole asp.net core.

Katana was the first implementation for OWIN on IIS, you won’t feel much change as it is still running using the System.Web assembly but at least it enables you to run your middlware under IIS.

To understand more about the motivation, the moving parts and samples of OWIN, you can watch the following video in Arabic.

 

 

Security Options for Asp.Net (Arabic)

This is a series of videos to explain the available options to secure your asp.net application. Although the demonstration is done using MS Stack, it applies to any technology.

In each video, we will take an authentication type, explain its concept and show a demo on an asp.net application. we should cover the following authentication types

  1. Basic
  2. Digest
  3. Windows
  4. Certificate
  5. Forms
  6. Claims
  7. OAuth

 

Here is the link to the YouTube playlist, I will add each video once it is recorded. Feel free to leave any comments or suggestions

Tips and tricks to enhance asp.net security

There are many ways to enhance Asp.Net Web Application security, either on the web, back-end or database level, in this post, I will list some useful tips and tricks that will help you make your web application more secure.

Although the article discusses the concerns in asp.net context, it is applicable to any other framework

Web Level

Connection String

There are 2 ways to configure the security for your database connection in connection string. The first one is  windows authentication, your application connect to the database server using the Application Pool Identity, and this is the recommended way as it doesn’t include writing any User Passwords in your connection string, not to mention the need to edit web.config with the new password if it has expired, which will cause a restart in the IIS application pool.

The second way is to connect through SQL authentication, in this case, you should not have the password added as a plain text, you must secure your connection string by encrypting it, .Net already support this by running the following command

aspnet_regiis -pe “connectionStrings” -app “/MyCustomApplication”

The aspnet_regiis exe can be found in your Microsoft.net folder under C:\Windows\Microsoft.net, you should find a folder with the version of your framework, ex: v2.0.*

the -pe parameter specifies which section of your web.config you want to encrypt and the -app parameter specifies which IIS web site you want to use.

The algorithm used to encrypt is RSA, for more information about how does this work, please refer to https://msdn.microsoft.com/en-us/library/dtkwfdky.aspx?f=255&MSPPError=-2147217396

Service Accounts Privillages

The service account is used to run the application pool which in turn run the web application, and it is the account that you web application uses as identity when it needs to connect to the database if you specified Integrated Security=True or if you need to have an access to file system, for ex: Logging.

This account must have the least privilege, on the file system, only give it a read access to a specific folder if this is all what you need, on the database, don’t just get lazy and give it db_owner, only grant it the permissions it needs, if it only needs read or write on certain tables, then let it be and don’t give it access to everything.

Why is that? because simply, if someone succeeded to get to this service account, they could harm your servers, I have seen many cases where people uses a domain admin as a service account, imaging what a hacker could do with such account.

Cross Site Scripting (XSS)

Imagine you have a web application, and there is the page with the URL: http://somesite/dashboard/pay?amount=1000.

This page is requested using GET and it needs the user to be authenticated, now the user can only access if he is logged in, but think of the following scenario:

You access a site named: http://someothersite, and in this site, there is an image with a src =http://somesite/dashboard/pay?amount=1000&toAccount=123-123-123.

What will happen is that your browser will automatically try to fetch the source of the image and make a get request to the URL, and by design, the browser will automatically send the cookies for somesite domain including the authentication cookie, and the result will be the execution of the URL and pay the amount of 1000 because the request seems legitimate.

To prevent this issue, you must implement something called Anti-Forgery tokens, this simply means that with each request, the site will put a hidden field in the page and store its value in the user session or cookie, and when the user posts the page to the server, the server will validate that the value in the hidden field is equal to the value stored in the session or cookie, then it will clear the value in the session, this way, if the user tried to submit the page again or in our example, another site request the URL on behalf of the user, the server will deny the request because the request will not include the correct value for the anti-forgery token.

MVC has an excellent support for this by calling the @Html.AntiForgeryToken() in the view and annotating the action method by [ValidateAntriForgery] attribute

to read more about it, follow these links Anti-Forgery in asp.net , Anti-Forgery in web API

SQL Injection

Although this is a very old issue and nearly most of the existing ORM solutions handle it, still some people fall into this problem, the danger of this issue, is that the hacker can get to your database, your database server and from there, he can get to all your other servers, so always validate the user input, always use parameterized queries and skip the user strings if you are building a dynamic SQL.

Web.Config Encryption

Same issue as the connection string, but applies also to keys stored in app.config, you can also call the same command and encrypt the appsettings section, you don’t need to worry about the decryption cause .Net makes it automatically for you

HTTPS

If your application has sensitive data and you want to prevent any chance of the network between the user and the web server being spoofed, then enable HTTPS, it works by encrypting the traffic between the browser and the server by a public/private key using certificate.

Cookies

If your site is using cookies for authentication or maintaining client state, then make sure no sensitive data is there, also if you don’t need to access the cookie inside your client browser, then mark the cookie as Http Only, if the cookie has httponly enabled then it will not be accessible through JavaScript and the document.cookie JavaScript object will not contain this cookie.

Another trick is to mark your cookie as secure only, this will make it accessible and the client will send it to the server only if HTTPS was enabled on the web site, this will ensure that your cookie is safe

Files under virtual directory

Don’t leave any backups in your virtual directory, sometimes we make a backup from web.config in the shape of web.bak or some other format, these files may be served to the client and a hacker can use the info inside it to hack your application.

Session Hijacking

If you are using session variables, which is mainly some variables stored on server session or a state server, depending on your configuration, the web server has to has a key for your session in order to be able to retrieve the data for your session, this key is called Session ID, and by default it is stored in a cookie named ASP.NET_SessionId, if someone gets to this cookie value, they will be able to log in to the site, use any cookie editor extension and update the session ID to your ID and from there they will be logged in as you, so if your site has an edit profile page or a page that allows the user to add credit card info, this data will be stolen.

To prevent session hijacking, you have to do the following:

  1. Use SSL, this way no one will be able to capture the traffic between your users and the web application, consequently, they won’t capture the session ID cookie value
  2. Make the session ID value harder to guess, the worst thing to do is to make the session ID as incremental value, if I logged in and found my session ID as 12345, then I can easily edit it to another number and it will be easy to obtain the session for another user
  3. Set the session ID cookie to be always Http Only, this will prevent any JavaScript from reading its value and will make it harder for any XSS
  4. Regenerate the session ID, this will make it harder for the attacker, even if he got the session ID, it won’t be valid for long time
  5. Prevent concurrent sessions, if you have your user logged in from one session, and he tried to login from another session, there is a probability that he is not the same person, you can be more certain by validating the IP and time used, ex: if the user IP is from USA, and at the same time the user is trying to connect from Europe, then most probably this is an attack

Eliminate unneeded headers

Some headers are not necessary in your web page response, such as Server, Asp-Net version and so on, these headers not only add a performance headache, but make the attacker job easier as he already knows what framework, servers and environment he will be attacking, so always edit your web.config or IIS Console and remove these unneeded headers

Database Level

Data Encryption

A very simple case for encrypting the data is the user passwords, never store it in plain text, you have to use a strong algorithm to encrypt the important data in your database, but note that this will affect the performance of the application because the web application will have to do some calculations to encrypt the data before persistence and decrypt it after retrieval, also you will not be able to write queries that include any of the encrypted columns.

SQL Server 2016 has a great feature called Dynamic Data Masking, it allows you to mask the data stored in a column in some predefined formats, ex: Credit Card, Social Security Number or an email address, permissions can be granted for some users and only those users will be able to see the data unmasked.

To read more about the topic, please follow this link

IPSec

This feature blocks all incoming connections to a specific server on all ports and allows you to allow certain IP and certain port, so in this case, the database server will not allow any connections but from the web servers for your application

IPsec Configuration

SQL Server Transparent Data Encryption (TDE)

In case all the above precautions failed to protect your database, at least you can protect the data on rest, TDE allows you to encrypt the database’s data file itself so that no one can open it on another computer without a secret key, to encrypt your database file, you need a master key, certificate protected by this master key, a database encryption key which is protected by the certificate and finally set the database to use encryption.

To read more about it, following this link https://msdn.microsoft.com/en-us/library/bb934049.aspx

Building a LOB application with MVC 5 – Part 4 – Controllers, Routes and Areas

This is the fifth part of building line of business application using MVC 5, you can read the previous parts through the following links

  1. Building a LOB application with MVC 5 – Part 0
  2. Building a LOB application with MVC 5 – Part 1
  3. Building a LOB application with MVC 5 – Part 2 – Models and Generic Repository
  4. Building a LOB application with MVC 5 – Part 3 – EntityFramework

In the previous part, we introduced EntityFramework code first migration and created the generic repository using EF, then we applied the migration to create a database and ran some unit tests to make sure our Repository layer is working fine.

In this part, we will introduce Controllers, routes and areas

MVC Controllers

The “C” in MVC, if you recall some design patters, you may heard of the Front Controller pattern, this is basically the guy at the front door who receives the request, validate it, call whatever services for you and later forward you to the suitable view.

To understand how controllers work and how the URL you type in the browser is mapped to a piece of code, lets see how the MVC application life-cycle works, but take into consideration that MVC is based on the same System.Web used in Web Forms, so they share some life cycle events

  1. A request is received by the IIS Server and forwarded to an MVC handler, this is a normal HTTP handler
  2. MVC handler gets the request and invokes the routing API which we will explain later and it examines the route table to see which controller and action should handles the request
  3. The MVC handlers creates the controller using the method CreateController from the interface IControllerFactory, the default implementation for this interface is DefaultControllerFactory, it simply create a new instance from the controller class, if your controller constructor doesn’t have parameter-less constructor, this method will fail and throw and exception, later when we see dependency injection, we will see how to override the default controller factory by calling ControllerBuilder.Current.SetDefaultControllerFactory in Application_Start in Global.asax.cs
  4.  Invoke authentication filters, we will read more about filters and authentication in future posts
  5. Bind query string parameters, form values and route values to the action method parameters using the default model binder, we will see later how to build custom model binders
  6. Invoke the action and action filters
  7. Execute the results(View, Content, Empty, JSON, File) and any result filters
  8. Send the response to the user

You can read more about the MVC Application Life-Cycle here

Convention over Configuration

You can think of a controller as a way to group a module features together in one place, for ex: a CustomerController will have all the code related to Customers like Browse Services, view service details ..

By Default MVC 5 works with the convention over configuration model, it means you will not find a code that maps the exact URL to the exact controller name and action, nor you will find a config that tells how to relate the action name with the view page, this is all following a specific convention.

  1. URL mapping to controllers and actions is configured using routing template
  2. Every controller in the controllers folder will have a folder inside the Views folder with the same name without “Controller” word
  3. Every action inside  controller will have  a view inside the Views\controller folder with the action name unless you specified another view name explicitly

Now, lets create some controllers that will be used in out Appointment Manager application

  1. In the AppointmentManager.Web project, right click the Controllers folder and choose Add -> Controlleradd controller.PNG
  2. You have 3 options, either an empty controller which is self explanatory , with read/write actions which is created with some get, post, delete and put methods, and the last one is used when you have an entity framework context inside your web project which is not recommended, this option created a CRUD controller for the selected entity, ex: if you selected our DB Context and choose the entity as Appointment, then it will generate CRUD for the appointment class, in our case, we will choose Empty
  3. Enter the name as “Customers” and Click Add, this will create an empty controller with a single action called Index

If you have a look at the Index action, you will find it has some few properties

 public ActionResult Index()
        {
            return View();
        }
  1. The method is public
  2. The return type is ActionResult or anything that inherits from IActionResult
  3. It used a helper method called View to return an object that inhers from ActionResult , there are many other help functions like File(), Json(), Empty()

By default the action has no view, and if you run the application now and navigated to http://localhost:16106/Customers/, you will have the following screen, your application may be running on a different port

view not found.PNG

This is the convention over configuration part, it tried to find a view with the name Index which is the action name in the path View/Customers and Views/Shared, and since we didn’t specify a view name in the return View() method, then it fails.

To create a view, you can either go to related view folder for your controller and create a view with the same name as the action using Right Click, Add new item, and Select view.

or you can just right click in the action method and choose “Add View”

Now, Lets create the needed controllers along with its actions and views

  1. Customers
    1. Services
    2. Providers
    3. ProviderDetails
    4. BookAppointment
    5. Calendar
  2. ServiceProviders
    1. Calendar
    2. AppointmentDetails
    3. ManageServices
    4. AvailableTime

Create empty view for each action, after you are done, your views folder should be the same as below, you can of course change the name as you like, and you can now navigate to each path and make sure it opens without errors, the path should be controller name/action name, ex: Customers/Calendar

views.PNG

Routes

When we opened the URL http://localhost:16106/Customers, it automatically knew that URL should be handled by the Customes controller, the Index method, this is configured in the file App_Start\RouteConfig.cs

This is the content of the file:

 public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }

the most important line is the one that defines the “Default” route, you can find the url property that has the value “{controller}/{action}/{id}”, and the line below it, it specifies default values from these placeholders, in this case if the URL was http://localhost:16106/ without anything else, this means the controller will be HomeController, and if you specify  the controller but no action, like the case in http://localhost:16106/Customers, then, the action will have the default value “Index” which why the Index action is called when we didn’t specify it.

You can add as many routes as you want, but make sure to make the most specific at the top, think of it like exception handing, if you put a try and catch and inside the catch you put the root Exception class, then any other catches will not be called as the Exception class will map to all exceptions.

ex:

 routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

            routes.MapRoute(
               name: "Calendar",
               url: "Calendar",
               defaults: new { controller = "Customers", action = "Calendar", id = UrlParameter.Optional }
           );

In this case, the calendar route will never get a chance to fire, because the path: /calendar will be alreayd cought by the default route and the controller will be mapped to Calendar and you will find an exception that it can’t find any controller with the name Calendar, so you have to put the calendar route before the default route

There is also Attribute based routeing, where you simply put an attriibute before the action name or controller name like the below

 [Route(template:"Customers/BrowseServices")]
        public ActionResult Services()
        {
            return View();
        }

You can read more about attribute based route here on asp.net site 

Areas

You noticed that we didn’t add a controller for the admin side, but in the admin case, we will have many controllers, ex: Services, ServiceCategories, Users …

It will be like a sub project, how can we isolate it from the rest of the project?

The answer is MVC Areas, it helps us to isolate business domains in a separate folder that can be handled by separate teams, in an eCommerce scenario, we would have an area for Shopping cart, another for inventory management, and another one for shipping

To create an area, right click the project itself and choose Add => Area and type Admin as the area name and click Add, you will have a new folder called Areas and inside it you will have the folder Admin and just inside this folder you will have the same project structure

area

create the following controllers inside the Admin area, to do that, follow the same instructions that we used to create the customers  and service providers controllers but this time select the controllers folder inside the admin area

  1. Services
  2. ServiceCategories
  3. Users
  4. Reports

 

So far, we created the needed controllers, routes, and views, in the next post, we will start working on the views and add some UI that makes the application life.

 

You can get the full source code from GitHub Repository https://github.com/haitham-shaddad/AppointmentManager

Building a LOB application with MVC 5 – Part 3 – EntityFramework

This is the fourth part of building line of business application using MVC 5, you can read the previous parts through the following links

  1. Building a LOB application with MVC 5 – Part 0
  2. Building a LOB application with MVC 5 – Part 1
  3. Building a LOB application with MVC 5 – Part 2 – Models and Generic Repository

In the previous part, we created all the model objects, a Generic repository interface and a generic service layer interface with its implementation.

In this part, we will build the generic repository implementation using EntityFramework 6, and before we proceed, I will introduce EntityFramework first.

What is EntityFramework?

EntityFramework is Microsoft’s Object Relational Mapper (ORM) framework, it is used to map objects to database tables and vice versa, it uses mapping file or metadata to generate a dynamic SQL at run time.

This is a different model than the one we used to do, which basically depend on Stored Procedures and code generation tools to save time, there is a debate regarding performance of dynamic SQL against compiled stored procedures, but in my point of view, this is not a major concern now and you can utilize caching to overcome the difference, besides, with EntityFramework you can still utilize Stored Procedures for important operations that may need special performance needs.

EntityFramework Usage Models

EntityFramework can be used in 3 ways

  1. Database First: Start with the database, and use it to generate code, this model should not be used for new projects, it is not flexible as you can’t edit the generated code directly but through a partial classes and all the mapping exist in a single file which make it hard to share between team members if more than one wants to apply changes at the same time.
  2. Model First: Start with a model and design the entities, attributes and its relationships, use this model to generate both the classes and database, once this happened for the first time, it will be the same as Database First
  3. Code First: Start with a pure classes and then use something called Migration to generate the database, this model is the recommended way to go and EF 7 has no database first model, it is all about Code First now, with Code First, you can have snapshots of your code and database, and you can rollback at any point to any previous version.

Build the Generic Repository

To start generating our generic Repository implementation, we will add the EntityFramework package to the AppointmentManager.Repository.EntityFramework project as this will be our repository project.

In order to do that, we will use NuGet which is a package manager for .Net, we use it to install, uninstall and update packages instead of referencing DLL files directly from a shared folder, the package can be a simple DLL or code file, JavaScript, special configuration or entries in web.config

To install the package, Click Tools -> NuGet Package Manager -> Package Manager Console

Make sure “AppointmentManager.Repository.EntityFramework” is selected in the default project drop down list, then type the following command and press enter

install-package EntityFramework
NuGet EntityFramework

In the project  “AppointmentManager.Repository.EntityFramework”, do the following:

  1. Add Reference to “AppointmentManager.Repository”  and “AppointmentManager.Models”
  2. Add a new class named EFRepository
public class EFRepository<T, K> : IRepository<T, K> where T : class
    {
        private DbContext _context;
        private DbSet<T> _entitySet;

        public EFRepository(DbContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            this._context = context;
            _entitySet = _context.Set<T>();
        }

        public T Add(T item)
        {
            _entitySet.Add(item);
            _context.SaveChanges();
            return item;
        }

        public bool Delete(T item)
        {
            _entitySet.Attach(item);
            _entitySet.Remove(item);
            _context.SaveChanges();
            return true;
        }

        public bool DeleteByID(K id)
        {
            var item = _entitySet.Find(id);
            _entitySet.Remove(item);
            _context.SaveChanges();
            return true;
        }

        public T GetByID(K id)
        {
            return _entitySet.Find(id);
        }

        public IQueryable<T> GetAll()
        {
            return _entitySet;
        }

        public IQueryable<T> GetAll(Expression<Func<T, K>> orderBy)
        {
            return _entitySet.OrderBy(orderBy);
        }

        public IQueryable<T> GetAll(int pageIndex, int pageSize)
        {
            return _entitySet.Skip((pageIndex - 1) * pageSize).Take(pageSize);
        }

        public IQueryable<T> GetAll(int pageIndex, int pageSize, Expression<Func<T, K>> orderBy)
        {
            return _entitySet.Skip((pageIndex - 1) * pageSize).Take(pageSize).OrderBy(orderBy);
        }

        public IQueryable<T> Find(Expression<Func<T, bool>> predicate)
        {
            return _entitySet.Where(predicate);
        }

        public IQueryable<T> Find(Expression<Func<T, bool>> predicate, int pageIndex, int pageSize, Expression<Func<T, K>> orderBy)
        {
            return _entitySet.Where(predicate).Skip((pageIndex - 1) * pageSize).Take(pageSize).OrderBy(orderBy);
        }

        public bool Update(T item)
        {
            _entitySet.Attach(item);
            _context.SaveChanges();
            return true;
        }
    }
 

Build the project and make sure it compiles successfully

Add a new class called AppointmentManagerContext, this class is needed only for the migration to work, we will not be using it as the EFRepository class is using the base DbContext class, you can still change it and add properties of type DbSet<T> for each entity in the application, this will be helpful specially if you want to do some joins between different entities.

 public class AppointmentManagerContext : DbContext
    {
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<User>()
             .Map<Customer>(m =>
             {
                 m.Requires("UserType").HasValue((int)UserRole.Customer);
                 m.ToTable("Users");
             }
             ).Map<ServiceProvider>(m =>
               {
                   m.Requires("UserType").HasValue((int)UserRole.ServiceProvider);
                   m.ToTable("Users");
               }
               )
               .ToTable("Users");

            modelBuilder.Entity<ServiceProviderReview>().HasRequired<Appointment>(a => a.Appointment);

            base.OnModelCreating(modelBuilder);
        }
    }

After that, we need to generate our database using Code First Migration, open the Package manager console and type the following command, make sure the project AppointmentManager.Repository.EntityFramework is selected

Enable-Migrations

You may encounter an issue regarding the TimeSlot class that it has no key defined, just add an integer property called ID to this class and rerun the command again
Then run the following command to add the first migration which will compare the model classes to the database and add code to generate the database, later when we add new classes or properties, we will run the command again which will add another migration that will generate code to add the new properties to the database.

Add-Migration -Name Initial

After running the command, you will notice that a new class named Initial_[time].cs, where [time] is the date time for the time the file was generated

migration.PNG

Before we proceed, I would like to explain the content for the method OnModelCreating:

Since we have all classes inheriting from the BusinessEntity class, and we have the Customer and ServiceProvider classes inherit from the User class, we must tell EntityFramework how it will generate tables for these classes.

By default, EntityFramework will generate a new class for each entity with its properties including these properties of the parent class which is fine in the BusinessEntity class as we want all common properties like ID and CreationDate to exist in all tables.

But for the Customer and ServiceProvider classes, we need a table per hierarchy, both classes must be mapped to a single table called Users, to do that, we added the code at line 5 where we tell EF to map the User class to another 2 sub-classes and for each one we told it to use the same table name “Users”, this way EF will combine the properties of the 3 classes and add it to one table.

There are another 2 types of inheritance, table per type (TPT), in our case, it would have been 3 tables (User, ServiceProvider and Customer), The third option is table per class (TPC) which will generate only 2 tables (ServiceProvider and Cutomer) and each table will have the properties in the parent User class.

So far, we still don’t have our database created, and to do that, we will have to run this command in the package manager console

Update-Database

After you do, you will have the following error

Introducing FOREIGN KEY constraint 'FK_dbo.Appointments_dbo.Users_ServiceProviderID' on table 'Appointments' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.

This is the time we will have to change the generated migration Initial file manually to override the cascade action.

Open the intiial file, in line 70 and line 148, change cacadeDelete to false and run the update-database command again

Now we have our backend ready, and the database is created, but we didn’t specify any connection strings, so where has been the database created? by default there is a connection string named DefaultConnection and it reference the localdb database, so open management studio and connect to server (localdb)\mssqllocaldb

you should find the database created with the name of the context class, if you want to override that, you can add a connection string named Defaultconnection in the project configuration file with the database name and server you want and then run the update-database command again.

db.PNG

 

Lets make sure now our backend is working fine, create a new unit test project and name it “AppointmentManager.Repository.EntityFramework.Test”

Add reference to the following projects:

  1. AppointmentManager.Repository.EntityFramework
  2. AppointmentManager.Repository
  3. AppointmentManager.Models

Rename the default file “UnitTest1” to GenericRepositoryTest

Rename the default method to TestInsertion and put the following code

[TestClass]
    public class GenericRepositoryTest
    {
        [TestMethod]
        public void TestInsertion()
        {
            var context = new AppointmentManagerContext();
            IRepository<ServiceType, int> serviceRepository = new EFRepository<ServiceType, int>(context);

            var serviceType = new ServiceType()
            {
                CreatedBy = "admin",
                CreationDate = DateTime.Now,
                LastModificationDate = DateTime.Now,
                Name = "Test service"
            };

            serviceType = serviceRepository.Add(serviceType);

            Assert.IsNotNull(serviceType);
            Assert.AreNotEqual(serviceType.ID, 0);

            serviceType = serviceRepository.GetByID(serviceType.ID);
            Assert.IsNotNull(serviceType);

            serviceRepository.Delete(serviceType);
            serviceType = serviceRepository.GetByID(serviceType.ID);
            Assert.IsNull(serviceType);
        }
    }

Right click the method in VS and choose Run Tets and make sure the test succeeds, you can double check by putting a break point before the line that deletes the serviceType object and check the database and ensure the record is there

By this, we have a read repository that we can use to manage our data, in the next part we will start building the UI.

 

You can get the full source code from GitHub Repository https://github.com/haitham-shaddad/AppointmentManager