Scott Hanselman

The Weekly Source Code 47 - ASP.NET 3.5 Dynamic Data: FilterRepeaters and Dynamic Linq Query Generation

January 13, 2010 Comment on this post [5] Posted in ASP.NET | ASP.NET Ajax | ASP.NET Dynamic Data | ASP.NET MVC | Source Code
Sponsored By

First, let me start this post by thanking Tatham Oddie. He helped my buddy John Batdorf and I debug our issue remotely from Australia. He's patient, kind, opinionated and Tatham's got a darn fine blog that you should subscribe to now. I also found great inspiration from Stephen Naughton's excellent blog. He's continually pushing ASP.NET and Dynamic Data to do fun things and I was able to use 95% of his auto-complete code as I found it. And finally Marcin Dobosz's blog is where I started, taking his Dynamic Data sample Filter Repeaters and ending up at the Dynamic Data Futures samples.

Technical Disclaimer: This is me just messing about with the .NET 3.5 SP1 Dynamic Data samples as this non-profit wanted .NET 3.5. The Filters I'm messing with here are from the VS2008 SP1 Dynamic Data Futures from May of 2009 but are included out of the box and are much cooler in .NET 4 in Visual Studio 2010 Beta 2. Some method names and base classes have changed, but the idea is effectively the same. If you're using .NET 4 you'll notice you'll get the ~/DynamicData/Filters folder when you do File | New Project and a new QueryableFilterRepeater rather than an AdvancedFilterRepeater, and like that. In my 3.5 project I included the DynamicDataExtensions Project in my solution, while in .NET 4 all this functionality is in System.Web.DynamicData itself. I'll update this project and blog a sample when .NET 4 is closer to release.

Ok, the story is that John and I are doing some volunteer work and updating a local non-profit's website on the side in our copious spare time. They have simple needs, on the front end they want a nice simple autocomplete textbox and maybe a filter drop down or two. This'll send the user to a results page with a non-editable paged grid and a dynamically generated image for printing. On the backend, they want a few more dropdowns for admins and then a nice editable grid for searching, paging, etc.

We each thought this would be a fun opportunity to see if could put it together in a night and learn more about ASP.NET Web Forms and Dynamic Data at the same time. It's all very meta, so we decided we'd be meta also. (Remember, that means effectively "solution non-specific." We'd like to be able to use whatever things we create for ASP.NET Dynamic Data in other projects.)

Meta Meta

When creating a site with ASP.NET Dynamic Data, the pages you create are templates - they are kinds of pages - rather than instances of pages themselves. So, I create a List.aspx, and it's not specific to (in my case) Brick objects. It's for listing any kind of object I want to in my solution.

Here's a simplified example of my list.aspx in my Dynamic Data solution. The things we care about are that it has a:

  • LinqDataSource
  • GridView
  • AdvancedFilterRepeater - Again, this was the name it had in the 3.5 Dynamic Data Futures. It's an extension of the standard Filter Repeater that comes with ASP.NET. It has a DelegatingFilter in its ItemTemplate.

And that's it.

<ContentTemplate>
<asp:ValidationSummary ID="ValidationSummary1" runat="server" EnableClientScript="true"
HeaderText="List of validation errors" />
<asp:DynamicValidator runat="server" ID="GridViewValidator" ControlToValidate="GridView1" Display="None" />

<asp:AdvancedFilterRepeater id="AdvancedFilterRepeater" runat="server">
<HeaderTemplate>
<table>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td valign="top"><%# Eval("DisplayName") %>:</td>
<td><asp:DelegatingFilter runat="server" ID="DynamicFilter" OnSelectionChanged="OnFilterSelectedIndexChanged" />
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:AdvancedFilterRepeater>

<asp:GridView ID="GridView1" runat="server" DataSourceID="GridDataSource"
AllowPaging="True" AllowSorting="True" CssClass="gridview">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:HyperLink ID="DetailsHyperLink" runat="server"
NavigateUrl='<%# table.GetActionPath(PageAction.Details, GetDataItem()) %>'
Text="Details" />
</ItemTemplate>
</asp:TemplateField>
</Columns>

<PagerStyle CssClass="footer"/>
<PagerTemplate>
<asp:GridViewPager runat="server" />
</PagerTemplate>
<EmptyDataTemplate>
There are currently no items in this table.
</EmptyDataTemplate>
</asp:GridView>

<asp:LinqDataSource ID="GridDataSource" runat="server" EnableDelete="false" EnableInsert="false" EnableUpdate="false">
<WhereParameters>
<asp:DynamicControlParameter ControlID="AdvancedFilterRepeater" />
</WhereParameters>
</asp:LinqDataSource>

<br />
</ContentTemplate>

The AdvancedFilter repeater is what's cool and it's what Marcin blogged about and that ended up in the samples. Stephen Naughton has since extended the FilterRepeater even more with his wonderful "Dynamic Data Futures" series.

The FilterRepeater is a special Repeater control that binds to a collection of columns (properties) in an table (list<object>) and make dynamically make controls that will allow filtering.

For example, here's my Brick object model (actually the "buddy metadata class" of my Brick object. It's a mirror of what's in the database:

[MetadataType(typeof(Brick_MD))]
public partial class Brick
{
public class Brick_MD
{
[Filter(FilterControl = "AutoComplete")]
public object Name { get; set; }

[Filter(FilterControl = "Integer", AuthenticatedOnly = true)]
public object Square { get; set; }

[Filter(FilterControl = "Integer")]
public object Year { get; set; }

public object Side { get; set; }

public object Row { get; set; }

public object BrickNum { get; set; }

public object Order { get; set; }
}

}

imageNotice the Filter attributes. I've extended it with a property called AuthenticatedOnly for properties that only authenticated users (non-anonymous) can see filters for. I've also said that one property/column needs an AutoComplete style filter (without being specific about the implementation) and two others need Integer style (again, implementation non-specific metadata. Could be combo, slider, whatever.)

ASP.NET Dynamic Data uses a special folder by convention called DynamicData with sub-folder like FieldTemplates for specific kinds of fields (Integer, DateTime, Person, etc) and PageTemplate for specific pages (List, Edit, Details, etc.)

The FilterRepeater sample extends this concept with a ~/DynamicData/Filters folder. This is set in the GetFilterControl method inside the FilterFactor if you want to see where the actual mention of the 'Filters' directory exists in the code. The point is that there's a string on my meta-model that says "AutoComplete" and that maps by convention to the ~/DynamicData/Filters/AutoComplete.ascx control, just as "Integer" maps to Integer.ascx, etc.

This is all Integer.ascx is, for example:

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Integer.ascx.cs" Inherits="AdvancedFilterRepeaterSite.Integer_Filter" %>

<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="true" EnableViewState="false">
<asp:ListItem Text="All" Value="" />
</asp:DropDownList>

It's just a dropdown list. When the FilterRepeater is checking out the columns/properties on the Brick_MD object, it's saying, "oh, I to put a Integer.ascx here" and it'll feed it the data it needs. All these controls like the Integer one derive from FilterUserControlBase and just have to override the properties or methods for the behavior they want to change.

All this is pretty straight forward, and you can do really cool stuff with it.

Dynamically Generating Generically Specific LINQ Queries

However, things got hairy when we noticed that the DropDownList being created didn't have the values sorted ahead of time. This made weird dropdowns with the integers in whatever order they were in the database already.

You would think you'd want some code like this behind your Integer Filter Control:

protected void Page_Init(object sender, EventArgs e) {
var items = Column.Table.GetQuery();

//magic psuedo code here
var result = items.Select(row => row.Property).Distinct.OrderBy(colvalue => colvalue)

foreach (var item in result) {
if (item != null) DropDownList1.Items.Add(item.ToString());
}
}

Notice the "magic" part. That's the LINQ query we wish we could call, but remember that I don't want to make a query against a specific table and a specific column. If I did, this wouldn't be the Integer Filter Control, it'd be the Year Filter Control or the Month Filter Control. It'd be specific, not generic.

We need to dynamically create a LINQ query. But just like you don't want to just make dynamic SQL by concatenating strings, the same applies for LINQ. Instead we will use the System.Linq.Expressions.Expression base class to creating a tree that represents our LINQ query. Another metaphor/example would be using XmlDocuments and the DOM to make XML rather than making it with a bunch of strings.

We want effectively, this query, made specific. Each time this Integer Filter Control is run it'll be specific to the column (and even the type, arguably, could be something other than integer) we're currently making a control for.

Items.Select(row => row.Property).Distinct.OrderBy(colvalue => colvalue)

We need the building blocks for this query, starting with whatever "row" is.

// row
var entityParam = Expression.Parameter(Column.Table.EntityType, "row");

Because we are a FilterUserControlBase, we can ask Dynamic Data for the typeof(row) which is in Column.Table.EntityType. We'll be using the types and info that DynamicData provides to create this query.

Next we get the Property of row by passing in (building on) the entityParam we just made. Now "columnLambda" is of type LambdaExpression.

// row => row.Property
var columnLambda = Expression.Lambda(Expression.Property(entityParam, Column.EntityTypeProperty), entityParam);

Next the Select. Here we are dynamically generating a LINQ function call. This is LINQ's idea of Reflection, effectively. The Type[] are the params. Remember what the Select call looks like: Select<TSource, TResult>(IEnumerable<TSource>, Func<TSource, TResult>) and we're giving it just those things it wants.

// Items.Select(row => row.Property)
var selectCall = Expression.Call(typeof(Queryable), "Select", new Type[] { items.ElementType, columnLambda.Body.Type }, items.Expression, columnLambda);

Now we add just Distinct to the previous call. Look at the comment. See how we are building on the previous selectCall expression?

// Items.Select(row => row.Property).Distinct
var distinctCall = Expression.Call(typeof(Queryable), "Distinct", new Type[] { Column.EntityTypeProperty.PropertyType }, selectCall);

Here's what Tatham saved our bacon. I got turned around and he broke up my crazy one-liner into clearer code. Eilon Lipton helped me get this straight as well.

In the first expression, the "sortValue" string doesn't matter. It just has to be something so we can make a lambda that is x=>x.

In the second, we call the standard LINQ OrderBy and give it the (x=>x), or in this example the year=>year, so really,
Items.Select(brick => brick.Property).Distinct.OrderBy(year => year)

// colvalue => colvalue
var sortParam = Expression.Parameter(Column.EntityTypeProperty.PropertyType, "sortValue");
var columnResultLambda = Expression.Lambda(sortParam, sortParam);
// Items.Select(row => row.Property).Distinct.OrderBy(colvalue => colvalue)
var ordercall = Expression.Call(typeof(Queryable), "OrderBy",
new Type[] { Column.EntityTypeProperty.PropertyType, columnResultLambda.Body.Type },
distinctCall, columnResultLambda);

Then we actually CALL the query and fill the dropdown list so here's the whole thing:

protected void Page_Init(object sender, EventArgs e) {
var items = Column.Table.GetQuery();
var entityParam = Expression.Parameter(Column.Table.EntityType, "row");

// row => row.Property
var columnLambda = Expression.Lambda(Expression.Property(entityParam, Column.EntityTypeProperty), entityParam);

// Items.Select(row => row.Property)
var selectCall = Expression.Call(typeof(Queryable), "Select", new Type[] { items.ElementType, columnLambda.Body.Type }, items.Expression, columnLambda);

// Items.Select(row => row.Property).Distinct
var distinctCall = Expression.Call(typeof(Queryable), "Distinct", new Type[] { Column.EntityTypeProperty.PropertyType }, selectCall);


// colvalue => colvalue
var sortParam = Expression.Parameter(Column.EntityTypeProperty.PropertyType, "sortValue");
var columnResultLambda = Expression.Lambda(sortParam, sortParam);

// Items.Select(row => row.Property).Distinct.OrderBy(colvalue => colvalue)
var ordercall = Expression.Call(typeof(Queryable), "OrderBy",
new Type[] { Column.EntityTypeProperty.PropertyType, columnResultLambda.Body.Type },
distinctCall, columnResultLambda);

var result = items.Provider.CreateQuery(ordercall);

foreach (var item in result) {
if (item != null) DropDownList1.Items.Add(item.ToString());
}
}

So what?

Conclusion

image Now I can add [Filter(FilterControl = "Integer")] to any meta-model in any of my ASP.NET Dynamic Data sites and automatically get a nice simple set of Filters, driven completely by attribute with their implementation both separate from my logic and completely generic to my tables.

For a complete working sample, go check out the VS2008 SP1 Dynamic Data Futures from May of 2009. I modified (barely) the Integer.acsx.cs to add the OrderBy which was flummoxing me. I needed to break it all down to figure out those few lines.

I also modified the FilterAttribute to add the AuthenticatedOnly attribute like [Filter(FilterControl = "Integer", AuthenticatedOnly = true)] :

using System;

namespace Microsoft.Web.DynamicData.Extensions {
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple=false)]
public sealed class FilterAttribute : Attribute {

public FilterAttribute() {
Order = Int32.MaxValue;
Enabled = true;
}

public string FilterControl { get; set; }

// Lower values take precedence before greater values
public int Order { get; set; }

public bool Enabled { get; set; }

public bool AuthenticatedOnly { get; set; }
}
}

