Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

A simple POC using ASP.NET Web API, Entity Framework, Autofac, Cross Domain Support

4.92/5 (43 votes)
27 Oct 2012CPOL11 min read 351.3K   8.3K  
The purpose of this article is to create a proof of concept to demonstrate the feasibility of ASP.NET Web API Beta Version.

Download Application.zip

Download packages.zip

Download AdventureSite.zip

or 

Download CodePlex  

Introduction  

The purpose of this article is to create a proof of concept to demonstrate the feasibility of ASP.NET WebAPI final release.

Update  

  • Modify code for Visual Studio 2012
  • Replace Autofac with Castle Windsor for Dependency Injection   

Prerequisite: 

Goals to achieve: 

  • Any user should be able to view products
  • Product would be filtered by Categories
  • User should be able to add products in cart
  • Only registered user will be able to submit cart
  • User should be able to register into system
  • User will get confirmation Invoice after generating order in system.

Architecture for this POC

I am not following any specific Architecture Pattern, although while designing this POC, I try to follow some of the key design principles like:

Again I am repeating my word try to follow….. :). So, all comments/suggestions are more than welcome to discuss and I am always ready to adopt changes, or you can do it yourself by join @CodePlex. The initial Skeleton for this application is as per below diagram:

Architecture

Step-by-Step

Step 1: Create a basic Application Structure Layout

For more information on ASP.NET WebAPI, watch Videos and Tutorials here

Add a blank Solution named as Application

Add a new Project (Class Library) named as Application.DAL

Repeat above process and create four more Class Library projects named as –

  • Application.Model
  • Application.Repository
  • Application.Common
  • Application.Service
Now, application will look like below screen-shot

Solution 1

Next, add a new Project named as Application.API as ASP.Net MVC 4 Web Application. After click on OK, select template as Web API

This will add a full functional Web API template Application into our main solution.

Solution 1

Right click on Application.API project in Solution Explorer and go to properties, than assign a port to be use by this service application ( In my case its 30000). Hit F5 and we are ready with a dummy data as below screen:

Solution 1

Step 2: Prepare Application.DAL, Application.Repository and Application.Model Layers

Check @MSDN for more information on Entity Framework

Add Entity Data Model to Application.DAL project. To do this select ADO.NET Entity Data Model from the Data Templates list, and name it as AdventureWorks.edmx

Choose “Generate from Database” and provide the connection string to read the database schema. This will add AdventureWorks.edmx into current project. Right click on the blank space in Entity Model surface and click on properties (or alternate press Alt +Enter on keyboard). Now set its Code Generation Strategy as None.

Solution 1

Now create POCO entities for AdventureWorks Data Model into Application.Model Solution. Check this article to do this job for you on MSDN to use T4 Templates and the Entity Framework
Then, create all required repositories under Application.Repositories.

Solution 1

To build solution, add reference of Application.Model.dll to Application.DAL.dll and Application.Repository.dll projects, and add reference of Application.DAL.dll to Application.Repository.dll project. (See below picture for reference)
Before proceeding to next step (designing Application.Service and Application.API Layer), let us see what we have designed yet:

Solution 1

Step 3: Add files to Application.Service Layer

Before adding files to Service layer let us look at the Goals which we want to achieve.

  • Any user should be able to view products
  • Product would be filtered by Categories
  • User should be able to add products in cart
  • Only registered user will be able to submit cart
  • User should be able to register into system
  • User will get confirmation Invoice after generating order in system.

To achieve the above mention Goals, we need to add three service classes to Application.Service as per below image

Solution 1

Solution 1

I am not going in deatils, as the implementation for each methods are clear and easy to understand.

Step 4: Add files to Application.API Layer

To make this Web API accessed from a broad range of clients like any browsers which use JavaScript or any windows/Mobile platform which supports .dll files, we need to add Controllers with base class as ApiController. More details here

First, we remove the default controllers added by template (HomeController and ValuesController) and then add three new controllers as per below diagram:

Solution 1

Again, nothing special in the implementation of these methods, they all jst push the data to service layer.

Step 5: Integration of Dependency Injection with WebAPI using Castle Windsor 

The implementation is very much straight forward and clear.  

In Application.API Solution Explorer, right-click the References node and click Manage NuGet Packages

Solution 1

It will open the below screen. 

Image 12 

 Click on install and it will add below two references in solution

Image 13 

Create an adopter for Castle Windsor as WindsorActivator to implement IHttpControllerActivator  

C#
public class WindsorActivator : IHttpControllerActivator
{
    private readonly IWindsorContainer container;

    public WindsorActivator(IWindsorContainer container)
    {
        this.container = container;
    }

    public IHttpController Create(
        HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType)
    {
        var controller =
            (IHttpController)this.container.Resolve(controllerType);

        request.RegisterForDispose(
            new Release(
                () => this.container.Release(controller)));

        return controller;
    }

    private class Release : IDisposable
    {
        private readonly Action release;

        public Release(Action release)
        {
            this.release = release;
        }

        public void Dispose()
        {
            this.release();
        }
    }

} 

 The installer uses the container parameter of the Install method to Register controllers using Windsor's Fluent Registration API. 

Now we need to tell Castle Windsor to know about all dependencies for it to manage them. For that, we create a new class DependencyInstaller to implement IWindsorInstaller. The installer uses the container parameter of the Install() method to Register dependencies using Windsor's Fluent Registration API. As per below implementation every time we add any new class to Controller/Service/Repository in our application, it will be automatically registered, the only thing we need follow is naming convention, i.e. all Controller class should end with Controller similarly Service and Repository classes should end with Service and Repository. 

C#
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
                    Component.For<ILogService>()
                        .ImplementedBy<LogService>()
                        .LifeStyle.PerWebRequest,

                    Component.For<IDatabaseFactory>()
                        .ImplementedBy<DatabaseFactory>()
                        .LifeStyle.PerWebRequest,

                    Component.For<IUnitOfWork>()
                        .ImplementedBy<UnitOfWork>()
                        .LifeStyle.PerWebRequest,

                    AllTypes.FromThisAssembly().BasedOn<IHttpController>().LifestyleTransient(),

                    AllTypes.FromAssemblyNamed("Application.Service")
                        .Where(type => type.Name.EndsWith("Service")).WithServiceAllInterfaces().LifestylePerWebRequest(),

                    AllTypes.FromAssemblyNamed("Application.Repository")
                        .Where(type => type.Name.EndsWith("Repository")).WithServiceAllInterfaces().LifestylePerWebRequest()
                    );
    }
} 

 Next, in final step create and configure the Windsor container in Application's constructor inside Global.asax.cs,  so that it will be automatically dispose when application exits. 

C#
public class WebApiApplication : System.Web.HttpApplication
{
    private readonly IWindsorContainer container;

    public WebApiApplication()
    {
        this.container =
            new WindsorContainer().Install(new DependencyInstaller());
    }

    public override void Dispose()
    {
        this.container.Dispose();
        base.Dispose();
    }


    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        GlobalConfiguration.Configuration.Services.Replace(
            typeof(IHttpControllerActivator),
            new WindsorActivator(this.container));
    }
}<logservice><ilogservice><unitofwork><iunitofwork><databasefactory><idatabasefactory>
        </idatabasefactory></databasefactory></iunitofwork></unitofwork></ilogservice></logservice>

Now we are ready to test our application. Hit F5 and check the browser with following url’s:
http://localhost:30000/api/products : will result all Product Categories

Solution 1

http://localhost:30000/api/products/2 : will result all Products with ProductcategoryID as 2

Solution 1

To check other methods, First we will add some Views in current solution.

Step 6: Add View to solution

Delete all default files and folders from Views Folder, and add three Html and their supportive Javascript File as. For this POC we will use html files, and some jquery libraries like:

jquery.tmpl.js : to create template based screen, and

jquery.validationEngine.js : to validate form data

Now the structure will looks like below picture :

Solution 1

For details code in html and JS file please look into the code. In next step I am going to resolve problems.

Step 7: Resolve issues

Before run the application Right Click on Products.htm and click on Set As start Page. Now hit F5

Solution 1

It means, two WebAPI methods in ProductsController.cs are working perfectly. In JavaScript, on page load we are calling first method inside product.js as:

JavaScript
var productcategoriesAddress = "/api/products/";
$(function () {
    $('#tblRight').hide();
    $.getJSON(
            productcategoriesAddress,
            function (data) {
                var parents = jQuery.grep(data, function (a) { return a.ParentProductCategoryID == null });
                var childs = jQuery.grep(data, function (a) { return a.ParentProductCategoryID != null });
                $.each(parents,
                    function (index, value) {
                        var categorydata = [];
                        var subCategory = '';
                        var subChild = jQuery.grep(childs, function (a) { return a.ParentProductCategoryID == value.ProductCategoryID });
                        $.each(subChild,
                            function (index, item) {
                                var serviceURL = productcategoriesAddress + item.ProductCategoryID;
                                subCategory = subCategory + "  " + "<a href="%20+%20serviceURL%20+%20" class="menuButton">" + item.Name + "</a>";
                            });
                        categorydata.push({
                            'ProductCategoryID': value.ProductCategoryID,
                            'ParentCategory': value.Name,
                            'ChildCategory': subCategory
                        });
                        $("#categoryTemplate").tmpl(categorydata).appendTo("#categories");
                        $("#" + value.Name).html(subCategory);
                    });

                    GetProducts(1);
            }
        );

This call hits below method in ProductsController, and returns all Product Category to create the Upper Menu Part

C#
    public IQueryable<productcategory> GetProductCategories()
    {
        loggerService.Logger().Info("Calling with null parameter");
        return _productservice.GetProductCategories().AsQueryable<productcategory>();
    }
</productcategory></productcategory>

In ProductService implementation is :

C#
    public IQueryable<productcategory> GetProductCategories()
    {
        loggerService.Logger().Info("Calling with null parameter");
        return _productservice.GetProductCategories().AsQueryable<productcategory>();
    }
</productcategory></productcategory>

The second call is to fetch all products depends on selected category. In javascript

JavaScript
  function GetProducts(ProductCategoryID) {
    var serviceURL = productcategoriesAddress + ProductCategoryID;
    $('#categories li h1').css('background', '#736F6E');
    $('#mnu' + ProductCategoryID).css('background', '#357EC7');
    $("#loader").show();
    $.ajax({
        type: "GET",
        datatype: "json",
        url: serviceURL,
        context: this,
        success: function (value) {
            $("#productData").html("");
            $("#productTemplate").tmpl(value).appendTo("#productData");
            $("#loader").hide();
        }
    });
    return false;
}

and in ProductsController it goes to

C#
    public IQueryable<product> GetProductByProductCategoryID(int id)
    {
        loggerService.Logger().Info("Calling with null parameter as : id : " + id);
        return _productservice.GetProductByProductCategoryID(id).AsQueryable<product>();
    }

</product></product>

In ProductService implementation is :

C#
    public IQueryable<product> GetProductByProductCategoryID(int id)
    {
        loggerService.Logger().Info("Calling with null parameter as : id : " + id);
        return _productservice.GetProductByProductCategoryID(id).AsQueryable<product>();
    }
</product></product>

Next, let us try to add some products to Cart and Checkout cart

Solution 1

On clicking on checkout It redirect to Checkout.htm page and asking for either Login using existing CustomerID or Register for a new Customer.
Click on Register link to create a new Customer.

Solution 1

Submit form after clicking on Register button and see if Posting is working or not. Again its success!!!

Now we will try to update records for AddressLine2, and Update is also working fine!!!

In both scenario (Add or Update Customer) we are calling below method of CutomerControllers.cs

C#
 public int PostCustomer(CustomerDTO customer)
{
    loggerService.Logger().Info("Calling with parameter as : customer: " + customer);
    return _customerService.SaveOrUpdateCustomer(customer);
}

In CustomerService implementation is :

C#
public int SaveOrUpdateCustomer(CustomerDTO customer)
{
    string passwordSalt = CreateSalt(5);
    string pasword = CreatePasswordHash(customer.Password, passwordSalt);
    Customer objCustomer;

    if (customer.CustomerID != 0)
        objCustomer = _customerRepository.GetById(customer.CustomerID);
    else
        objCustomer = new Customer();

    objCustomer.NameStyle = customer.NameStyle;
    objCustomer.Title = customer.Title;
    objCustomer.FirstName = customer.FirstName;
    objCustomer.MiddleName = customer.MiddleName;
    objCustomer.LastName = customer.LastName;
    objCustomer.Suffix = customer.Suffix;
    objCustomer.CompanyName = customer.CompanyName;
    objCustomer.SalesPerson = customer.SalesPerson;
    objCustomer.EmailAddress = customer.EmailAddress;
    objCustomer.Phone = customer.Phone;
    objCustomer.PasswordHash = pasword;
    objCustomer.PasswordSalt = passwordSalt;
    objCustomer.ModifiedDate = DateTime.Now;
    objCustomer.rowguid = Guid.NewGuid();

    if (customer.CustomerID != 0)
        _customerRepository.Update(objCustomer);
    else
        _customerRepository.Add(objCustomer);

    _unitOfWork.Commit();
    SaveOrUpdateAddress(customer, objCustomer.CustomerID);
    return objCustomer.CustomerID;
}

Next, click on Submit Order Button, to make previously selected products as an Order and receive Order Invoice.

Solution 1

!!!Hurray!!! Order is created in system and invoice is generated with a new Purchase Order. But, wait we got one BUG 

Issue 1. is fixed with the final release of Asp.Net WebAPI  

Issue 1: we have an issue with Date Format.

Solution 1

According to, Scott Hanselman’s post this is a bug and will resolve in next release of ASP.Net WebAPI. This issue is resolved using solution provided in Henrik's Blog.

Solution 1

Issue 2: Another issue was, when I try to Checkout Cart using the last created Customer. The error was:

Solution 1

After looking this error, first thought comes that we missed the method in CustomersController.cs, but the method was already there. Further investigation make it clear, that, the reason behind this error was name of this method as ValidateCustomer():

C#
 public CustomerDTO ValidateCustomer(int id, string password)
{
    loggerService.Logger().Info("Calling with parameter as : id and password: " + id + " and " + password);
    return _customerService.ValidateCustomer(id, password);
}

In CustomerService implementation is :

C#
public CustomerDTO ValidateCustomer(int id, string password)
{
    Customer objCustomer = _customerRepository.GetById(id);
    if (objCustomer == null)
        return null;
    string strPasswordHash = objCustomer.PasswordHash;
    string strPasswordSalt = strPasswordHash.Substring(strPasswordHash.Length - 8);
    string strPasword = CreatePasswordHash(password, strPasswordSalt);

    if (strPasword.Equals(strPasswordHash))
        return CreateCustomerDTO(objCustomer);
    else
        return null;
}

By default the route configuration of Asp.Net WebAPI follows RESTFUL conventions meaning that it will accept only the Get, Post, Put and Delete action names. So when we send a GET request to http://localhost:30000/api/customers/ValidateCustomer/30135/test we are basically calling the Get(int id) action and passing id=30135 which obviously crashes because we don’t have any method starting with Get which accept Id as parameter. To resolve this issue I need to add a new route definition in Global.asax file as:

C#
routes.MapHttpRoute(
      name: "ValidateCustomer",
      routeTemplate: "api/{controller}/{action}/{id}/{password}",
      defaults: new { action = "get" }
  );

Adding this line done the magic and Login functinality start waorking…. :)

Step 8: Deploy on IIS

To deploy this API on IIS version 7.5, first, publish only Application.API solution to a Folder.

Next, open IIS Manager and create a new website and set path to Publish folder, and we are done with deployment.

Now, check whether page is opening or not, in my case, initially I got below error, which is self-explanatory.
The error comes because this application is running on Asp.Net Framework Version 2.0. So, we need to change it to Asp.Net Framework Version 4.0:

Solution 1

Solution 1

To change the Asp.Net Framework from 2.0 to 4.0, follow below steps.

Solution 1

Solution 1

That was not the only reason not to run application, because after made above change in Application pool next error was:

Solution 1

To resolve this error again go to Advance Settings of Application pool and change the identity to Local System from ApplicationPoolIdentity

Solution 1

After modify the Application pool to use Local System, Application starts working on browser... :)

Solution 1

Note:Although IIS errors are depending on servers/Firewall/Network or system to system, so it is very difficult to assume that you will also get the same error as mine. Check these links which help me to resolve some of the IIS issues in my work environment.

Step 9: Cross Domain Support

The idea behind the Cross Domain Support comes when I try to remove all HTML pages from Application.API solution and created a new Empty Web Application (Adventure.Website) which includes only .html, .Js and .css files along with images. The structure for this application is as per below image:

Solution 1

Point all URL address to deployed code on IIS, after build and run application, output was null.

Solution 1

To resolve this issue I would like to give my special thanks to Carlos’ blog on MSDN Implementing CORS support in ASP.NET Web APIs

Quote:
“By default, a web page cannot make calls to services (APIs) on a domain other than the one where the page came from. This is a security measure against a group of cross-site forgery attacks in which browsing to a bad site could potentially use the browser cookies to get data from a good site which the user had previously logged on (think your bank). There are many situations, however, where getting data from different sites is a perfectly valid scenario, such as mash-up applications which need data from different sources.”  

Code Setup 

Due to size restriction I have attached code in three parts :

 Part 1 : Application.Zip 

 Part 2 : Packages.Zip 

 Part 3 : AdventureSite.Zip 

Part 1 and Part 3 are independent applications. Whereas, for Part 2 we need to copy its content inside the package folder of Application (Part 1). Please refer below image :

 Image 33 

Conclusion 

That's it !!! Hope you enjoy this article. I am not an expert, and also I have not followed the entire industry standard at the time of writing this article (time factor).

All comment/Vote are more than welcome…. :), whenever, I get time, will try to modify this solution. You can also join/contribute to this project on CodePlex.

Thank's for your time.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)