Pages

Men

rh

10/27/2014

Develop distributed WCF based MVC4 web application with TDD and Moq

Introduction

This project intends to develop a demo MVC4 web application with a WCF service via TDD and Moq. We assume to sell health products online. The SOA design pattern is used to design multiple layers of a WCF service which could exchange data between MVC data controllers and a SQL Server database. Therefore the WCF service will not be able to connect with data persistence directly. The SOA will create a data access layer which will separate the WCF service from the data resource, a database layer which can delegate the data access layer with real data tables in a SQL Server database via LINQ-to-Entity patterns, and a WCF service. We expect this web solution will contain multiple projects such as a database layer class library project, a data access layer class library project, a WCF service library project, and an MVC4 internet web application. TDD and Moq are tools web developers use for developing MVC4 web applications. TDD is a nice way to develop code in a testing environment before the code is implemented. The Moq Nuget package can make TDD more powerful. Some TDD test projects which use the Moq Mock object to test data access objects without accessing a real SQL Server database also are developed in this project.

Main Tasks

1. Database layer class library project

After a blank solution is created in VS 2012, a new database layer class library project is added with the name “Databaselayer”. A new ASP.NET entity data model is created in order to connect to a persistent product table in a SQL Server database as below.
This data model can create a data context class automatically. The data context class will provide a method for a data access layer which will delegate this database layer later and supply real data from a database for the WCF service (see image shown below).
The data model manages garbage collections and error processing in the application automatically to prevent memory leaks. The .NET Framework really gives us a very nice process to create a data class for distributed applications.

2. Data access layer class library project

.NET Mock that is shopped in the Moq Nuget package allows us to test the data access object without connecting to a real database. Therefore, before developing this data access layer project, a new TDD test project with a Mock object is created to test whether the data access class we developed in this project is working properly as in the following description.

TDD test data access object

A datalayertest class library project is created to provide a dummy data object as below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Databaselayer;

namespace MVCWCF.DataLayer
{
    public interface Ibusdata //interface object
    {
        List< MoqProduct > productmoq(); 
    }
    public class MoqProduct   //test data class
    {
        #region IProduct Members
        public int Productid {get;set;}
        public string Name  {get;set;}
        public string Category  {get;set;}
        public string Type  {get;set;}
        public string Description  {get;set;}
        public string maker  {get;set;}
        public DateTime make_date  {get;set;}
        public DateTime expired_date  {get;set;}
        public decimal unit_price  {get;set;}
        public int stock_level  {get;set;}
        public bool deleted  {get;set;}
        #endregion
    }

    public static class IEnumerableExt
    {
        public static IEnumerable<t> Yield<t>(this T item)
        //extension method to convert object into Enumerable object
        {
            yield return item;
        }
    }

    public class busdata : Ibusdata  //implement interface
    {

        public List<moqproduct> productmoq()  //create a dummy data object for testing
        {
            MoqProduct p0 = new MoqProduct();
            p0.Productid = 1;
            p0.Name = "Fish Oil";
            p0.Category = "Health";
            p0.Description = "for human's brain";
            p0.maker = "blackmores";
            p0.make_date = DateTime.Parse("2012-03-03");
            p0.expired_date = DateTime.Parse("2013-03-03");
            p0.stock_level = 2;
            p0.deleted = false;

            MoqProduct p01 = new MoqProduct();
            p01.Productid = 2;
            p01.Name = "Fish Oil 2";
            p01.Category = "Health";
            p01.Description = "for human's brain";
            p01.maker = "blackmores";
            p01.make_date = DateTime.Parse("2012-03-03");
            p01.expired_date = DateTime.Parse("2013-03-03");
            p01.stock_level = 1;
            p01.deleted = false;

            List<moqproduct> pp = new List<moqproduct>();

            pp.Add(p0);
            pp.Add(p01);
            return pp;
        }
    }
}
The test method in this TDD project is created as below:
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVCWCF.DataLayer;
using System.Linq;
 
namespace datalayer
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
                Moq.Mock <mvcwcf .datalayer.ibusdata=""> bb=
                  new  Moq.Mock <mvcwcf .ibusdata="" .datalayer=""> ();
                MVCWCF.DataLayer.busdata bs = new busdata();

                 List<moqproduct> datap = new List<moqproduct>();
                 var vvf = bb.Setup(m => m.productmoq()).Returns(datap);
              
            int prodctid = 1;
            MoqProduct vv = (from p in bs.productmoq() where p.Productid == prodctid select p).First();
                Assert.AreEqual(1, vv.Productid);
        }
    }
}
We need to test if the data access layer has returned a dataset of dummy data from the MoqProduct object. Test result shows that data access layer object really returns the expected result and the test is passed as shown below.
So far so good, after the Moq package is downloaded from the internet via Nuget, the Mock object is introduced to create dummy database data for data access object testing. Now a real data access layer project is created. As we know, the database layer has already provided a data class for the data access layer to use. This project will provide business logic to exchange data between the data class and a WCF service. The database layer class assemble is added into this project as a reference via the "Add Reference" option in VS 2012. The class code is shown below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Databaselayer;               //introduce database layer as reference

