Technology Ninja. Loving Father.

Archive for March 2011

Automatically send behavior-based campaigns with Performable

No matter how much pleasure you get from putting together a solid, subscriber-focused email marketing campaign (hah!), there's probably a point where you go, "By golly, I wish I could automate parts of this process." Okay, so email campaigns are never going to write themselves, but when it comes to lining up emails to send at the most effective time, then both our app and our friends at Performable Analytics are here to help with triggered campaigns.

Last year, we released autoresponders to allow you to send campaigns when an event occurred within your subscriber list. Some of these campaigns were things like follow-up emails immediately after someone joined to your list, or an email offer automatically sent a week before a subscriber's birthday. Now, Performable have taken this one massive step further with Engage, a service which extends the autoresponders in your Campaign Monitor account to schedule campaigns based on events occurring on your site. For most of us, it's called "Wow, I just got a reminder email after abandoning my online shopping cart!" For the propeller-heads, it's called behavioral marketing.

Starting condition in Performable Engage

This might all seem pretty fiddly, however the folks at Performable have smoothed out the process of connecting sites to their marketing analytics service, then in turn, connecting Engage to your Campaign Monitor account. For example, lets go back to the earlier shopping cart scenario. Lets say you've got Performable Engage rolling and a basic, 'New subscriber signs up' autoresponder (or two) in your account. You then want to send an email to folks who have signed up as customers, added an item to their online cart, but never completed their purchase. You also want to delay the email being sent by a couple of days and heck, send a follow-up email if additional criteria is met. Here's what this workflow would look like in Performable Engage (click to enlarge):

Flow featuring multiple emails

Wow, that's some pretty clever email marketing right there. Now, imagine extending this to folks who have signed up to receive your cracking latest album of ambient chiptunes, but haven't gone on to download it. Or with Performable's Zendesk integration, being able to contact people who have lodged a ticket with you, but haven't logged back into their account for your web app. How crazy automated is that?

Engage stats

