Bracora
Tuesday, 19 August 2008
Mastering iTunes using Linq To Xml
Last updated : 19th August 2008
Background
Lets us assume for this scenario that we are adding a new feature to our web dating web site. Yes, let's presume that we already match people based on their interests etc but what if we could match them based on the music they like. In fact, what if we could scan their iTunes library and see what kind of music they listen to most often and match this against other daters listening habits? Wouldn't that be a good proposition?
Just about everyone I know has an apple music product of some description which inevitably means they will have iTunes installed to manage this. But how do we get to this information? Fortunately, all this music data is stored within an XML document maintained by iTunes. The format of this data is not ideal though. The music data is stored within a <dict> node that contains all of its data in a pair of nodes that are similar in structure to a key value pair. I have shown this below.
Strategy
To enable this matching we need to provide a facility to upload a daters iTunes Music Library information which includes all the music they have along with all the usage data as well. This will allow us to search through this information and use it on our web site. All this information is stored in an XML file called, funny enough, iTunes Music Library. Now, to help us extrapolate this data we will use a nice new feature of the .Net Framework 3.5 called Language Integrated Query (LINQ). More specifically we'll use LINQ to XML, as this provides a simple way to interrogate XML data using simple queries which look a lot like standard SQL.
Implementation
Before we start, let's have a look at how the final web site will look. In the figure below you will see that users are provided with a simple interface to upload their iTunes XML file. When they click on the Find Me My Match button, the system will :
- Upload this file
- Parse the file for the current users favourite tracks
- Match this against a simulated database of users
- The system will then make a decision about who would be the best match
In Visual Studio we need to create a new web application. We need to make sure that we select .Net framework 3.5 and we will use VB.NET for this demo.
To save a load of time on the user interface I downloaded a free web site template from AnVisionWebTemplates.com. This template gives us the html blueprint for our design and we only need to add the upload functionality into the overall website application. You would normally spend a bit of time to take out the common html elements and place them into a master page or pages. I have just copied this into my directory structure and modified the aspx page that I'll be using. You can download the files in this article from the link provided above.
The Project structure should resemble the figure below:
I have created a simple XML file called userdb.xml and this is simulating a database of users that we may have on our dating website. The structure of this file is shown below:
Next we want to create a class to do all the work. In the App_Code folder I created a new class called Bracora.Matchmarker that will be implementing the couple of static methods we need.
''' <summary>
''' General class for demo
''' </summary>
''' <remarks>Note: you would want to seperate the logic out into seperate classes (tiers)
''' in a real world application
''' </remarks>
Public NotInheritable Class MatchMaker
''' <summary>
''' </summary>
''' <remarks>Don't instantiate</remarks>
Private Sub New()
End Sub
Now let's start using some of this lovely Linq functionality. The first method I want to implement is the one to upload all the user information in the usersdb XML file and use this as the basis for matching later on. For this simple demo, to match music I am merely looping through this information and performing a basic string comparison so I am returning a general IEnumerable interface for my simulated database.
As you can see from the code below, I simply load the data into a XDocument object that is part of the Linq namespace. This allows me to run queries against this data as if it were held in a relational database. The query syntax is similar to SQL but slightly more intuitive. Another nice feature of .net 3.5 is the ability to return anonymous types. In the query below I am returning a strongly typed collection of objects containing two properties, userId and track. The compiler does all the hard work and saves me having to define this class just to return these values.
''' <summary>
''' Get all the data we have in our simulated database. We would normally keep all this
''' information in a database. But this is fine for the demo.
''' </summary>
''' <returns>A list of tracks an associated user ID's</returns>
''' <remarks>Just hard coding values for the demo</remarks>
Public Shared Function GetAllMusic() As IEnumerable
Dim databasePath = HttpContext.Current.Server.MapPath("App_Data") + "/userdb.xml"
If System.IO.File.Exists(databasePath) Then
Dim userdb = XDocument.Load(databasePath)
' Just return a list of our simulated users
Dim dbSongs = From s In userdb...<song> _
Order By s.<UserID>.Value _
Select _
s.<UserID>.Value, _
s.<track>.Value
Return dbSongs
Else
Return Nothing
End If
End Function
The next thing I need to do is create a method that will read through an uploaded iTunes library document and grab the top 25 songs most often played. Again, using Linq to XML this is an easy task.
''' <summary>
''' Process the uploaded file and get the top tracks baced on how often they
''' are played. In a real world we perform this processing offline.
''' </summary>
''' <param name="iTunesFile">The file location of the uploaded iTunes file</param>
''' <returns>A list of top songs for that user</returns>
''' <remarks>We are only taking the bare minimun information for this example.
''' Note how LINQ feturns a generic list of anonymous types without us having to
''' create a class definition for this ;-)
''' </remarks>
Public Shared Function GetUsersTopTracks(ByVal iTunesFile As String) As IEnumerable
If System.IO.File.Exists(iTunesFile) Then
Dim iTunes = XDocument.Load(iTunesFile)
' Drill into the iTunes XML structure to get the music information
' Transform it into a more meaningful XML structure
Dim tracks = From t In iTunes.<plist>.<dict>.<dict>.<dict> _
Select New XElement("song", _
From key In t.<key> _
Select New XElement(key.Value.ToString.Replace(" ", ""), _
CType(key.NextNode, XElement).Value.ToString))
' Get the top 25 most played songs
Dim topTracks = From song In tracks _
Order By song.<PlayCount>.Value Descending _
Take (25) _
Select _
Artist = song.<Artist>.Value, _
TrackName = song.<Name>.Value
Return topTracks
Else
Return Nothing
End If
End Function
There are a couple of things to note here. You will notice that I am drilling into the iTunes XML structure using what is known as Child Axis properties. VB.NET provides me with this syntax. The line of code (iTunes.<plist>.<dict>.<dict>.<dict>) is basically me drilling into where the music information is stored within the file hierarchy. I don't like the way iTunes manages this data so I am taking this raw information and transforming it into an XML structure that works for me. After I do this I am then sorting on descending Playcount value and then taking the top 25 songs. Again, I am simply returning a strongly typed collection of objects that contain 2 properties, Artist and Trackname.
And now for the matching routine....
To hold the matching data I have created a simple class.
Namespace Bracora.Demo
Public Class MatchedEntry
Public Sub New()
End Sub
Private _userID As String
Public Property UserId() As String
Get
Return _userID
End Get
Set(ByVal value As String)
_userID = value
End Set
End Property
Private _trackName As String
Public Property TrackName() As String
Get
Return _trackName
End Get
Set(ByVal value As String)
_trackName = value
End Set
End Property
End Class
End Namespace
All I want to do now is pass in two lists to a routine, one with the top 25 music tracks for the current user and another list that contains our simulated database with all of our stored users favourite selections. We then loop through the data and store all matches in a generic list of MatchedEntry objects. From here we run a Linq query to group and sort the matches based on the most 'hits' and then we select the first or top match.
If we have a valid match at this stage, we then execute another Linq query that gathers all the music tracks that were common to both users. So we now have a userId and an array of tracks that were matched (Note the .ToArray() method on the Linq object). Now thank you .net 3.5, we now just create an anonymous type with this information and return it back :-)
''' <summary>
''' Attempts to find the most matched user based on checking their music information
''' against the music information we have in our simulated database
''' </summary>
''' <param name="userTracks">A list of user tracks to match</param>
''' <param name="dbTracks">A list of music tracks against users we have in our 'database'</param>
''' <returns>The best matched userID or nothing for no matches</returns>
''' <remarks>Don't try this at home. Honestly there are much better ways to do this!</remarks>
Public Shared Function GetTopMatchedUser(ByVal userTracks As IEnumerable, _
ByVal dbTracks As IEnumerable) As Object
' contains a list of non-unique userIDs when songs are matched
Dim MusicMatches As New Generic.List(Of MatchedEntry)
Dim Match As MatchedEntry = Nothing
' loop round each of the users favourite tracks and try to match this
' against the list of tracks we have in our simulated database. If
' a match occurs the add the userId. The more instances of that userId
' then the more music they have in common.
For Each track In userTracks
For Each song In dbTracks
If track.TrackName = song.track Then
Match = New MatchedEntry()
Match.UserId = song.UserID
Match.TrackName = song.track
MusicMatches.Add(Match)
End If
Next
Next
' Group by the userID and then work out the count for each one
Dim top = From u In MusicMatches _
Group By u.UserId Into g = Group _
Order By g.Count() Descending _
Select _
UserId, _
NumberOfMatches = g.Count()
If top.Count() > 0 Then
Dim topMatchedUser = top.First()
' Get the matched songs based on the userId
Dim matchedSongs = From u In MusicMatches _
Where u.UserId = topMatchedUser.UserId _
Select u.TrackName Distinct
Dim MatchedUser = New With {.User = topMatchedUser.UserId, .Songs = matchedSongs.ToArray}
Return MatchedUser
End If
Return Nothing
End Function
To finish up with this class we need to create a controller routine that ties all these methods together.......
''' <summary>
''' Main entry point for the demo and controller
''' </summary>
''' <remarks></remarks>
Public Shared Function FindMeMyMatch(ByVal fileName As String) As Object
Dim userSongs As IEnumerable = GetUsersTopTracks(fileName)
Dim allUsersSongs As IEnumerable = GetAllMusic()
Return GetTopMatchedUser(userSongs, allUsersSongs)
End Function
Now, all we need to do now is implement the user interface. Therefore we need a FileUpload control, a few labels, an image control to show matched users and a button to trigger the whole thing off. The full details are contained in the download and I would encourage you to get it and run the application as there are no database requirements. Below is the main routine that uploads the iTunes file and displays the outcomes, either matched or unmatched.
One point to note is that ASP.NET constrains the upload size by default. As iTunes library files can be quite large, you'll need to modify the web.config file to increase the allowed upload size.
<!-- need to increase the size of the permitted upload size -->
<httpRuntime maxRequestLength="20480" />
Here is the code behind for the web page. It's pretty straight forward.
- We check that it's a proper XML file
- We generate a new unique filename and save the uploaded file
- We then pass the filename to the matching routine and await our fate!
I have coded some simple display logic for the demo. But we either match or we don't and display accordingly.
Protected Sub btnUpload_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnUpload.Click
Dim savePath As String = HttpContext.Current.Server.MapPath("uploads") + "/"
If (FileUpload1.HasFile) Then
'make sure it's an XML file
If (FileUpload1.PostedFile.ContentType.ToLower <> "text/xml") Then
message.Text = "This is not a valid iTunes file. Please make sure you upload the proper file."
Exit Sub
End If
' create a unique name for the uploaded file.
Dim fileName As String = Guid.NewGuid().ToString + ".xml"
' Append the name of the file to upload to the path.
savePath += fileName
FileUpload1.SaveAs(savePath)
Dim MyMatch = Bracora.Demo.MatchMaker.FindMeMyMatch(savePath)
Dim HeaderText As String = String.Empty
Dim ImageOfMatchedUserUrl As String = String.Empty
Dim DisplaySongs As String = String.Empty
If MyMatch IsNot Nothing Then
Select Case MyMatch.User
Case "1"
ImageOfMatchedUserUrl = "App_Themes/Standard/images/user1.jpg"
Case "2"
ImageOfMatchedUserUrl = "App_Themes/Standard/images/user2.jpg"
Case "3"
ImageOfMatchedUserUrl = "App_Themes/Standard/images/user3.jpg"
Case Else
ImageOfMatchedUserUrl = "App_Themes/Standard/images/user4.jpg"
End Select
For i As Int32 = 0 To MyMatch.Songs.Length() - 1
DisplaySongs += String.Format("<li>{0}</li>", MyMatch.Songs(i))
Next
HeaderText = String.Format("<h3>{0}</h3>", "Success, we found a match!")
Else
' No matches show the user who has been waiting longest :-)
ImageOfMatchedUserUrl = "App_Themes/Standard/images/user4.jpg"
DisplaySongs = String.Format("<li>{0}</li>", "You have no songs in common, here is your closest match")
HeaderText = String.Format("<h3>{0}</h3>", "No luck I'm afraid!")
End If
Me.headerMatch.Text = HeaderText
Me.songList.Text = DisplaySongs
imageOfMatchedUser.ImageUrl = ImageOfMatchedUserUrl
imageOfMatchedUser.Visible = True
Else
' Error message for no file
message.Text = "<p class=""error"">You did not specify an iTunes file to upload.</p>"
End If
End Sub
And that's all there is to it. Linq to Xml is a wonderfully powerful addition to the .net framework.
Summary
Hopefuly this tutorial has shown how you might use Linq in your own applications. Linq to XML is really nice feature and makes working with XML documents really easy. The leaning curve is very smooth and you can quickly pick up the core concepts without too much effort and grow into the more complex aspects of Linq namespace later on.
The code download also contains a few sample iTunes in the upload directory.
Note: iPod® and iTunes® are registered trademarks of Apple Inc.
Monday, 5 November 2007
Generic GridView Delete Strategy
Last updated : 5th November 2007
Background
There are a number of ways to implement a JavaScript delete confirmation in a GridView. Most of these involve changing the command columns on the GridView into template columns. From here you can either add the onClientClick code to the template directly or search for the control on the row data bound event of the gridview.
Below is a way to get this done in a re-usable way without having to change each pages GridView command columns.
Strategy
We want to create a shared (VB) or static (C#) method that can be used by all pages within the web application. We will still utilise the RowDataBound event but we will encapsulate this functionality and call the method with a single line of code.
We will be using a ObjectDataSource connected to a Gridview to demonstrate this.
Implementation
I have extracted a slimmed version of the Categories table in Northwind and saved it as XML for this article. I have provided the source code so I'll not be covering the simple Data object class.
Firstly we want to create a re-usable routine that can be called from any page. Each page may vary the command column location on the GridView. Some users like this to be the first column on the left, others prefer the 'Edit', 'Delete' commands to be on the right. Therefore we need to keep this flexible to prevent excessive control look-ups on the RowDataBound events. The completed code is shown below:
Public NotInheritable Class GridViewHelper
Private Sub New()
End Sub
''' <summary>
''' Adds a javascript pop-up delete confirmation message to ANY LinkButton control
''' found in the passed column index that has a text value of Delete
''' </summary>
''' <param name="deleteColumnIndex">the column you wish to
''' restrict your search for a Delete LinkButton control</param>
''' <param name="e">The GridView row agruments when the GridView is in RowDataBound</param>
''' <remarks></remarks>
Public Shared Sub AddDeleteConfirmation(ByVal deleteColumnIndex As Integer, ByVal e As GridViewRowEventArgs)
'' Check you are only working with Data Rows, not headers/footers
If e.Row.RowType = DataControlRowType.DataRow Then
'' Loop through each control in the GridView command column
For Each ctl As Control In e.Row.Cells(deleteColumnIndex).Controls
Try
'' If it is a LinkButton with a text value of Delete then add the confirmation message.
If TypeOf (ctl) Is LinkButton Then
Dim DeleteLink As LinkButton = CType(ctl, LinkButton)
If DeleteLink.Text.ToLower = "delete" Then
DeleteLink.Attributes.Add("onclick", _
"return confirm(""Are you sure you wish to delete this record?"")")
End If
End If
Catch ex As Exception
' just ignore for deployment. May want to re-throw for development.
End Try
Next
End If
End Sub
End Class
Now, we want to hook this up to our GridViews. We will use the RowDataBound event to loop through the LinkButton controls on the command column of the GridView. If it finds one that has a text value of 'delete' it will add the necessary JavaScript to provide users with the delete confirmation message box. The code is shown below:
Protected Sub GridView1_RowDataBound(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView1.RowDataBound
'' The command buttons are in the first column (zero index)
GridViewHelper.AddDeleteConfirmation(0, e)
End Sub
The finished article will produce the following results:
Summary
As you can see this provides the basis of a generic delete pop-up confirmation routine that you can use without having to convert your command column to template fields. You can further extend this by passing 'unique' row information to provide a more detailed message to the client.
Thursday, 11 October 2007
Create A Data Access Layer (DAL) without writing any code
Last updated : 11th October 2007
Background
You spend a lot of your time writing very repetitive code just to grind out standard objects that mirror your database tables and all of the CRUD operations that goes along with this. If only there was a way to take care of this and allow us to dive straight into the business logic and UI functionality. Well this article will present one solution.
Strategy
We will be using the Repository Factory Package from the Microsoft Patterns & Practices Team to create our data access layer. The Repository Factory is a CodePlex project that integrates with Visual Studio and it can be used to generate all our business objects, stored procedures and data access code.
Implementation
To implement the features in this article you require the following.
Pre-requisites:
Visual Studio with SP1 (Need to support web applications projects)
A build of Repository Factory Package
Guidance Automation Tools for Visual Studio (GAT and GAX July 2007 CTP)
Note: - This article cannot be used with any of the Visual Studio Express Versions.
In this example we will create a simple web application that can be used to monitor the power capacity of computer racks based in data centres. The database schema is very straightforward and concurrency is not an issue.
Each Rack can accommodate a number of servers to a maximum power capacity. Each Rack is housed in a Cabinet that can contain up to 20 Racks. Each Cabinet is located on a Floor and in a particular Location. Each Rack can be assigned to a customer and may have one or more power sources.
At this stage we have the database designed. So now we want to generate all the grunt code so we can go straight into coding the business rules. So how doe we do it?
Step 1 - Download and Install the Repository Factory Package
Go to http://www.codeplex.com/RepositoryFactory
Click on the releases Tab and download the latest MSI release.
Close any Visual Studio windows and run the MSI. This will Install the Repository Package on your machine.
Step 2 - Install Guidance Automation Extensions and Guidance Automation Toolkit
Download and install both of these Microsoft products to let you run guidance packages with Visual Studio.
GAT and GAX July 2007 CTP Download.
Step 3 - Create the Visual Studio Project Structure
The Repository Package generates all the user code in C#. However if you are a VB.Net developer then don't panic you can still use the classes without having to write a line of C# code.
VB.Net developers: You need to create 2 web applications. 1 C# (Host project for Repository Factory) and 1 VB.
After you have completed all your code generation tasks all you need from the C# project is the web.config file. Copy this to the VB web application and then you can delete the C# web application. Not ideal, but workable.
Create a visual studio solution with the following 3 projects.
- User Interface Web Project (C# and VB) - Start-up Project
- Data Access Project (Must be C#)
- Business Logic Layer Project (C# or VB)
Figure 2.
Step 4 - Enable the Guidance Package for the solution
Within Visual Studio, select Tools and then click on the Guidance Package Manager.
The Following screen will pop up. Click on the Enable/Disable Packages button.
From here you will be presented with packages that are installed on your machine. There may be more or less displayed depending on which packages you have downloaded. Choose the Repository Factory package.
Step 5 - How to use the Repository Factory
Now that you have enabled the package there are a number of 'Recipes' to choose from. I tend to think of Recipes as glorified macros that execute and perform a series of tasks for you. The Repository package contains the following
Our workflow with this package will be:
- Specify Project Responsibilities
- Add a database connection
- Create CRUD stored procedures
- Create Business Entities
- Create Data Repository Classes
Step 6 - Specify Project Responsibilities
For the package to generate our data access code and classes it needs to know into which projects will it place the final code. If you think again of a macro scenario then you are simply supplying the parameters. The package need to know :
The Host Project - This will be our web application
The Data Access Project - This will be our PowerCapacityDal class project
The Business Entities Project - This will also be our PowerCapacityDal class project
*Note: You could create a new project for your business entities but I prefer just to put them in the DAL
Right click the PowerCapacityUI project. You will see that a new 'Repository Factory' menu option has appeared. Select the specify project responsibility option.
From here, select Host Project and click the Finish button.
Next right click the PowerCapacityDal project. Again, select Repository Factory and then specify the project responsibility option. This time choose both Business Entities Project AND Data Access Project. This tells the package that you want to store all the generated code into this project.
Step 7 - Add a database connection
We need to tell the package which database we would like to connect to. This will be stored in the web.config file of the web application. So, right click the PowerCapacityUI project and select Add database connection.
Type in PowerCapacityConnection in the Connection Name field. And click on the builder button (...) to the right side of the Connection String field.
Step through the wizard to create the connection string to the database.
Step 8 - Create CRUD stored procedures
The package will create a sql file that contains scripts to generate all the necessary stored procedures. This file can be run against the database.
Right click on the PowerCapacityDal project and select create CRUD stored procedures.
This will open a wizard. Select the connection name from the drop down list.
Select all the tables that you wish to generate stored procedures for.
The package will generate all the necessary stored procedures. Simply click next to select the default names and CRUD operations.
Specify a file to save the script produced.
We now have a file in the PowerCapacityDal that can be executed against our database. Use SQL Query Analyser or equivalent to execute this script. (see below for SQL 2000 users)
Note for SQL 2000 Users
The SQL generated is in SQL Server 2005 syntax. If your stuck on SQL 2000 don't panic, you can edit this file to get it to run. Remove the RethrowError SP at the top and just find and replace 2005 specific syntax.
Find: sys.objects
Replace: sysobjects
Find: BEGIN TRY
Replace:
Find: END TRY
Replace:
Find: BEGIN CATCH
Replace:
Find: END CATCH
Replace:
Find: EXEC RethrowError;
Replace:
At this stage you should have all your select, update, insert and delete stored procedures created on the database just by going though a few wizards. Easy or what!
Step 9 - Create Business Entities
The next stage is to get the Repository Factory to generate our Business Entities for us. We can then use these objects within our web application to hold and manipulate the data.
Right click the PowerCapacityDal project and select the create business entities from database recipe.
Again, choose the connection name from the drop down list.
From here you can customise which elements are needed within the application. I have just accepted the default to include all fields in the database. Each field is mapped to a property on the Business Entity with the relevant get and set statements created for you.
The final stage is to customise the objects themselves. You may want to change the names and determine if the properties should only have a get method for the given attribute (read only). I have just accepted the default of all.
Click on the Finish button and the Repository Factory will generate all your business entity classes.
You can have a look the class view to see the properties that have been exposed.
Step 10 - Create Data Repository Classes
By this stage we have our Business Entities and all of our database stored procedures. The next stage is to create classes that we can use to execute commands against our database.
Right click the PowerCapacityDal project and select the Create data repository classes from business entities.
This again starts a wizard to walk you through the process. First step is to choose your connection name from the dropdown list. You should be getting used to this by now ;-)
Then, just confirm where the wizard can find the business entities. We chose to put them in the PowerCapacityDal project but as mentioned before you can create a separate project for these objects.
Next, click on the objects that you want to generate repository classes for. I have clicked on them all.
Next you need to tell the Factory which operations you are interested in. By default there is a GetAll, Insert, Update and Delete.
I also need a GetOne for each object e.g. Bring me back a Cabinet object based on a Cabinet Id that I pass. So click on the Add... button and select Get One.
Next, specify which stored procedure to use to get the one that you want. For me I want to use GetCabinetByCabinetId.
Next, you need to confirm the mapping between output columns and the business object properties. This has been done for me as the field names in the database are the same as my Cabinet business entity.
Click Finish.
You need to do this for each object that you require a GetOne operation. Perhaps in future releases this behaviour will be defaulted. Now you see all the operations that will be generated. Notice the GetCabinetByCabinetId operation below.
When you click Finish this time the Repository Factory will go and create all the classes needed for your data access layer.
You will notice that the Repository Factory has referenced all the necessary dll's that are required. It also uses the Enterprise Library for that actual database calls so this is copied in for you. Each Business Entity get's its own folder with the necessary classes for each CRUD operation. I won't go into the detail here of the programming patterns used as you can just think of it as a black box for now.
So there you have it. A complete data access layer created without you having to write a single line of code. The only thing left is to use it.
Step 11 - Consuming the Data Access Layer
First build the solution to make sure you don't have any errors.
Next, right click your PowerCapacityBll project add a reference to the PowerCapacityDal project.
Next, Add a reference to the Repository dll. This should be in the following folder:
C:\Program Files\Microsoft Patterns & Practices\Data Access Guidance Package Setup\
Next, in your PowerCapacityBll project add a new class called CabinetManager
Add the following code to get a list of cabinet objects from the database and save a cabinet object back to the database.
using System;
using System.Collections.Generic;
using System.Text;
using PowerCapacityDal;
using Microsoft.Practices.Repository;
namespace PowerCapacityBll
{
public static class CabinetManager
{
/// <summary>
/// Get all cabinet objects
/// </summary>
/// <returns>A generic list of cabinet objects</returns>
public static List<Cabinet> GetAll()
{
// The Repository creates the proper object based on the interface provided
// This mapping is held in the web.config file
ICabinetRepository cabinetRepository = RepositoryFactory.Create<ICabinetRepository>("PowerCapacityConnection");
// In real life you may want to check the identity of the caller or perform
// other operations prior to returning the list
return cabinetRepository.GetAllFromCabinet();
}
/// <summary>
/// Save a cabinet object to the database
/// </summary>
/// <param name="cabinet">the cabinet object to save</param>
public static void Save(Cabinet cabinet)
{
ICabinetRepository cabinetRepository = RepositoryFactory.Create<ICabinetRepository>("PowerCapacityConnection");
cabinetRepository.Save(cabinet);
}
}
}
The Repository Factory 'works out' which class to create based on mapping information held in the web.config. You don't have to worry about this as the Repository Factory deals with it. I show this below for information only.
<repositoryFactory>
<repositories>
<add interfaceType="PowerCapacityDal.ICabinetRepository, PowerCapacityDal" repositoryType="PowerCapacityDal.CabinetRepositoryArtifacts.CabinetRepository, PowerCapacityDal" />
<add interfaceType="PowerCapacityDal.ICustomerRepository, PowerCapacityDal" repositoryType="PowerCapacityDal.CustomerRepositoryArtifacts.CustomerRepository, PowerCapacityDal" />
<add interfaceType="PowerCapacityDal.IFloorRepository, PowerCapacityDal" repositoryType="PowerCapacityDal.FloorRepositoryArtifacts.FloorRepository, PowerCapacityDal" />
<add interfaceType="PowerCapacityDal.ILocationRepository, PowerCapacityDal" repositoryType="PowerCapacityDal.LocationRepositoryArtifacts.LocationRepository, PowerCapacityDal" />
<add interfaceType="PowerCapacityDal.IPowerSourceRepository, PowerCapacityDal" repositoryType="PowerCapacityDal.PowerSourceRepositoryArtifacts.PowerSourceRepository, PowerCapacityDal" />
<add interfaceType="PowerCapacityDal.IRackRepository, PowerCapacityDal" repositoryType="PowerCapacityDal.RackRepositoryArtifacts.RackRepository, PowerCapacityDal" />
</repositories>
</repositoryFactory>
This is how you can construct your Business Layer. Now that you see how easy it is to call your Data Access methods all we need to do is link up the user interface.
Step 12 - Implement the User Interface
By this stage we have a fully functioning data access layer created entirely without writing a single line of code. We have added a business logic layer that wraps over this allowing you to implement any business rules that are required.
Next, we will create a web page with a GridView control that will bind to a method in the PowerCapacityBll that will in turn call a data access layer method in our PowerCapacityDal project. A nice n-tier design.
Right click the PowerCapacityUI web project and add a reference to the PowerCapacityBll project so we can call its methods.
In the code behind page of the default.aspx page add the following code to the page load event.
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GridView1.DataSource = PowerCapacityBll.CabinetManager.GetAll();
GridView1.DataBind();
}
}
And thats it. What's more, it's up to you to decide how much you want from the Repository Factory package.
Summary
Hopefully this article will encourage you to download and use the Repository Factory package. This package is still work in progress so I'm sure the team would welcome any feedback you have on it but already it provides many benefits.
As you have seen, it's possible to have Visual Studio generate all of your data access code using this package. Think how much time you can save and how little 'grunt' coding you are required to write. At a bare minimum you can use this to generate all of your database stored procedures. You can make the choice whether you want to implement the business entity objects and data access code yourself or extend and modify the code that is generated.
Further Reading
Dave Hayden has a number of good blog entries and screencasts on the Repository Factory. You can find more details at the following links:
David Hayden - Repository Factory Blog
© Bracora 2013 . Powered by Bootstrap , Blogger templates and RWD Testing Tool