in

Fort Worth .NET Users Group

David.Yancey

  • Taking a look at Dynamic Data

    One of the best things about being a programmer is new technologies,  taking a look at exciting new approaches to everyday tasks.  The .NET world is continually growing, in fact it was only 6-months ago that the 3.5 Framework was released giving you new tools such as LINQ, WPF, and Silverlight.  Now comes ASP.NET 3.5 Extensions Preview and you can add to the list Dynamic Data Controls. Dynamic Data Controls will enable you to build data driven website's that will work against a LINQ to SQL and LINQ to Entities object model.  With a fully functional website built on top of the MVC framework you can optionally override or customize any of the view templates giving you full control over our website.

    Dynamic data works with the standard data controls enabling them to automatically support foreign key relationships,  giving you a friendly name display of foreign key values along with built in UI validation support based upon the constraints set in your data-model.

    Once you download the new Dynamic Data controls (you can down load them from here ) unzip them to your favorite directory.  Inside the directory you will find a series of DLL's, Zip files, CMD files and a readme.  Inside the readme you will find instructions on setting up your environment and your first project of which both of these we will go over in this blog.  So now its time to get to the fun.

    image

    For this example you will need

    Visual Studio 2008
    SQL Express
    ASP.NET 3.5 Extensions Preview
    Northwind database

    The examples here will be in VB.NET, however you can use C# if you so desire.

    Installation

    First you need to install the Dynamic Data controls.  To do this you can either open up a command window and navigate to the directory in which you unzipped the files, or you can simply double click on the appropriate one to install depending upon your system.  You will notice that there is support for both 32-bit and 64-bit operating systems.

    Your first Project

    Launch Visual Studio 2008, and click on File > New > Website and choose Dynamic Data Website (Preview).

    imageA

     

    Directory Structure:
    You will notice that you have a pre-built website complete with a Dynamic Data directory structure.  The key area's I want to point out here are the CustomPages, FieldTemplates, and PageTemplates directories. 

    imageB

     

    FieldTemplates:These are the templates which are used to render each of the field types with in your datamodel.  You will notice the standard data types such as Boolean, DateTime, Integer, and Text.  You should also notice templates to handle the rendering of ForeignKey and Children relationships.  What you should also notice is that the templates for Image and XML data types are missing.  As of this release they are not installed by default; however you can create your own templates to control the rendering of these datatypes.  You can also override/customize the existing templates depending upon your needs.

    PageTemplates:These are the default page templates to display / edit the data. 

    CustomPages:This is where you will put any custom pages or override any pages in the default PageTemplates directory.

     

     

     

    Data Model:

    Add your database to your project.

    Add a LINQ to SQL Class

    imageC

    Add the desired tables to your datacontext.

    imageD

    Now its time for the magic.  Open your global.asax and uncomment the line:

    '    model.RegisterContext(GetType(YourDatacontext), New ContextConfiguration() With {.ScaffoldAllTables = False})

    Then replace "YourDatacontext" with the name of the datacontext we just created and set ScaffoldAllTables = True.  Setting this value will build the dynamic pages based upon the data model.

       1: <%@ Application Language="VB" %>
       2: <%@ Import Namespace="System.Web.Routing" %>
       3: <%@ Import Namespace="System.Web.DynamicData" %>
       4:  
       5: <script RunAt="server">
       6: Public Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
       7:     Dim model As New MetaModel
       8:  
       9:     ' Uncomment this line to register your data context with the Dynamic Data engine.
      10:     ' Only set ScaffoldAllTables = true if you are sure that you want all your tables
      11:     ' to support a scaffold (i.e. templated) view.
      12:         model.RegisterContext(GetType(NorthwindDataContext), New ContextConfiguration() With {.ScaffoldAllTables = True})
      13:  
      14:     routes.Add(New DynamicDataRoute("{table}/ListDetails.aspx") With { _
      15:         .Action = PageAction.List, _
      16:         .ViewName = "ListDetails", _
      17:         .Model = model})
      18:  
      19:     routes.Add(New DynamicDataRoute("{table}/ListDetails.aspx") With { _
      20:         .Action = PageAction.Details, _
      21:         .ViewName = "ListDetails", _
      22:         .Model = model})
      23:  
      24:     'routes.Add(New DynamicDataRoute("{table}/{action}.aspx") With { _
      25:     '    .Constraints = New RouteValueDictionary(New With {.Action = "List|Details|Edit|Insert"}),
      26:     '    .Model = model})
      27: End Sub

     

    Now run the site.

    imageE

    Drilling down to the Product details you will see that the friendly names for the categories are in both the drop down list, and the grid view.  You will also notice the ability to edit and delete.

    So now go off and have some fun with this.  Next time we will look at adding support for the Image data type based upon Scott Hunter's sample and building our own custom pages.

    So until next time, have fun and I'll see ya on the flip side.

    David Yancey

  • Using Provider Model with Unit Testing

    We are going to look at the Provider model and Offloader/Worker model and how we use this approach for our Test Driven Development (TDD) and Unit Testing.  For this Demo we will be using the Northwind Database, and developing this in VB. To start off we need to set up 3 projects in the same solution:

    1. TCNorthwindClass:  This is were we will place our class files for the BLL, DAL, and Interface.
    2. TCNorthwindWeb:  For this demo we will be creating a webservice.
    3. TCNorthwindTest:  For our Unit Tests.

    Now lets look at what the provider model is and how we use it with in our application.

    Provider Model

    We are going to define the provider model as a model in which the provider (access to the DAL) is passed in as an argument to the worker methods.  When developing your application using the provider model, you are going to create first your Interface with which all provider classes will implement.Diag1  

    Looking at the diagram to the left you will see that we have our Interface along with 2 class files of which one we are defining as a mock.  We'll look more at this later.  What's important to see here is that in our provider class files we are defining what is to be done with the data.  Each class implements from the interface the function "GetInventoryCountByCategory" and has passed into the function a categoryID.

    Reason for developing with this model is to provide a mechanism for changing data providers; for example moving from .NETTIERS to LLBLGEN or LINQ while maintaining control over what methods, properties are exposed from the DAL to the BLL.  Another reason for this model is it provides a means of creating a Mock DAL to Unit test our methods with out interacting with the Database.  This is a key factor when integrating your unit tests into the CI Builds in TFS (Team Foundation Server).  Now you might be thinking to yourself that the Data layer providers such as .NETTIERS, SubSonic, and LLBLGEN provide means of unit testing through mock objects or that you could use RhinoMock and other Mock Object frameworks to mock your objects and DAL with much more functionality.  Well you maybe right, but that also comes with larger overhead and a large amount of functionality that isn't needed. 

    Now that we've had a look at the base of our provider model, lets look at some code and the differences between the two providers in diag1 (diagram above).

    As you examine the code below you will notice that both providers implements the Function GetInventoryCountByCategory, however they do so in different ways.  The NorthwindProvider is our "custom" DAL which queries the Northwind DB to get the count of products based upon the categoryID and then returns that count.  While the Mock simply takes the categoryID and then returns an integer value based from the select case statement.  We'll look more into the how we set this up as a unit test in a moment.

    IProvider.VB

    .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
       1: Public Interface IProvider
       2:     Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer
       3: End Interface

    TCNorthwindProvider.vb .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

       1: Imports System
       2: Imports System.Data
       3: Imports System.Data.Sql
       4: Imports System.Data.SqlClient
       5:  
       6: Public Class TCNorthwindProvider
       7:     Implements IProvider
       8:  
       9:  
      10:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer Implements IProvider.GetInventoryCountByCategory
      11:         Dim count As Integer
      12:         Dim sqlText As String = "SELECT Count(productID) FROM Products WHERE CategoryID = @CategoryID"
      13:         Dim sqlCommand As New SqlCommand(sqlText, GetConnection)
      14:  
      15:         With sqlCommand
      16:             .CommandType = CommandType.Text
      17:             .Parameters.AddWithValue("@categoryID", CategoryID)
      18:             .CommandText = sqlText
      19:  
      20:             Dim dr As SqlDataReader = .ExecuteReader
      21:  
      22:             While dr.Read()
      23:                 count = dr(0)
      24:             End While
      25:             dr.Close()
      26:         End With
      27:  
      28:         Return count
      29:     End Function
      30:  
      31:     Private Function GetConnection() As SqlClient.SqlConnection
      32:         Dim oConn As New SqlClient.SqlConnection
      33:         oConn.ConnectionString = "Data Source=8X5SGF1;Initial Catalog=Northwind;Integrated Security=True"
      34:         oConn.Open()
      35:  
      36:         Return oConn
      37:     End Function
      38:  
      39:    
      40: End Class
    TCNorthwindMock.vb
       1: Public Class TCNorthwindMock
       2:     Implements IProvider
       3:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer Implements IProvider.GetInventoryCountByCategory
       4:         Select Case CategoryID
       5:             Case 1
       6:                 Return 3
       7:             Case 2
       8:                 Return 5
       9:             Case 3
      10:                 Return 7
      11:             Case 4
      12:                 Return 6
      13:             Case 5
      14:                 Return 4
      15:             Case 6
      16:                 Return 2
      17:             Case 7
      18:                 Return 0
      19:             Case 8
      20:                 Return 1
      21:         End Select
      22:     End Function
      23:  
      24:     
      25: End Class

    Now lets take a moment to look at the offloader/worker design pattern. 

    Offloader/Worker

    offloader-worker

     

     

     

     

     

     

     

    What you'll notice when looking at this design pattern is that both the Offloader and Unittest (TCNorthwindMethodsTest) pass the work to the worker which in this case is (TCNorthwindMethods).  What this does for us, is allows us to write Unit tests for our worker methods directly, and gives us a single point of entry into the Business logic for the application.  This single point of entry will help with the trouble shooting and the maintenance aspects of the application.  Each method in the Worker class is dependent upon the provider being passed in.  Lets take a quick look at the worker class, Offloader class and unit test to see how they each work together.

     

    Worker Class

    You will notice in the worker class that we are passing in the provider and then calling the appropriate method in the provider and returning back to the offloader.  This is an example of a provider dependency injection.  There are other types of dependency injections which we will discuss some other time.  For now lets move down to the Offloader and Unit test to see how we work with the Worker class to achieve our results.

       1: Public Class TCNorthwindMethods
       2:     Public Function GetInventoryCountByCategory(ByVal categoryID As Integer, ByVal Provider As IProvider) As Integer
       3:         Return Provider.GetInventoryCountByCategory(categoryID)
       4:     End Function
       5:     Public Function GetProductsByCategoryID(ByVal categoryID As Integer, ByVal Provider As IProvider) As Products
       6:         Return Provider.GetProductsByCategoryID(categoryID)
       7:     End Function
       8:  
       9: End Class

     

    Offloader Class

    Here you will notice that we are defining 2 private members.

    • TCMethods: Our worker class
    • TCNorthWindProvider: Our Provider

    Looking at Line #7 you will see that we are calling GetInventoryCountByCategory in our worker class and passing in which the provider we want to use.  Contrast this against how we are accessing the same method in our unit test.

       1: Public Class TCNorthwindOffLoader
       2:  
       3:     Private TCMethods As New TCNorthwindMethods
       4:     Private TCNorthWindProvider As New TCNorthwindProvider
       5:  
       6:     Public Function GetInventoryCountByCategory(ByVal CategoryID As Integer) As Integer
       7:         Dim iCount As Integer = TCMethods.GetInventoryCountByCategory(CategoryID, TCNorthwindProvider)
       8:         Return iCount
       9:     End Function
      10:     Public Function GetProductsByCategoryID(ByVal CategoryID As Integer) As Products
      11:         Dim products As Products = TCMethods.GetProductsByCategoryID(CategoryID, TCNorthwindProvider)
      12:         Return products
      13:     End Function
      14:  
      15: End Class

     

    Unit Test

    Looking at our unit test, you will see in our first test method on line #20 we are setting a private member to our provider model.  In this case its our mock model we created earlier.  Now look at line #36 where we are setting a local member to our worker class and then at line 43 where we pass the provider into our worker class.  In this unit test we are hard coding in the value of our categoryID and what our expected return value is.  We will then compare the expected return value against the actual return value to determine if the test passed.  This type of Unit testing is used to determine if our logic / design is going to give us the results we need for the application and wether or not we need to redesign the application before we've accumulated to much technical debt.

       1: 'The following code was generated by Microsoft Visual Studio 2005.
       2: 'The test owner should check each test for validity.
       3: Imports Microsoft.VisualStudio.TestTools.UnitTesting
       4: Imports System
       5: Imports System.Text
       6: Imports System.Collections.Generic
       7: Imports TCNorthwindClass
       8: Imports TCNorthwindClass.TCNorthwindOffLoader
       9:  
      10:  
      11: '''
      12: '''This is a test class for TCNorthwindClass.TCNorthwindMethods and is intended
      13: '''to contain all TCNorthwindClass.TCNorthwindMethods Unit Tests
      14: '''
      15:  _
      16: Public Class TCNorthwindMethodsTest
      17:  
      18:  
      19:     Private testContextInstance As TestContext
      20:     Private tcNorthWindMock As New TCNorthwindMock
      21:  
      22:     Public Property TestContext() As TestContext
      23:         Get
      24:             Return testContextInstance
      25:         End Get
      26:         Set(ByVal value As TestContext)
      27:             testContextInstance = value
      28:         End Set
      29:     End Property
      30:  
      31:     '''
      32:     '''A test for GetInventoryCountByCategory(ByVal Integer)
      33:     '''
      34:      _
      35:     Public Sub GetInventoryCountByCategoryTest()
      36:         Dim target As TCNorthwindMethods = New TCNorthwindMethods
      37:  
      38:         Dim categoryID As Integer = 3 'TODO: Initialize to an appropriate value
      39:  
      40:         Dim expected As Integer = 7
      41:         Dim actual As Integer
      42:  
      43:         actual = target.GetInventoryCountByCategory(categoryID, tcNorthWindMock)
      44:  
      45:         Assert.AreEqual(expected, actual, "TCNorthwindClass.TCNorthwindMethods.GetInventoryCountByCategory did not return th" & _
      46:                 "e expected value.")
      47:     End Sub
      48:  
      49:  
      50:  
      51:     '''
      52:     '''A test for GetProductsByCategoryID(ByVal Integer, ByVal TCNorthwindClass.IProvider)
      53:     '''
      54:      _
      55:     Public Sub GetProductsByCategoryIDTest()
      56:         Dim target As TCNorthwindMethods = New TCNorthwindMethods
      57:  
      58:         Dim categoryID As Integer = 3 'TODO: Initialize to an appropriate value
      59:  
      60:  
      61:  
      62:         Dim expected As New Products
      63:         For i As Integer = 0 To categoryID
      64:             Dim tProduct As New Product
      65:             With tProduct
      66:                 .ProductID = i
      67:                 .ProductName = String.Format("Item{0}", i)
      68:                 .QuantityPerUnit = i.ToString
      69:                 .ReorderLevel = i
      70:                 .Discontinued = False
      71:             End With
      72:             expected.product.Add(tProduct)
      73:         Next
      74:  
      75:  
      76: