Skip to content

Wait, is this thing still alive?

Well, according to WordPress, I have about 20 active subscribers. That means there’s about 20 of you who are seeing this post pop up in your inbox making you think, “Wait.. what was that thing again?”

Yes, it’s been many years. KitchenPC was an amazing project and a highlight of my long career in tech. Being involved in the Seattle startup ecosystem was rewarding as well as a huge boost to my career trajectory. Everyone in tech should do at least one startup in their lives, even if it’s just a small side project.

With that said, I have some news! The last few weekends I’ve been trying to fix up and modernize the hugely outdated and legacy KitchenPC code base. Digging through the code, I’m amazed at how awesome a lot of it still is, but seeing how it’s written on the .NET 4.6 Framework (before .NET was called .NET Core and then just .NET again) and requires a Windows machine to build and run (I haven’t run Windows in years!), it was time to either fix up the code, or just call it a day and take the server offline. After all, I was paying Amazon about $50/mon just to host the thing on a Windows VM and Postgres RDS instance. Switching over to a Linux host would be far cheaper, but require me to bring up the code base to this decade’s tech stack. With that said, I have a few announcements.

The KitchenPC Open Source Framework is now netstandard2.0

The open source version of KitchenPC (which was basically pulled out of the KitchenPC website and made “generic” enough to run on any data store and configure for any purpose) has been brought up to date and now compiles with the latest C# compiler and framework.

KitchenPC Core Library is available on NuGet (Finally)

Before, you’d have to fork the repo and build the source if you really wanted to try out the project. I’m not sure how many people actually did this ever. I’d imagine 90% of them would have given up anyway.

The open source framework now builds on GitHub and automatically deploys to NuGet as a package anyone can reference:

   <ItemGroup>
      <PackageReference Include="KitchenPC.Core" Version="1.0.0" />
      <PackageReference Include="KitchenPC.DB" Version="1.0.0" />
   </ItemGroup>

It’s now a lot easier to try out the framework and use it in your own projects!

Imp is also open source!

Back when I first released the site, I remember getting a lot of comments on how fast it was. At the time, the .NET ecosystem was based around ASP.NET WebForms. Which I absolutely hated, and still hate to this day. The MVC Razor stuff is not much better. So, I wrote a proprietary framework called Imp, which stands for “In-Memory Pages”. The idea is there’s no HTML files on the file system, they’re all embedded as DLL resources. All the templates are stored as XML in memory and there’s a page compiler that converts that into an HTML output, substituting dynamic data where needed. I remember running JMeter tests on this framework and being able to serve up hundreds of pages per second, where WebForms would give me maybe 20.

The idea of this framework is that every URL is a class, and every URL parameter is a property of that class. There’s no complex routing or anything. For example, /Recipes/ViewRecipe/?id=123 would map to a class called Recipes.ViewRecipe which had a public int id property. Imp will instantiate that class and set the id property, then call the Render method. Render could just write out some data, or it could use a template that contains the actual HTML. Dynamic properties in the template would be mapped to methods in the class, for example if the template had <Dynamic.UserName /> it would call a method called UserName() on the class. It was insanely fast and also dirt simple, exactly what I needed for this site.

The old version that KitchenPC used was a managed handler configured in IIS, bypassing ASP.NET completely. The new open source version has been rewritten as ASP.NET Middleware, so it could be run is Kestrel or what not.

With that said, Imp has been pulled out of the KitchenPC website and made open source. It’s also packaged up on NuGet for anyone who wants to try it out and needs a very simple way to render HTML pages.

   <ItemGroup>
      <PackageReference Include="KitchenPC.Imp" Version="0.1.0" />
   </ItemGroup>

I doubt I’ll work on it much (it does what I need), since there’s very little chance Imp takes off in popularity and becomes the next “thing”, but I’m also happy to take pull requests if those come along.

KitchenPC (the website) now uses the open source framework!

This one was a massive undertaking. When I wrote KitchenPC, the website and the core framework (which did all the fancy stuff with ingredients, recipes, natural language parsing, recipe modeling, etc) were basically wrapped up into one giant project. When I decided to open source KitchenPC, I pulled out all the guts (anything non website related) and packaged it up into a new project, which ended up on Github for the public. That new project had Fluent configuration, much more generic concepts, a completely abstracted data loading mechanism (you could run the engine without a database just off your file system for example), and also removed a bunch of concepts of the website (such as meal plans).

The website itself (which is still closed source) now uses the open source code engine directly. This required a lot of changes and improvements to the open source engine to support everything the website needs to do, but it’s also a fantastic “proof of concept” to show what you can actually build on the open source framework. I can now literally say you can build something like the KitchenPC website using the framework I’ve released.

The KitchenPC Website now runs on .NET 10

This was another massive undertaking. The major headache with this was the website was written using WCF, which was never ported to .NET Core or .NET 5 or above. I heavily relied on WCF for calling KitchenPC APIs, serializing JSON and error information, authentication, all that.

.NET WebApi is so much different, it took a ton of work to rewire everything. For example, WCF allows you to call web methods with multiple parameters, such as Recipe GetRecipe(guid recipeId, RecipeOptions options); whereas WebAPI only supports a single parameter, so you’d have to wrap everything up as an object. WCF also generates a very cool little JavaScript proxy script that just lets you call things on the server. I would include this proxy script (automatically generated on the server and cached) and in Javascript, just call KPCServer.GetRecipe(...); to load the data. This is a feature I loved so much, I ended up just writing the same feature for WebAPI in a mostly compatible way.

Modern Hosting for the website

In the beginning, I used RackSpace for hosting KitchenPC. The very first beta version as well as the public product launch used a VM on Rackspace which was a single Windows 2008 server running IIS and Postgres 9. A while later, I moved over to a VM on Amazon (Also Windows Server 2008 and never since upgraded!) and used Amazon RDS for the Postgres database, which made things a bit cheaper since Rackspace was around $80/mon.

Probably about a year or two ago, I kept getting nasty emails from Amazon saying I needed to update my Postgres version because “version 12” was being EOL’ed. There was one problem. The newer versions of Postgres used a TLS version that my ancient version of Nlpgsql (the Postgres driver for .NET) didn’t support. I didn’t even have a Windows machine to build KitchenPC on anymore and I had totally lost my entire setup for deploying site changes. It was actually easier to stop using Amazon RDS and just install Postgres 12 directly on the Windows VM and port my database there. So that’s what I did, which also means I’ve been using a super out of date version of Postgres. It was a bit cheaper though!

