Posted by Will on Thursday, August 14, 2008 at 12:57 PM

One of the big new features in the upcoming release of HappyFish will be the ability of users to upload their data to their account and then download it elsewhere.  This is perfect for users who have HappyFish installed both at home and work.  Each time HF starts it will pull down the latest changes and then push them back up when it exits.  The value added feature in uploading will be that user's subscriptions will be anonymously aggregated to form a feed directory.  The result (I hope) is a directory of feeds that people actually subscribe to rather than a huge spam list.  Optionally, users will also be able to share, in a public feed of their own, their feed subscriptions along with favorite feed items and related comments.  I don't know about you, but that sounds like a perfectly good excuse for testing out the ASP.Net MVC framework.

For this article I am assuming you've worked through some introductory material like the tutorial videos found on the ASP.net MVC page.  At the time of this writing even though I've got Preview 4 installed on my machine the Preview 3 introductory videos are still relevant.  The videos do a good job of covering the basics of getting started.  My goal here is to expand on some of the details associated with setting up an MVC site that were not apparent to me when I first began.

Controllers and Actions

One of the differences in working with an MVC application that stands out even before delving into the code structure is the way the URLs in an application change.  No more http://mysite.com/default.aspx.  Under MVC the default page address probably looks like http://mysite.com/home.  The parts of URL specify controllers, actions, and arguments.  This is similar to class, method, arguments of the typical programming model.  The URL pattern maps to a route in the Global.asax file.  The default route specified is

routes.MapRoute(
    "Default",                                             // Route name
    "{controller}/{action}/{id}",                          // URL with parameters
    new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);

Based on that I began organizing my application around intended controller actions and arguments:

Controller Action Argument Description
Feeds Search keywords Search by keywords in the feed's title or description
  Browse letter of alphabet Alphabetical listing by feed title
  Recent number of new feeds to show The newest x number of feeds added to the directory
  Rating number of stars Lists feeds by user rating
  User username Not sure about this one

Everything is pretty straightforward until I come to the User action.  For the user's feed subscriptions I want two things, a web page view of the user's subscriptions and the subscriptions output in OPML format.  Likewise, with the user's favorite feed items I want to provide a web page view as well as output in RSS format.  Sounds like I need to promote the User action concept in my application to a controller. 

Controller Action Argument Description
User Feeds username, format The users feeds as either a web page or OPML.
  Favorites username, format The users favorites as either a web page or RSS.

This seems reasonable.  I have a lot of options for the action argument.  I could accept the username with an optional dotted suffix a la

  • Directory/User/Feeds/thirstycrow - outputs the feeds in a web page
  • Directory/User/Feeds/thirstycrow.opml - outputs the feeds in opml format

or simply always output the feeds in OMPL format and style them for browser consumption with xslt.  <groaning and gnashing of teeth />  I think I'll go with the former - a single argument.  It cleanly expresses the intent and will look familiar to users to see the .opml or .rss suffixes.  Still, seeing username, format made me wonder how to route multiple arguments to an action like

public ActionResult Feeds(string username, string format) { ... }

I fooled around with the Default route in the Global.asax page, extending the path with more slashes and parameters, but could not even get a single parameter Action to work. 

"id" Matters

When I set up my first test controller action it was

public ActionResult Search(string keyword) { ... }

However, when I tested ../Feeds/Search/test the argument 'test' wasn't making into the method.  Experimenting I found that ../Feeds/Search/?keyword=test worked, which was not the attractive URL that MVC had promised.  Is this some sort of bait and switch?  It turns out that by changing my method to

public ActionResult Search(string id) { ... }

the 'test' argument made it through.  In other words {id} in the default route "{controller}/{action}/{id}" should be the name of the argument in the method as well.  Despite going through several tutorials I never picked that up.  Applying this to the two argument question I now see the answer is an action on my User controller that looks like this

public ActionResult Feeds(string id, string format) { ... }

with a corresponding URL of ../User/Feeds/thirstycrow?format=opml.  Not as aesthetically pleasing as ../User/Feeds/thirstycrow.opml, but it's the answer none the less.  The thing to keep in mind is that what I've said above applies only if I want to funnel everything through the default route.  Ultimately what I realized I needed to do was establish other routes in my Global.asax file.  And this is where the routing concept finally clicked.  Back to Global.asax.

Expanding Routes

The purpose of routes in the MVC is to map a given URL pattern to a controller's action and pass in the associated arguments, if any.  Understanding that and based on what I had learned from experimentation I saw that if I want ../User/Feeds/thirstycrow.opml to map to

public ActionResult Feeds(string username) { ... }

on the User controller, instead of modifying the default route, I needed to add a route.

routes.MapRoute(
    "UserFeeds",               //Route name is arbitrary
    "User/Feeds/{username}",
    new { controller = "User", action = "Feeds", username = "" });

And by extension, a multi-argument action

public ActionResult FormattedFeeds(string username, string format){ ... }

requires a different route.

routes.MapRoute(
    "FormattedUserFeeds",               //Route name is arbitrary
    "User/Feeds/{username}/{format}",
    new { controller = "User", action = "FormattedFeeds", username = "",
    format = "" });

The FormattedUserFeeds route above would handle ../User/Feeds/thirstycrow/opml.  Not bad.  I still prefer the .opml variant, but you can see the point.  Don't funnel everything through the default route.  It's important to note in the above snippets I did not overload the Feeds method as you might expect.

public ActionResult Feeds(string username) { ... }               //will not work
public ActionResult Feeds(string username, string format){ ... } //will not work

Attempting an overload, even with differentiated routes like those shown above will throw an error along the lines of "More than one action named 'Feeds' was found on controller..."

Next Step

Having grasped the relationship between URLs, routes, controller actions, and arguments the next step is to write the bodies of the controller actions and create the associated views.  In the case of the HappyFish feed directory I'm using the new SQL Server Data Services beta for the back end.  In the next post I'll cover some of what I've learned while experimenting with the private beta.

Comments [0]     Categories: asp.net | MVC              
Posted by Will on Thursday, August 07, 2008 at 8:44 PM

Here's one of those things I do just infrequently enough to forget a step.  Now I just need to remember that I've put all the steps here.

  1. Provision a database.  It does not need to be new, but it cannot already have the ASP.net membership provider tables in it.  If it does, skip ahead to step 3.
  2. Use the aspnet_regsql.exe tool to create the necessary tables and stored procedures in the database.  The tool can be found at <system drive letter>:\Windows\Microsoft.NET\Framework\v2.0.50727.  The tool has a GUI wizard that will lead you through the steps.
  3. Create a web site.  A local web site works fine.
  4. Add the connection string to your database to the web.config file.  Connection strings are placed just inside the configuration root:

    <configuration>
      <connectionStrings>
        <add name="SqlMembershipConnection"
             connectionString="Data Source=IPADDRESS;Initial Catalog=DBNAMEHERE;User
             ID=DBUSERNAMEHERE;Password=PASSWORDHERE" />
      </connectionStrings>
    <configuration>

  5. Add the SqlMembershipProvider to the web.config file under the system.web node:

    <membership>
      <providers>
        <clear/>
        <add name="AspNetSqlMembershipProvider"
             type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
             connectionStringName="SqlMembershipConnection"
             enablePasswordRetrieval="false"
             enablePasswordReset="true"
             requiresQuestionAndAnswer="false"
             requiresUniqueEmail="true"
             passwordFormat="Hashed"
             maxInvalidPasswordAttempts="5"
             minRequiredPasswordLength="7"
             minRequiredNonalphanumericCharacters="1"
             passwordAttemptWindow="10"
             passwordStrengthRegularExpression=""
             applicationName="NAMEOFAPPHERE"/>
      </providers>
    </membership>

    NOTE: The connectionStringName parameter of the membership provider must match the name you chose for the connection string above.  The applicationName parameter allows you to use the same database for different web applications.  Choose a suitable name and set the other parameters as needed.

  6. Add a CreateUserWizard control to the default.aspx page of your web site. 
  7. Right click on the default.aspx page and choose "View In Browser" from the context menu.
  8. You know the rest...
Comments [0]     Categories: asp.net | SQL Membership Provider              
Posted by Will on Tuesday, August 05, 2008 at 3:52 PM

One of the benefits of working alone or in a small shop is the freedom to be an early adopter.  I'm not the bleeding edge type, but I certainly keep an eye out for new technologies and try to apply them in real world situations.  Typically this starts first with HappyFish.  (Side note: Every developer should have at least one pet project for experimentation and learning.)  For whatever reason I was slow to respond to LINQ.  I have since embraced it and am still surprised at how easily I can express my intent through LINQ.  Sure, at first the syntax feels like reaching around your back to scratch your navel, but there is a lot of power to be had.

My focus lately has been on SQL Server Data Services (SSDS) and ASP.Net MVC.  SSDS is a cloud based data service similar to what is currently available through Amazon Simple Storage Service program. While overall Amazon's offering is a more complete cloud computing solution, SSDS is focused on an infinitely scalable off site data storage solution.  This would be comparable specifically to Amazon's Simple DB, which like SSDS is in private beta.  I signed up for both but still have not gotten into Amazon's program so I cannot offer a comparison.  However, I really like what I've seen with SSDS so far.  Here are some related links for getting familiar with SSDS:

The ASP.Net MVC framework is my other focus lately.  From the official site:

"ASP.NET MVC provides a framework that enables you to easily implement the model-view-controller (MVC) pattern for Web applications."

Some argue that the code behind page model of ASP.Net represents the MVC pattern, but this is a more complete synthesis of the idea.  The separation of concerns - that is - one part being responsible for a single task - achieved via the framework makes it easier to test and maintain the code.  In fact, when you create an MVC project a testing project is created along side.  Preview 4 was released July 16 and is available for download from the CodePlex site.  Here are some related links:

The thing to keep in mind with both SSDS and MVC is that both projects are under development and are evolving quickly.  Some of these links will probably be stale in a week or so.  Still, I wanted to put them out there for early adopters like myself or others who may just need a push to get started.  Remember - adopt early, adopt often.

Comments [0]     Categories: asp.net | MVC | SSDS              
Posted by Will on Tuesday, July 29, 2008 at 2:06 AM

I've had this problem before.  I'm not exactly sure what causes it, but rarely I'll open a dataset in the design view and only the XML markup shows.  The first time this happened I made the mistake of running some Visual Studio reinstall command I found out there and lost a bunch of my personal settings.  Here's the quick, painless solution:

  1. Close the dataset file. 
  2. Find the file in the solution explorer, and click on the plus sign [+] to expand the subfiles of the .xsd file.
  3. Delete the .Designer.cs or .Designer.vb subfile.  It will probably be the first of three subfiles.
  4. Reopen the .xsd file to regenerate the .Designer.cs/.vb file thereby restoring your design view.

If that doesn't work you can always try a Visual Studio repair of some sort, but remember to backup your Visual Studio settings and any Code Snippets you've tweaked.

Comments [0]     Categories: Visual Studio              
Posted by Will on Monday, July 21, 2008 at 2:46 PM

I'm not sure if there is a term for following someone on Twitter just to get them to click on your link but I'm suggesting the following:

Spamollow: (verb)

  1. To follow someone on Twitter for the purpose of getting the user being followed to click on a link they would not otherwise in their worst moments of depravity or boredom click on.

Spamollower: (noun)

  1. One who spamollows.
  2. A parasite.
Comments [0]     Categories: Twitter