I'm Just a Caveman - The Hanselman Corollary to the Clarke/Wheeler Laws
Any problem in computer science can be solved with one additional layer of indirection. But that usually will create another problem. - David Wheeler
Any sufficiently advanced technology is indistinguishable from magic. - Arthur C. Clarke.
These are two classic truisms. Recently while staring at some code trying to figure out what the heck was going on, I realized the obvious.
One additional layer of indirection is indistinguishable from magic. - Scott Hanselman, this morning in a rare moment of clarity while hopped up on Diet Coke.
In recent talk at Mix on ASP.NET MVC (there's video there also) I mentioned that a certain line of code was magic:
public void Update(int id) { try { viewData.Product = db.Products.Single(p => p.ProductID == id); //MAGIC STARTS HERE
Binding.UpdateFrom(viewData.Product, Request.Form);
//END MAGIC db.SubmitChanges(); RedirectToAction("List"); } catch (InvalidOperationException err) { viewData.ErrorMessage = err.Message; RenderView("edit", viewData); } }
Why did it feel like magic? A few reasons.
- It does a LOT. It takes all the values from a Form POST and lines them up with the public Properties in an object instance. This is done in the context of a Form POST to a Controller Action in ASP.NET MVC.
- It isn't named well. Update is a verb, so that's cool, but the "From" makes me feel inverted.
- The parameters are (possibly) in the wrong order. Given the name, I'd have expected UpdateFrom(Form, Product), but even then it doesn't feel write.
All of this adds up to an impedance mismatch, IMHO. It's too confusing and I'm just a caveman (video). As such, I declare it as black magic.
This magic was brought up to the team (I think I remember hitting someone, there may have been some swearing. ;) ) and Rob changed it in a number of good ways.
- It's discoverable. He hung it off of the Request where you can actually stumble upon in.
- It's named directly. Rather than the inverted UpdateFrom, it is now DeserializeTo which reads better left to right as in Request.DeserializeTo(product).
- It's simpler. Because it's hanging off the object that was previously a parameter, it has one less parameter and again, reads nicely left to right.
//FEELS LESS MAGICAL
Request.DeserializeTo(viewData.Product);
Jeff Moser's (see I told you I'd mention him twice) fine post talks about how cognitive "chunks" that represent solutions to problems can be more easily stored in our brains if they flow. Scott Bellware has called this concept "solubility." Code that uses frameworks with some balanced between focus on aesthetic and usability is more easily grokked.
I don't know if this is the final method, it's probably not, but I really enjoy discussions like this that aim to make things less like magic and more obvious and discoverable.
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
var custBusinessObject = CustomerModel.GetCustomerById(custID);
custBusinessObject.CopyTo(CustomerViewData);
That could be handy for not exposing too much to the views. But I keep on wondering if it matters whether the views see all of the business model data or not. I mean that's their purpose right? But by not giving them the full objects it explicitly disallows the views from updating the model. What do you think?
How about:
Request.ApplyFormDataTo(viewData.Product)
To my knowledge this extension method takes the form data and updates the product with whatever values map to form field names.
Or perhaps:
Request.DeserializeInto(viewData.Product)
Note use of the "Into" as opposed to "To". I feel this better indicates that the form will be deserialized, then those form values that are mappable or resolvable with respect to the product will be dumped into the product, effectively overwriting some but not all of the values. We're not creating a new product here, we're updating some of an existing one, right?
Or even:
Request.ApplyFormValuesTo(viewData.Product)
All in the name of grokkability.
Is indirection the same thing as abstraction? And, how does that relate to Ockham's razor, which is essentially "all things being equal, the simplest solution is best"?
Sometimes I think we get "abstraction crazy" and think every piece of our app should be replaceable. So we end up with more "frameworks" than "libraries" and we are too often programming against the lowest common denominator.
Michael Cote has a great article talking about this in the Java world.
David's suggestion of ApplyFormDataTo is much closer to what I would be looking for. I would probably shorten it to ApplyTo.
Another option would be to use CopyTo as that's really what it sounds like you're doing there -- copying the values from the request to the data object. But, I tend to associate CopyTo to working with collections so I much rather prefer something like ApplyTo.
I'll never forget \rnc (Range Name Create) menu shortcut (and plenty of others) from Lotus 123 in the late 80's.
Request.TakePostedValuesAndSetPropertiesOfTheObjectWithTheSameNameToThePostedValueUsingReflection(product);
???
I like ApplyTo and CopyTo, but technically Deserialize is also correct. That's a tough call. I would say that if the source were XML rather than name/value pairs we wouldn't object to the term deserialize. What do you think?
As for the question about Indirection vs. Abstraction, I think they are pretty darn different, but often they are used incorrectly as homonyms in colloquial speech. Indirection is use to reduce coupling between classes, while abstraction hides complexity (presumably making things simple, but not magic.)
- Sorry to break up the flow here but I love the "Sho Nuff" avatar! LOL
Sincerely,
Kevin AKA "Bruce Leroy"
viewData.Product.UpdateFrom(Request.Form)
via an extension method?
static public void UpdateFrom<T>(this T obj, HtmlForm form);
Or:
Binding.Update(viewData.Product).From(Request.Form);
Where Binding.Update(T) returns a generic type that has a From(HtmlForm form) method?
Binding.cs:
Updateable<T> Binding.Update(T obj);
Updateable.cs:
Updateable<T> From<T>(T obj);
Just a couple crazy ideas.
"but even then it doesn't feel write"That was just so subtly "slipped" in, yet so aptly applied. Bravo!
I was using this automatic databinding stuff in MonoRail. At first I missed it when I moved to the new MS WebEx stuff but I soon got used to stuffing information into ViewData using LINQ and anonymouse types I started to prefer it.
01: Someone else writes my views. If I specifically fill the view data with a subset of data I can not only prevent them from accidentally showing sensitive information but I can also have the structure of my view totally independant from my business objects' structure.
02: The opposite of 1. I can take post data in any format and post it to multiple objects, the data I accept might not even go directly into properties. E.g. a text box asking how old someone is today might change only the year part of Person.DateOfBirth, or their full name might get split up into givenname/familyname etc.
Personally I keep my UI completely separate from my business objects. You will never find a business object in my viewdata. The view is there to allow the user to enter information about a task they are performing, not edit objects as if they were rows in a table!
Request.Form.DeserializeTo
Reuqst.QueryString.DeserializeTo
The nice thing with this solution is that we need to extend the NameValueCollection type or NameObjectCollectionBase. In that case we have a nice extension method that we can use to map a key/value pair to an object, isn’t that cool ;)
I’m not a fan about the name Deserialize in this context based on how the data structure looks like, but out from a “less magic” perspective I like the name. I like the word ApplyTo, even if name ApplyTo will probably be more magic than DeserializeTo, in my opinion.
Binding.UpdateFrom(viewData.Product, Request.Form);
is vulnerable to an injection attack if your target object (viewData.Product) has more properties than the form you coded is exposing. This is an important point to consider when designing this API and reinforce when showing/explaining it to developers out there. All input is evil and helper APIs like this that hide details are sometimes also evil ;)
Currently the pain point in my own house is the Oregon Scientific weather station I was given for Xmas.
Brock you make a good point, but usually when we're accepting values from a form we take care to catch naughty input at other points? We have the option of validation logic prior to processing the form as well as a second validation before persisting to a data store. To my mind it's just a nice helper class. Liking it.
Scatter takes a class instance, and matches property names with form IDs, pushing data from the class instance to the controls.
Gather does the opposite - takes values from controls (or Request.Form in this case) and pushes them to a class instance.
My old boss did this in Clipper a couple decades ago, and we used to do it in classic ASP, matching Request.Form with stored procedure parameter names. I think it's a brilliant and powerful idea.
Programmers are information plumbers, moving bits from point DB to point USER, and back again to DB.
Comments are closed.
"Any problem in computer science can be solved with another layer of indirection, but that usually will create another problem." - David Wheeler
Layers of indirection aren't cures. They invite thinking about problems from a different angle by creating new ways of expressing it.
Offering just the first part of the quote suggests that layers of indirection are a solution. That's not only a contradiction of the quote, but a statement that should be self-evidently inconsistent with experience. If you offer this quote in its incomplete form to folks who can't measure it against experience due to a lack of experience, you're likely causing more harm than good, and putting arbitrary obstructions in the way of their learning.