namespace MVCWCF.DataLayer
{
    public interface Ibusdata 
    {
        List<product> productmoq();  //use real product data class
    }
    
    public class busdata:Ibusdata
    {
       Databaselayer.EFDBSalesEntities_dbb edd = new EFDBSalesEntities_dbb();
       //data access layer dependency
       
        public List<product> productmoq()  // use product class definition in database layer
        {
            IQueryable<product> pp = (from cc in edd.Products select cc).AsQueryable ();
            return pp.ToList();   //convert Querable object into Enumberable object     
        }
    }
}

3. WCF service library project

Now a new WCF service library project is created to deal with data from the data access layer and is named “MVCWCF.WcfServiceP”. Two operation contracts and one data contract are created in this project (see below):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel; 
using System.Text;

namespace MVCWCF.WcfServiceP
{ 
    [ServiceContract]
    public interface IProductservice
    //interface object to implement two operation contracts
    {
        [OperationContract]
        string GetProductID(); //return new product ID from Product table

        [OperationContract]
        IEnumerable<databaselayer.product> GetProductlist(int productid);
        //search product record via Product ID
    }

     [DataContract]
    public class Product {   //data contract class definition
        #region IProduct Members
       [DataMember]
       public int Productid {get;set;} 
       [DataMember]
         public string Name{get;set;} 
       [DataMember]
         public string Category{get;set;} 
       [DataMember]
         public string Type {get;set;}
       [DataMember]
         public string Description{get;set;} 
       [DataMember]
         public string maker{get;set;} 
       [DataMember]
         public DateTime make_date{get;set;} 
       [DataMember]
         public DateTime expired_date{get;set;} 
       [DataMember]
         public decimal unit_price{get;set;} 
       [DataMember]
         public int stock_level{get;set;} 
       [DataMember]
         public bool deleted{get;set;} 
      #endregion
    }
}
This WCF service class delegates the data access layer to implement both operation contract methods. The contract methods will return a new product ID and the searched dataset of the product list to the controller in the MVC4 web site (see code below), respectively.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using MVCWCF.DataLayer;  // add data access layer as reference
using Databaselayer;  // use product class definition only

namespace MVCWCF.WcfServiceP
{
    public static class IEnumerableExt
    //extension method to convert object into Enumerable object
    {
        public static IEnumerable<t> Yield<t>(this T item)
        {
            yield return item;
        }
    }

    public class Productservice : IProductservice   //implement WCF service
    {
        #region IProduct Members
        MVCWCF.DataLayer.Ibusdata bbd = new MVCWCF.DataLayer.busdata();
        // data access layer object dependency
        
        public string GetProductID()
        {
            try
            {
             return   bbd.productmoq().Max (c => c.ProductId).ToString();
             //get max product id
            }
            catch 
            {
                return null; throw;
                // throw exception to system such as window event logs
                // or IIS web server event logs.
            }
            finally 
            { 
                GC.Collect();
                GC.SuppressFinalize(this); //garbage collection
            }
        }

        public IEnumerable<databaselayer.product> GetProductlist(int prodctid)
        {
            try
            {
                var ccc = from dd in bbd.productmoq() select dd;
                //query data from data access layer, use product class definition in databae layer

                Databaselayer.Product vv = (from p in ccc
                                           where p.ProductId == prodctid 
                                           select p).First();
                return vv.Yield <databaselayer.product>();
             }
            catch  
            { 
                return null; throw; 
            }
            finally 
            { 
                GC.Collect();
                GC.SuppressFinalize(this);
            }
       }
        #endregion
    }
}
The GetProductlist(int id) operation contract method will search the product list from the data access layer with the product ID and return the data set of the search result as an enumerable data list for the controller/Razor view in MVC. The GetproductId() method will fetch the maximum product ID in the database and return a new ID back to the system.
The LINQ query returns the data set with the product object that is not the enumerable data list. Therefore, a generic Yield<t> extension method is developed to convert this data list from the product object into an enumerable product data list.
The service throws an exception to the system and does the garbage collection when completed. A built-in WCF test client in VS2012 is used to test this new WCF service we developed as shown below.

4. WCF service client TDD test

A new TDD unit test project is created to test whether this WCF service can be called properly after we add this web service reference into the TDD project as in the code below:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace wcfcmdclienttest
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod11()
        {
            ServiceReference1.ProductserviceClient plc = 
              new ServiceReference1.ProductserviceClient();   //wcf service dependency
            Assert.IsNull ( plc.Endpoint.Address.Identity);
            Assert.AreEqual(1, plc.GetProductlist(2).Length); 
        }
    }
}
The testing is successful when we get the correct service behaviors and query results as in the following snapshot.
Of course, the following test fails because the WCF service really is able to create an endpoint for the client to connect to.

5. Consume WCF service with the console client

Now we can create a new console application project to delegate with the WCF service (see code below).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WcfserviceClientcmd
{
    class Program
    {
        static void Main(string[] args)
        {
            wcfservicelayer.ProductserviceClient plci = 
              new wcfservicelayer.ProductserviceClient();//a proxy client is generated 
            foreach (var item in plci.GetProductlist(2))
            {
                Console.WriteLine("ID: " + item.ProductId.ToString());
                Console.WriteLine("Name: " + item.Name.ToString());
                Console.WriteLine("Desriptionn: " + item.Description.ToString());
                Console.WriteLine("Category: " + item.Category.ToString());
                Console.WriteLine("Manufacturer: " + item.maker.ToString());
                Console.WriteLine("Manufacture Date: " + item.make_date.ToString());
                Console.WriteLine("Exprited Date: " + item.expired_date.ToString());
                Console.WriteLine("Stock Level: " + item.stock_level.ToString());
                Console.WriteLine("Is Deleted: " + item.deleted.ToString());
            }
            Console.ReadLine();
        }
    }
}

Service returns the expected value to console client.

6. MVC4 web application

When the WCF service is generated and tested, we can implement it in an IIS web server to keep this service running. An MVC4 web application is created to consume this WCF service in the IIS web server. The WCF web service is added as a web reference in the MVC4 web application (see below).
Add this service reference in the correct way in VS 2012 carefully. Before this service reference is added, the "reuse type in reference assembles" option should be deselected and the "always generate message contracts" box should be ticked in Advanced Settings to generate the correct reference.cs file as in the image shown below.
If we do not do it this way, the MVC4 controller will not be able to see this WCF service proxy object. Now a product controller in the MVC4 web application is created to consume this WCF service (see code below).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Mvcproduct.Controllers
{
    public class ProductController : Controller
    {
        //
        // GET: /Product/
        public ActionResult Index(int id)  //pass product ID
        {
            Serviceentity.ProductserviceClient plc = 
              new Serviceentity.ProductserviceClient();//service proxy client
            Serviceentity.GetProductlistRequest ft = 
              new Serviceentity.GetProductlistRequest(); //request for this proxy object
            ft.productid = id;
            var rlt = plc.GetProductlist(ft).GetProductlistResult; // List<Product>
            return View(rlt);
        }
    }
}
This controller calls the Serviceentity.ProductserviceClient service proxy client which is generated from the reference.cs file and creates a new object to handle the request. The getproductlist(int id) method is called to return a datset from the SQL Server database. The parameter id can not be passed directly and must be wrapped into the Request object which then passes this product ID into the method. The controller will return the data list to the Razor view in Index.cshtml.
An index view finally is created to show the data from the product controller as below:
@model IEnumerable< Mvcproduct.Serviceentity.Product > // return Product object
@{
    ViewBag.Title = "Index";
}
< h2 >Get Product List

@foreach (var item in Model)
{ 
   < ul >
        < li >Product ID: @item.ProductId < /li >
        < li >Product Name: @item.Name < /li >
        < li >Product Category: @item.Category < /li >
        < li >Product Type: @item.Type < /li >
        < li >Product Manufacturer: @item.maker < /li >
        < li >Product Manufacture Date: @item.make_date < /li >
        < li >Product Expired Date: @item.expired_date < /li >
        < li >Product Stock Level: @item.stock_level < /li >
    < /ul >
}
The @model IEnumerable<mvcproduct.serviceentity.product> model is added to contain the data list from the controller inside the Razor view. The model will be another instance of the Product object in the MVVM Model. The final result will be displayed in the view successfully as below:
By passing id as a querystring in the URL we can query the product details with different product IDs as in the example below:

7. CSS and jQuery in Razor View

The layout in Razor view is the default Razor view from MVC templates. Extra CSS and jQuery files can be developed and added into the MVC4 web site to create a Smart UI pattern for interfaces. MVC4 contains a default _layout.cshtml that is the global template for all possible views in MVC web applications. We can add another level of template into this global view template to modify each interface at an individual view level (see below).
The PageScripts section is rendered here as an interface that will be implemented in individual Razor views such as the following script that is added inside the index HTML.
The pagescripts section inside Index.cshtml looks after this index view only and overrides the scripts in the_layout.cshtml global template. Now open the product.css file and add the following code:
ul 
{
    list-style-image:url('sqpurple.gif');
}
The new product list layout is modified and will now look like this:
jQuery functions can also be added into the Razor view to manipulate product list element behaviors. A simple jQuery function has been added into the productapp.js file as below.
$(document).ready(function () {
    $("ul li").each(function () {
        $(this).mouseover(function () {
            $(this).css("color", "red");
        }).mouseout(function () { $(this).css("color", "green"); });
    });
});
The result list below is the new layout of the product list with different colors when mouse is moved over each list element.

Conclusion

This demo project only shows you the basic steps to develop a WCF service based on a distributed MVC4 web application with TDD, jQuery, and Moq. Real world will require MVC to do concurrent access controls in a distributed environment via the LINQ-to-Entity pattern. I will continue to go through those advanced topics.

Source from
http://www.codeproject.com/Articles/614167/Develop-distributed-WCF-based-MVC-web-application

No comments :

Post a Comment