Thư viện tri thức trực tuyến
Kho tài liệu với 50,000+ tài liệu học thuật
© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

Pro ASP.NET MVC Framework phần 3 ppt
Nội dung xem thử
Mô tả chi tiết
<configSections>
<section name="castle"
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler,
Castle.Windsor" />
<!-- ... leave all the other section nodes as before ... -->
</configSections>
Then, directly inside the <configuration> node, add a <castle> node:
<configuration>
<!-- etc -->
<castle>
<components>
</components>
</castle>
<system.web>
<!-- etc -->
You can put the <castle> node immediately before <system.web>. Finally, instruct
ASP.NET MVC to use your new controller factory by calling SetControllerFactory() inside
the Application_Start handler in Global.asax.cs:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory());
}
At this point, it’s a good idea to check that everything still works as before when you run
your application. Your new IoC container should be able to resolve ProductsController when
ASP.NET MVC requests it, so the application should behave as if nothing’s different.
Using Your IoC Container
The whole point of bringing in an IoC container is that you can use it to eliminate hard-coded
dependencies between components. Right now, you’re going to eliminate
ProductsController’s current hard-coded dependency on SqlProductsRepository (which, in
turn, means you’ll eliminate the hard-coded connection string, soon to be configured elsewhere). The advantages will soon become clear.
When an IoC container instantiates an object (e.g., a controller class), it inspects that type’s
list of constructor parameters (a.k.a. dependencies) and tries to supply a suitable object for
each one. So, if you edit ProductsController, adding a new constructor parameter as follows:
public class ProductsController : Controller
{
private IProductsRepository productsRepository;
public ProductsController(IProductsRepository productsRepository)
{
this.productsRepository = productsRepository;
}
CHAPTER 4 ■ SPORTSSTORE: A REAL APPLICATION 99
10078ch04.qxd 3/26/09 12:52 PM Page 99
public ViewResult List()
{
return View(productsRepository.Products.ToList());
}
}
then the IoC container will see that ProductsController depends on an IProductsRepository.
When instantiating a ProductsController,Windsor will supply some IProductsRepository
instance. (Exactly which implementation of IProductsRepository will depend on your
web.config file.)
This is a great step forward: ProductsController no longer has any fixed coupling to any
particular concrete repository. Why is that so advantageous?
• It’s the starting point for unit testing (here, that means automated tests that have their
own simulated database, not a real one, which is faster and more flexible).
• It’s the moment at which you can approach separation of concerns with real mental
clarity. The interface between the two application pieces (ProductsController and the
repository) is now an explicit fact, no longer just your imagination.
• You protect your code base against the possible future confusion or laziness of yourself
or other developers. It’s now much less likely that anyone will misunderstand how the
controller is supposed to be distinct from the repository and then mangle the two into a
single intractable beast.
• You can trivially hook it up to any other IProductsController (e.g., for a different database or ORM technology) without even having to change the compiled assembly. This is
most useful if you’re sharing application components across different software projects
in your company.
OK, that’s enough cheerleading. But does it actually work? Try running it, and you’ll get an
error message like that shown in Figure 4-11.
Figure 4-11.Windsor’s error message when you haven’t registered a component
Whoops, you haven’t yet registered any IProductsRepository with the IoC container. Go
back to your web.config file and update the <castle> section:
100 CHAPTER 4 ■ SPORTSSTORE: A REAL APPLICATION
10078ch04.qxd 3/26/09 12:52 PM Page 100
<castle>
<components>
<component id="ProdsRepository"
service="DomainModel.Abstract.IProductsRepository, DomainModel"
type="DomainModel.Concrete.SqlProductsRepository, DomainModel">
<parameters>
<connectionString>your connection string goes here</connectionString>
</parameters>
</component>
</components>
</castle>
Try running it now, and you’ll find that things are working again. You’ve nominated
SqlProductsRepository as the active implementation of IProductsRepository. Of course, you
could change that to FakeProductsRepository if you wanted. Note that the connection string is
now in your web.config file instead of being compiled into the binary DLL.10
■Tip If you have several repositories in your application, don’t copy and paste the same connection string
value into each <component> node. Instead, you can use Windsor’s properties feature to make them all
share the same value. Inside the <castle> node, add <properties><myConnStr>XXX</myConnStr>
</properties> (where XXX is your connection string), and then for each component, replace the connection string value with the reference tag #{myConnStr}.
Choosing a Component Lifestyle
Castle Windsor lets you select a lifestyle for each IoC component—lifestyle options include
Transient, Singleton, PerWebRequest, Pooled, and Custom. These determine exactly when the
container should create a new instance of each IoC component object, and which threads
share those instances. The default lifestyle is Singleton, which means that only a single
instance of the component object exists, and it’s shared globally.
Your SqlProductsRepository currently has this Singleton lifestyle, so you’re keeping a single LINQ to SQL DataContext alive as long as your application runs, sharing it across all
requests. That might seem fine at the moment, because so far all data access is read-only, but
it would lead to problems when you start editing data. Uncommitted changes would start
leaking across requests.
Avoid this problem by changing SqlProductsRepository’s lifestyle to PerWebRequest, by
updating its registration in web.config:
<component id="ProdsRepository"
service="DomainModel.Abstract.IProductsRepository, DomainModel"
type="DomainModel.Concrete.SqlProductsRepository, DomainModel"
lifestyle="PerWebRequest">
CHAPTER 4 ■ SPORTSSTORE: A REAL APPLICATION 101
10. That’s not a record-breaking feat—ASP.NET has native support for configuring connection strings in the
<connectionStrings> node of your web.config file anyway. What’s advantageous about IoC is that you
can use it to configure any set of component constructor parameters without writing any extra code.
10078ch04.qxd 3/26/09 12:52 PM Page 101
Then register Windsor’s PerRequestLifestyle module in your <httpModules> node:11
<httpModules>
<add name="PerRequestLifestyle"
type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule,
Castle.MicroKernel" />
<!-- Leave the other modules in place -->
</httpModules>
If you’re later going to deploy to an IIS 7 web server, then be sure to add the following
equivalent configuration to your web.config file’s <system.webServer>/<modules> node, too
(you’ll learn more about configuring IIS 7 in Chapter 14):
<remove name="PerRequestLifestyle"/>
<add name="PerRequestLifestyle" preCondition="managedHandler"
type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule,
Castle.MicroKernel" />
This is the great thing about IoC containers: the amount of work you can avoid doing.
You’ve just accomplished the DataContext-per-HTTP-request pattern purely by tweaking your
web.config file.
So that’s it—you’ve set up a working IoC system. No matter how many IoC components
and dependencies you need to add, the plumbing is already done.
Creating Automated Tests
Almost all the foundational pieces of infrastructure are now in place—a solution and project
structure, a basic domain model and LINQ to SQL repository system, an IoC container—so
now you can do the real job of writing application behavior and tests!
ProductsController currently produces a list of every product in your entire catalog. Let’s
improve on that: the first application behavior to test and code is producing a paged list of
products. In this section, you’ll see how to combine NUnit, Moq, and your componentoriented architecture to design new application behaviors using unit tests, starting with that
paged list.
■Note TDD is not about testing, it’s about design (although it also takes care of some aspects of testing). With
TDD, you describe intended behaviors in the form of unit tests, so you can later run those tests and verify that
your implementation correctly satisfies the design. It allows you to decouple a design from its implementation,
creating a permanent record of design decisions that you can rapidly recheck against any future version of your
code base. “Test-driven development” is an unfortunate choice of name that misleads by putting the emphasis
on the word test. You might prefer the more up-to-date buzzphrase “Behavior-Driven Design (BDD)” instead,
though how that differs from TDD (if indeed it differs at all) is a topic for another debate.
102 CHAPTER 4 ■ SPORTSSTORE: A REAL APPLICATION
11. Windsor uses this IHttpModule to support PerWebRequestLifestyleModule, so that it can intercept the
Application_EndRequest event and dispose of anything it created during the request.
10078ch04.qxd 3/26/09 12:52 PM Page 102