The new .NET 10 version of the website has been completely modernized. It’s built on a build server with GitHub Actions and hosted on Render, which allows running containerized applications. Render is hooked up to GitHub and when a new commit is pushed to main, it runs the Dockerfile and bundles up the app and runs it. Previously, I never even had CI/CD for KitchenPC! I just deployed directly from my computer using a PowerShell script I wrote. Half the time that script didn’t run right and I had to RDP into the server and screw with things.

Render also handles HTTPS. Don’t get me started on how much of a pain it’s been over the years to keep that Windows SSL cert up to date whenever it expired and trying to remember the slew of openssl commands required to convert PEM to PKCS. There was some hack you had to do as well to support the old encryption that Windows Server 2008 used. Render creates an SSL cert automatically, installs it, and keeps it up to date for free. I didn’t even do anything! It just did all that by itself.

So, I no longer have to buy an SSL cert for the site (So long, SSLs.com). The Render CLI is also amazing, allowing me to basically stream server logs in real time and make changes from the command line. Everything works right away out of the box, and oh BTW, it’s a lot cheaper than Amazon. I’m also using a Render Postgres instance for the database, which is version 18 and maintained and upgraded for me. No more worrying about any sort of server administration and deploying changes is super easy.

I also updated email notification handling. The old version of the site (which has probably been broken for years) ran a little background Windows Service called KPCQueue. The website would drop a file to the file system, the queue would pick it up, and send an email. If the queue crashed, it was fine because as soon as it would restart, it would see all the files and start processing where it left off. It used, I believe, some Google SMTP server I had setup to actually send the emails. It worked okay, but was pretty hacky.

The new website uses Resend, which holy crap is amazing. If you’re looking for a good way to send transactional emails, check out Resend. As a developer, this was exactly 100% what I needed. First, it’s free because I just don’t send enough emails to require one of their paid tiers. The developer support was crazy good, you just pull down a NuGet package and type a few lines of code. The web interface supports all the kind of template “mail merge” stuff you want to do (no need to port that stuff from KPCQueue) and provides various pretty graphs and stats to see what’s going on. I wrote all the code and configured my API key and made a basic email template and the whole thing worked on the very first try. This allowed me to kill the entire KPCQueue background service and implement “Share Recipe”, “Forgot Password” and “Welcome to KitchenPC!” emails directly in the website.

Hopefully more?

Don’t expect KitchenPC to come back full force. It was a fun project but I doubt it will really go anywhere. I also have a full time job that takes most of my time and mental energy. However, I’m hoping to contribute here and there to KitchenPC and its open source components on occasion and maybe even get this blog going again. It’s a fun side project and lets me try out new frameworks, technologies and services.

Hopefully the 20 of you reading this will not just go, “Ugh who is this? <unsubscribe>” or even worse, “Ugh who is this? <mark as spam>” but go check out the site again and maybe even dig around GitHub to see some of my work. That’s it for now!

2014 in review

The WordPress.com stats helper monkeys prepared a 2014 annual report for this blog.

Here’s an excerpt:

The concert hall at the Sydney Opera House holds 2,700 people. This blog was viewed about 10,000 times in 2014. If it were a concert at Sydney Opera House, it would take about 4 sold-out performances for that many people to see it.

Click here to see the complete report.

Creating custom column mapping for enumerable types

Since the KitchenPC database adapter has to work with various database systems out of the box, it must use data types common to all dialects for column mappings.  For example, the IngredientForms model has a UnitType property of type Units.  Units is an enum in C#, but will be mapped to a string in SQL.

However, what if we want to use a custom type on our backend as well?  For example, Postgres allows enumerable types to be defined, which could be useful here.

For this reason, KitchenPC comes with an easy way to define NHibernate conventions for enumerable types, which allow the user to override this default mapping.  Let’s start by first defining an ENUM in Postgres for both Units and UnitType:

CREATE TYPE UnitType AS ENUM ('Unit', 'Volume', 'Weight');
CREATE TYPE Units AS ENUM ('Unit', 'Teaspoon', 'Tablespoon', 'FluidOunce', 'Cup', 'Pint', 'Quart', 'Gallon', 'Gram', 'Ounce', 'Pound');

Now, we have a new data type in Postgres that we can use for columns, function parameters, etc. Next, we need to map the C# UnitType enum to the Postgres UnitType enum, as well as Units with Units. We can do this using the handy EnumMapper class that comes with KitchenPC. It has a static method that builds an NHibernate IPropertyConvention instance, so it can be used fluently in the configuration:

var dbConfig = Configuration<DBContext>.Build
   .Context(DBContext.Configure
      .Adapter(DatabaseAdapter.Configure
         .DatabaseConfiguration(
            PostgreSQLConfiguration.PostgreSQL82
               .ConnectionString(@"Server=localhost;Port=5432;User Id=Website;Password=password;Database=KPCSample")
         )
         .AddConvention(EnumMapper<Units>.Convention)
         .AddConvention(EnumMapper<UnitType>.Convention)
      )
   ).Create();

Note we’re calling AddConvention twice, adding a convention to map Units to Units and UnitType to UnitType. What exactly is this class doing under the covers?

The code is pretty simple:

public class EnumMapper<T> : EnumStringType<T>
{
   public override SqlType SqlType
   {
      get
      {
         return new SqlType(DbType.Object);
      }
   }

   public static IPropertyConvention Convention
   {
      get
      {
         return ConventionBuilder.Property.When(
            c => c.Expect(x => x.Type == typeof (GenericEnumMapper<T>)),
            x =>
            {
               x.CustomType<EnumMapper<T>>();
               x.CustomSqlType((typeof (T).Name));
            });
      }
   }
}

First, we’re inheriting from the EnumStringType base class, which is what NHibernate will use by default to map an enum. However, we’re overriding the SqlType to cause NHibernate to treat this type as an object, not as a string. This will prevent NHibernate from doing silly things like trying to cast SQL parameters to text. Without this, we’d have:

INSERT INTO Foo (SomeEnumColumn) VALUES ('Cups'::text);

Where what we want is:

INSERT INTO Foo (SomeEnumColumn) VALUES ('Cups'); -- No cast

The Convention property builds an NHibernate convention that finds all mapped properties of type T, then sets their CustomType as well as the SQL type. The latter will be used to correctly provision the database using the new type. In other words, the CREATE TABLE commands will now use Units and UnitType.

So, there you have it; two lines you can add to your configuration to change how enums are mapped to the database. Two things to keep in mind. First, this code will not create those enums for you. You’ll need to do this yourself, and before you provision your database using InitializeStore. Second, notice we call CustomSqlType using typeof(T).Name, meaning the type name in your database must match the enum type in C#. If you wanted to change this, you’d have to write your own convention to do so.

Let’s Make a Recipe!

The Beta version of KitchenPC allowed users to enter their own recipes, but it was such a giant pain, pretty much no one did it. The re-launch gave KitchenPC a new goal; to be a powerful recipe search engine, where existing recipes were collected from all over the Internet.

However, the KitchenPC platform allows you to add new recipes to the database. This could be done through a UI, an automated process, crawler, etc.

To do so, we use the fluent Recipes.Create action:

var result = context.Recipes
   .Create
   .WithTitle("Puff Pancake")
   .WithCredit("KitchenPC.com")
   .WithCreditUrl(new Uri("http://www.kitchenpc.com"))
   .WithImage(new Uri("http://images.kitchenpc.com/ccb1d90f-c49c-4271-8545-cdf6e2859444.png"))
   .WithCookTime(60)
   .WithPrepTime(15)
   .WithDescription("Great for breakfast!")
   .WithMethod("Mix everything together, pour in a glass pie dish, bake for 23 minutes at 400 degrees.")
   .WithRating(Rating.FiveStars)
   .WithServingSize(4)
   .WithTags(RecipeTag.Breakfast | RecipeTag.NoMeat | RecipeTag.Easy)
   .WithIngredients(i => i
      .AddRaw("4 eggs")
      .AddRaw("1 cup flour")
      .AddRaw("1 cup milk")
   )
   .Commit();

Obviously, some of these properties are optional, but you get the idea. The WithIngredients method is fairly flexible as to how you can express ingredients. The easiest way, as shown above, is to call AddRaw, which invokes the natural language parser to understand ingredient usages. As all recipes in KitchenPC must be normalized, KitchenPC will throw an exception if it is unable to parse an ingredient usage.

You can also manually specify an ingredient or usage, using AddIngredient or AddIngredientUsage:

.WithIngredients(i => i
   .AddIngredient(Ingredient.FromId(new Guid("948aeda5-ffff-41bd-af4e-71d1c740db76"))) // Eggs
)

Or, to specify an amount along with a prep note:

.WithIngredients(i => i
   .AddIngredientUsage(c => c
      .WithIngredient(Ingredient.FromId(new Guid("948aeda5-ffff-41bd-af4e-71d1c740db76"))) // Eggs
      .WithForm(IngredientForm.FromId(new Guid("28c374f6-2bd7-4085-a053-f68d0565050a")))   // Unit form
      .WithAmount(4, Units.Unit)
      .WithPrepNote("beaten")
   )
)

You can also divide your ingredients up into sections, which is frequently used to separate parts of a recipe (the cake part and the frosting part, for example). Let’s add some fruit toppings to our puff pancake.

.WithIngredients(i => i
   .AddRaw("4 eggs")
   .AddRaw("1 cup flour")
   .AddRaw("1 cup milk")
)
.WithIngredients("toppings", i => i
   .AddRaw("bananas")
   .AddRaw("maple syrup")
)
.Commit();

Now, we have our main ingredients in an unnamed section, and bananas and maple syrup in a section called toppings.

I think I know what I’m eating for breakfast tomorrow.

KitchenPC Logging 101

KitchenPC, as well as most open-source .NET projects, uses log4net for all internal logging.  If you’re already familiar with log4net, most of this post will be fairly useless for you.  Just note you can configure log4net appenders to see various KitchenPC activity.

For the rest of you,  here’s a basic tutorial.  First, you’ll need to reference log4net in your project.  This can be done manually, or by running:

Install-Package log4net

in the NuGet console.

Once you have log4net added to your project, you need to configure it. You can do this in one line of code using:

BasicConfigurator.Configure();

However, this will dump an absolutely insane amount of data to your console which you probably don’t want. Instead, it’s best to configure log4net in your application’s .config file. For this, add the following line of code:

XmlConfigurator.Configure();

Then create the following section in your .config file:

<configSections>
   <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>

This must appear at the top of the config file, as the first child to the configuration node. You might already have that node, in which case just add the <section> tag. Next, add the following element to the configuration section:

<log4net>
   <appender name="Trace" type="log4net.Appender.ConsoleAppender, log4net">
      <layout type="log4net.Layout.PatternLayout,log4net">
         <param name="ConversionPattern" value="[%level] - %m%n"/>
      </layout>
   </appender>
   <root>
      <level value="ERROR"/>
      <appender-ref ref="Trace"/>
   </root>
   <logger name="KitchenPC.Context">
      <level value="DEBUG"/>
   </logger>
   <logger name="KitchenPC.DB.Provisioning">
      <level value="DEBUG"/>
   </logger>
   <logger name="KitchenPC.NLP">
      <level value="DEBUG"/>
   </logger>
   <logger name="KitchenPC.Modeler">
      <level value="DEBUG"/>
   </logger>
</log4net>

You can control individual configurations of the various KitchenPC loggers. This uses the ConsoleAppender, which writes data out to the console, but log4net comes with various other appenders depending on how you want your data logged, and which events should be logged where. You can read about how to configure these appenders here.

There’s also one more interesting logging feature of KitchenPC, and that’s the NLP tracing logger. The NLP engine is so complicated, it has the ability to trace into individual queries and diagnose exactly how the query was parsed. You can enable an NLP tracer using the NlpTracer.SetTracer() method. This method takes an ITracer implementation which basically has a single method, Trace. The NLP engine will call it if one is configured.

You can write your own ITracer which does whatever you wish with the log data, but there is a default tracer which simply logs each trace event using log4net. Note, this is much more detailed than the standard NLP logging. To configure this trace, you’d use:

NlpTracer.SetTracer(new DefaultTracer()); // Log NLP to log4net

Now if we run the query:

var test = KPCContext.Current.ParseIngredientUsage("a dozen bananas");

We can see the NLP trace:

[DEBUG] - Loaded Template: [ING]
[DEBUG] - Loaded Template: [ING]: [AMT] [UNIT]
[DEBUG] - Loaded Template: [ING]: [AMT]
[DEBUG] - Loaded Template: [FORM] [ING]: [AMT]
[DEBUG] - Loaded Template: [FORM] [ING]: [AMT] [UNIT]
[DEBUG] - Loaded Template: [AMT] [UNIT] [FORM] [ING]
[DEBUG] - Loaded Template: [AMT] [UNIT] [ING]
[DEBUG] - Loaded Template: [AMT] [UNIT] of [ING]
[DEBUG] - Loaded Template: [AMT] [UNIT] of [FORM] [ING]
[DEBUG] - Loaded Template: [AMT] [ING]
[DEBUG] - Loaded Template: [AMT] [UNIT] [FORM], [ING]
[DEBUG] - Loaded Template: [AMT] [FORM] [ING]
[DEBUG] - Loaded Template: [AMT] [ING], [PREP]
[DEBUG] - Loaded Template: [AMT] [UNIT] [ING], [PREP]
[DEBUG] - Loaded Template: [AMT] [UNIT] [FORM] [ING], [PREP]
[DEBUG] - Loaded Template: [AMT] [ING] - [PREP]
[DEBUG] - Loaded Template: [AMT] [UNIT] [ING] - [PREP]
[DEBUG] - Loaded Template: [AMT] [UNIT] [ING] (optional)
[DEBUG] - Loaded Template: [AMT] [UNIT] [FORM] [ING] (optional)
[DEBUG] - Loaded Template: [AMT] [UNIT] [ING] (divided)
[DEBUG] - Loaded Template: [AMT] [UNIT] [ING], divided
[DEBUG] - Loaded Template: [AMT] [UNIT] [ANOMALY]
[DEBUG] - Loaded Template: [AMT] [UNIT] [ANOMALY], [PREP]
[DEBUG] - Loaded Template: [AMT] [UNIT] [ANOMALY] - [PREP]
[DEBUG] - Loaded Template: [AMT] [ANOMALY]
[DEBUG] - Loaded Template: [AMT] [ANOMALY], [PREP]
[DEBUG] - Loaded Template: [AMT] [ANOMALY] - [PREP]
[INFO] - Usage "a dozen bananas" matches the grammar "[AMT] [ING]"
[DEBUG] - [BuildResult] Ingredient: bananas
[DEBUG] - [BuildResult] No custom unit found, so cannot get form based on unit.
[DEBUG] - [BuildResult] No known form found, so cannot get form based on form synonym.
[DEBUG] - [BuildResult] Linking to default Unit paired form 638df06b-780c-4e0d-a3bd-f2986e235fc6
[INFO] - [BuildResult] SUCCESS: Linked form is compatible with usage reference.

Here, we have a successful parse. First, we looped through a bunch of grammar templates to see if any of them matched our input. The template [AMT] [ING] (an amount followed by an ingredient, “a dozen” being an amount and “bananas” being an ingredient) matched. The match data didn’t contain any custom form, such as “slices” or “heads” or what not. In fact, there was no form specified at all, so it was linked with the default form, which is “Units”. Lastly, it verified units of bananas was a valid way to express bananas, which it was, so we have a successful match.

Now, let’s run a query that doesn’t make any sense.

var test = KPCContext.Current.ParseIngredientUsage("5 cheese");

Here, we get the following trace:

[INFO] - Usage "5 cheese" matches the grammar "[AMT] [ING]"
[DEBUG] - [BuildResult] Ingredient: cheese
[DEBUG] - [BuildResult] Re-Link to Root Ingredient: cheddar cheese
[DEBUG] - [BuildResult] No custom unit found, so cannot get form based on unit.
[DEBUG] - [BuildResult] No known form found, so cannot get form based on form synonym.
[DEBUG] - [BuildResult] Could not find any default pairing for the unit type: Unit
[DEBUG] - [BuildResult] Running anomalous parsing.
[DEBUG] - [BuildResult] Could not clarify form through prep note, since unit type is not volumetric or there is no prep note.
[DEBUG] - [BuildResult] Could not auto-convert form since there is no form to convert.
[DEBUG] - [BuildResult] ERROR: Anomalous parsing could still not find a form for this usage.
[INFO] - Could not find match for usage: 5 cheese

Now, the input 5 cheese does match a template. We parsed out the ingredient, which is cheese. However, cheese is an ingredient synonym, so it’s actually resolved to its root ingredient, cheddar cheese. Again, no unit was specified nor any form. However, here’s where we go wrong. There is no default pairing for whole units, as there was with bananas. Meaning, KitchenPC does not know how to process a whole number of cheeses. At this point, we kick in the anomalous parser, which is basically calling in the big guns. Sometimes, we have weird ways to express ingredients that don’t logically make sense, but humans will basically know what it means through intuition. For example, sometimes a prep note can clarify what they mean. An example of this would be something like “3 crushed graham crackers”. Well, in this case, 3 is not a valid measurement for the volumetric form crushed, however graham crackers do have a prep note called crushed, so it’s able to interpret this as “3 graham crackers (crushed)”. In this case, we can’t do this. At this point, the anomalous parser gives up, so we return a result of NoForm. This error indicates we understood the ingredient, but not the form it was expressed in.

Is this useful to you? Well, possibly if you’re tinkering with the NLP parser or adding ingredient data to the database. If not, you can go about your day.

KitchenPC Database Provisioning 101

As I mentioned before, KitchenPC is able to connect to a SQL database using the DBContext context type, and a configured IDBAdapter, such as the DatabaseAdapter found in KitchenPC.DB.dll. What you may not know is KitchenPC contexts are able to talk with each other, and migrate data between themselves. They can also configure their own data stores. This means DBContext can create a database for you, with the default schema, and copy the data from a StaticContext’s KPCData.xml file.

Database provisioning made easy.

So, how do we do all this? First, we need to set up two contexts. Hold on a second. Two contexts? But, I thought there could only be a single context in a process. Well, I lied. You can create as many contexts as you wish, only one can be the initialized context available through KPCContext.Current.

First, we need a database. I use PostgreSQL, so my syntax will match that.

CREATE DATABASE &quot;KPCSample&quot;
  WITH ENCODING='UTF8'
       OWNER=&quot;Website&quot;
       CONNECTION LIMIT=-1;

