Thursday, April 29, 2010

More Thoughts on ASP.NET MVC

I've had two months to work with ASP.NET MVC since my last post on the topic.  Since then, I've worked through two projects and have picked up some good experience.  There are plenty of pros and cons (more pros in my opinion).  Let's take a look at how it differs from other .NET web technologies.

Routing
ASP.NET MVC is more than just Model-View-Controller pattern (the MVC which lends its name).  One of the key features is routing.  With routing, a URL does not point to a physical resource on the server.  Instead, it contains information that allows the Controller to instantiate the appropriate Model and send the data to a specific View.  You're probably familiar with the pattern.  Let's take a look at the URL for my previous post: http://jeremybytes.blogspot.com/2010/02/new-to-me-aspnet-mvc.html.

The first part is (http://jeremybytes.blogspot.com) points to the server.  The next part (2010) would normally be parsed as a physical path (file folder) on the server.  But instead, it is a parameter that references the year of the post.  The same is true of the "02" portion (the original post is from February 2010).  The final part is the name of the article.  Now, I can't tell you what lies behind the blogspot link or how it is parsed, but I'm pretty sure that there are no physical directories that refer to the year and month of my posts.

You use this same type of routing in the ASP.NET MVC world.  The default is for a URL to take the form of {controller}/{action}/{id}, but you can alter this default implementation however you like.  As an example: http://www.example.com/sales/customer/217.  This would call the "Sales" controller and ask for Customer #217.

As mentioned previously, this is an "action-first" or "resource-first" model (as opposed to WebForms "page-first" model).  I say "action" or "resource" because that will depend on what you are doing.  In the above example, we are requesting a specific Customer resource.  But we could just as well be requesting an action, such as "UpdateCustomer".

This strikes me as very REST-ful, although that is a topic for another time.  The good news for those who like the idea of routing but aren't ready to dive into the rest of ASP.NET MVC: Routing is included in ASP.NET 4.0 (it's actually also available somewhat in 3.5 but requires some extra work).

ASP.NET MVC vs. WebForms: Repeaters
I spent 3 years developing WebForms apps, so I've tried quite a few different techniques for displaying data.  The control I ended up liking best is the Repeater.  This gives me the most flexibility in laying out my data.  But it does have one severe limitation: you cannot nest Repeaters.

The idea of a Repeater is that it will loop through your data set and "repeat" a specified section for each record.  This is fine for single level lists, but it gets more complex when you have parent-child relationships.

Let's say that I have data that includes Category, Sub-Category, and Details.  I only want to display the Category label at the top of each Category (not on each record).  The same is true for Sub-Category.  But since I can't nest Repeaters, that means that I must include Category, Sub-Category, and Detail in every single one of my records (flattened data rather than a hierarchy).  Then, to get the display that I want, I hook into the Event that runs before displaying each record.  In that event handler, I check to see if the current Category is the same as the Category of the previous record.  If so, then I suppress (hide) the Category display.  Same for Sub-Category.  This creates quite a bit of messy, conditional code.

ASP.NET MVC handles this situation entirely different.  You are much closer to the metal.  So, if I wanted to implement the above scenario, it's quite a bit more elegant.  In the View, I simply have a foreach loop that goes through all of the Categories and displays the Category header.  Inside there, I have a nested loop that goes through the Sub-Categories and displays that.  And inside there, I have a nested loop that goes through the details.  This means that I can have a much more natural structure to my data (Model) with a parent-child-grandchild relationship.  Add some LINQy goodness, and you have an easily understandable solution without the messy conditionals.

ASP.NET MVC vs. WebForms: File Size
Yes, file size is still important -- just take a look at how prominent smart phones and other mobile devices are becoming.  I took an existing WebForms application and converted it to ASP.NET MVC.  This was a proof-of-concept so I could try out the technologies.  Since the existing application had a well-defined business layer and clear separation from the UI, it was easy to put the ASP.NET MVC front end in place.

Here's what I found: a particular page on the WebForms apps that was 24KB was only 9KB in the ASP.NET MVC version!

I know your initial reaction: just turn off ViewState, moron.  But ViewState was turned off.  The WebForms page is so much bigger because it uses server-generated names for all of the controls.  This can look like "ctl00_mainRepeater_headerLabel_001".  When you multiply this by the number of controls for each record and the number of records in the data set, it all adds up.

Conversely, the majority of the ASP.NET MVC output is text -- the equivalent of using a Response.Write instead of using a server-side control.  When you look at the output (view source in the browser), it looks like hand-coded HTML rather than machine-generated code.  This will vary depending on how you code up your Views, of course.  My experience is that the output is pretty straight forward compared to WebForms output.

ASP.NET MVC vs. Silverlight
I won't really get into this debate.  Both technologies have their uses and benefits.  I was recently listening to .NET Rocks! and heard about a "challenge" that took place at the Visual Studio 2010 Launch Event.  Apparently Phil Haack (and team) and Rocky Lhotka (and team) were tasked with creating an application in ASP.NET MVC and Silverlight, respectively.  I have a great deal of respect for both of these guys, so I'll just leave that discussion there.

After using Silverlight myself, I was determined that I would never create another WebForms app again (the programming style is just so much more natural).  But there is a problem (mentioned above): mobile users.

The reason that I started using ASP.NET MVC is because I needed to create a solution for Blackberry users.  And I'm not talking about the shiny-new Blackberry Bolds.  I'm talking about (relatively) old devices with browsers that are a step above WAP.  Obviously, Silverlight was not an option.  ASP.NET MVC, however, allowed me to put together a clean and efficient application.

One Last Thing: Parameters
There are a number of things I like about ASP.NET MVC, but I'll wrap up with one item: Action parameters.  The ASP.NET MVC framework does everything it can to automatically pull in the parameters for you.

Let's say that I have an Action (method) like this: UpdateCustomer(int Id, string firstName, string lastName).  When this method gets called, the framework tries to populate the parameters from the following locations:
  • Routing Parameters: These are the parameters that appear as part of the URL "path", such as the "217" in the Customer example above.
  • Query String: These are additional parameters that are part of the URL query string.  These are separated from the rest of the URL with a question mark and take the form name=value (separated by ampersands).
  • Form Data: These are pulled from fields that are posted back in a form.
  • Post Parameters: These are the parameters provided as part of a POST (not necessarily part of a form).
  • Header Information: These items can be pulled from the request header.
If the framework can find matching, case-insensitive names that match the parameter names, it will pass them through to your method.  There's something about this that strikes me as very cool -- anything that makes my life easier is good.

Wrap Up
I've grown to like ASP.NET MVC.  At a later date, I'll put together a case study with my actual code to give you a better idea of exactly how the technology works.  For now, we'll just stay with what we've discussed so far.

ASP.NET MVC 2 is now shipping as part of Visual Studio 2010.  I've only taken a cursory look at it, but it seems to have added some good features to make views even easier and cleaner to generate.  Plus, having it included in Visual Studio means that other members of your team will also have it available without having to install the bits separately.  If you're doing web programming, this is definitely something to look into.

Happy Coding!

Tuesday, April 6, 2010

A Programmer's Bookshelf

Due to several requests, I added a Bookshelf to my website.  This has a listing of many of the books that I have read over the last several years.  My ultimate goal is to create a searchable Silverlight application, but I'm waiting for Silverlight 4 to come out before I get started on that.  In the meantime, we'll have to settle for a static list.

If you have any books that you are fond of, feel free to drop me a note.

Happy Coding!

Monday, April 5, 2010

BackgroundWorker Component Questions

I recently received an e-mail with a few questions regarding the BackgroundWorker Component (original article).  I decided to share the answers here.

Page Navigation with the BackgroundWorker
Question: I want to navigate to another page in the BackgroundWorker thread -- for example: "frameNavigator.Navigate(new OtherPage());" in the DoWork method.  I tried this, but an exception occurred because the Page class uses the main thread whereas the BackgroundWorker uses a separate thread.  How can I get around this?

Answer:  As you noticed, things like Pages, Winodws and ListBoxes are all items on the UI thread (and they need to stay on the UI thread).  And the BackgroundWorker component processes items on a different thread (not the UI thread).  So, you will not be able to do things like "Navigate" in the DoWork method.

But here's what you should do: try to figure out why the navigation process is slow.  This is usually due to something that happens when your new Page loads -- it could be loading data from a database, transferring a file across the network, or calling a web service.  So, even though you cannot put the "Navigate" into the background, you may be able to take whatever is in your Page Load process and put that in the background.  In this type of scenario, you could put the database call method or the file transfer method into the background, not the entire Page object.  This will have the effect of keeping your UI thread running and responsive while your data loads in the background.

Using the Progress Bar
Question: I want to get the progress completed from the actual process time, not from a predetermined time as you used in the walkthrough.  The application must know how much time the BackgroundWorker process will take to navigate to the target page and then show a progress bar based on this information.  How can I accomplish this?

Answer: Progress is a tricky subject.  In the example in my walkthrough, I am not technically doing the progress bar based on a predetermined time; I am calculating the progress (a percentage) based on the number of iterations completed in the loop.  I purposely kept my background process simple so that I could focus on the methods, events and properties of the BackgroundWorker component itself.  But you can use the progress event for anything you can calculate a percentage complete for.  For a more complex example, you can look at the BackgroundWorker examples on MSDN: http://msdn.microsoft.com/en-us/library/c8dcext2.aspx.  One in particular, calculates a Fibonacci sequence using a recursive method call and calculates a percentage for a progress bar.

As an example of a calculation: if you are doing a file transfer, you can calculate a percentage based on the number of bytes received compared to the total number of bytes.  So, if you have received 257 bytes of a 1,356 byte file, you are 19% complete.  The limitation is that you have to know how far along you are in the process.  If you are doing something like making a database call or a web service call, you may not know how long it will take to complete or how much data you are getting back.  In those situations, you cannot calculate a percentage and so you may be better off using a "busy animation" (and there are plenty of examples available on the web for WPF and Silverlight).

Complex Results
Question: I don't like examples of BackgroundWorker using results of "string" or "int".  I want a real object.  How would I do this?

Answer: The e.Result parameter in the DoWork and RunWorkerCompleted can be as simple or as complex as you like.  In my example, I simply use an "int", but e.Result is of type "object", so you can put whatever you want in there (including an entire database result set).  There are a few limitations such as the objects that need to be on the UI thread (like we mentioned above).  As another option, you can use a separate variable (or set of variables) that is accessible to both the UI and the background process.  If you do this, you will want to look into the "lock" method to ensure that you don't end up with multiple processes trying to modify the value at the same time.

Round Up
The BackgroundWorker component is not the right solution for everything.  But it can be helpful in quite a few situations.  As we saw above, you may not be able to do exactly what you want (such as navigating in the background), but if you think about the problem a little differently, you can often come up with a workable solution.

Happy Coding!

Friday, April 2, 2010

Target Practice Live Demo

I have finally posted a live demo of the Silverlight 3 Target Practice demo application: Target Practice Live Demo

As a reminder, this is a mini-app created entirely with XAML (no code-behind).  The original article is here: Silverlight Target Practice.

And you can get the source code here: SilverlightTargetPractice.zip

Happy Coding!