Thursday, August 14, 2008 : 7:32 AM
- (1 comment)
A couple of days ago, Stephen Walther made a great post (ASP.NET MVC Tip #31 – Passing Data to Master Pages and User Controls) on a subject that is bound to come up 100 times more until MVC is fully understood. This post is a follow-up to that one, in hopes to have a clear sense of what to do with the issue.
The issue is as follows: MVC is rests on the pillars of 1) Testability and 2) Single Responsibility. Every function (action, page, whatever you want to call it) should be fully testable, and it should only handle one point of concern. So the question is, how do we handle supplemental data?
Our Scenario
Imagine if you built an MVC web application for a car dealership that had the following actions (or pages):
- Home - ContactUs
- Home - OurLocations
- Vehicles - ListCars
- Vehicles - ViewCarDetail
At first glance, this would be very straight forward and very easy to solve (and it is). I would make a "HomeController" and have the ContactUs and OurLocations actions there, and I would make a "VehiclesController" and put the ListCars and ViewCarDetail actions there. So Where's the Problem?
A Slightly More Complex Page
Imagine, though, if you didn't just want to list all the cars on the "ListCars" page. What would you do if you wanted to also show the "Top 5 Custom Testimonials" in a <div> on the right side of the page. You want to have that shown on both of the 'vehicles' pages (ListCars and ViewCarDetail).
So the question comes in... "Who's responsibility is it to retrieve that data from the data store?" Would you add that code to each function?
public ActionResult ListCars()
{
GetTestimonials();
}
public ActionResult ViewCarDetail()
{
GetTestimonials();
}
You would *NEVER* want to do the above as that is a 100% violation of one of the pillars of MVC (the Single Responsibility Principle). Not to mention that you're working harder than you have to.
Would you make a custom ActionFilter and decorate your methods?
[TestimonialGetter]
public ActionResult ListCars()
{
}
[TestimonialGetter]
public ActionResult ViewCarDetail()
{
}
That's hideous, and is simply putting a new dress on an old wench. So then, what do we do?
Look At the Data for What It Is
The problem here is that we're losing focus of what the data really is. That supplemental data is not the main focus of the "View", and it's throwing us off. If we didn't have that "Top 5 Testimonials" stuff, we wouldn't have a problem.
To prove my point, imagine if we didn't want to show "Top 5 Testimonials", but instead we wanted to show... let's say... the logged in user's name! Would that be so hard?
"That's easy", you say, "all I have to do is <%= User.Identity.Name %>". True, that is easy. But did you notice that we're not freaking out about the fact that we just got some "data" from a "data base" and displayed it in our View :)
The same is true for other, non-view-specific data. So, to solve our issue above, we would most-likely create a base class for our Vehicles controller that got that extra data (in the constructor) and put the needed info into the ViewData collection.
Is that testable? Does that allow a single responsibililty? Yes, it is and yes it does. In your testing framework, you would mock up the data store, and then you could test each action to make sure that it has all the data (including the Top 5 Testimonials stuff).
Important Conclusion
MVC is a great design pattern, and it's been around for a long time. However, ASP.NET MVC is new, and there will likely be many revisions and redefinitions to how it will address issues like this. But as long as you keep to the guidelines of MVC then you will be able to reap it's benefits.
Monday, August 11, 2008 : 12:09 PM
- (0 comments)
This was just released a couple of hours ago, but when I tried looking for the installer, I didn't see anyone blogging about it yet. So, I'll save you the trouble of looking. Here's the URL to the installer, and more imporantly the URL for the full 850 meg ISO for those of you who perfer the full installer at one time (like me).
Oh, also, if you are trying to install SQL Server 2008 RTM, and you have Visual Studio 2008 installed, you'll have issues until you upgrade to SP1.
Enjoy!
Update: Aug 14 2008If you're wondering what is contained in this service pack, then check out Lost In Tangent. Jonathan Carter is a Technical Evangelist for Microsoft, and does a great job covering SP1 goodness on his blog.
Monday, August 4, 2008 : 10:42 PM
- (0 comments)
Like most other developers who have been using LINQ for a while (and in this particular case, LINQ to SQL) - I've unknowingly made a few 'bad queries' that performed great at first, but eventually made some pages crawl. This post isn't necessarily about performance, but if you don't know how to answer that question ("am I hitting the database"), then you'll likely find yourself in a world of problems down the road when you're using LINQ to SQL.
The Scenario - "Method ... has no supported translation to SQL"
If you've seen this error before, then you probably already know where I'm going with this post. But, for those of you who haven't run across this little gem of an error, I'll fill you in.
When you write LINQ statements that act against a LINQ to SQL data context - eventually your expression will be translated into SQL statements and executed against the database. Example:
this.MyGridView.DataSource = context.Persons
.Where(dude => dude.FirstName.StartsWith("T"));
will translate into...
SELECT [t0].[ID], [t0].[FirstName], [t0].[LastName]
FROM [dbo].[People] AS [t0]
WHERE [t0].[FirstName] LIKE @p0
What you might be taking for granted here is that the developers at Microsoft (the LINQ to SQL team in particular) spent a lot of time making 'translations' for a lot of .NET functions out there (such as "String.StartsWith"). What this means is that you as a developer may be thinking that even your functions will be translated just like magic. This, of course, isn't the case. So, the following LINQ query:
this.ExpensiveOrdersGridView.DataSource = context.Persons
.SelectMany(person => person.GetExpensiveOrders());
will result in the following error:
Where There Be Dragons
You may be wondering, "what's the problem... if my method doesn't work, then so what?"... and that's a great question. The 'problem' here is that at first you may not fully understand that LINQ to SQL isn't able to translate this method at all, so you may tinker around a bit and find a way to "make it work."
This slight change to the LINQ query will work, and will give you the results you expect:
this.ExpensiveOrdersGridView.DataSource = context.Persons
.ToArray()
.SelectMany(person => person.GetExpensiveOrders());
... but this is a huge sin. Why you might ask? Let's break down what we just told LINQ to do, and you'll see:
this.ExpensiveOrdersGridView.DataSource = context.Persons
.ToArray()
.SelectMany(person => person.GetExpensiveOrders());
Conclusion
There isn't much of a moral here other than "make sure you understand what your query is doing." Queries like these can linger for a while in your code without you seeing a performance problem. Then, as your database continues to grow, your app will eventually crawl to uselessness.
Please, don't be afraid of LINQ, and don't be afraid of LINQ to SQL. Just make sure you understand what you're doing (as with any other new technology).