Now, we have a completely blank database with no tables or anything.

Next, let’s create a DBContext and a StaticContext:

// Context connected to local database
var dbConfig = Configuration.Build
   .Context(DBContext.Configure
      .Adapter(DatabaseAdapter.Configure
         .DatabaseConfiguration(
            PostgreSQLConfiguration.PostgreSQL82
               .ConnectionString(@&quot;Server=localhost;Port=5432;User Id=Website;Password=password;Database=KPCSample&quot;)
               .ShowSql()
         )
      )
   ).Create();

// Context connected to local data store
var staticConfig = Configuration.Build
   .Context(StaticContext.Configure
      .DataDirectory(@&quot;C:\KitchenPC\ConsoleTest\LocalStore\&quot;)
   )
   .Create();

At this point, we have configuration builders for both contexts, and the DBContext is configured to logon to the database we just created. The StaticContext points to a directory that has KPCData.xml.

Now, we’ll add this:

var dbContext = dbConfig.Context as DBContext;
dbContext.InitializeStore();

Go ahead and run your program now, just to make sure the database stuff works and what not. In theory, it should pause for a bit and then you’ll have a created database with 18 tables, all empty. If not, hopefully it threw some sort of helpful exception.

Ok, now we have a database. Which means we can populate it with data. We’ll get this data from our StaticContext. You can comment out the .InitializeStore() line since you don’t need to run it again, but if you do run it again, it will simply drop the tables and re-create them.  Yes, InitializeStore() is a very dangerous method; be careful with it.

var staticContext = staticConfig.Context as StaticContext;
staticContext.Initialize();
dbContext.Import(staticContext);

First, we need to intialize the StaticContext. A context must be initialized before you can import from it, since the data must be loaded into memory. We could also use KPCContext.Initialize() to do this, which in turn calls .Initialize() on the context, but we don’t really need to set a global context in this case.

The next line calls Import() on the DBContext instance. Note, we do not initialize the DBContext. Doing so would have thrown an exception, because none of the data exists for it to initialize yet. In other words, the context you’re importing the data from must be initialized so said data is available, but the context you’re importing the data into should not be initialized.

The .Import() method accepts an IProvisionSource, which both StaticContext and DBContext implement. So, you’d do the exact thing in reverse if you wanted to pull data from your database and dump it into a static XML file on disk.

When you run this, it’ll import all the data from KPCData.xml into your database. After that, you’ll be able to initialize the DBContext context and use KitchenPC.

Since this is a short post, I thought I’d throw out one more trick. The StaticContext is also able to compress its data on disk. When you initialize your StaticContext, you can say:

var staticConfig = Configuration.Build
   .Context(StaticContext.Configure
      .DataDirectory(@&quot;C:\KitchenPC\ConsoleTest\LocalStore\&quot;)
      .Identity(() =&gt; new AuthIdentity(new Guid(&quot;c52a2874-bf95-4b50-9d45-a85a84309e75&quot;), &quot;Mike&quot;))
      .CompressedStore
   )
   .Create();

The CompressedStore flag will cause the context to look for a file called KPCData.gz instead of KPCData.xml, which is a GZip compressed version of the XML file. When StaticContext imports data, it will create a compressed file. When you initialize it, it will look for a compressed file on disk. If you configure StaticContext to use a compressed file, but only KPCData.xml exists, it will throw a file not found error. In the future, there might be a mechanism to compress a currently uncompressed file, but for now, you can just use 7-Zip or what not.

Getting Started with KitchenPC

Getting up and running with KitchenPC is hopefully an easy process.  First, KitchenPC is written in C# which means it can be used with any .NET language.  My examples (and future documentation) will be in C#, but feel free to use VB.Net or any other CLR compliant language.  If you’re compiling from source, you’ll need to add a reference to KitchenPC.dll.

Mono should also be fine, though I haven’t done a lot of testing with this compiler and runtime. Eventually, Mono will be officially supported.

The first concept to understand is contexts.

What Is a Context?

Basically any interaction with the KitchenPC API is done through a context.  A context can be thought of as a session with KitchenPC, similar to a connection with a database.  At a lower level, a context provides KitchenPC with the ability to figure out who the current user is, how to save and load data, and the configuration of the environment it’s running in.  In summary, a context wraps up everything KitchenPC can do into a nice package with a common interface.

At a technical level, a context is any class that implements IKPCContext.  This interface has methods for things like initialization, saving data, loading data, parsing ingredient usages, etc.  While you are more than welcome to implement your own IKPCContext, KitchenPC comes with two implementations out of the box! They are…

StaticContext

A StaticContext is, by far, the easiest way to get started with KitchenPC.  This context loads all its required data from an XML file on disk.  It also doesn’t save any changes to disk.  Any modifications will be lost when the process ends.  The most important thing to keep in mind about StaticContext is it is absolutely, positively not designed to be used in any production capacity.  Do NOT use this context on your website or with any production code.  It’s a playground; a way to mess around with KitchenPC to see how it works by providing a super easy solution to get up and running.  This context is in no way thread safe.  The performance is horrendous (pretty much any search operation will be O(n) at best!)  It’s designed to hold maybe a few dozen recipes and data for a single user; any more than that, expect everything to quickly be unusable.  Luckily, since the interface with any context is the same, you can start out using StaticContext and switch to another one when you’re ready.

So, how do you go about creating a StaticContext?  Well, let’s jump into some code!  Finally!

// Context connected to local data store
var staticConfig = Configuration<StaticContext>.Build
   .Context(StaticContext.Configure
      .DataDirectory(@"C:\KitchenPC\ConsoleTest\LocalStore\")
      .Identity(() => new AuthIdentity(new Guid("c52a2874-bf95-4b50-9d45-a85a84309e75"), "Mike"))
   )
   .Create();

KPCContext.Initialize(staticConfig);

Ok, so a few things going on here. First, a context is initialized by creating a configuration, which can be done fluently using Configuration<T>.Build, where T is an implementation of IKPCContext.  The first method is .Context, which takes a ConfigurationBuilder<T>, which are obtainable using the static .Configure property of the context.  The ConfigurationBuilder for StaticContext allows us to set a few properties relevant to this context.

The DataDirectory is a path on the local file system that contains a file called KPCData.xml.  A sample of this file will be available with the source code or binaries, and will contain a few sample recipes, shopping lists, menus, etc.  This will allow you to experiment with realistic data without having to create it yourself.

The .Identity method takes a Func<AuthIdentity> parameter, which can reference any function that returns an AuthIdentity (basically, a representation of a user in the KitchenPC universe).  Since a context exists in static memory, it can be shared by different threads.  For example, if you were building a website, you would run that above code in the Application_Start, and implement .Identity to figure out who the current user was, based on cookies or what not.  In our example, we just hard code the function to return a user named Mike, which happens to exist in our sample KPCData.xml file.

At this point, staticConfig is a IConfiguration<StaticContext> object, which holds everything needed to create a context based on that configuration.  Next, we just need to initialize that context, which loads necessary data into memory (in this case, from the XML file) so things like parsing and recipe modeling work.  This is an expensive operation, so it should only be done once when your process starts. Eventually, you’ll be able to enable and disable individual features, which will help speed up the initialization process. For now, it takes several seconds.

We call the static method KPCContext.Initialize() and pass in our configuration.  KPCContext.Initialize will create the context, initialize everything, and set the current context within your process.  From this point on, KPCContext.Current will refer to our context.

DBContext

You’ve probably already guessed what the other out of the box context is; DBContext.  This context is able to connect KitchenPC with a SQL database, used for saving and loading data in a relational way.  This context was pretty much designed around the schema of my KitchenPC website database.  The DBContext class actually doesn’t do that much, because it requires an implementation of IDBAdapter to actually work.  IDBAdapter tells a DBContext how to actually talk to a specific database.  If you wanted to connect KitchenPC to another store, such as Mongo or memcache or your own made up thing, implementing IDBAdapter is where you’d want to start.

Luckily for you, KitchenPC also comes with an implementation of IDBAdapter as well!  This implementation is called DatabaseAdapter, and is available in KitchenPC.DB.dll, which you’ll need to reference if you plan on using it.  I decided to split these up into different assemblies because KitchenPC.DB.dll has a more complicated set of dependencies, and I wanted to make the core KitchenPC engine a really quick and easy download.

DatabaseAdapter is a general purpose database adapter built on NHibernate.  This allows DatabaseAdapter to pretty much talk to any common SQL database, such as Microsoft SQL Server, Oracle, mySQL, or my favorite, PostgreSQL.  In general, if NHibernate supports it, you can use it.

Writing a general purpose SQL adapter has its drawbacks.  Since different RDMS platforms have their own features and ways of optimizing, DatabaseAdapter can only support the lowest common denominator.  For example, since there’s no SQL standard implementation of full text search, recipe searching must be done with standard SQL syntax (such as using a non-indexable LIKE operator for keyword searches).  Luckily, DatabaseAdapter provides various virtual methods you can override to control exactly how it interacts with your database, or you can always write your own IDBAdapter implementation which controls exactly how everything should work.  I’m tempted to write a PostgreSQL specific adapter, just because it has so many cool features that I wasn’t able to port over from my website code.

Creating an instance of DBContext is slightly more complex, since we have to configure the context and the adapter.  We can do the whole thing fluently as well:

// Context connected to local database
var dbConfig = Configuration<DBContext>.Build
   .Context(DBContext.Configure
      .Adapter(DatabaseAdapter.Configure
         .DatabaseConfiguration(
            PostgreSQLConfiguration.PostgreSQL82
               .ConnectionString(@"Server=localhost;Port=5432;User Id=Website;Password=password;Database=KPCTest")
               .ShowSql()
         )
         .SearchProvider(NHSearch.Instance)
      )
      .Identity(() => new AuthIdentity(new Guid("c52a2874-bf95-4b50-9d45-a85a84309e75"), "Mike"))
   ).Create();

Let’s take a look at what’s going on here. It looks similar to StaticContext, only we’re passing in a DBContext.Configure builder instead. This builder has a method called Adapter, which takes an IConfigurationBuilder object, which can be obtained through DatabaseAdapter.Configure. If you were writing your own database adapter, you’d probably also want to write your own IConfigurationBuilder.

The DatabaseAdapter configuration builder has a method called DatabaseConfiguration, which directly takes in an NHibernate IPersistenceConfigurer object.  This will be familiar to anyone who has used NHiberate before.  In this example, we’re creating a PostgreSQLConfiguration which will use the PostgreSQL dialect to talk with the database.  This configurer also takes a connection string used to connect.  The .ShowSql() method tells NHibernate to log SQL statements to the console window.  You can comment that out if you don’t want to clutter your console.

The DatabaseAdapterBuilder also has a SearchProvider method.  This method takes a Func<DatabaseAdapter, T> (a function that takes a DatabaseAdapter and returns a T), where T is an implementation of ISearchProvider.  As I said before, recipe searching is so database dependent, I predict it will be a very common scenario to want to implement your own searching mechanism.  If you wanted to use, say, Lucene.NET for searching, implementing ISearchProvider is where you’d start.  However, you can use the built-in NHSearch search provider, which uses standard SQL to search for recipes.  It’s not super fast, but it’ll probably do for the common website.

Once again, you’d just call KPCContext.Initialize(dbConfig); to initialize the context.  This will be quite a bit slower since it needs to load in various KitchenPC data from the database and index it into memory.  Again, you’d do this once every time your application starts.  One nice thing about DBContext is the initialization mechanism is multi-threaded, so you can actually start to use the context right away.  If you do anything that requires a fully initialized context (such as NLP or the recipe modeler), the thread will be blocked until initialization is complete.

The DatabaseAdapter obviously expects some sort of schema to exist, with default data to load.  Luckily for you, DBContext is actually able to do all the database provisioning for you!  However, we’ll get more into that later. In fact, this will most likely be the very next post.

So, I have a context.  Now what?

Now, you can interact with KitchenPC and experiment.  It doesn’t matter what context you’re using, all the code will be the same.  This allows you to quickly change contexts without changing any of your application code.

For the most part, interacting with KitchenPC is done fluently using one of the five action categories.

Recipes

context.Recipes allows you to interact with recipes into the database.  For example, if you wanted to load the sample Weeknight Cheese Quiche recipe, as well as Berry-Good Mousse, you’d use:

var context = KPCContext.Current;
var recipes = context.Recipes
   .Load(Recipe.FromId(new Guid("ee371336-c183-448d-889c-d6b1f767bf59"))) // Weeknight Cheese Quiche
   .Load(Recipe.FromId(new Guid("e6ab278f-5f4d-4249-ab7d-8f66af044265"))) // Berry-Good Mousse
   .WithMethod
   .WithUserRating
   .List();

Notice the Load action takes a Recipe object, which is used globally within KitchenPC to represent a recipe. Perhaps these Recipe objects came from some other piece of data, such as a user’s menu. In this case, we’re just creating them based on their raw ID in the database.

Also, notice how we can chain up multiple recipes to load as well. You can add as many calls to .Load as you want, and it will load all the necessary data in a single database call. The Fluent interface behind KitchenPC allows us to batch up complicated calls into single database operations.

There’s also a few properties. The WithMethod property indicates we want to load the recipe methods as well (which can get long), and WithUserRating JOINs in the user’s rating data to see if they’ve rated the recipe before. At the end of the Fluent expression, we call .List() which performs the database call (or, digs through the XML file) and returns an IList<> of Recipes.

It’s important to keep in mind absolutely nothing is done *until* that .List method is called, so you can programatically manipulate that query or add more conditions at runtime before materializing the data.

Menus

Now, suppose we wanted to create a menu for the current user.  The Menus action lets us manipulate menus.  For example, if we wanted to add our quiche recipe to a new menu called My New Menu, we’d simply do:

var result = context.Menus
   .Create
   .WithTitle("My New Menu")
   .AddRecipe(Recipe.FromId(new Guid("ee371336-c183-448d-889c-d6b1f767bf59"))) // Weeknight Cheese Quiche
   .Commit();

The Create action creates a new menu. We then set a title, and add a recipe. AddRecipe can of course be called any number of times. When we call .Commit(), the menu is created and a result is returned. This result has the ID of the newly created menu.

Oops, we forgot to add our Mousse recipe as well! Well, let’s update the menu!

context.Menus
   .Update(Menu.FromId(result.NewMenuId.Value))
   .Add(Recipe.FromId(new Guid("e6ab278f-5f4d-4249-ab7d-8f66af044265"))) // Berry-Good Mousse
   .Rename("Renamed Menu")
   .Commit();

Here, we’re updating an existing menu and adding another recipe to it. Oh, while we’re at it, let’s rename the menu to “Renamed Menu” just for fun.

Shopping Lists

Shopping Lists are a fun one. It really ties together a lot of the raw power behind KitchenPC. We can aggregate recipes, add arbitrary ingredients in, and even throw in any ol’ random thing like paper towels. KitchenPC will automatically combine like ingredients, convert amounts and units, etc.

KitchenPC can track multiple shopping lists for a single user, but all users have a default shopping list which can be referred to using ShoppingList.Default. Let’s add a bunch of items to our default shopping list!

context.ShoppingLists
   .Update(ShoppingList.Default)
   .AddItems(a => a
      .AddRecipe(Recipe.FromId(new Guid("ee371336-c183-448d-889c-d6b1f767bf59"))) // Weeknight Cheese Quiche
      .AddIngredient(Ingredient.FromId(new Guid("948aeda5-ffff-41bd-af4e-71d1c740db76"))) // Eggs
      .AddItem("12 bananas")
      .AddItem("paper towels")
   )
   .Commit();

In this code, we’re updating an existing list (ShoppingList.Default), and adding a bunch of items. First, we’re adding in a recipe. It’s our quiche recipe again. Next, we’re throwing in eggs, which is probably already in the quiche so those ingredients will be combined. Next, we’re adding in “12 bananas”. This string will be parsed using natural language parsing, and converted into a normalized ingredient and amount. In other words, if I added 3 more bananas, I’d have 15 bananas in my shopping list. We also add in “paper towels” which can not be parsed using NLP, so this item will just be added as text.

This method will return a fully normalized ShoppingList object, which will allow you to get the item IDs for each item in the list. The item IDs are important for when you want to update an existing item in the shopping list:

Let’s update two imaginary items in our list:

context.ShoppingLists
   .Update(ShoppingList.Default)
   .UpdateItem(ShoppingListItem.FromId("d7506487-83e3-4893-ade9-aa6e685810fe"), u => u
      .CrossOut
   )
   .UpdateItem(ShoppingListItem.FromId("4e479943-0c21-4624-a3b9-1f63722cee8f"), u => u
      .NewAmount(new Amount(3, Units.Cup))
   )
   .Commit();

Here, we’re crossing out one item (this allows you to create interfaces where shopping list items can be crossed out without actually removing them) and setting the amount of another item to 3 cups. You can only change the amounts or crossed-out state using UpdateItem. If you want to actually change the text or ingredient itself, you have to remove the old one and add a new one.

Queue

Remember the queue? Well, it’s back. The queue provides the user with a way to store arbitrary recipes without having to store them in a menu.

Adding recipes to the queue is easy:

context.Queue
   .Enqueue
   .Recipe(Recipe.FromId(new Guid("ee371336-c183-448d-889c-d6b1f767bf59"))) // Weeknight Cheese Quiche
   .Recipe(Recipe.FromId(new Guid("e6ab278f-5f4d-4249-ab7d-8f66af044265"))) // Berry-Good Mousse
   .Commit();

As is removing a recipe:

context.Queue
   .Dequeue
   .Recipe(Recipe.FromId(new Guid("e6ab278f-5f4d-4249-ab7d-8f66af044265"))) // Berry-Good Mousse
   .Commit();

Bye-bye Mousse. Want to clear the entire queue?

context.Queue
   .Dequeue
   .All
   .Commit();

Easy enough.

Modeler

Ah, the modeler. Also known as What Can I Make?. I like the feature, though it never took off on my site. Still, it’s pretty cool technology that lets you figure out what recipes you can make based on what ingredients you have available.

The modeler is extremely complicated, so I hope to devote future blog posts to it. However, it runs under the context of a modeling session. A modeling session describes the goals of the user; for example, what ingredients they have, what they don’t like, what recipes not to repeat, etc. You can also use the modeler under an anonymous context, which means We don’t know anything about this user:

var results = context.Modeler
   .WithAnonymous
   .NumRecipes(5)
   .Generate();

This just means give me five recipes. The results will contain a list of recipe IDs and a score indicating how efficient the set is. You can set the .Scale property if you want to control if sets should be more efficient or higher rated. You can also compile the results, which means load in the full recipe briefs and create a normalized ingredient set containing all the ingredients and amount you would need to create those recipes. This is how I was able to draw real time shopping lists on my web site.

var results = context.Modeler
   .WithAnonymous
   .NumRecipes(5)
   .Compile();

This returns a CompiledModel object, which has recipe and aggregation data. I’ll get more into this in a separate post.

Now, instead of using WithAnonymous, you can also use WithProfile. WithProfile takes an IUserProfile implementation which tells the modeler about your user. On my website, I had an implementation that loaded their favorite tags, their blacklist, their pantry, all that stuff. You could, of course, implement this yourself based on your needs. However, you can also build your own profile fluently.

var bananas = context.ParseIngredientUsage("12 bananas");
var carrots = context.ParseIngredientUsage("carrots");

var results = context.Modeler
   .WithProfile(p => p
      .AddBlacklistedIngredient(Ingredient.FromId(new Guid("948aeda5-ffff-41bd-af4e-71d1c740db76"))) // No Eggs!
      .FavoriteTags(RecipeTag.Dessert | RecipeTag.GlutenFree) // I love gluten free desserts!
      .AddPantryItem(bananas.Usage) // I have 12 bananas
      .AddPantryItem(carrots.Usage)    // I have carrots
   .NumRecipes(5)
   .Compile();

First, we’re using the NLP engine to parse “12 bananas” and “carrots” into normalized ingredient usages so we can use them to construct a pantry.

Next, we create a profile for this session. First, we’ll blacklist eggs. The modeler will not return any recipe that calls for eggs. We indicate we like desserts and gluten free recipes, so the modeler will score those recipes higher. We’ll also indicate we have 12 bananas and an unspecified amount of carrots. The modeler will attempt to efficiently use those ingredients, then compile them into a set of results for us.

I have no idea if my sample database will come up with anything for that query, but you get the idea.

What’s Next?

Feel free to mess around with these examples and come up with your own scenarios. This post hasn’t even scratched the surface of what all is possible. I hope to post many more articles focusing on individual subjects, so please email me if you have any suggestions on topics. If you have any ideas, or find things that blow up spectacularly, let me know! Have fun!

KitchenPC Is Now Open Source!

Hey everyone!  It’s been a while, but I  have some exciting news.  After about a year of thinking about exactly what I wanted to do with KitchenPC, I’ve come to the decision to open source the majority of the source code behind the website.

There’s a few reasons for this.  First off, it’s a fantastic way to give back to the community.  I built KitchenPC using several open source technologies.  PostgreSQL is the big one, as well as frameworks such as NHibernate, YUI, Castle, and Json.NET.  It seems only fair that I give back to that community, in hopes that others can innovate further using the code I’ve spent years developing.  Code that can understand recipes, relationships between ingredients, natural language, and classifications.  Second, it creates an opportunity for KitchenPC to realize its full potential.  As a consumer website, it didn’t work.  That’s because consumer websites almost never work.  Apparently, the world didn’t need a way to organize meal plans or another search engine.  However, the technology behind KitchenPC, if I do say so myself, is awesome.  I’m sure a  lot of the code I’ve written can help others develop products that actually do gain traction, and I’d love for KitchenPC to live on through that purpose.  As an open source product, it also provides the ability for others to contribute.  It allows KitchenPC to become something that one person alone cannot achieve.

So, you may ask, what exactly are you open sourcing?  The web site?  The database?  A bunch of HTML code?  Just dumping everything online and hoping for the best?

Well, I’ve spent the last couple months trying to figure out exactly where to draw that line.  An open source website isn’t really all that useful, as there doesn’t really need to be a bunch of KitchenPC clones out on the web.  My site never really gained much traction, so it’s fair to say my implementation didn’t lead to anything viable.  However, I think a lot of the technology itself can help others who are building recipe related projects.  Whether those projects are websites, mobile apps, tablet apps, Windows Store Apps, or even research projects, I think the code I’ve built can be re-factored in a generic way to provide a fantastic starting point.

Over the past three years, I’ve gotten dozens of emails from other entrepreneurs working on similar and exciting projects, and many have expressed interest in what I’ve built and asked if it could help them achieve their goals.  Well, I’m hoping the answer is “Yes.”

What is the KitchenPC Platform?

Therefore, today, KitchenPC is being re-invented as an open source platform used to provide a standardized model for interacting with recipes, shopping lists, and menus.  I’ve stripped out all the UI components from KitchenPC and re-organized the code as a set of APIs used for working with recipe related data.  These APIs are database agnostic and fully extensible to encapsulate a wide variety of uses.  My goal is to make getting started with KitchenPC quick and easy, with a low learning curve.  However, once you dive in, you’ll be able to customize exactly how the engine behaves and use as much or as little of the platform as you wish.

The KitchenPC Platform is free and open source, licensed under the MIT License.  It’s currently available on GitHub, and soon to be available through NuGet as well.

The early versions will be unstable and in a constant state of flux, so early adopters can expect lots of breaking changes, weird exceptions, and scenarios I didn’t account for.  That’s why I’m looking to find a few testers who want to mess around with the code, develop prototypes, and provide feedback on what works and what doesn’t.

My next several posts will be geared around working with this code, explaining the architecture of the platform, and lots and lots of examples.  Enjoy!

2013 in review

The WordPress.com stats helper monkeys prepared a 2013 annual report for this blog.

Here’s an excerpt:

The concert hall at the Sydney Opera House holds 2,700 people. This blog was viewed about 9,800 times in 2013. If it were a concert at Sydney Opera House, it would take about 4 sold-out performances for that many people to see it.

Click here to see the complete report.

Circuitous Innovation

I love text messaging.  Never before has there been such an easy way to communicate instantly with anyone else in the world, at any time.  I’m texting people constantly.

In fact, the other day as I was driving home from the gym, my girlfriend sent me a text message which I wanted to respond to right away.  However, to avoid texting while driving, I decided to use the speech to text feature of the messaging app so I could simply respond with my voice and have that translated into a text message.  Innovation is fantastic!

Now, having to read text is somewhat cumbersome and not always convenient.  What would be truly innovative is the ability to have a text message read back to you in a human-sounding voice.  More innovation!

Lastly, what would be absolutely perfect is if the synthesized voice could somehow mimic the sound of the sender’s voice, to avoid sounding too robotic and create an extra personal touch.

Here’s a basic prototype of what this true pinnacle of communication technology might look like.  Can someone start working on this?