...and the AdvancedFilterRepeater to act on that new property:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.DynamicData;
using System.Web.UI.WebControls;
using System.Web;

namespace Microsoft.Web.DynamicData.Extensions {
public class AdvancedFilterRepeater : FilterRepeater {

protected override IEnumerable<MetaColumn> GetFilteredColumns() {
// sort the filters by their filter order as specified in FilterAttribute.
return Table.Columns.Where(c => IsFilterableColumn(c)).OrderBy(column => column, new FilterOrderComparer());
}

protected bool IsFilterableColumn(MetaColumn column) {
if (column.IsCustomProperty) return false;

var filterAttribute = column.Attributes.OfType<FilterAttribute>().FirstOrDefault();
if (filterAttribute != null)
{
if (filterAttribute.AuthenticatedOnly == true && (HttpContext.Current.User == null || HttpContext.Current.User.Identity.IsAuthenticated == false)) return false;

return filterAttribute.Enabled;
}

if (column is MetaForeignKeyColumn) return true;

if (column.ColumnType == typeof(bool)) return true;

return false;
}

private class FilterOrderComparer : IComparer<MetaColumn> {
public int Compare(MetaColumn x, MetaColumn y) {
return GetWeight(x) - GetWeight(y);
}

private int GetWeight(MetaColumn column) {
var filterAttribute = column.Attributes.OfType<FilterAttribute>().FirstOrDefault();
return filterAttribute != null ? filterAttribute.Order : Int32.MaxValue;
}
}

}
}

Disclaimer: I did this in .NET 3.5 SP1. I'm still learning this deeper stuff myself, and I'd say that while this meta-LINQ stuff is powerful, it's too complex to generate dynamic LINQ. Remember also that this post is on the outer outer edge of what you'd want to do. It's not representative of a typical ASP.NET Dynamic Data experience, or a typical LINQ experience. However, it is nice to know that the whole thing is incredibly extensible just that if I want to go this deep, I can. I'll go talk to that team and see what they've got planned for the future! If we've missed something, leave it in the comments or email me and I'll update this post. I'll find out both what has been improved for ASP.NET Dynamic Data 4 and for C# 4.

Related Links

Good ASP.NET WebForms Resources and Blogs

Webforms Q&A

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.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Hanselminutes Podcast 193: Axum - A domain-specific concurrent programming language with Niklas Gustafsson

January 08, 2010 Comment on this post [0] Posted in Learning .NET | Podcast | Programming
Sponsored By

image My one-hundred-and-ninety-third podcast is up. Scott talks to Niklas Gustafsson about Axum (formerly Maestro), an incubation project at MSDN DevLabs. Axum is a new language based on the Actor model that targets the CLR. It focuses on making concurrency fundamental with principles of isolation, agents, and message-passing.

Subscribe: Subscribe to Hanselminutes Subscribe to my Podcast in iTunes

Download: MP3 Full Show

Links from the Show

Do also remember the complete archives are always up and they have PDF Transcripts, a little known feature that show up a few weeks after each show.

Telerik is our sponsor for this show.

Check out their UI Suite of controls for ASP.NET. It's very hardcore stuff. One of the things I appreciate aboutTelerik is their commitment tocompleteness. For example, they have a page about their Right-to-Left support while some vendors have zero support, or don't bother testing. They also are committed to XHTML compliance and publish their roadmap. It's nice when your controls vendor is very transparent.

As I've said before this show comes to you with the audio expertise and stewardship of Carl Franklin. The name comes from Travis Illig, but the goal of the show is simple. Avoid wasting the listener's time. (and make the commute less boring)

Enjoy. Who knows what'll happen in the next show?

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.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Hanselminutes Podcast 192: The Spark View Engine for ASP.NET MVC with Louis DeJardin

January 08, 2010 Comment on this post [2] Posted in ASP.NET | ASP.NET MVC | Podcast
Sponsored By

My one-hundred-and-ninety-second podcast is up. Scott chats with new Microsoft employee and ASP.NET team member about his open source ASP.NET MVC ViewEngine called "Spark." It's a totally new DSL (Domain Specific Language) that might make your MVC Views more fun to write!

What is Spark?

Spark is a view engine for Asp.Net Mvc and Castle Project MonoRail frameworks. The idea is to allow the html to dominate the flow and the code to fit seamlessly.

<viewdata products="IEnumerable[[Product]]"/>
<ul if="products.Any()">
  <li each="var p in products">${p.Name}</li>
</ul>
<else>
  <p>No products available</p>
</else>

Subscribe: Subscribe to Hanselminutes Subscribe to my Podcast in iTunes

Download: MP3 Full Show

Links from the Show

Do also remember the complete archives are always up and they have PDF Transcripts, a little known feature that show up a few weeks after each show.

Telerik is our sponsor for this show.

Check out their UI Suite of controls for ASP.NET. It's very hardcore stuff. One of the things I appreciate aboutTelerik is their commitment tocompleteness. For example, they have a page about their Right-to-Left support while some vendors have zero support, or don't bother testing. They also are committed to XHTML compliance and publish their roadmap. It's nice when your controls vendor is very transparent.

As I've said before this show comes to you with the audio expertise and stewardship of Carl Franklin. The name comes from Travis Illig, but the goal of the show is simple. Avoid wasting the listener's time. (and make the commute less boring)

Enjoy. Who knows what'll happen in the next show?

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.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

How SparkFun (and 800.com) and small commerce or startup websites can scale

January 07, 2010 Comment on this post [7] Posted in ASP.NET | ASP.NET MVC
Sponsored By

image Somewhere in late 1997, early 1998 (as I recall) I was working at a place called 800.com. Me and three guys (Joe Tillotson, Javan Smith and Patrick Cauldwell) from our consulting firm were helping create the storefront, shopping cart and business systems. We did it all in COM and Classic ASP. I did most of the front-end ASP, HTML, cut graphics, did scale, browser compat, the whole thing. Javan ended up working there full time for some years as I recall.

It was deep in the beginning of the first boom. We were sleeping with our servers. The place had no offices yet, and we were sitting on the stairs of the Tyee Group in Portland. March 1, 2002 800.com sold to Circuit City and disappeared. I still say they could have been like Amazon, but it just didn't work out.

Anyway, the CEO Greg Drew and his folks had this crazy idea for a promotion. It was simple, 3 DVDs for $1. That's it. Free shipping and everything. It was insane.

image In the same vein of crazy promotions, SparkFun Electronics had "Free Day" today. It was also a brilliant promotional idea. They offered $100 per person, until they hit $100,000. Of course, that's only 1000 people but you don't say that part. ;) Every geek on the planet had their finger on the "Buy" button and word spread like wildfire on Twitter. The whole thing was over in 1h 44m 50s, according to their website. I spent an hour trying to check out my cart, and only succeeded in loading a page twice. Bummer.

With 800.com, our 3 DVDs for $1 promotion lasted longer, a few days as I recall, and word travelled much slower, on USENet and Email. Barbarians. ;)

Here's some of the things we did to optimize the 800.com site for this massive traffic surge in that pre-cloud era. It worked for us. Perhaps SparkFun did, or could have, done similar things.

Think about the Ratio of Reads to Writes

When people visit your site, are they mostly reading data, like browsing a product catalog? Or are they mostly writing data, like putting something in a shopping cart and trying to check out?

There's basically these four types of data on a web site: resource or catalog data, infrequently-used reference data, user session data, and clickstream or activity oriented data. Each is accessed in a different way, and you can dramatically change the way your site behaves when you have crazy promotions.

When 800.com started our promotion, our sites dramatically switched from 95% of people browsing and 5% of people checking out, to 95% percent of people checking out and only 5% browsing. When the sites fundamental ratio changed we had to change the site.  We hadn't designed for this!

For the short term we started by turn the product catalog pages that were the most frequently visited into static pages. Don't go poo-poo that power of the static page. For small companies with just a few web servers there's little faster than serving a static page.

Cache Everything (Resources and Reference Data)

Memory is exceedingly cheap, and you really can't have too much.  Additionally if you're a small company with a small site, chances are your product catalog isn't that large. I would be surprised if most medium sized, catalogs can be kept in memory.  Additionally, the data in a product catalog using change very often, so it can be cached certainly for hours at a time, if not longer. After our short-term static files solution, we moved to just caching the entire catalog in memory. When all you need to do is show products and categories, caching is your friend.

Move Images Somewhere Else (Make it someone else's job)

We learned early on that the pages that the web servers that were doing the work really didn't need to be worrying about serving images, so we put those on http://images.800.com. This might seem obvious now in 2010, but it was pretty cool thinking in 1997. This allowed us to put all of our product images on a separate server farm and scale them out differently.

Today a lot of people put their images up on S3 at Amazon or other cloud services. For image heavy sites like product catalogs, moving them not only saves you time, effort and hardware, but it can also save bandwidth.

Partition Your Responsibilities (Break it up)

Even though you might have a single web server, or you might have six, think about partitioning responsibility early on, with separate mini-sites for things like images, profiles, web services, product catalogs, shopping cart, and check-out. If your site's characteristics change quickly and and you have to scale out and add new web servers, you can even create separate web farms for these mini-sites just checking out or just images. This can be as easy as having a separate virtual directory, and treating it as an application.

Embrace Stale Data (Realtime is a Lie)

One of the things we had at 800.com that was kind of revolutionary at that time was an AS/400 inventory system that we hooked up directly to the web.  You could see a real time inventory of any product even to the point where you could hit refresh and see products being sold. Remember that this was 1997.  In this case, it was silly of us to plug the AS/400 directly into the web server, so eventually we built an intermediate database. But the real root issue was that we realized what "realtime" meant to our business. The president of the company said he wanted realtime inventory, so we assumed he meant it.  But that didn't mean we needed to go back to the inventory system every second.  In fact, when pressed, we learned that realtime to our president could be within 5 or 10 minutes.

Updating data every 10 minutes is infinitely easier than updating it in real time as you might guess. Ask yourself, if you've got realtime data on an otherwise mostly static page, how stale can my data be?

Thanks for indulging me on this trip down memory lane.  Certainly, a lot of this stuff is obvious in 2010, and not just obvious but required in large enterprise systems, but these basic principles still apply today for small businesses running a relatively small web sites on, say, less than 10 servers.

Congratulations to SparkFun for their successful promotion. Even though I was unable to visit their site for two hours, the buzz they generated on twitter is no doubt invaluable.

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.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Video Review: Eye-Fi Wi-Fi SD Card for Digital Cameras and Your Life's Workflow

January 06, 2010 Comment on this post [30] Posted in Reviews
Sponsored By

I'm afraid I'm a utilitarian gift giver. I can't imagine not being this way. I basically look at a person's life-workflow and I look for ways to make some tiny thing slightly easier. Like a teacup with your name on it if yours keeps getting pinched at work, or a key-shaped USB key to fit better with a bunch of unruly keys.

I've added a short 2 minute view demonstration to the right here.

My wife LOVES to take pictures of the boys. But she HATES to upload them to the computer or server. She'll fill 16 gigs of photos and then come find me, "can you put these on the server."

I've tried putting software like Live Photo Gallery on her machine and adding an SD Card slot. Seriously, just put the card in and it'll automatically upload the photos. But, it doesn't fit into her life's workflow. It's not intuitive.

What is intuitive, however, is just bringing the camera home and putting it back on the shelf. And that's her workflow now.

I bought her a 4 gig "Eye-Fi Wi-Fi SD Card." Seriously, this thing is bananas. It's an SD memory card, looks, acts, works, IS an SD card. But, it has freakin' Wi-Fi built in. It'll work with basically any camera.

I'm all about miniature. Micro-SD is amazing. 2 gigs and you can accidentally swallow it. But, I'm amazed that this little card can effectively leach enough power from the host camera to run a tiny Wi-Fi client.

image Here's what you do.

  • You get the card for the first time and put it in it's little USB adapter and plug it into your computer.
    • The software is already loaded on the card - nice, thoughtful touch.
    • You install the Eye-Fi Manager little tray-icon on whatever machine in your house will be on the most. It uses 8megs on my machine, and I've already forgotten about it.
  • You sign into their Eye-Fi Manager Website and tell the card what network to automatically connect to.
    • Again, Kudos to them for supporting WPA2 and instantly seeing my network.
  • You tell the manager where to save photos. I chose \\server\photos\2010 and it'll automatically make a new folder for each day, although you can change the format of the folder name.

Boom, that's it. Now, whenever you take photos, your camera will automatically upload them to your computer when it's near your Wi-Fi. It's a dream.

(Here's the cheesy part of the review)

But what, seriously, that's not all. It'll also do "relayed" uploads, so you can upload to an intermediate website of theirs for temporary storage, and when your computer is on later, it'll download the photos.

EyeFiAnother nice feature is that it'll automatically upload if it sees a known public hotspot like a hotel or McDonald's, or WayPort. Still, not a reason to eat at McDonald's, but a reason to get a soda through the drive through. ;)

The final cool feature is the Eye-Fi Geo (or you can upgrade your existing card) will automatically geo-tag your photos (if you like) by looking at a database of known Wi-Fi access points around you. Awesome for trips to big cities.

I seriously don't have a bad thing to say about this card. The wife is thrilled and the WAF (Wife Acceptance Factor) remains high. I'm not a pro photographer, by any means, but this card works great in my Nikon D40 and my new FujiFilm point and shoot. Some pro folks on the Amazon Reviews have said they've had trouble shooting really really fast or that they've had problems with the speed of upload, but I just haven't. Be aware though and read the reviews to make your own decision.

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.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.