After running into a lot of testing problems with my current architecture in ASP.NET, I decided that it was time to look into an IoC Framework. I have heard the marvels of how it makes code so much more isolated and clean (SOLID), but I couldn’t see my code ever getting to this point. Because this project has a ton of legacy code, there was little incentive to add anything else when it was already so highly coupled. I needed to take a step back and look at it from a different perspective.
I understood how IoC (Inversion of Control) Frameworks worked and knew how this technique is used, but I didn’t know how I could integrate it in my application. Most of my action/service methods were static classes that instantiated new objects to return what I needed.
Here’s an example:
On the member page, there is a list of upcoming conferences that calls this code:
Public Class ConferenceService
Public Shared Function GetUpcomingConferences(ByVal DisplayDate As DateTime) As IList(Of ConferenceEntity)
Using repo As New Repository
Dim bucket As New RelationPredicateBucket(ConferenceFields.IsActive = True)
Dim startfilter As New PredicateExpression(ConferenceFields.StartDisplayDate = System.DBNull.Value)
startfilter.AddWithOr(ConferenceFields.StartDisplayDate <= DisplayDate)
bucket.PredicateExpression.AddWithAnd(startfilter)
Dim endfilter As New PredicateExpression(ConferenceFields.EndDisplayDate = System.DBNull.Value)
endfilter.AddWithOr(ConferenceFields.EndDisplayDate >= DisplayDate)
bucket.PredicateExpression.AddWithAnd(endfilter)
Dim sorter As New SortExpression(ConferenceFields.StartDateTimeUtc Or SortOperator.Ascending)
Return repo.GetCollection(Of ConferenceEntity)(bucket, sorter).ToList()
End Using
End Function
End Class
I am using LLBLGen (Adapter) as my DAL so the Repository class is responsible for calling a new object to create what LLBLGen calls an EntityCollection. This is easy to call because all I have to do is call ConferenceService.GetUpcomingConferences(Date.Now()) to get a list of upcoming conference. This is very hard to test though without using something like Typemock.
In order to start my refactoring to be able to integrate StructureMap, I needed to look at how my ConferenceService was being constructed. There are 5 constructor calls in this method alone. You can imagine how the rest of the application looks like.
Adding and Setting Up StructureMap
First, download StructureMap from the website. I downloaded version 2.5.3. Next was to look how to get StructureMap set up on the web project (found here http://structuremap.sourceforge.net/ConfiguringStructureMap.htm). I had to use for VB and lambdas are tricky so this is how the Bootstrapper was defined:
Imports StructureMap
Imports StructureMap.Configuration.DSL
Public Class Bootstrapper
Public Shared Sub BootstrapStructureMap()
ObjectFactory.Initialize(AddressOf ConfigStructureMap)
End Sub
Private Shared Sub ConfigStructureMap(ByVal x As IInitializationExpression)
x.AddRegistry(New RepositoryRegistry)
End Sub
Public Class RepositoryRegistry
Inherits Registry
Overrides Protected Sub configure()
'Will be used later after we refactor
'ForRequestedType(Of IRepository)().TheDefaultIsConcreteType(Of Repository)().CacheBy(Attributes.InstanceScope.Hybrid)
End Sub
End Class
End Class
And then, in our Global.asax, on the Application_Start, call the Bootstrapper.BootstrapStructureMap() method. That is all we need for StructureMap config for now. That will get us started in the right direction.
Refactoring
The first refactoring is to Extract an Interface from the Repository class. If you have a refactoring tool such as Refactor Pro! or Resharper, this is made very easy. I called mine, IRepository. This will make the Repository class implement the newly created interface. We can now start on the ConferenceService.
The next refactoring is to create a constructor for the ConferenceService class that takes an IRepository as a parameter. This will help us create Constructor Injection.
After we add the parameter, we can remove the code that uses the concrete Repository object.
Public Class ConferenceService
Private _repo As IRepository
''' <summary>
''' Initializes a new instance of the ConferenceService class.
''' </summary>
''' <param name="repo"></param>
Public Sub New(ByVal repo As IRepository)
_repo = repo
End Sub
Public Function GetUpcomingConferences(ByVal DisplayDate As DateTime) As IList(Of ConferenceEntity)
Dim bucket As New RelationPredicateBucket(ConferenceFields.IsActive = True)
im startfilter As New PredicateExpression(ConferenceFields.StartDisplayDate = System.DBNull.Value)
startfilter.AddWithOr(ConferenceFields.StartDisplayDate <= DisplayDate)
bucket.PredicateExpression.AddWithAnd(startfilter)
Dim endfilter As New PredicateExpression(ConferenceFields.EndDisplayDate = System.DBNull.Value)
endfilter.AddWithOr(ConferenceFields.EndDisplayDate >= DisplayDate)
bucket.PredicateExpression.AddWithAnd(endfilter)
Dim sorter As New SortExpression(ConferenceFields.StartDateTimeUtc Or SortOperator.Ascending)
Return _repo.GetCollection(Of ConferenceEntity)(bucket, sorter).ToList()
End Function
End Class
The next piece of code is back in our Bootstrapper that we have to uncomment.
'ForRequestedType(Of IRepository)().TheDefaultIsConcreteType(Of Repository)().CacheBy(Attributes.InstanceScope.Hybrid)
Now, instead of calling the static method as such:
Dim upcomingConf as IList(Of ConferenceEntity) = ConferenceService.GetUpcomingConferences(Date.Now())
We can call it as such:
Dim upcomingConf as IList(Of ConferenceEntity) = new ConferenceService(ObjectFactory.GetInstance(Of IRepository)()).GetUpcomingConferences(Date.Now())
This allows us to decouple the concrete Repository object and allows for testing out our method. We’ll be able to write tests now to cover our legacy code, and allow for more flexible code.
Posted
11-25-2009 10:51 PM
by
Stephen Wright