首先,按我自己的理解,简单说一下TDD 和 BDD 有何区别?
首先是思路上的区别, 传统的 TDD 关注的是接口是否被正确地实现了, 所以通常每个接口有一个对应的单元测试类。而BDD是通过故事模板和场景,描述产品在用户操作时的具体功能表现,有点类似传统的Use Case。写BDD的测试时更像测试人员平常写的测试用例。由于BDD一般是使用自然语言来表达,所以就算不是开发人员也看得懂。以下就是一个BDD的例子:

Behat是PHP下的一个BDD框架,可以配合PHPUnit来使用。
更详细的介绍可以去behat的官方网站查看,http://behat.org/
在对Web应用做自动化测试时,碰到界面测试时,无论是使用TDD和BDD都会遇到一些麻烦。
通常有两种解决方案:
1. 绕过GUI的测试。由于大部分的Web开发都会使用MVC框架,根据MVC的原理,Controller只需将正确的数据传递给View就算完成任务了。因此,我们在做测试时只需只需验证Controller是否把正确的数据传递给指定的View就可以了,至于View能否正确展现数据,那就要靠集成测试来验证了。
2. 使用一些GUI测试框架或浏览器模拟器。今天我要讲的使用浏览器模拟器。
浏览器模拟器大概可以分为两种:
- Headless browser emulators - browser emulators, that can be executed fully without GUI through console. Such emulators can do HTTP request and emulate browser applications on high level (HTTP stack), but on lower level (JS, CSS) they are totally limited. But they are much faster than real browsers, cuz you don’t need to parse CSS or execute JS in order to open pages or click links with them.
- 简单说这种模拟器比真浏览器要快速,但无法处理CSS或JS。
- In-browser emulators - this emulators works with real browsers, taking full controll of them and using them as zombies for their testing needs. This way, you’ll have standart fully-configured real browser, which you will be able to controll. CSS styling, JS and AJAX execution - all supported out of the box.
- 这种模拟器更接近真实浏览器,可以处理CSS和JS,但是速度相对来说会比第一种慢。
但其实在真正做测试时,一般两种模拟器都需要用到。因为你不能全部都使用In-browser模拟器做测试,因为当测试比较多时,那速度是无法忍受的。但是当你需要测试类似Ajax请求时,你又不能使用Headless模拟器,必须使用In-browser模拟器。
由于这两种模拟器是很不同的,各自拥有不同的API,同时使用两套API来做测试是很麻烦的。
这时MINK的优势就出来了,MINK是一个浏览器模拟器抽象层,用同一套API同时支持In-browser和Headless模拟器。
下面就介绍如何使用Behat和MINK对WEB应用做BDD。
1. 安装Behat
$ pear channel-discover pear.symfony.com
$ pear channel-discover pear.behat.org
$ pear install behat/behat
2. 安装MINK
$ pear channel-discover pear.behat.org
$ pear install behat/mink-beta
3. 新建一个sosobaike目录,进入sosozhidao目录,执行behat --init命令
$ mkdir sosobaike
$ cd sosobaike
$ behat --init
这时behat会帮你创建一个features目录和features/bootstrap/FeatureContext.php文件
FeatureContext.php是真正存放单元测试的方法的地方
4. 创建features/search.feature文件

但如果我们没有使用MINK,直接运行behat命令时,behat会建议你在features/boostrap/FeatureContext.php加上一些测试方法,你需要自己在这些测试方法使用累似Selenium之类的脚本进行断言。

5. 但如果你使用MINK的话,事情就简单多了,只需修改features/bootstrap/FeatureContext.php文件,
加入两行代码。
a) require_once 'mink/autoload.php';
b) 将FeatureContext的父类由BehatContext修改为Behat\Mink\Behat\Context\MinkContext
6. 执行behat -dl命令,如果一切正常的会,你将会看到累似以下的结果

以上其实就是MINK支持的一些API。
7. 执行命令behat features/search.feature,你将会看到以下累似的结果

到此,就算完成了一个BDD测试,整个过程做起来很简单,并不比写一个测试用例所耗费的时间长,并且获得测试用例所没有的功能 -- 可以自动执行去验证功能。
因此,个人觉得做测试的同学可以尝试使用此方法代替测试用例,可以节省不少时间。
对于开发的同学,我个人认为对Web应用做BDD或TDD时,应该使用绕过GUI的测试的方案,也就是只测试Controller的方案。
原因有以下几点:
1. 开发人员和测试人员对测试的运行速度要求是不一样的,测试人员对测试的运行速度没有太高的要求,他们可以把测试起起来,然后去做其他事情,一会回来看结果就好了。但是开发人员就不一样了,他们每修改一处代码都需要运行一次完整的测试,以确保自己的修改没有破坏其他功能。如果运行一次完整的测试很耗时间的话,那么就会有开发人员想做单元测试了。使用MINK虽然比在真正的浏览器里访问要快,但是由于始终是访问真正的产品代码,也就因为着很多测试会访问数据库,一旦测试很多的话,所有测试跑完是很耗费时间的。
2. 由于使用MINK是会访问真正的产品代码,那就意味者需要配置相关环境(依赖库,数据库等),甚至需要恢复数据库到初始状态。只要有任何一个配置环节出错,都有可能引起原来通过的单元测试会无法通过。这是无法达到一个好的单元测试的标准的。好的单元测试必须任何时候运行都必须保证运行结果是一致的。要达到这个效果,那就不能依赖外部条件(例如:数据库、网络),对于这些外部条件应该使用代码去尽量模拟。
首先简单介绍以下PC遥控器是个神马东西。
你只需在被控电脑端安装上 PC遥控器,设置好微博登录账号(最新版同时支持新浪和腾讯微博)。这样无论你在何处何地,只要发个微博,就可以对你的电脑进行关机、重启、关闭显示器、查看屏幕截图等操作!简单方便!非常实用!
PC遥控器目前是靠我业余时间往里面添砖加瓦,但是一个人的力量和精力是有限的,为了让这款微博小应用的功能更加丰富和实用,我决定把PC遥控器开源,希望园里面有朋友能为PC遥控器贡献点代码。
PC遥控器的官方博客:http://suchuanyi.sinaapp.com/
源码是托管在github上面,地址是:https://github.com/terryso/PCRemote
对PC遥控器有兴趣,想贡献代码的园友,你只需把你的源码提交到你自己的仓库,然后在github上面通知我去拉就可以了。
PC遥控器的源码里面还包含了一个我自己写的微博SDK,目前支持新浪和腾讯微博的API,只不过目前只包含PC遥控器用到的几个API而已。
不过在这个SDK的基础上新增一个API也是很简单的,对这个SDK有兴趣的园友也可以看看,给些意见。
PC遥控器是一款新浪微博的PC端小工具,有了这个小工具,你就可以随时随地通过发微博来控制你家里的电脑。
使用场景:
有时你要急着出门,但是你想下载的电影还没下载完成,你希望回来的时候可以马上观看。这时PC遥控器就发挥作用了,你出门前只需打开PC遥控器,设置好微博账号后就可以了。两个小时后,你只需发一条微博:屏幕截图$$看看我的电脑在干什么。一分钟后,PC遥控器就会把你家里电脑当前的屏幕截图发到你的微博上。这时,你看到电影已经下载完成,马上发一条微博:关机$$省电,这时PC遥控就会帮你关闭你的电脑了,既省电又环保。
安装须知:
- WIN7直接安装就可以使用
- XP需要安装.net framework 3.5
- 下载地址:http://pcremote.sinaapp.com/publish.htm
- 产品主要:http://suchuanyi.sinaapp.com
命令格式:命令 + $$ + 任意内容 (任意内容就是指随便写什么都可以)
例子:关机$$今天天气真是哈,哈,哈
目前支持的命令列表:
| 命令 | 作用 |
|---|---|
| 关机 | 默认5分钟后关闭计算机 |
| 注销 | 注销计算机 |
| 重启 | 重启计算机 |
| 关闭显示器 | 关闭显示器 |
| 减小音量 | 减小20%的音量 |
| 加大音量 | 加大20%的音量 |
| 静音 | 将计算机设置为静音 |
| 终止关机 | 取消关闭计算机 |
| 取消静音 | 取消静音 |
| 屏幕截图 | 把计算机当前的屏幕截图发到微博上 |
In my first post, I explained a little bit about how sagas can be leveraged to deal with the problem of nested transactions—”transactions” that span more than a single message. There were a few community questions related to me redefining the concept of a saga. That’s definitely not what I’m trying to do. Per my understanding, a saga sits there and handles multiple messages and exists to coordinate multiple distinct transactions and perform compensating actions relative to the entire “transaction” as a whole. While by definition a saga may not be responsible for message ordering and de-duplication of messages, as a side-effect of implementation it can handle those things without difficulty. If anything, the way I’m utilizing sagas is perhaps somewhat too narrow—a selected subset of the overall meaning and capability of a saga. Regardless, the main concern herein is how to apply event sourcing as the persistence mechanism for sagas. One final thought before we look at some code: why should we even bother using event sourcing for sagas? Why not just persist the state and be done with it? That’s a great question. In fact, there isn’t anything wrong with persisting the state and moving forward, per se. But as we have seen when event sourcing is applied to aggregates, it gives us, as programmers, the ability to re-evaluate our model, adapt it, and even re-implement it in a completely different way. Furthermore, in the community there have been various examples of the benefits of testing aggregates through watching the events that are persisted after handling a command. Anyone who has done this can see that testing in this way is much more natural and it avoids the brittle tests that often exist when doing purely state-based testing against aggregates. This same idea can be applied to sagas. In essence, applying event sourcing to sagas allows our sagas to be more agile. Okay…now the code. How do we leverage event sourcing to implement a saga? It’s actually not that bad. Let’s examine at what ISaga might look like: This should look somewhat similar to the interface of an aggregate root that utilizes event sourcing. The primary difference is that we now have an additional set of methods related to getting and clearing undispatched messages. When a message arrives to be processed it is routed into the appropriate message handler whose “Handle” would look something like this: Granted, this could potentially be inlined into the saga itself, but for our purposes hear, I am choosing to keep the concept of handling a message separate from giving it to the saga. We’re not attempting to use any kind of unit of work in this example. We simply get a message, provide it to the saga, and save the saga using a repository. The Transition method of such a saga is fairly straightforward: Typically the base class would be responsible for receiving event messages from the caller (in this case the bus message handler) and then routing it either dynamically or statically to the specific Transition method capable of working with the specific event message. Be aware that we’re not using a 100% pure state machine here. There are two member variables—“orderReceived” and “orderTotal”. These variables give the saga a little bit of “memory”. We will want those variables later. “orderReceived” is used to help us determine if the state machine can transition to a completed state, while “orderTotal” will be used on the resulting command message once we transition to a Completed state. (Implementation note: Rather than having separate boolean variables to track each message that is received, you can use an enum and concatenate it using bitwise operations, e.g. orderProgress = orderProgress | Progress.OrderReceived). State Machines, Transitions, and Memory In multi-message scenarios, especially as the number of messages being handled grows, it becomes increasingly difficult to use a pure state machine to know where things are without having some kind of “memory”. This is like the “vending machine state machine” problem. In that problem, every single pathway for every possible permutation of coin must be explicitly modeled up to the price of the item. It gets nasty fast, especially as the different kinds of coins such as nickels and pennies are accepted and the price of the item increases. By utilizing a little bit of “memory”, we are able to side-step a large amount of complexity to keep things simple. This is where the “orderReceived” variable comes into play in the sample code above. By using it along with other memory variables, we can leverage the best parts of a state machine: The above code is an example of a state machine called Stateless, which is still actively developed as compared to SimpleStateMachine which that author has basically abandoned. The above code says that when we are in the Open state and the OrderReceived trigger occurs, transition to the “Completed” state when the order has been received AND payment has been received. When we transition to the Completed state is when something observable happens: In the above code, we have configured the state machine to invoke the OnCompleted event when the state transitions to Completed. When that occurs, we add a new message to dispatch into our list of undispatched messages. Later, during the repository “Save” operation, we’ll perform the actual dispatch. Message Idempotency In this above example, no matter how many times the OrderReceived message is handled, it will only result in the state transition to Completed once. For messages that increment/decrement values, such as CashDeposited and the like, we could easily keep track of the identifier for the message and only increment the value if the message hasn’t been handled before. Persistence To load the saga from persist storage, we grab the committed events and transition a new instance of the particular type of saga: To persist a saga, we do the following: In case of an optimistic concurrency exception, our messaging infrastructure will re-deliver the message and we would rebuild the saga, but this time with the committed events from the other, competing process that caused the concurrency exception. Then, we would re-apply the message that was the victim of the concurrency exception. What About “Now”? One quirk related to event sourcing is the concept of “now”. Because you’re rebuilding the saga from scratch every time, you can’t persist what “now” is. If you use DateTime.UtcNow just anywhere inside of your saga, it can result in subtle bugs because DateTime.UtcNow changes each time you rebuild the saga from the events. A rule of thumb is to ensure that each incoming event message always carries with it the concept of time, e.g. when the order was accepted. Any timeout messages that occur can also carry the time at which they timed out. Lastly, any messages that are going to be dispatched can get DateTime.UtcNow. Conclusion Why use event sourcing for sagas? Options. Just like event sourcing for aggregates opens up a world of possibilities, it can do the same thing with sagas. Even if you don’t buy into the idea of using event sourcing with sagas, do try to understand the importance of sagas as a pattern and where they fit in a messaging-oriented world. I have applied the above concepts, including ISaga, SagaBase, and SagaRepository, into myCommonDomain project on GitHub. Perhaps this isn’t quite the best place for the code, but I wanted to get something out for people to see.public interface ISaga
{
Guid Id { get; }
long Version { get; } // for optimistic concurrency
void Transition(object message);
ICollection GetUncommittedEvents();
void ClearUncommittedEvents();
ICollection GetUndispatchedMessages();
void ClearUndispatchedMessages();
}public void Handle(OrderReceivedEvent message)
{
var sagaId = message.OrderId; // purchase correlation
var saga = repository.GetById
saga.Transition(message);
repository.Save(saga);
}void ISaga.Transition(OrderReceivedEvent message)
{
orderReceived = true;
orderTotal = message.Total;
stateMachine.Fire(Trigger.OrderReceived);
// to avoid duplication, these should be in a base class
version++;
uncommittedEvents.Add(message);
}stateMachine.Configure(OrderState.Open)
.PermitIf(Trigger.OrderReceived, OrderState.Completed,
() => orderReceived && paymentReceived);stateMachine.Configure(OrderState.Completed)
.OnEntry(OnCompleted);
....
private void OnCompleted()
{
undispatchedMessages.Add(new DoSomethingInterestingCommand
{
OrderId = orderId,
Total = orderTotal,
...
});
}public TSaga GetById<TSaga>
{
var saga = new TSaga(); // can be done in different ways
var stream = this.eventStore.Read(sagaId);
foreach (var @event in stream.Events)
saga.Transition(@event);
saga.ClearUncommittedEvents();
saga.ClearUndispatchedMessages();
return saga;
}public void Save(ISaga saga)
{
var events = saga.GetUncommittedEvents();
eventStore.Write(new UncommittedEventStream
{
Id = saga.Id,
Type = saga.GetType(),
Events = events,
ExpectedVersion = saga.Version - events.Count
});
foreach (var message in saga.GetUndispatchedMessages())
bus.Send(message); // can be done in different ways
saga.ClearUncommittedEvents();
saga.ClearUndispatchedMessages();
}
For starters, what is a saga? A saga is a “long-lived business transaction or process”. Okay, so what does that mean? Well, first of all, the “long-lived” part doesn’t have to mean hours, days, or even weeks—it could literally mean something as short as a few seconds. The amount of time is not the important part. It’s the fact that the “transaction”, business process, or activity spans more than one message. Udi has written several times on sagas. His articles are always worth reading. The foundational theory behind sagas is to avoid the use of blocking (and locking) transactions across lots of resources. Locks are one of the primary enemies of scalability. Late last week I was communicating with Rinat Abdullin on the CQRS Google Group about sagas and explaining how they might be implemented using event sourcing. I have spoken of this to others in the past as well. In the aforementioned thread, I didn’t go into much detail about the “how”. Hence this post. Perhaps we should explain why a saga is even necessary when we have aggregates and domain objects that manage state. Aggregates are great business objects that wrap a lot of business complexity. The problem with aggregates is that they only care about their little part of the universe. Sagas, on the other hand, are coordinating objects. They listen for what’s happening and they tell other objects to take appropriate action. Picture them as choreographers—let’s try and avoid the words orchestra and orchestration altogether. In essence, sagas listen for events and instruct other parts of the system to perform tasks based upon the events. This is juxtaposed to aggregates which are told to do something and then alert the world that they performed some action. This could be generalized into the following: Sagas listen to events and dispatch commands while aggregates receive commands and publish events. Sagas manage process. They contain business behavior, but only in the form of process. This is a critical point. Sagas, in their purest form, don’t contain business logic. During Greg Young’s workshop, he hammered this one pretty hard. Most programmers think “logic” and aren’t used to thinking “process”. Let me put it this way. If you have a saga with “if else” statements, you’ve got logic. Process is best implemented using a state machine. The state machine we use to manage process within each saga is called Stateless by Nicholas Blumhardt,the creator of Autofac. In a message-oriented world, there are two fundamental problems—ordering and duplicates. These are related to message infrastructure and its corresponding guarantees. Sagas are able to act as a kind of firewall to the outside. They encapsulate the mess and help shield the domain from much of the nasty details of reality. With message ordering, let’s consider how a typical shipping or fulfillment department might utilize sagas to help carry out their responsibilities. Let’s imagine that fulfillment is not to ship the goods until payment has been received. As messages can arrive in any order, what would happen if “PaymentRecieved” was the first message to arrive? In that situation, fulfillment wouldn’t even have a clue what to ship because they haven’t even been made aware of a corresponding order. With sagas we can easily outline the various state transitions and then dispatch commands only when the appropriate conditions are met—such as the receipt of both the OrderReceived and PaymentReceived events, regardless of the sequence in which they arrive. Looking at duplicates and idempotency with a state machine-based saga, it’s not hard to see that regardless of the number of times a message is received, it will only cause a state transition once. Think about it like a DVD player. You hit stop and then stop again. Not matter how many times you press stop, the state transition only occurs once. So it is with sagas. A message can be received multiple times but will only cause the desired state transition once. This makes duplicate messages naturally idempotent. Sweet. The other really great thing about sagas is that they understand the concept of time. For example, let’s suppose we wanted to notify first-time customers of a discount if they didn’t complete checkout within 48 hours. The saga could implement a timeout by scheduling a timeout message to be sent back to the saga after 48 hours. When the timeout message was received 48 hours later, the saga would attempt to perform the state transition related to the timeout. If the customer did something in the last 48 hours, the timeout message wouldn’t cause a state transition. But if the person had not yet completed checkout, the saga could dispatch a message to the appropriate component to take corresponding action. This component would decide the “who” and the “how”. All the saga did was to tell the component: “There hasn’t been any activity, notify the customer.” The component would then decide whether or not to notify based upon business logic, e.g. is this a first-time customer, etc. To Be Continued… Now that we’ve explained why sagas are important and how state machines can help us implement a saga which deals with the complexity of duplicate messages, out-of-order messages, and timeout messages, in Part II of this article, we’ll look at some of the details of how to use event sourcing to create testable, robust, yet fluid sagas.
Brad Wilson在他自己的博客写了有关ASP.NET MVC3依赖注入支持的一系列文章,但由于某些原因(你懂的),需要翻墙才能阅读。
为了方便自己也方便喜欢的同学,特意翻墙再拷贝回来发布,以下是这一系列文章的索引:
In ASP.NET MVC, it’s common for views to be compiled into classes. In MVC 1.0, we shipped the WebFormViewEngine as the default view engine, which allowed the user to write views using the familiar <% %> syntax from ASP.NET WebForms. The first time the view is rendered, the view file goes through a code generation step, which is compiled into a class. The WebFormViewclass is responsible for creating and executing the class that resulted from the view. In ASP.NET MVC 3, we introduced a new Razor view engine. Like the WebForm view engine, the .cshtml and .vbhtml files from Razor go through a code generation step at run-time which compiles down into classes. The RazorView class is the Razor view engine counterpart to the WebFormView class. We have introduced a new base class (BuildManagerCompiledView) and a new service (IViewPageActivator) which is responsible for instantiating instances of the view classes that were compiled from the view files. We have made the IViewPageActivator instance findable via the dependency resolver. This blog post talks about ASP.NET MVC 3 Beta, which is a pre-release version. Specific technical details may change before the final release of MVC 3. This release is designed to elicit feedback on features with enough time to make meaningful changes before MVC 3 ships, so please comment on this blog post or contact me if you have comments. When we introduced the new Razor view engine, it was clear that there was a lot of common code between the infrastructure classes of our two view engines; namely, the WebFormView and RazorView (which both implement the IView interface) had significant shared code that was centered around BuildManager. In the ASP.NET runtime, BuildManager is the class that is responsible for converting view files (like .aspx and .ascx files in WebForms, and .cshtml and .vbhtml files in Razor) into the underlying classes using code generation. BuildManager uses build providers to do most of that transformation work (which is outside the scope of this blog post). The important takeaway is that we extracted a common base class for the two view classes: BuildManagerCompiledView. The logic which creates view page class instances was centralized into this class, and the new IViewPageActivator service was introduced to allow pluggability of the creation of the view page classes. Developers which are creating view engines which use build providers and BuildManager to convert view files into classes can take advantage of this new base class for their implementation(s) of the IView interface. The new IViewPageActivator interface contains a single method for creating a view page instance: Given the controller context and the view page class type, implementers of this interface must create the view page instance. This is a “singly registered” style service introduced in MVC 3. There is no static registration point for this service as its purpose is strictly to support dependency injection; as such, the only way to register an instance of this service is through the dependency resolver. The logic in BuildManagerCompiledView consults the dependency resolver, calling GetSerivce(typeof(IViewPageActivator)) and using the provided service when present. If there is no IViewPageActivator present in the dependency resolver, we will then ask the dependency resolver to create the concrete view page type by calling GetService(viewPageType). If the dependency resolver also fails to create the concrete view page type, we finally fall back to the MVC 2 behavior of using Activator.CreateInstance to create the view page type. This is the end of the road for MVC 3 Beta. We believe we are now reasonably feature complete for MVC 3 and dependency resolution. If there are specific areas where you wish the framework would better enable dependency resolution, please don't hesitate to discuss them on the MVC Forums. Thanks for reading!View Page Activator
Disclaimer
New Base Class: BuildManagerCompiledView
Implementing IViewPageActivator
public interface IViewPageActivator {
object Create(ControllerContext controllerContext, Type type);
}
Location: IViewPageActivator
What's Next?
In MVC 1.0, we introduced IControllerFactory to allow better dependency injection of controller instances. We also provided theDefaultControllerFactory, which created controller instances with Activator.CreateInstance. Some of this is discussed in Part 2 of this series. We realized that DefaultControllerFactory was actually doing two things: turning the controller name into the controller type, and then instantiating the instance of that type. In ASP.NET MVC 3, we’ve split out the action of instantiating the controller instance into a new service: IControllerActivator. We have made the IControllerActivator instance findable via the dependency resolver. This blog post talks about ASP.NET MVC 3 Beta, which is a pre-release version. Specific technical details may change before the final release of MVC 3. This release is designed to elicit feedback on features with enough time to make meaningful changes before MVC 3 ships, so please comment on this blog post or contact me if you have comments. The new IControllerActivator signature is identical to the DefaultControllerFactory.GetControllerInstance virtual method: Developers who previously implemented IControllerFactory by deriving from DefaultControllerFactory just to override the GetControllerInstance method for dependency injection purposes should now implement IControllerActivator instead. This is a “singly registered” style service introduced in MVC 3. There is no static registration point for this service as its purpose is strictly to support dependency injection; as such, the only way to register an instance of this service is through the dependency resolver. The logic in DefaultControllerFactory was updated to consult the dependency resolver, calling GetSerivce(typeof(IControllerActivator)) and using the provided service when present. If there is no IControllerActivator present in the dependency resolver, we will then ask the dependency resolver to create the concrete controller type by calling GetService(controllerType). If the dependency resolver also fails to create the concrete controller type, we finally fall back to the MVC 2 behavior of using Activator.CreateInstance to create the controller type. In the final post of this series, we will discuss the new View Page Activator service.Controller Activator
Disclaimer
Implementing IControllerActivator
public interface IControllerActivator {
IController Create(RequestContext requestContext, Type controllerType);
}
Location: IControllerActivator
What’s Next?
ASP.NET MVC 2 introduced a new method to find value providers: the ValueProviderFactory class. Value providers are used by the model binding system in MVC to populate the values of model objects. MVC includes value providers for several common value sources (including query string, form, route data, uploaded files, and JSON postbacks); MVC Futures includes several mode (including cookies, server values, session values, and temp data values). In ASP.NET MVC 3, we have made ValueProviderFactory instances findable via the dependency resolver. This blog post talks about ASP.NET MVC 3 Beta, which is a pre-release version. Specific technical details may change before the final release of MVC 3. This release is designed to elicit feedback on features with enough time to make meaningful changes before MVC 3 ships, so please comment on this blog post or contact me if you have comments. Developers who implement this class must provide an implementation of GetValueProvider which, given a controller context, optionally returns an instance of a class which implements the IValueProvider interface. The developer may also choose to return null if there is no appropriate value provider or values. For reference purposes, there are several implementations of ValueProviderFactory in the MVC and MVC Futures source code. For a simple example, see the pair of classes: QueryStringValueProviderFactory and QueryStringValueProvider. Developers who implement this interface provide implementations of two methods: ContainsPrefix and GetValue. The former method is used to determine if there are any values in the provider with the given prefix (so the model binding system knows when to stop recursively binding). The latter method is used to get the value for a specific key. There are two implementations of IValueProvider (NameValueCollectionValueProvider and DictionaryValueProvider) will accept collections of values to be used for the value provider implementation. Most of the value providers in the MVC source code actually derive from one of these two base classes. This is a “multiply registered” style service introduced in MVC 2. The static registration point for this service is atValueProviderFactories.Factories for non-DI users. The logic in ValueProviderFactoryCollection (which implements ValueProviderFactories.Factories) was updated to consult the dependency resolver, calling GetServices(typeof(ValueProviderFactory)) and adding any services found to the list of statically registered services. Value providers all collaborate to provide values for model binding, with a “first one to provide the value wins” strategy, so registration order is important. The factories found in the dependency resolver will always run in the order they are returned from the resolver (and before the statically registered value provider factories); similarly, the value providers will be consulted in the order they are returned from the factories. Next up we’ll talk about Model Binders.Value Providers
Disclaimer
Implementing ValueProviderFactory
Implementing IValueProvider
Location: ValueProviderFactory
What’s Next?
ASP.NET MVC 1.0 introduced the IModelBinder interface. Developers who implement this interface are responsible for creating models from values obtained from value providers. In ASP.NET MVC 3, we introduced a new interface (IModelBinderProvider) which allows developers to dynamically provide implementations of IModelBinder. We have made IModelBinderProvider instances findable via the dependency resolver. This blog post talks about ASP.NET MVC 3 Beta, which is a pre-release version. Specific technical details may change before the final release of MVC 3. This release is designed to elicit feedback on features with enough time to make meaningful changes before MVC 3 ships, so please comment on this blog post or contact me if you have comments. In prior versions of MVC, developers could only register static mappings from types to model binder instances (throughModelBinders.Binders). While this satisfied simple uses, it made complex and powerful model binders a little more challenging to write. When we decided to open model binding up for dependency resolution, we knew that we were going to need to introduce a layer of indirection, since model binders are usually only valid for a limited number of types (unless you are the default model binder). We solved both problems by introducing a new interface: Developers who implement this interface can optionally return an implementation of IModelBinder for a given type (they should return null if they cannot create a binder for the given type). The system continues to rely on a “default model binder” if the system cannot find any appropriate model binder for the given type. You can change the default model binder by setting ModelBinders.Binders.DefaultBinder. Out of the box, this is set to an instance of DefaultModelBinder. There are no implementations of IModelBinderProvider in MVC. However, there is a similar system in MVC Futures from MVC 2 (in the ModelBinding folder), and there are several model binder providers that could be used to spark ideas about writing providers for MVC 3, including model binder providers for arrays, binary data, collections, complex objects, and dictionaries. This is a “multiply registered” style service introduced in MVC 3. The static registration point for this service is at ModelBinderProviders.BinderProviders for non-DI users. The logic in ModelBinderProviderCollection (which implements ModelBinderProviders.BinderProviders) consults the dependency resolver, calling GetSerivces(typeof(IModelBinderProvider)) and adding any services found to the list of statically registered services. Model binder provides are run in-order, with a “first one to provide a model binder wins” strategy, so registration order is important. The providers found in the dependency resolver will always run in the order they are returned from the resolver (and before the static registered providers). Next up we’ll look at the new Controller Activator service.Model Binders
Disclaimer
Implementing IModelBinderProvider
public interface IModelBinderProvider {
IModelBinder GetBinder(Type modelType);
}
Location: IModelBinderProvider
What’s Next?