Performable Engage is also backed up by solid reports, both from within Performable and your Campaign Monitor account. Engage will tell you how many emails have sent and are scheduled to go (alongside Performable's regular lifecycle marketing metrics) and we'll keep tabs on opens, clicks and more via your autoresponder reports.

Based on this model, you may be well keen to check out our monthly pricing - with unlimited autoresponders and campaigns bundled into most plans, it's simply less stress.

Of course, in the words of Master Splinter, with great power comes great responsibility. Just as most folk were sensible enough to not go mad with plain ol' autoresponders, this kind of triggered campaign should be used with a bit of humanity. In short, to gently encourage subscribers to come visit you again, over being, well, annoying.

Finally, the folks at Performable have extended readers like you a 50% discount on their small business plans. To see if its suite of marketing analytics and email automation tools are suitable for your site, trial Performable for free and start scheduling 'em emails.

Convention Based Localization With ASP.NET MVC – Davy Brion’s Blog

A feature of ASP.NET MVC that i really like is that when you use the LabelFor extension method in a strongly-typed view, the LabelFor implementation will try to retrieve and use metadata for the property you're creating a label for. For instance: @Html.LabelFor(m => m.SomeProperty) This will generate an HTML label for the SomeProperty property of your model. If you need localized views, you can annotate the property in your model like this: [Display(ResourceType = typeof(Resources), Name = "Res...

Projection Screen Basics

Like Andrew, I also would like to have a projection setup someday. There's simply no replacement for the sheer envelopment that a projector can bring with a 100"+ screen. Unfortunately, my current abode isn't ideal, but I plan to set up a dedicated screening room in my next residence so I like to stay informed on the technology. Following on their HD projector overview, HD Guru goes over the various projection screens available (and yes, you do need a screen and not just some painted wall).

A few days ago I wrote an article espousing the virtues of front projection. I am an unabashed fan and hopelessly biased towards PJs. But to get the best performance out of a projector, you’re going to need a screen.

And this is the point where I lose the audience.

Still with me? Screens may seem boring, and there’s a lot to learn, but if you’re spending any amount of money on a projector you owe it to yourself to get a screen that lets you get the most from your new purchase.

HD Guru

Hover on “Everything But”

Adding a hover state to an element is durn easy. Let's use an opacity change as an example:

div {
   opacity: 1.0;
}
div:hover {
   opacity: 0.5;
}

But what if we want to have that hover state apply to everything but the element actually being hovered over? (e.g. other adjacent sibling divs)

Let's assume this basic HTML:

<section class="parent">
  <div></div>
  <div></div>
  <div></div>
</section>

We'll apply the current CSS properties to all the children of the parent when the parent is in the hover state.

.parent:hover > div {
  opacity: 0.5;
}

Then when the parent is hovered and the individual div is hovered, we bump the opacity back up, giving the final effect we are looking for.

.parent:hover > div:hover {
  opacity: 1.0;
}

Real World?

A similar kind of thing is in the Twitter for Mac app on individual tweets:

Demo

This idea can be extended into multiple levels of depth. Here is an example of three "lists." All list items have full opacity in their regular state, but as you roll over the lists, the currently hovered list is slightly more opaque than then others, and the currently hovered list item is fully opaque.

View Demo

And yes, old you-know-who browsers don't do :hover on anything but anchor links. If it's mission critical, use JavaScript to detect mouseenter events on them and apply/remove class names.

The wages of sin: Proper and improper usage of abstracting an OR/M

Originally posted at 3/11/2011

This time, this is a review of the Sharp Commerce application. Again, I have stumbled upon the application by pure chance, and I have very little notion about who wrote it.

In this case, I want to focus on the ProductRepository:

image

In particular, those methods also participate in Useless Abstraction For The Sake Of Abstraction Anti Pattern. Here is how they are implemented:

public AttributeItem GetAttributeItem(int attributeItemId)
{
    return Session.Get<AttributeItem>(attributeItemId);
}

public Attribute GetAttribute(int attrbuteId)
{
    return Session.Get<Attribute>(attrbuteId);
}

public IEnumerable<Attribute> GetAllAttributes()
{
    return Session.QueryOver<Attribute>()
        .Future<Attribute>();
}

public void SaveOrUpdate(Attribute attribute)
{
    Session.SaveOrUpdate(attribute);
}

And here is how they are called (from ProductService):

public AttributeItem GetAttributeItem(int attributeItemId)
{
    return productRepository.GetAttributeItem(attributeItemId);
}

public Attribute GetAttribute(int attrbuteId)
{
    return productRepository.GetAttribute(attrbuteId);
}

public void SaveAttribute(Attribute attribute)
{
    productRepository.SaveOrUpdate(attribute);
}

 public IList<Product> GetProducts()
 {
     return productRepository.GetAll();
 }

 public Product GetProduct(int id)
 {
     return productRepository.Get(id);
 }

 public void SaveOrUpdate(Product product)
 {
     productRepository.SaveOrUpdate(product);
 }

 public void Delete(Product product)
 {
     productRepository.Delete(product);
 }

 public IEnumerable<Attribute> GetAllAttributes()
 {
     return productRepository.GetAllAttributes();
 }

Um… why exactly?

But as I mentioned, this post is also about the proper usage of abstracting the OR/M. A repository was originally conceived as a to abstract away messy data access code into nicer to use code. The product repository have one method that actually do something meaningful, the Search method:

public IEnumerable<Product> Search(IProductSearchCriteria searchParameters, out int count)
{
    string query = string.Empty;
    if (searchParameters.CategoryId.HasValue && searchParameters.CategoryId.Value > 0)
    {
        var categoryIds = (from c in Session.Query<Category>()
                           from a in c.Descendants
                           where c.Id == searchParameters.CategoryId
                           select a.Id).ToList();

        query = "Categories.Id :" + searchParameters.CategoryId;
        foreach (int categoryId in categoryIds)
        {
            query += " OR Categories.Id :" + categoryId;
        }
    }

    if (!string.IsNullOrEmpty(searchParameters.Keywords))
    {
        if (query.Length > 0)
            query += " AND ";

        query += string.Format("Name :{0} OR Description :{0}", searchParameters.Keywords);
    }

    if (query.Length > 0)
    {
        query += string.Format(" AND IsLive :{0} AND IsDeleted :{1}", true, false);

        var countQuery = global::NHibernate.Search.Search.CreateFullTextSession(Session)
            .CreateFullTextQuery<Product>(query);

        var fullTextQuery = global::NHibernate.Search.Search.CreateFullTextSession(Session)
            .CreateFullTextQuery<Product>(query)
            .SetFetchSize(searchParameters.MaxResults)
            .SetFirstResult(searchParameters.PageIndex * searchParameters.MaxResults);

        count = countQuery.ResultSize;

        return fullTextQuery.List<Product>();
    }
    else
    {
        var results = Session.CreateCriteria<Product>()
            .Add(Restrictions.Eq("IsLive", true))
            .Add(Restrictions.Eq("IsDeleted", false))
            .SetFetchSize(searchParameters.MaxResults)
            .SetFirstResult(searchParameters.PageIndex * searchParameters.MaxResults)
            .Future<Product>();

        count = Session.CreateCriteria<Product>()
            .Add(Restrictions.Eq("IsLive", true))
            .Add(Restrictions.Eq("IsDeleted", false))
            .SetProjection(Projections.Count(Projections.Id()))
            .FutureValue<int>().Value;

        return results;
    }
}

I would quibble about whatever this is the best way to actually implement this method, but there is little doubt that something like this is messy. I would want to put this in a very distant corner of my code base, but it does provides a useful abstraction. I wouldn’t put it in a repository, though. I would probably put it in a Search Service instead, but that isn’t that important.

What is important is to understand where there is actually a big distinction between code that merely wrap code for the sake of increasing the abstraction level and code that provide some useful abstraction over an operation.

HTML5 Boilerplate MVC 3 Websites with AppHarbor in 3 Easy Steps

Here’s a quick way to get a new .Net MVC 3 site up that incorporates HTML5 Boilerplate patterns and practices.

Step 0: Get Git (If you don’t have it)

Git for Windows is available from the msysgit repository on Google Code; this guide was written for the 1.7.4 preview version.

There’s even a guide for installing on windows if you need some help.

Step 1:  Create Your AppHarbor Project

Go to the AppHarbor home page and create a new account if you haven’t done so yet.  Then click the Create New link on the Applications tab to get started.

Newapp

Once you’ve got your application created, copy the Repository URL so you can push your source up to your new site.

Gitlink

Step 2:  Create Your MotherEffin HTML5 MVC 3 Site Project

I’ve created a super easy HTML5 Boilerplate MVC 3 template project that can deploy to AppHarbor with no modifications.  You can get it from the Visual Studio Extension gallery, or through the new project dialog by clicking the Online Templates tab item on the left and searching for “HTML5?.

Html5projecttemplate

Make a note of your project location so we can use the Git Bash shell to push our source.

Step 3: Push it to AppHarbor with Git

Once you’ve got your project all set up and ready to deploy, navigate to your project’s location, right click on the root directory and select “Git Bash Here”.

Enter the following commands to initialize your new Git Repository
  1. git init
  2. git add .
  3. git commit -m "initial check in"

Once you have your files committed locally, you are ready to push up to the AppHarbor Git Repository Url.

Here are examples of the commands to do that:

  1. git remote add appharbor [YourSiteRepositoryUrl]
  2. git push appharbor master

Gitbashhere

Gitinitapp
Gitpushapp
Enjoy Your Site

Congratulations, you’ve just deployed your first HTML5 website.

Now all you have to do is navigate to your app’s url; i.e http://best-site-evar.apphb.com.

Next step, add a database and add the code first entity framework NuGet package for fast database scaffolding.  Hopefully I’ll get a tutorial up for that soon.

See the original post on my posterous blog

Hanselminutes Podcast 257 – Selenium for Web Automation Testing with Jim Evans

image Scott chats with Jim Evans from the Selenium team about how to get into Web Automation Testing. What's new in Selenium v2? Can you use Selenium with any browser? How does .NET fit into the process? All this and more in this Web Testing Episode.

Download: MP3 Full Show

Links:

NOTE: If you want to download our complete archives as a feed - that's all 257 shows, subscribe to the Complete MP3 Feed here.

Also, please do take a moment and review the show on iTunes.

Subscribe: Subscribe to Hanselminutes or Subscribe to my Podcast in iTunes or Zune

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.

Building quality software is never easy. It requires skills and imagination. We cannot promise to improve your skills, but when it comes to User Interface and developer tools, we can provide the building blocks to take your application a step closer to your imagination. Explore the leading UI suites for ASP.NET AJAX, MVC, Silverlight, Windows Forms and WPF. Enjoy developer tools like .NET Reporting,ORM,Automated Testing Tools, Agile Project Management Tools, and Content Management Solution. And now you can increase your productivity with JustCode, Telerik’s new productivity tool for code analysis and refactoring. Visit www.telerik.com.

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?



© 2011 Scott Hanselman. All rights reserved.

Product Blog update: Changes to Basecamp email notifications, using Highrise with MailChimp and Formstack, etc.

Basecamp
Tips: Make sure your time stamps show up accurately in Basecamp. You can create an announcement that appears at the top of a project’s Overview page. The announcement can describe the project, give a special heads up, or say anything else you want people to see. The footers of Basecamp messages now show a list of everyone who is receiving the email. We also changed the way Basecamp email notifications appear in your inbox. Notification subjects will always show the name of the project in brackets at the beginning of the subject line. Maybe you’d like to set up an email filter for messages, like this one in Apple Mail:

Extras: BugDigger lets you create bug reports at the push of a button — and it integrates with Basecamp. bcToolkit, a Basecamp reporting tool, announced an update that fixes issues with moved to-do lists. Video Bug Recorder for Basecamp is a Windows tool that records video from your screen and uploads it to your Basecamp account with just a few clicks.

Buzz: Basecamp is the Editors’ Choice winner of PC Mag’s “The Best Free Online Project Management Software.” They say, “Basecamp is the simplest, fastest, and most scalable project management service available.” Mark White has an eBook that promises to reduce your workweek from 70 to 40 hours and advises using Basecamp to avoid phone calls.

Case studies: Cyber-Duck is a digital agency based in London that swears by Basecamp. Matthew Egan, President of SEO firm Image Freedom, wrote to tell us how his team uses Basecamp. Gregor McKelvie offers tips on how a small company can get more out of Basecamp.

Highrise
Extras: Randall Robinson offers advice on using MailChimp and Highrise together to create mailing lists and track campaigns. Use Mumboe with Highrise to keep track of business agreements and contracts. CompanionLink Express lets you sync Highrise contacts, tasks, and cases with your phone. Ringio moves your phone system into the cloud and it now integrates with Highrise. Sage Wedding Pros’ Michelle Loretta wrote about how she loves the integration between Formstack and Highrise. The FreshBooks Add-on Store opened up and includes a free add-on for Highrise.

Buzz: A NY Times article discussed people who run a business alone but want it to appear bigger than it really is — as if it has teams of employees and lots of resources. One of the article’s subjects, Peter Sorgenfrei, uses Highrise to keep track of his email. Basecamp and Highrise top eCommerce Hacks’ “7 Indispensable Web Tools Every Ecommerce Merchant Should Be Using.”

Backpack
Extra: Version 2.0 of Pouch brings Backpack’s Reminders, Calendar, and Journal to the iPhone and iPad.

Case study: tap tap tap’s John Casasanta creates iPhone apps and he published a list of tools that help the members of his virtual office collaborate: “Out of all the apps and services I use throughout the day, I find myself spending most of my time in Backpack.”

REWORK
Buzz: In the Sioux City Journal, Corey Westra, the commissioner of the Great Plains Athletic Conference, talks about how he’s found REWORK insightful. REWORK is one of the best business books of all time, according to UK brand strategist Ben Austin. Scott Buchmann’s REWORK review praises getting your hands dirty. He wrote, “The world is littered with classes on how to do anything: how to ride a motorcycle, how to paint, how to juggle, how to make money. Fact is, that you will never learn to do any of these things unless you actually go out and do these things.”

Subscribe to the Product Blog RSS feed.

Get to Know Action Filters in ASP.NET MVC 3 Using HandleError – .net DEvHammer

What’s an Action Filter? If you’re just getting started with ASP.NET MVC, you may have heard of something called action filters, but haven’t had the chance to use them yet. Action filters provide a convenient mechanism for attaching code to your controllers and/or action methods that implements what are referred to as cross-cutting concerns, that is, functionality that isn’t specific to one particular action method, but rather is something you’d want to re-use across multiple actions. An action filter is a .NET class that inherits from FilterAttribute or one of its subclasses, usually ActionFilterAttribute, which adds the OnActionExecuting, OnActionExecuted, OnResultExecuting, and OnResultExecuted methods, providing hooks for code to be executed both before and after the action and result are processed. Because action filters are subclasses of the System.Attribute class (via either FilterAttribute or one of its subclasses), they can be applied to your controllers and action methods using the standard .NET metadata attribute syntax:...
Shout it
Only $4.95/month – Windows 2008/ASP.NET 4 Hosting!

Slicehost: How A Goal-Setting Bootstrapper Launch & Sold His Business – with Matt Tanase

If you meet Matt Tanase in person, ask him to show you the wallpaper photo on his phone. It'll probably be of a...