Click here to Skip to main content
15,891,689 members
Articles / Web Development / ASP.NET

A Binder that Breaks the Rules

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
8 Jun 2012CPOL5 min read 4.4K  
A binder that breaks the rules

There are many binders in my big rusty toolchest. Some are good boys (and girls), others just like to misbehave. I mean, they do what they're destined for, and they do it really good, but in the process of doing it, they break one or several Holy Laws that our Holy President wants us to abide by.

Nobody complains though.

This Particular One Saves Me A Lot of Repetitive Coding

Web requests tend to contain simple values. We developers like to work with objects. We like it so much that we are even willing to create objects from simple values. In particular, we often have to retrieve an object from the database (or from the cache), using its ID. Believe it or not, I was doing it at the beginning of almost every action method, sometimes two or three times. I felt so exhausted that I would postpone writing the rest of the method until after a lunch break.

And Then I Saw the Light

A few years ago Scott Hanselman wrote an article about an IPrincipal model binder (I use it a lot as well). And I thought, hey, these binders are not just for slapping your form values together, they can do more than that!

And I wrote the EntityBinder.

This particular binder may be frowned upon by respectable developers. Binders should know their place, you know. They are meant to stay somewhere between your M, V, and C. Your database and the business layer should be a forbidden territory for them.

Ok guys, you may have your controller full of boring repetitive code. I'm done with this.

Another Bad Thing that this boy (or is it a girl?) does, is that it does two things instead of one (what?? you forgot about the Single Responsibility principle???). It serves both as a custom binder attribute and a Binder. I did it this way because I wanted to save several keystrokes writing [EntityBinder("projectId")] instead of [EntityModelBinder(typeof(EntityBinder), "projectId")]. While one can argue that the code for the binder became less maintainable, the code that used it became twice as maintainable, and that was a huge gain.

The downside is that I couldn't use Dependency Injection in that binder (at the time of the writing, I couldn't use it anyway, because it was the first version of ASP.NET MVC), so I had to resort to Service Location (and had never had any problem with that).

What Exactly Does This Shiny Binder Do, Anyway?

The binder that I'm going to show you looks at the parameter name and type and tries to guess the name of the field that holds the ID, and the type of the entity. So, given a declaration like this...

C#
public ActionResult AnketaDefinition([EntityBinder] Project project)

...it looks first for a request value named "projectId", and if it cannot find it, for a value named "Id". Then it asks the ORM for an entity of the type Project with that Id.

In case we don't want the defaults, we can provide our own, but it happens very rarely.

There is one question though. What do we do if we don't find the id in the request? It turns out that there are cases when we want to return null, and cases when we want to throw an exception. There is an additional boolean parameter called "relaxed" which you can use for that. What is default behavior, you decide. I'd recommend throwing an exception, just in case.

And Finally the Code That You Can Steal Use

The code together with a sample application can be found at GitHub. The main part, however, is below:

C#
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
	var fieldName = bindingContext.ModelName + "Id";
	var result = bindingContext.ValueProvider.GetValue(fieldName);
	if (FieldNotFoundOrValueIsEmpty(result))
		fieldName = _idName;
		result = bindingContext.ValueProvider.GetValue(fieldName);
	if (FieldNotFoundOrValueIsEmpty(result)) {
		if (_relaxed) return null;
		throw new MissingFieldException("Could not find the request parameter: " + fieldName);
	}
	var entityType = _entityType ?? bindingContext.ModelType;
	var session = ObjectFactory.GetInstance<ISession>();
	var id = GetId(result, fieldName);
	object instance = session.Get(entityType, id);
	if (instance == null)
		bindingContext.ModelState.AddModelError("null", 
			new HttpException(404, 
				string.Format("Could not find {0} ({1}: {2}", entityType, fieldName, id)));
	return instance;
}

First, I look for the field value using the rules I mentioned above. Next, I figure the entity type. If not specifically set in the attribute, the type should be that of the parameter we're binding to. Next, I use StructureMap.ObjectFactory to get an instance of NHibernate.ISession. You can use any container and ORM you like. The rest is simple. I have omitted the part where you handle array valued parameters, you can see it in the original source.

Writing a Test For Our Great Binder

As always, I prefer writing an integration test, the one that actually executes an ASP.NET request, because it lets me demonstrate how powerful Ivonna, my ASP.NET testing tool, is. This time, however, I'm adding a little bit of mocking (so it's not a 100% integration test). Because I don't want to setup NHibernate with all that mapping, bootstrapping, and stuff, I'm just stubbing the DB access using the new Ivonna/CThru Stub syntax:

C#
session.Stub<ISession>("Get").Return(entity);

Here, you have some kinda brute force stubbing, where you don't need much flexibility, and you don't want anything to "force" you into a supposedly good design (which is close to impossible when writing integration tests anyway). Just make the Get method on any ISession instance return this object, regardless of the arguments (strictly speaking, we should verify that the argument is as intended, but let's not overcomplicate our test). Here is the full test:

C#
var entity = new Entity();
// We don't want to set up an ORM, 
// so we'll just fake ISession
var session = new TestSession();
session.Stub<ISession>("Get").Return(entity);
 
// Now let's execute a Web request
var response = session.Get("/Sample/Get?entityId=1");
 
// Check the result
Assert.AreEqual(entity, response.ActionMethodParameters["entity"]);

As you see, we prepare an Entity instance, have it returned from the stubbed ORM call, then execute our request, and verify the parameter of the action call. Our Web should have the SampleController class with the Get method having the following signature:

C#
public ActionResult Get([EntityBinder()] Entity entity)

That's it for today. I do hope you'll find it useful, and please tell me that you like to break the rules as much as I do, whenever it makes your (and others') life happier. I do believe that by releasing this binder to the general public, I'm doing a good thing, and the world will become a better place because of this, and maybe even a couple of whales will be saved from brutal killing, but maybe not.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer GeekSoft
Lithuania Lithuania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --