Blog Stats
  • Posts - 5
  • Articles - 0
  • Comments - 4
  • Trackbacks - 0

 

Monday, March 12, 2012

OneNote or Evernote?


For the past couple of years or so, I’ve been using both OneNote and Evernote to collect random pieces of information I need to remember: todo lists, random thoughts, recipes, photos of things like the meds I’m currently taking, etc. There is no rhyme or reason to which app I use; it’s just whatever comes to mind first.

Recently, the content of each has become large enough that I think it’s time to pick one and go with it. They both have desktop and mobile versions, so that obvious criterion is out. OneNote is quite a bit more flexible, but it’s also more complicated, and that adds to my stress. The notebooks are lined up on the left side, sections across the top, and pages (and subpages) down the right side. Add in all the features in the ribbon above, and it comes across as quite cluttered. Just like a real notebook, I guess.

Evernote, on the other hand, is pretty straight-forward. The interface is fresh and clean, making it feel lighter and less burdensome. Unfortunately, it’s not as flexible as OneNote. You can’t put items just anywhere on the page. It’s much more like a word processor than a notebook, making note-taking with it a little less satisfying.

On my mobile devices (iPhone and iPad), the situation is sort of flipped. The team at Microsoft did a good job figuring out how to render OneNote notes to the device, but note creation and editing leaves a lot to be desired. Checkboxes are in, but text font, color, and size are all out. Evernote’s app has all these.

I still haven’t decided which one to choose long-term, but I’m leaning toward Evernote. Just wish the text editor didn’t feel like Windows Wordpad so much. I’m not the only one.

Friday, November 19, 2010

An Elegant MVC Template for Items in Rows


My coworker and I were working a little later than our teammates this evening, and while reflecting on this week's work, we discovered this gem thanks to the venerable ReSharper and its code analysis (it detected an unclosed tag):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Product>>" %>

<div class="product-table grid">
    <% var i = 0;
       var j = 0;
       foreach (var item in Model)
       { %>
        <% if (i % 4 == 0)
           { %>
                    <% if (j != 0)
                     { %></div><% } %>
                    <div class="products-row">
        <% } %>
        <%= Html.DisplayFor(x => item) %>
        <% i++;
            j++;
        } %>
    </div>
</div>

I've edited this slightly to keep it a bit more anonymous. This, folks, is how not to do a template. I'm not trying to embarrass anyone here; this was likely originally done under the gun with the thought that we could clean it up later. Unfortunately, looking at our history, instead of being cleaned up, it's been modified and made worse several times.

If you didn't have a way to get this to render, it would probably take you a bit to figure out what it's actually doing. If you want me to save you the time, it's taking a collection of Products and displaying them in rows of 4 items each. This code is a problem for me because I subscribe to the clean, short, and easy-to-read philosophy. If the single-letter variables and their use wasn't enough to make you cringe, the broken HTML right there in the middle should. That's precisely the type of thing we want to avoid, and I'm glad ReSharper brought it to our attention. In a few minutes, we had replaced the above with this:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Product>>" %>
<%@ Import Namespace="Extensions" %>
<div class="product-table grid">
    <% foreach (var groupOfFour in Model.GroupsOf(4))
       { %>
    <div class="products-row">
        <%= Html.DisplayForMany(x => groupOfFour) %>
    </div>
    <% } %>
</div>

I don't know about you, but I can figure out that little template pretty quickly. You'll notice we have a couple new methods there which we pulled out for reusability: .GroupsOf() and .DisplayForMany(). These are both extension methods. The latter we already had in place; it simply iterates over an enumerable and calls Html.DisplayFor each of the items. The former we created for this view:

public static IEnumerable<IGrouping<int, T>> GroupsOf<T>(this IEnumerable<T> items, int count)
{
    return items
             .Select((item, index) => new { Item: item, Index = index / count })
             .GroupBy(x => x.Index, x => x.Item);
}

Not bad, eh? :-)

Wednesday, June 09, 2010

5 Ways Android Still Disappoints (Me)


