This is a continuation article. The original article can be found here.
The converted 4-tier architecture is about having flexible views and data access layers. The view is simply a group of concrete classes that all implement something that is referenced in the presentation layer. The presentation layer instantiates the concrete views (from a different assembly) by using the IOC container.
In the presentation layer we'd create our IoC. The presentation layer references the business logic layer, so it can see all of the interfaces. So when our presentation layer needs an implementation of ICoupon, the IoC will retrieve the data access layers' implementation that is in a different assembly.
ICoupon
From Business Logic Layer:
Public Interface ICoupon Inherits CouponService.IEditableObject ''' <summary> ''' Simple on and off states that allow simpler searches. ''' </summary> ''' <remarks>Instead of being required to validate every date in an open setting, this allows ''' the class itself to handle that interpretation.</remarks> Enum ExpirationState As Integer NotExpired = 0 Expired = 1 End Enum ''' <summary> ''' Gets the coupon id. ''' </summary> ''' <value> ''' The coupon id. ''' </value> ''' <remarks>This is primarily for the database.</remarks> ReadOnly Property CouponIdentification As Integer ''' <summary> ''' Gets or sets the item. ''' </summary> ''' <value> ''' The item. ''' </value> ''' <remarks>The item property is simply a name given by the person who has the coupon. This ''' name doesn't have to be used and is purely for assistance ''' in searching for specific coupons.</remarks> Property CouponName As String ''' <summary> ''' Gets or sets the expiration. ''' </summary> ''' <value> ''' The expiration. ''' </value> ''' <remarks>This is a set expiration date for the coupon.</remarks> Property ExpirationDate As DateTime ''' <summary> ''' Gets the type of the expiration. ''' </summary> ''' <value> ''' The type of the expiration. ''' </value> ''' <remarks>The expiration type has been added to allow a quick retrieval of getting a coupon ''' that has or has not been expired.</remarks> ReadOnly Property ExpirationType As ExpirationState ''' <summary> ''' Gets or sets the quantity products required. ''' </summary> ''' <value> ''' The quantity products required. ''' </value> ''' <remarks>This is the number of products that are required before the reduced price or the ''' reduced percentage can take into affect on the transaction.</remarks> Property QuantityOfProductsRequired As Integer ''' <summary> ''' Gets or sets the quantity products reduced. ''' </summary> ''' <value> ''' The quantity products reduced. ''' </value> ''' <remarks>The quantity of products that are ''' allowed to be reduced in price.</remarks> Property QuantityOfProductsReduced As Integer ''' <summary> ''' Gets or sets at percentage. ''' </summary> ''' <value> ''' At percentage. ''' </value> ''' <remarks>The percentage of the reduction of price.</remarks> Property Percentage As Nullable(Of Integer) ''' <summary> ''' Gets or sets at amount. ''' </summary> ''' <value> ''' At amount. ''' </value> ''' <remarks>The amount of the reduction in price.</remarks> Property Amount As Nullable(Of Double) ''' <summary> ''' Gets or sets the products required. ''' </summary> ''' <value> ''' The products required. ''' </value> ''' <remarks>A list of products in which one or more may be required to have been bought in ''' order to reduce the price.</remarks> Property AvailableProductsRequired As IList(Of IProduct) ''' <summary> ''' Gets or sets the products reduced. ''' </summary> ''' <value> ''' The products reduced. ''' </value> ''' <remarks>A list of products in which the price ''' may drop for. The product has to be bought ''' for the coupon to take effect.</remarks> Property AvailableProductsReduced As IList(Of IProduct) ''' <summary> ''' Gets or sets the storage code. ''' </summary> ''' <value> ''' The storage code. ''' </value> ''' <remarks>This is a way of allowing the end users ''' to write a storage code or number.</remarks> Property StorageInformation As String ''' <summary> ''' Gets or sets the required store. ''' </summary> ''' <value> ''' The required store. ''' </value> ''' <remarks>Some coupons work only at certain stores. ''' This may be a requirement</remarks> Property RequiredStore As IStore Sub GetCouponsByExpirationState(state As ExpirationState, _ callback As Action(Of IList(Of ICoupon))) End Interface
The data access layer:
Imports CouponService Imports CouponService.Naming ''' <summary> ''' ''' </summary> Public Class Coupon Implements CouponService.ICoupon, CouponService.IEditableObject, _ System.ComponentModel.INotifyPropertyChanged, _ System.ComponentModel.INotifyPropertyChanging Public Event IsNowClean(sender As Object, e As System.EventArgs) _ Implements CouponService.IEditableObject.IsNowClean Public Event IsNowDirty(sender As Object, e As System.EventArgs) _ Implements CouponService.IEditableObject.IsNowDirty Public Event PropertyChanged(sender As Object, _ e As System.ComponentModel.PropertyChangedEventArgs) _ Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged Public Event PropertyChanging(sender As Object, _ e As System.ComponentModel.PropertyChangingEventArgs) _ Implements System.ComponentModel.INotifyPropertyChanging.PropertyChanging Private _IsDirty As Boolean Private _IsNew As Boolean Private _IsEditable As Boolean Private CouponContext As CouponContext Public Sub New(dataContext As Data.Linq.DataContext) CouponContext = dataContext _IsEditable = True _IsDirty = False _IsNew = True End Sub #Region "IEditableObject Properties" Public ReadOnly Property IsDirty1 As Boolean Implements _ CouponService.IEditableObject.IsDirty Get Return _IsDirty End Get End Property Public Property IsDirty As Boolean Get Return _IsDirty End Get Set(value As Boolean) If _IsDirty <> value Then _IsDirty = value If value Then RaiseEvent IsNowClean(Me, EventArgs.Empty) Else RaiseEvent IsNowDirty(Me, EventArgs.Empty) End If End If End Set End Property Public ReadOnly Property IsEditable1 As Boolean Implements _ CouponService.IEditableObject.IsEditable Get Return _IsEditable End Get End Property Public Property IsEditable As Boolean Get Return _IsEditable End Get Set(value As Boolean) _IsEditable = value End Set End Property Public ReadOnly Property IsNew1 As Boolean Implements _ CouponService.IEditableObject.IsNew Get Return _IsNew End Get End Property Public Property IsNew As Boolean Get Return _IsNew End Get Set(value As Boolean) _IsNew = value End Set End Property #End Region Public ReadOnly Property CouponIdentification As Integer _ Implements CouponService.ICoupon.CouponIdentification Get Return Me.CouponID End Get End Property Public Property ExpirationDate As Date Implements CouponService.ICoupon.ExpirationDate Get Return Me.Expiration End Get Set(value As Date) If value <> Me.ExpirationDate AndAlso IsEditable Then IsChanging(GetPropertyName(Function() ExpirationDate)) Me.Expiration = value HasChanged(GetPropertyName(Function() ExpirationDate)) End If End Set End Property Public ReadOnly Property ExpirationType As _ CouponService.ICoupon.ExpirationState Implements CouponService.ICoupon.ExpirationType Get If Me.ExpirationDate < Now Then Return CouponService.ICoupon.ExpirationState.NotExpired Else Return CouponService.ICoupon.ExpirationState.Expired End If End Get End Property Public Property CouponName As String Implements CouponService.ICoupon.CouponName Get Return Me.Item End Get Set(value As String) If value <> Me.Item AndAlso IsEditable Then IsChanging(GetPropertyName(Function() CouponName)) Me.Item = value HasChanged(GetPropertyName(Function() CouponName)) End If End Set End Property Public Property StorageInformation As String Implements _ CouponService.ICoupon.StorageInformation Get Return Me.StorageCode End Get Set(value As String) If value <> Me.StorageCode AndAlso IsEditable Then IsChanging(GetPropertyName(Function() StorageInformation)) Me.StorageCode = value HasChanged(GetPropertyName(Function() StorageInformation)) End If End Set End Property Public Property RequiredStore As CouponService.IStore _ Implements CouponService.ICoupon.RequiredStore Get Return Me.Store End Get Set(value As CouponService.IStore) If value.StoreId <> Me.Store.StoreID AndAlso IsEditable Then IsChanging(GetPropertyName(Function() RequiredStore)) Me.Store = value HasChanged(GetPropertyName(Function() RequiredStore)) End If End Set End Property Public Property Amount As Double? Implements CouponService.ICoupon.Amount Get Return Me.AtAmount End Get Set(value As Double?) If value <> Me.AtAmount AndAlso IsEditable Then IsChanging(GetPropertyName(Function() Amount)) Me.AtAmount = value HasChanged(GetPropertyName(Function() Amount)) End If End Set End Property Public Property Percentage As Integer? Implements CouponService.ICoupon.Percentage Get Return Me.AtPercentage End Get Set(value As Integer?) If value <> Me.AtPercentage AndAlso IsEditable Then IsChanging(GetPropertyName(Function() Percentage)) Me.AtPercentage = value HasChanged(GetPropertyName(Function() Percentage)) End If End Set End Property Public Property AvailableProductsReduced As System.Collections.Generic.IList(Of _ CouponService.IProduct) Implements CouponService.ICoupon.AvailableProductsReduced Get Return Me.ProductsReduced.ToList End Get Set(value As System.Collections.Generic.IList(Of CouponService.IProduct)) If IsEditable Then IsChanging(GetPropertyName(Function() AvailableProductsReduced)) Me.ProductsReduced.Clear() For Each prod As CouponService.IProduct In value Me.ProductsReduced.Add(prod) Next HasChanged(GetPropertyName(Function() AvailableProductsReduced)) End If End Set End Property Public Property AvailableProductsRequired As System.Collections.Generic.IList(Of _ CouponService.IProduct) Implements CouponService.ICoupon.AvailableProductsRequired Get Dim ProductsToReturn As IEnumerable(Of IProduct) = _ From p In Me.ProductsRequired Select p.Product Return ProductsToReturn.ToList End Get Set(value As System.Collections.Generic.IList(Of CouponService.IProduct)) If IsEditable Then IsChanging(GetPropertyName(Function() AvailableProductsRequired)) Me.ProductsRequired.Clear() For Each prod As CouponService.IProduct In value Me.ProductsRequired.Add(prod) Next HasChanged(GetPropertyName(Function() AvailableProductsRequired)) End If End Set End Property Public Property QuantityOfProductsReduced As Integer Implements _ CouponService.ICoupon.QuantityOfProductsReduced Get Return Me.QuantityProductsReduced End Get Set(value As Integer) If value <> Me.QuantityProductsReduced AndAlso IsEditable Then IsChanging(GetPropertyName(Function() QuantityOfProductsReduced)) Me.QuantityProductsReduced = value HasChanged(GetPropertyName(Function() QuantityOfProductsReduced)) End If End Set End Property Public Property QuantityOfProductsRequired As Integer Implements _ CouponService.ICoupon.QuantityOfProductsRequired Get Return Me.QuantityProductsRequired End Get Set(value As Integer) If value <> Me.QuantityProductsRequired AndAlso IsEditable Then IsChanging(GetPropertyName(Function() QuantityOfProductsRequired)) Me.QuantityProductsRequired = value HasChanged(GetPropertyName(Function() QuantityOfProductsRequired)) End If End Set End Property Private Sub IsChanging(propertyName As String) RaiseEvent PropertyChanging(Me, _ New System.ComponentModel.PropertyChangingEventArgs(propertyName)) End Sub Private Sub HasChanged(propertyName As String) If IsEditable Then IsDirty = True End If RaiseEvent PropertyChanged(Me, _ New System.ComponentModel.PropertyChangedEventArgs(propertyName)) End Sub Public Sub GetCouponsByExpirationState(state As CouponService.ICoupon.ExpirationState, _ callback As Action(Of IList(Of ICoupon))) Implements _ CouponService.ICoupon.GetCouponsByExpirationState If state = ICoupon.ExpirationState.Expired Then callback.Invoke(GetExpiredCoupons) Else callback.Invoke(GetNonExpiredCoupons) End If End Sub Friend Function GetExpiredCoupons() As IList(Of ICoupon) Dim CouponsToReturn As IEnumerable(Of Coupon) = _ From CurrentCoupon As Coupon In CouponContext.GetTable(Of Coupon)() _ Where CurrentCoupon.Expiration < Now _ Select CurrentCoupon Return MarkAsEditable(CouponsToReturn).ToList End Function Friend Function GetNonExpiredCoupons() As IList(Of ICoupon) Dim CouponsToReturn As IEnumerable(Of Coupon) = _ From CurrentCoupon As Coupon In CouponContext.GetTable(Of Coupon)() _ Where CurrentCoupon.Expiration >= Now _ Select CurrentCoupon Return MarkAsEditable(CouponsToReturn).ToList End Function Private Function MarkAsEditable(ByVal CouponsToMark _ As IEnumerable(Of ICoupon)) As IEnumerable(Of ICoupon) For Each CurrentCoupon As Coupon In CouponsToMark CurrentCoupon.IsNew = False CurrentCoupon.IsEditable = True CurrentCoupon.IsDirty = False Next Return CouponsToMark End Function End Class
Now this coupon class is a partial class that was generated using Linq2Sql. In other words, my presentation layer is referencing the business logic layers' ICoupon interface which is implemented in the data access layer which is the same as my Linq2Sql entity. I pass in a data context to the constructor. Now this data context concrete class is also in the Data access layer and it implements the interface IDataAccess which is in my original article.
Here is the concrete Data Access class.
Public Class DataAccess Implements CouponService.IDataAccess Private _DataContext As CouponContext Public Sub New(connectionString As String) _DataContext = New CouponContext(connectionString) End Sub Public ReadOnly Property GetDataContext As System.Data.Linq.DataContext _ Implements CouponService.IDataAccess.GetDataContext Get Return _DataContext End Get End Property Public Property Timeout As Integer Implements CouponService.IDataAccess.Timeout Get Return _DataContext.CommandTimeout End Get Set(value As Integer) _DataContext.CommandTimeout = value End Set End Property Public Function Commit() As Boolean Implements CouponService.IDataAccess.Commit Try _DataContext.SubmitChanges() Return True Catch ex As Exception Return False End Try End Function Public Function Commit(mode As System.Data.Linq.ConflictMode) As _ Boolean Implements CouponService.IDataAccess.Commit Try _DataContext.SubmitChanges() Return True Catch ex As Exception Return False End Try End Function Public Function ConnectionState() As System.Data.ConnectionState _ Implements CouponService.IDataAccess.ConnectionState Return _DataContext.Connection.State End Function Public Function OpenConnection() As Boolean Implements CouponService.IDataAccess.OpenConnection _DataContext.Connection.Open() End Function Public Sub Rollback() Implements CouponService.IDataAccess.Rollback For Each change As Object In _DataContext.GetChangeSet.Updates _DataContext.Refresh(Data.Linq.RefreshMode.OverwriteCurrentValues, change) Next For Each change As Object In _DataContext.GetChangeSet.Inserts _DataContext.Refresh(Data.Linq.RefreshMode.OverwriteCurrentValues, change) Next For Each change As Object In _DataContext.GetChangeSet.Deletes _DataContext.Refresh(Data.Linq.RefreshMode.OverwriteCurrentValues, change) Next End Sub #Region "IDisposable Support" Private disposedValue As Boolean ' To detect redundant calls ' IDisposable Protected Overridable Sub Dispose(disposing As Boolean) If Not Me.disposedValue Then If disposing Then ' TODO: dispose managed state (managed objects). End If ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below. ' TODO: set large fields to null. End If Me.disposedValue = True End Sub ' TODO: override Finalize() only if Dispose(ByVal disposing ' As Boolean) above has code to free unmanaged resources. 'Protected Overrides Sub Finalize() ' ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. ' Dispose(False) ' MyBase.Finalize() 'End Sub ' This code added by Visual Basic to correctly implement the disposable pattern. Public Sub Dispose() Implements IDisposable.Dispose ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. Dispose(True) GC.SuppressFinalize(Me) End Sub #End Region End Class
Eventually, I will be putting the whole application online when it's completed. If you have any questions please feel free to ask. I have a total of 6 assemblies. 2 of them are for automated tests. I have 1 that is testing the presentation layer that mocks the views and the DAL. My other one tests the concrete DAL classes.