Let me make this clear: I'm annoyed with Apple. I don't like their current policies and I don't like where Steve Jobs is taking the company. In general, I don't like it when any one company gets too much control in a market. When that happens, the leading company dictates the game and as consumers, our options all but disappear.

That said, I'm still going to buy a new iPhone next week. My Apple-hating friends seem to desperately want me to go Android instead, but frankly, it's not good enough for me, and here are the reasons why.

The Modern WinMo

One of the reasons that Microsoft has identified for Windows Mobile's rapid decline is the breadth of hardware. They exercised little control over manufacturer's implementations. In theory, that sounds great. We as consumers have lots of choice. In practice, though, it meant among other things that updates to the devices were left up to the manufacturers. As a result, that rarely happened. (I'm still bitter at Toshiba for leaving me hanging back in 2002.)

And now, Google is doing the same thing with Android. Case in point: my wife has a Motorola Backflip that we bought in April. It was released in March. Motorola says it will get Android 2.1 "sometime in Q3". Great. Meanwhile, I pull down the latest version of iPhone OS (now iOS) and install it the same day it's released.

You may say that I can't judge Android by one lazy manufacturer. Yup, I sure can. With Apple, my original iPhone has been supported perfectly for 3 years. With Android, I will have to wait for upgrades after Google releases them, possibly indefinitely. Not cool.

AT&T

We signed a new contract with AT&T in April to get my wife's phone. I've had a reasonable experience with them. I don't imagine my experience with Verizon would be any better, and I'm relatively confident that Sprint doesn't have the coverage it takes to work well for us. The fact is, AT&T, for whatever reason, doesn't have jack for Android phones. May not be Android's fault, but it's still a shortcoming that prevents me from having it just like the iPhone's exclusivity keeps some folks on other networks from having it.

Innovation? What Innovation?

Android has a nice dashboard and a great notification system and… nothing else original. I keep reading about how disappointing the iPhone is nowadays. "It has no innovation," people say. Who does? Android has modeled its behavior after the iPhone. That's fine, but if all you've got is a similar product and I'm invested both skill-wise and app-wise in my current platform, why should I change?

Microsoft's new Windows Phone 7 looks somewhat innovative, and I'm pretty excited to see what they'll bring to the table, but that's another six months away, at least. I've got a 3 year old phone that has some annoying issues now (thanks to recent encounters with water). I need a new phone now.

Is This Going to Work?

There's no shortage of criticism of Apple over its App Store policies, and I've vented my own anger about it. However, I will give them credit: their screening of apps has done a great job of weeding out the crap and gives an excellent indication that the app will work on my device.

How about Android? Nope. It might work on your phone. Maybe. You'll have to try it to see. Get burned by it? Well, write a nasty review to try to keep others from making the mistake you did. If you don't mind doing that stuff, then Android is the platform for you. Personally, I'd rather have a receptionist screening out the telemarketing and survey calls than hang up on them myself, but that's your call.

Slow, Slowing, Slower

All this yapping about multitasking. This is an area I've been on Apple's side from the beginning. Sorry folks, but this is the number one reason I hated Windows Mobile: the longer you use it, the slower it gets because it doesn't kill apps. I'm with Steve Jobs on this one: if you see a task manager, we're doing it wrong. I don't want to have to manually kill apps. I hate doing that on Windows let alone on a mobile device. To me, priority one should be keeping the device speedy. Waiting for your device to respond is unacceptable.

Bonus!

Taken from iPhone Letdown? 8 Things Apple Didn't Announce, here are my responses:

4G

Yeah, let me know if your area actually has it. I live in Lincoln, Nebraska. No carrier is going to have 4G here for at least 3 years. Meanwhile, you still get to pay for it. Yay!

Cloud iTunes/OTA Sync

You got me here. Of course, whether or not your Android device will be able to do it is always a good question.

3G Video Chat

You got me here, too. I'm sure you spent countless hours in front of your phone with video chat. Also, I can't wait for the "No Video Chat While Driving" laws.

Mobile Hotspot

This is a neat feature, but as the author points out, it's left up to the carrier whether to implement it or not. Pretty sure any Android phones that come to AT&T won't have this enabled in the foreseeable future. Is Verizon even allowing this? I just figured Sprint was because they're failing so hard at keeping customers.

Free MobileMe

I use Google's services with my iPhone. The only people I know who use MobileMe are Apple fanboys and fangirls. If you choose to pay for a service that you can get for free, that's your decision, not Apple's.

Voice Input

Voice input has been available on phones (even "dumb" phones) for years now. iPhone does have the ability, though limited. Why don't I hear people telling their phones what to do? Maybe because it's still easier to use your fingers than talk to it. Get back to me when this becomes an important feature.

Free Navigation

Maybe this will be a bigger deal to me now that I'm getting a phone with GPS, but when using my buddy's 3gs, Google maps has worked just fine. Maybe I just don't trust turn-by-turn navigation enough to want it.

Dashboard

The only legitimate complaint on this list, to me. iPhone's home screen is pathetic, doubly so for the iPad. What a waste of perfectly usable space. I also want to add notifications to this list. Android's notification panel is far superior to the iPhone's. I don't want to hunt all over my screen to find little red dots. Put 'em in one place, Apple.

Monday, April 12, 2010

Why Apple’s New SDK Limitation is So Offensive


I am not an Apple fanboy, nor have I ever been. However, I have owned a Mac, an iPod, and an iPhone in my lifetime, and for more than a decade, I have defended Apple against the untruths that the haters so enjoy spewing. I encouraged my wife to buy a MacBook when she needed a new laptop two years ago, and I often recommend them to my friends and relatives. I have proudly and happily used my first generation iPhone for nearly three years.

Now, for the first time in well over ten years, I find myself ready to swear off Apple and encourage everyone I know to do the same. I was disappointed when Apple wouldn't allow native apps, but I still bought the iPhone. I've stomached their ambiguous app approval process even though it's apparent that Steve may just reject your app because he doesn't like you or feels threatened by you (I'm still lamenting the rejection of the Google Voice app). But, as a developer, I can no longer tolerate Apple's terms and the kind of totalitarian control they indicate Apple wants.

In case you are not already familiar, Apple has dictated in their OS 4.0 SDK license agreement (the now infamous Section 3.3.1) that all apps developed for the iPhone must be coded in C, C++, or Objective C, and moreover, that using any cross-compiling platforms is a violation of the agreement.

For those of you who aren't developers, let me try to illustrate why this angers those of us who are.

Imagine you're a professional writer. You've had articles published in some journals and magazines, and you've got a couple popular books out there, too. You've got an idea for a new book, and so you take it to your publisher. Your publisher agrees that it's a good idea.

"But," says the publisher, "we want to hold our books to a tighter standard so that our readers get the experience we want them to have. Therefore, from now on, all our writers may only use words from this list of the 10,000 most common English words. Furthermore, if you cite any other works or quote anyone, they must comply with that same list, or you'll have to rewrite the entire work as well in case our readers want to look up your citation."

What do you do? If your work is a children's book, this probably isn't a big deal to you. If it's an autobiography, textbook, or even a novel, though, you're going to have a lot of trouble describing your content with only common words. It's going to take you longer to complete your book, too, since you'll be looking up less common words frequently to see if you can use them. You could always go to another publisher, but this one has the best ability to distribute your book. The next largest distributor can only do a quarter as much. You could abandon the project altogether, but then everyone loses.

Isn't this a silly scenario? Who would put such a limitation on writers? Yet this is very much what Apple is doing. They are using their dominant position in the market to coerce developers to write their apps exclusively for the iPhone OS by making it too expensive to write for multiple platforms. It is at least a threefold attack, striking at Adobe who is set to release a compiler that lets Flash source be compiled to iPhone binaries; striking at Google whose Android platform stands the best chance at the moment of providing serious competition to the iPhone; and reinforcing their own strong position by keeping popular apps exclusively to iPhone.

And while developers are already very upset about this, the sad fact is that most of us will cave and give in to Apple because consumers don't know any better. They will continue to buy Apple's toy forcing developers to play Apple's maniacal game in order to make any money, at least until Steve Jobs decides he doesn't like them or he intends to release a competing application (bye-bye OpenFeint).

Apple has been kept in check on the desktop front by a very dominant Microsoft, but I'm afraid that their success with iPods, iTunes, and iPhones has created a monster that we may have to bear until it is slain by an anti-trust suit or dies with the retirement of Steve Jobs.

Sunday, April 11, 2010

Caching NHibernate Named Queries


I recently started a new job and one of my first tasks was to implement a "popular products" design. The parameters were that it be done with NHibernate and be cached for 24 hours at a time because the query will be pretty taxing and the results do not need to be constantly up to date.

This ended up being tougher than it sounds.

The database schema meant a minimum of four joins with filtering and ordering criteria. I decided to use a stored procedure rather than letting NHibernate create the SQL for me. Here is a summary of what I learned (even if I didn't ultimately use all of it):

  • You can't, at the time of this writing, use Fluent NHibernate to configure SQL named queries or imports
  • You can return persistent entities from a stored procedure and there are a couple ways to do that
  • You can populate POCOs using the results of a stored procedure, but it isn't quite as obvious
  • You can reuse your named query result mapping other places (avoid duplication)
  • Caching your query results is not at all obvious
  • Testing to see if your cache is working is a pain
  • NHibernate does a lot of things right. Having unified, up-to-date, comprehensive, and easy-to-find documentation is not one of them.

By the way, if you're new to this, I'll use the terms "named query" and "stored procedure" (from NHibernate's perspective) fairly interchangeably. Technically, a named query can execute any SQL, not just a stored procedure, and a stored procedure doesn't have to be executed from a named query, but for reusability, it seems to me like the best practice.

If you're here, chances are good you're looking for answers to a similar problem. You don't want to read about the path, you just want the result. So, here's how to get this thing going.

The Stored Procedure

NHibernate has some guidelines when using stored procedures. For Microsoft SQL Server, you have to return a result set. The scalar value that the stored procedure returns is ignored as are any result sets after the first. Other than that, it's nothing special.

CREATE PROCEDURE GetPopularProducts
    @StartDate   DATETIME,
    @MaxResults  INT
AS
BEGIN
    SELECT [ProductId],
           [ProductName],
           [ImageUrl]
      FROM SomeTableWithJoinsEtc
END

The Result Class - PopularProduct

You have two options to transport your query results to your view (or wherever is the final destination): you can populate an existing mapped entity class in your model, or you can create a new entity class. If you go with the existing model, the advantage is that the query will act as a loader and you'll get full proxied access to the domain model. However, this can be a disadvantage if you require access to the related entities that aren't loaded by your results. For example, my PopularProduct has image references. Unless I tie them into the query (thus making it even more complicated and expensive to run), they'll have to be loaded on access, requiring more trips to the database.

Since we're trying to avoid trips to the database by using a second-level cache, we should use the second option, which is to create a separate entity for results. This approach is (I believe) in the spirit of the Command-Query Separation principle, and it allows us to flatten our data and optimize our report-generation process from data source to view.

public class PopularProduct
{
    public virtual int ProductId { get; set; }
    public virtual string ProductName { get; set; }
    public virtual string ImageUrl { get; set; }
}

The NHibernate Mappings (hbm)

Next up, we need to let NHibernate know about the query and where the results will go. Below is the markup for the PopularProduct class. Notice that I'm using the <resultset> element and that it has a name attribute. The name allows us to drop this into our query map and any others, giving us reusability. Also notice the <import> element which lets NHibernate know about our entity class.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
    <import class="PopularProduct, Infrastructure.NHibernate"/>
    <resultset name="PopularProductResultSet">
        <return-scalar column="ProductId" type="System.Int32"/>
        <return-scalar column="ProductName" type="System.String"/>
        <return-scalar column="ImageUrl" type="System.String"/>
    </resultset>
</hibernate-mapping>

And now the PopularProductsMap:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
    <sql-query name="GetPopularProducts" 
               resultset-ref="PopularProductResultSet" 
               cacheable="true" cache-mode="normal">
        <query-param name="StartDate" type="System.DateTime" />
        <query-param name="MaxResults" type="System.Int32" />
        exec GetPopularProducts @StartDate = :StartDate, @MaxResults = :MaxResults
    </sql-query>
</hibernate-mapping>

The two most important things to notice here are the resultset-ref attribute, which links in our resultset mapping, and the cacheable attribute.

The Query Class – PopularProductsQuery

So far, this has been fairly obvious if you're familiar with NHibernate. This next part, maybe not so much. You can implement your query however you want to; for me, I wanted a self-encapsulated Query class, so here's what it looks like:

public class PopularProductsQuery : IPopularProductsQuery
{
    private static readonly IResultTransformer ResultTransformer; 
    private readonly ISessionBuilder _sessionBuilder;

    static PopularProductsQuery()
    {
        ResultTransformer = Transformers.AliasToBean<PopularProduct>();
    }

    public PopularProductsQuery(ISessionBuilder sessionBuilder)
    {
        _sessionBuilder = sessionBuilder;
    }

    public IList<PopularProduct> GetPopularProducts(DateTime startDate, 
                                                    int maxResults)
    {
        var session = _sessionBuilder.GetSession();
        var popularProducts = session
                                 .GetNamedQuery("GetPopularProducts")
                                 .SetCacheable(true)
                                 .SetCacheRegion("PopularProductsCacheRegion")
                                 .SetCacheMode(CacheMode.Normal)
                                 .SetReadOnly(true)
                                 .SetResultTransformer(ResultTransformer)
                                 .SetParameter("StartDate", startDate.Date)
                                 .SetParameter("MaxResults", maxResults)
                                 .List<PopularProduct>();

        return popularProducts;
    }
}

Okay, so let's look at each line of the query statement. The first, GetNamedQuery, matches up with our NHibernate mapping for the sql-query. Next, we set it as cacheable (this is probably redundant since our mapping also specified it, but it can't hurt, right?). Then we set the cache region which we'll get to in the next section. Set the cache mode (optional, I believe), and my cache is read-only, so I set that as well. The result transformer is very important. This tells NHibernate how to transform your query results into a non-persistent entity. You can see I've defined ResultTransformer in the static constructor using the AliasToBean transformer. The name is obviously leftover from Java/Hibernate. Finally, set your parameters and then call a result method which will execute the query.

Because this is set to cached, you execute this statement every time you run the query and NHibernate will know based on your parameters whether to use its cached version or a fresh version.

The Configuration – hibernate.cfg.xml and Web.config

You need to explicitly enable second-level caching in your hibernate configuration:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
        [...]
        <property name="dialect">
            NHibernate.Dialect.MsSql2005Dialect
        </property>
        <property name="cache.provider_class">
            NHibernate.Caches.SysCache.SysCacheProvider,
            NHibernate.Caches.SysCache
        </property>
        <property name="cache.use_query_cache">true</property>
        <property name="cache.use_second_level_cache">true</property>
        [...]
  </session-factory>
</hibernate-configuration>

Both properties "use_query_cache" and "use_second_level_cache" are necessary. As this is for a web deployement, we're using SysCache which relies on ASP.NET's caching. Be aware of this if you're not deploying to the web! You'll have to use a different cache provider.

We also need to tell our cache provider (in this cache, SysCache) about our caching region:

<syscache>
    <cache region="PopularProductsCacheRegion" expiration="86400" 
           priority="5" />
</syscache>

Here I've set the cache to be valid for 24 hours. This XML snippet goes in your Web.config (or in a separate file referenced by Web.config, which helps keep things tidy).

The Payoff

That should be it! At this point, your queries should run once against the database for a given set of parameters and then use the cache thereafter until it expires. You can, of course, adjust settings to work in your particular environment.

Testing

Testing your application to ensure it is using the cache is a pain, but if you're like me, you want to know that it's actually working. It's a bit involved, though, so I'll create a separate post for it if comments indicate there is interest.

 

 

Copyright © TStewartDev