» Publishers, Monetize your RSS feeds with FeedShow: More infos (Show/Hide Ads)
About a month ago, I found myself transforming data for the reporting portion of our application. Like many reporting systems, ours works off of date ranges, with statistics grouped together by the day (and a few by the hour). An early prototype made heavy use of TSQL Date functions (like DateAdd and DatePart). However, I wasn't happy with this solution, my transformations were unnecessarily coupled to SQL Server. I mean, database date functions all do pretty primitive date arithmetic's, surely we can come up with a way to do that ourselves?
I decided to borrow the strategy long-used by the Unix and PHP community (or JavaScript or doubtless countless other platforms/languages) - to store dates as seconds relative to a starting point. Unix Epoch is January 1st, 1970. If you store seconds since then in an unsigned 32 bit integer, you can store up to 136 years. If you only need minute-resolution, you can store over 8 100 years. If you only need day-resolution, then you can store a bit under 12 million years.
Best of all, you can do straight up arithmetic's against these fields to manipulate the dates. If you want to group hits per user for a day, you can do:
insert into DailyHits (UserId, DaysSince, Count) select UserId, SecondsSince/86400, count(*) from Hits group by UserId, SecondsSince/86400 --86400 is the number of seconds in a day insert into HourlyHits (UserId, HoursSince, Count) select UserId, SecondsSince/3600, count(*) from Hits group by UserId, SecondsSince/3600 --3600 is the number of seconds in a hour
You can also manipulate relative times in C#, to apply as parameters:
public static readonly DateTime Epoch = new DateTime(1970, 1, 1);
public IList GetDailyHits(DateTime from, DateTime to)
{
command.CommandText = "select * from DailyHits where DaysSince > @From and DaysSince < @To";
command.Parameters.Add("@From", from.Substract(Epoch).TotalDays);
command.Parameters.Add("@To", to.Substract(Epoch).TotalDays);
...
}
If you are using NHibernate, you can use the IUserType interface to help map DateTimes to their relative counterparts (I think).
The examples above are simplistic, but they do work very well and have a number of benefits - the most significant being great independence from the underlying database.
My migration from SQL Server to PostgreSQL for an upcoming project continues. On day 1 I managed to get the application up and running quickly, thanks to NHibernate. Ayende was kind enough to post some advice to minimize the already-few problems I ran into (I've since implemented the ISQLExceptionConverter and am really happy about it).
Today I had a bigger challenge - making the OLTP to OLAP transformation script work. I knew this would be more difficult because I'm using straight up SQL (instead of HQL), as well as a couple instances of SQLBulkCopy. So far, most of the transformation is done in sql - there's very little data being pulled down and processed by in code. Luckily, I have a huge and thorough integration test, which populates the database with a couple days worth of data, executes the transformation script in steps (as it would be in real life), and then validates the results. Essentially my test figures out the results in C# (by looping over the entities and aggregating the results every which way) and compares it to what the transformation scripts does.
Aside from a couple syntactical differences, such as having used [ ] to escape the count column, 1 column ambiguity that PostgreSQL couldn't resolve (weird), and having used insert {table} instead of insert into {table}, I only ran into 2 major problem.
The first was my usage of SqlBulkCopy which is tied to SQL Server's bcp utility. I was only using it to bulk-load the data for my integration test, but it did make a huge difference, and I wanted to keep the same performance if possible. It turns out that PostgreSQL supports a COPY from STDIN command, which the .NET Npgsql library exposes as an NpgsqlCopyIn class. Although it didn't perform quite as well (it still rocked), I much prefer the implementation. You essentially write a CSV to a stream - much nicer than having to transform my objects to datatables. Here's the reusable code that I used:
public static void BulkCopy<T>(IEnumerable<T> items, Func<T, object[]> callback, string tableName, string columns)
{
var sql = string.Format("copy {0} {1} from stdin with delimiter '|'", tableName, columns >> string.Empty);
using (var connection = new NpgsqlConnection(_connectionString))
{
connection.Open();
using (var command = new NpgsqlCommand(sql, connection))
{
var copy = new NpgsqlCopyIn(command, connection);
try
{
copy.Start();
foreach (var item in items)
{
var data = SerializeData(callback(item));
var raw = Encoding.UTF8.GetBytes(string.Concat(data, "\n"));
copy.CopyStream.Write(raw, 0, raw.Length);
}
}
catch (Exception)
{
copy.Cancel("Undo copy");
throw;
}
finally
{
if (copy.CopyStream != null)
{
copy.CopyStream.Close();
}
copy.End();
}
}
}
}
private static string SerializeData(object[] data)
{
var sb = new StringBuilder();
foreach(var d in data)
{
if (d == null)
{
sb.Append("\\N");
}
else if (d is DateTime)
{
sb.Append(((DateTime) d).ToString("yyyy-MM-dd HH:mm:ss"));
}
else if (d is Enum)
{
sb.Append(((Enum) d).ToString("d"));
}
else
{
sb.Append(d.ToString());
}
sb.Append("|");
}
return sb.Remove(sb.Length - 1, 1).ToString();
}
You use it like so:
var users = GetAllUsers();
Func<User, object[]> callback = u => new object[] { p.Id, p.Email, p.UserName, p.Password };
BulkCopy(users, callback, "Users", null);
If you want to specify the columns, say because the Id is an auto incrementing field, simply do:
var users = GetAllUsers();
Func<User, object[]> callback = u => new object[] { p.Email, p.UserName, p.Password };
BulkCopy(users, callback, "Users", "(Email, UserName, Password)");
The code essentially turns each entity in your collection into a serialized string, which gets written to the stream.
The other problem I had was that one of my most complex queries was really slow. The query had joins, joining a subselect in the where clause. SQL Server didn't have a problem with it, PostgreSQL did. I wasn't too surprised because past experience with MySQL had taught me that such queries could be problematic. I saw a few statements hinting that PostgreSQL 8.4 (I'm running 8.3) might fix it. I spent some time trying to speed it up, had some success, and then simply decided to go about it a different way. While I think its very likely that I wasn't doing it properly, there's no denying that SQL Server handled this better.
I did time the execution of my integration test, using the same code. This was run on identically pathetic machines - an Atom 330 with 4gigs of ram and a 5400RMP hard drive. Postgresql 8.3 on Ubuntu server 9.04 and SQL Server 2008 on Windows Server 2008 R2. Without using any bulk copy, Postgresql was slightly faster. With their respective bulk copy, SQL Server was slightly faster. I'm not giving numbers because the results were really close, seconds apart when everything took minutes. Also, I do think the test was useful; however, the code was single threaded which means it didn't provide any insight on how the databases behave when under a more typical kind of load. My goal was really just to make sure I wasn't making a huge mistake, and it doesn't look, or feel, like I am.
Last night I decided to accelerate our plan to migrate from SQL Server to PostgreSQL and gave it a quick attempt to see what the work involved was. I was hoping that due to our usage of NHibernate, and by having meticulously written database agonistic code the migration would be cake.
Turns out I was right and although there were a couple bugs along the way, the process took less than 30 minutes. Today I want to talk about the small issues we faced in migrating our code, as well as provide an overview of what I've learned about PostgreSQL so far.
First though, why migrate? I want to avoid getting rantish [in this post], but the cost of ownership for SQL Server isn't remotely close to being competitive. SQL Express is great, provided you can live with (or are willing to) its numerous constraints. Bizspark (I only bring it up because I feel someone will mention it in a comment) is a subprime mortgage - it lulls you into a sense of free only to nail you with licensing cost down the road. I'm sure someone can quote some obscure feature that might be needed by some system, but for the rest of us, the only reason to use SQL Server is familiarity. Familiarity is a pragmatic and reasonable reason to pick a technology, but only for so long.
Secondly, why PostgresQL? Well, I've used sqlite and MySQL extensively in the past (including submitting patches to their .NET drivers). However, MySQL is currently in a weird state (the sun/oracle thing) and I need ACID compliance and my experience with InnoDB has been average.
Thirdly, its worth pointing out that the system being migrated has a comprehensive set of unit tests, including a number tests which hit the DB. While I generally consider unit tests a design-tool, there's no doubt that they provided a significant sanity check.
So, what problems did I run into?
Given the use of NHibernate, the application side of things was migrated quiet easily. However, I did have to modify a few things. This is possibly because of flaws in my code (vs limitations of NHibernate) - hopefully someone will point such flaws out.
Unique Constraints
I use unique indexes to ensure that things like user emails are unique throughout the system. However, I haven't yet figured out a way to catch such exceptions in a db-agnostic manner. My code looked something like:
try
{
Session.SaveOrUpdate(user);
transaction.Commit();
return true;
}
catch (GenericADOException ex)
{
transaction.Rollback();
var sql = ex.InnerException as SqlException;
if (sql != null && sql.Number == 2601)
{
return false;
}
throw;
}
catch (Exception)
{
transaction.Rollback();
throw;
}
Obviously this code isn't going to work with the PostgreSQL driver. I keep thinking there *is* a way to handle this across multiple databases, but haven't found the answer yet.
Count - Long vs Int
When I fetch the number of rows for a query - more often than not to do paging - i do a select count(*) with a UniqueResult<int>(). However, PostgreSQL's count(*) returns a long, while SQL Server returns an int (I've run into this before, since sqlite also returns a long). This causes a cast exception. Again, I'm sure there's a simple solution to this inconsistency.
Bit and Booleans
There was one specific query which tried to retrieve all active users. The IsActive field was declared as a bit in SQL Server (and a boolean in PostgreSQL). The following query didn't work in PostgresQL "from User where IsActive = 1". Why? because 1 isn't a valid boolean, and you'll get a database error that an integer can't be converted to a boolean. I had to change the query to use '1' (in single quotes) or one of the other valid boolean values for true.
Connection String
Its also worth mentioning that the connection string example listed here, didn't work for me using the latest Npgsql drivers. Instead of Initial Catalog, I had to use the Database key.
Aside from those little issues, the code was up in running in about 10 minutes. Most of my time was actually spent getting to know PostgreSQL. Here's what I've learned so far.
Learning PostgreSQL
- PostgreSQL doesn't consider two null values to be the same, meaning you can put a unique constraint on a nullable column without any problems. In SQL Server you need a Nullbuster column to achieve this same result (its a neat trick, but its neater not having to use it).
- PostgreSQL's
uniqueidentifiertype is calledUUID, abitis aboolean, and adatetimeis atimestamp. - All of PostgreSQL's text types are unicode by default (so there is no
nvarcharyou simply usevarcharand get the same benefits) - PostgreSQL's handling of auto incrementing (identity) is pretty different. Instead of declaring a column as
int not null identity(1, 1) primary key, you define it as serial not null primary key. "serial" is just some syntactical sugar around what actually happens - PostgreSQL generates a sequence table for that column, and sets the columns default value to the sequence's next value. Of course, identity(1, 1) is just some syntactical sugar around however SQL Server implements the feature - its just more transparent in PostgreSQL.
Hopefully as the weeks go by, I'll have more information to share about the migration, and what I've learned.
A couple weeks ago I bought a new computer. While my existing computer still had a lot of life in it, I was eager to laverage the power of the solid state drives (SSD) - specifically Intel's 2nd generation X25. I figured that since I'd be updating the hard drive and installing a new OS (Win7), then there'd really be no better time to upgrade everything else.
You can see a video of the computer opening and building the NHibernate solution: http://www.youtube.com/watch?v=kPdRwS3vEbE
A lot has been said about how fast SSDs are, and its all true. People who do straight up $/gb comparisons with normal mechanical hard drives are missing the point. Of course, if you can't afford it, you can't afford it, but if you are in the market for a new computer, I'd strongly suggest that you see if you can't get it to fit into your budget.
The core parts of my computer are:
Intel Core i7 860
G.SKILL DDR3-1600 4x2GB
Noctua NH-U12P SE2
Gigabyte P55-UD4P ATX
Intel X25-M 160GB G2
Western Digital Caviar Black 640GB
Corsair TX650W 650W ATX
2x Scythe Ultra Kaze 120MM 1000RPM
Antec P183 ATX
Intel Core i7 860
The new Lynnfield processors from Intel fit the LGA1156 socket. You'll likely be interested in the i7 860 or the i5 750. Both are quad cores, but the i7 supports hyperthreading. Benchmarks have shown that hyperthreading will improve performance from ~0-30%...with 10% often being cited. Given that the i7 860 costs more than 10%, the i5 750 is definitely a better value - and getting it instead of the i7 860 is a great way to fit an SSD into your build.
On a side note, you might also be interested in the i7 920, which is based on the LGA1366 platform. The difference between the two is that the LGA1366 supports tripple channel memory and has more bandwidth to the pci-e slots. Only the most memory intensive applications have shown to benefit at all from triple channel memory. However, if you're a heavy gamer and plan on doing SLI/Crossfire (2+ video cards), the LGA1366 and i7920 is probably for you.
Both types of chip have something called Turbo Mode - that is they detect when cores are being underutilized and speed up the working cores. The overall effect is that the chips all stay within the same thermal envelope while allowing single threaded applications run faster.
G.SKILL DDR3-1600 4x2GB
RAM is really cheap, so there's no reason not to do 4-8gb nowadays. My old computer had 8gb also, and although I only managed to pass the 5gb barrier a couple times (that I noticed), I still think its money well spent. Do note that today's systems are running on DDR3 RAM, and, as a consequence, DDR2 RAM is starting to go up in price. Do be mindful of the RAM's voltage. Many DDR3 modules are currently rated above the spec'd 1.5V.
Noctua NH-U12P SE2
I bought an after-market cooler for my CPU because I like silent computers, and I like to overclock a little. Since I bought the CPU the day of their release, I had limited choice, and while I'm happy with the performance, there's likely cheaper and/or better solutions currently available.
The i7 860 runs at 2.8ghz, with a maximum turbo mode of 3.46ghz. The maximum junction temperature (Tj. Max) for the chip is 100C - which is when things start to shut off, definitely something we want to stay away from. With this cooler, and my RAM which is rated at 1600mhz, I easily got the CPU running at 3.5ghz. My idle temperature is 40C, and my load temperature is 60C. My definition of "easy" is that I clicked a couple settings in the bios, didn't have to adjust the voltage to any components, and the system is rock solid. There's a lot more that could be done, but overall, this is good enough for me.
Gigabyte P55-UD4P ATX
Motherboards are very similar these days - especially since more and more features are being directly integrated into the CPU. The motherboard manufactures are trying all sorts of trick to differentiate themselves, but unless you are a power user just get a brand that you trust with the features that you like, compare the prices to make sure its reasonable, and buy it.
There are a couple things that you do need to be careful of. First of all, uATX is becoming more and more popular, so don't accidentally buy one of those (unless that's what you want). Also, motherboard manufactures pay a licensing fee to Nvidia to include SLI support on their motherboards. If you plan on doing SLI, make sure your motherboard supports it. If you don't, you can save $10 by buying a "lower" version (they don't pay a licensing to AMD to Crossfire support, so most motherboards will support that).
Intel X25-M 160GB G2
Whether you pick up a 160GB or an 80GB it really doesn't matter - both are plenty to store Windows, VS.NET, Office, a couple other core apps and a couple games. What is important is that you get this drive. It is as far beyond other SSDs as SSDs are beyond mechanical drives. It destroys everything else when looking at random reads and writes. Other SSDs are faster when doing sequential reads and writes, but that is meaningless to 95% of how your computer operates day to day.
The G2 will also (at some point in the near future) support the powerful TRIM command, which Windows 7 supports, via firmware upgrade. This is a command that Windows passes onto the SSD controller to tell it that a data block is no longer in use. Without it, SSDs slow down over time (Intel drives less than others).
Also of interest, Windows 7 does a handful of optimization tricks when it detects an SSD. It disables defragmentation and other features like ReadyBoost. I've also disabled Windows Indexing on the drive.
Western Digital Caviar Black 640GB
Although I'm running a NAS for backups, and file storage (mp3s, movies, pictures), I do feel a little claustrophobic with 160gb. Therefore I added another drive to my system to store files and applications which aren't performance sensitive. There's a lot of choice here...I've had great luck with WD.
Corsair TX650W 650W ATX
A lot of people pick overly powerful power supplies, the truth is that the 650W I got has plenty of headroom, and could easily power a dual video-card setup. Make sure that the efficiency of the PSU is clearly listed. At the very least you want a 80 PLUS Bronze rating.
2x Scythe Ultra Kaze 120MM 1000RPM
Just a couple case fans to keep everything cool. These puppies run at a very low 1000RPM, which means you can't hear them.
Antec P183 ATX
There are a lot of cases to chose from, they vary in price and looks greatly. I'm largely interested in tool-less access (thumb screws), compartmentalization, and good cable management.
Video Card
I didn't get a new video card, but I will be buying an ATI 5850 once 3rd party manufactures implement their own cooling solution to provide quieter operations. If you don't play games at all, any video card will do - provided that it has the necessary outputs, and can do your monitor's native resolution in 2d (and I bet they _all_ can). If you only play a little, an ATI 4870 or 5750 is currently a good buy. If you need something powerful right now, look at the 5850 or 5870.
Writing effective unit tests is as much about the test itself as it is about the code under test. All the experience in the world isn't going to help you write clean and meaningful tests against highly coupled code. Conversely, complex and messy unit tests don't add any value even if the code under test is perfectly designed. Ultimately, the secret is practice - the more tests you write, the better you are at it, and the cleaner your code becomes as a consequence.
One thing that I've noticed though is that one of the fundamental practices for maintainable code can actually be counter-productive when applied to unit tests: DRY (Don't Repeat Yourself). Outside of unit tests, DRY simply makes sense - it reduces the amount of code you have, generally increased readability, and makes maintenance considerably easier. As a developer you have a ton of choices to ensure code isn't repeated - from your typical OO paradigms (i.e. inheritance) to a slew of refactoring options, and even some language-specific features like extension methods .
However, when applied irresponsibly to unit tests, DRY can very likely make your tests brittle - brittle tests are what lead to developers complaining of "spending more time maintaining and fixing our tests than anything else." Let's look at a modified example from the CodeBetter.Canvas project. Our UserBinder class is responsible for binding both the new users and existing users, it looks something like:
public override User BindNew()
{
var user = BindBase(new User());
user.Credentials.Password = _encryptor.Encrypt(Get("Credentials.Password"));
return user;
}
public override User BindExisting(User user)
{
BindBase(user)
var password = Get("Credentials.Password");
if (!string.IsNullOrEmpty(password))
{
user.Credentials.Password = _encryptor.Encrypt(password);
}
}
private User BindBase(User user)
{
user.Name = Get("Name");
user.Credentials.Email = Get("Credentials.Email");
return user;
}
In order to avoid some repetition, the BindBase method is used to bind all the properties shared when binding new users and existing ones - a simple but effective Extract Method was used. So, let's write the unit tests for the BindNew Method:
public class BindNewUserTests : BaseFixture
{
[Fact]
public void BindsBaseProperties()
{
var encryptor = Dynamic<IEncryptor>();
var parameters = new NameValueCollection{ {"Name", "Giru"}, {"Credentials.Email", "giru@playa.cool"}, {"Credentials.Password", ""}, };
var binder = new UserBinder(encryptor);
SetField(binder, "_parameters", parameters); //ack that's ugly!!
encryptor.Stub(e => e.Encrypt(null)).IgnoreParameters().Return(null);
encryptor.Replay();
var user = binder.BindNew();
Assert.Equal("Giru", user.Name);
Assert.Equal("giru@playa.cool", user.Credentials.Email);
}
[Fact]
public void BindsEncryptedPassword()
{
var encryptor = Dynamic<IEncryptor>();
var parameters = new NameValueCollection{ {"Name", "Giru"}, {"Credentials.Email", "giru@playa.cool"}, {"Credentials.Password", "plain text"}, };
var binder = new UserBinder(encryptor);
SetField(binder, "_parameters", parameters); //ack that's ugly!!
encryptor.Expect(e => e.Encrypt("plain text")).Return("encrypted");
encryptor.Replay();
var user = binder.BindNew();
Assert.Equal("encrypted", user.Credentials.Password);
encryptor.VerifyAllExpectations();
}
}
So there's a bit of ugliness, but overall those are two decent tests. Now what about tests for the BindExisting method? We know we need to handle the special password handling (if it's blank, that means it wasn't changed, so we don't overwrite it):
public class BindExistingUserTests : BaseFixture
{
[Fact]
public void LeavesPasswordAsIsIfNotSet()
{
var original = new User{Password = "the originator" };
var parameters = new NameValueCollection{ {"Name", "Giru"}, {"Credentials.Email", "giru@playa.cool"}, {"Credentials.Password", string.Empty}, };
var binder = new UserBinder(null);
SetField(binder, "_parameters", parameters); //ack that's ugly!!
binder.BindExisting(original);
Assert.Equal("the originator", original.Credentials.Password);
}
[Fact]
public void EncryptsTheNewPassword()
{
var encryptor = Dynamic<IEncryptor>();
var original = new User{Password = "the originator" };
var parameters = new NameValueCollection{ {"Name", "Giru"}, {"Credentials.Email", "giru@playa.cool"}, {"Credentials.Password", "new password"}, };
var binder = new UserBinder(encryptor);
SetField(binder, "_parameters", parameters); //ack that's ugly!!
encryptor.Expect(e => e.Encrypt("new passwordt")).Return("newly encrypted");
encryptor.Replay();
binder.BindExisting(original);
Assert.Equal("newly encrypted", original.Credentials.Password);
encryptor.VerifyAllExpectations();
}
}
So far so good, but what about a test to verify that Name and Email are also properly bound? Your first instinct may be to skip it, since you know, from the tests against BindNew that everything works as expected. You may also consider mocking the BindBase call. Neither of these are good choices. Fundamentally, your test should be written in a way that's independent of the implementation (in large part because the implementation could change, resulting in a broken test). This means that you should effectively repeat your test:
[Fact]
public void BindsBaseProperties()
{
var original = new User();
var parameters = new NameValueCollection{ {"Name", "Giru"}, {"Credentials.Email", "giru@playa.cool"}, {"Credentials.Password", ""}, };
var binder = new UserBinder(null);
SetField(binder, "_parameters", parameters); //ack that's ugly!!
binder.BindExisting(original);
Assert.Equal("Giru", original.Name);
Assert.Equal("giru@playa.cool", original.Credentials.Email);
}
The benefit is that if the implementation of BindExisting changes, our test will catch it. On the flip side, if BindBase changes, two tests will break. You can mitigate against this by extracting some of the logic, for example:
[Fact]
public void BindsBaseProperties()
{
var original = new User();
var parameters = new NameValueCollection{ {"Name", "Giru"}, {"Credentials.Email", "giru@playa.cool"}, {"Credentials.Password", ""}, };
var binder = new UserBinder(null);
SetField(binder, "_parameters", parameters); //ack that's ugly!!
binder.BindExisting(original);
AssertBase(original, "Giru", "giru@playa.cool");
}
private static void AssertBase(User user, string name, string email)
{
Assert.Equal(name, user.Name);
Assert.Equal(email, user.Credentials.Email);
}
But that won't always be practical, and greatly decreases your tests readability.
There's two points to all of this. First and most importantly, your unit tests shouldn't know or be written in a way that relies on a specific internal implementation - else you will spend more time fixing your tests than anything else. This is the same reason why you shouldn't test private methods - because tests against public methods should adequately cover them. Secondly the goals of unit testing is different enough from system code that common principles should often be reconsidered. If your test is brittle or hard to read, find an alternative!
Today I updated the CodeBetter.Canvas project to target the latest releases of NHibernate (2.1), FluentNhibernate (1.0 ) and Nhiberate Linq (1.0).
More importantly is the switch away from the MVC WebForms View Engine to the Spark View Engine. I've been learning and using Spark for the last month or so, and I'm a pretty big fan. I do think that you should strongly consider it for your next ASP.NET MVC project, it increases the readability and maintainability of your views, while being very easy to learn.
I think Spark is one of those love-it-or-hate-it kinda things, but I do think there are things anyone can learn from it. I would hate for ASP.NET developers to think that the default MVC view engine is the best (or even only) way to do things. Its good that Microsoft made the view engine pluggable, its great that developers might see new ways to mix layout and code.
I do worry that CodeBetter.Canvas is a little overwhelming from a learning point of view. If you've been doing .NET development the traditional way, then you'll have a lot of new technologies and patterns to learn (MVC, Spark, DI, Unit Testing, Mocking, jQuery and O/R Mapping). I do worry about this, but I can't imagine NOT adding a worthwhile feature like Spark for the benefit of those lacking the ambition and interest in improving their skill set. Some developers will suck it up and learn what they can, others will continue to fall behind - so be it.
As always, you can grab the source from Google Code
I do suggest that you check out the SparkViewEngine website for a great guide on setting it up and using it. You may also want to grab the latest source and use it instead of the 1.0 release (which is what Canvas is using) for the added caching capabilities which are excellent. However, I would like to show some Spark samples from Canvas to give you a quick idea of what it offers.
The first thing to take note of is that <%=XXX %> are gone, replaced with ${XXX}. I do find that this simple change does increase the readability a little, but the real advantage is that ${} will automatically HTML encode values thanks to the SetAutomaticEncoding setting of Spark set in the global.asax. If you don't want encoding, you use !{XXX} instead (you'll notice that Canvas uses both as needed).
You can pretty much look at Views/Shared/Application.spark to get a good feeling for what Spark is all about. This is the default master page layout used by all spark views (which can be changed or overwritten at multiple levels). First, you'll notice that we are declaring a variable, errors for use later on (more on this in a bit). You should also notice that we are defining a global variable, Title - which individual views can set to control the page's title (look a bit further down and you'll see that the Title variable is being outputted within our head's title tags).
Things get a lot more interesting with the <use> tags, which are equivalent to ContentPlaceHolders. Not only are they much more concise, but anything outside of an explicit content tag is automatically placed in the "view" placeholder. This helps reduce our code's nesting and the ASPX soup-tag. Given a simple master page of:
<body> <use content="menu" /> <use content="view" /> </body>
Our view can simply be:
<content name="menu"> ....STUFF... </content> <p>The main content doesn't have to be nested in a content element</p> <p>Yay! it's a small but useful feature</p>
Next you might notice the <LoginLogoutMenuItem> and <PageData> elements. This is Spark's way to render a partial. Spark will look for a file named _LoginLogoutMenuItem.spark and _PageData.spark (in the shared folder, and in the controller's view folder) and render it out. What I like most about these is the ease with which we can pass information to our partial: simply add attribtes to the tag, such as (this is how the errors variable defined at the top of the layout is being used):
<SomePartial heading="'This Is A String Value'" products="Model.Products" />
Although there's a lot more to Spark, the last, and possibly coolest, feature is Spark's ability to embedded attributes within html elements and attributes. For example, one way to write an IF/ELSE is to use Spark's If/Else elements.
<if condition="Model == null">
<h3>No Results were Found</h3>
</if>
<else>
<table>
...do stuff
</table>
</else>
Another approach is to use the attributes on existing elements(which s much nicer with just an if):
<h3 if="Model == null">No Results were found</h3> <table if="Model != null">...</table>
Even more impressive the embedding of the each attribute. Take a look at the old PageError partial versus the new one:
<%@ Control Language="C#" AutoEventWireup="False" Inherits="CodeBetter.Canvas.Web.ApplicationViewUserControl<string[]>" %>
<ul id="pageErrors">
<% Model.Each(e => { %>
<li><%=e %></li>
<% }); %>
</ul>
versus
<ul id="pageErrors">
<li each="var d in data">${d}</li>
</ul>
The same can be done at the attribute level, from the Spark documentation, take a look at how cleanly styles (or any other attribute) can be controlled:
<li each="var product in Products" class="first?{productIsFirst} last?{productIsLast}">
${H(product.Name)}
</li>
(as a bonus, the xxxIsFirst and xxxIsLast are automagically created by Spark when it sees the each attribute).
So, I hope that gets you interested in Canvas and in the Spark View Engine. There's a lot more to both free projects than what you see here. Canvas covers a broad range of technologies and patterns. Spark offers caching (and donut-caching), javascript integration, macros, batch compilation and more.
Today I vowed to learn the Spark View Engine. While I don't hate ASP.NET's MVC WebForm View Engine, its clearly outclassed by the other MVC stacks. So, given my views (hahaha) on the matter and with a wonderful greenfield project in front of me, I figured I'd do the switch.
(I think some developers still don't get the distinction between traditional ASP.NET WebForms and ASP.NET's MVC WebForm View Engine. The latter is the default View Engine used for templates in ASP.NET MVC and its confusing name was likely picked because its similar to the former.)
Going through the documentation, I was pleased to see the inclusion of conditional attributes on standard HTML element. So, instead of writing:
<% if (CurrentUser != null) { %>
<span id="loggedInUser">Hello <%=Html.Encode(CurrentUser.Name)%></span>
<% } %>
I can simply write:
<span id="loggedInUser" if="CurrentUser != null">Hello ${CurrentUser.Name}</span>
OR
<if condition="CurrentUser != null">
Hello ${CurrentUser.Name}
</if>
The next thing I noticed was the lack of unless - a statement found in Ruby which some love, some hate, and many abuse. So, I figured I'd go ahead and try to implement support for it, if nothing else it'd be a great way to get more familiar with the inner workings of the framework. The minor changes required to implement this feature, and the fact that it took less than 10 minutes for someone with no familiarity with the code, is a testament to the readability and general quality of the codebase.
All my changes were made in the main Spark project.
unless is merely an inverted if, so my plan was to pinpoint the parsing and code generation around the Spark's if attribute and if element and piggy-back on their implementation. The first part of my journey took me to the ConditionalAttributeVisitor in the Spark.Compiler.NodeVisitors namespace. I got here by doing a search for "if" (with the quotes). From the name, and the code, I figured that the purpose of this class was to determine whether an attribute was a conditional attribute, I simply added a check for "unless" to both the qualified and unqualified code paths:
if (Context.Namespaces == NamespacesType.Unqualified)
return attr.Name == "if" || attr.Name == "elseif" || attr.Name == "unless";
if (attr.Namespace != Constants.Namespace)
return false;
var nqName = NameUtility.GetName(attr.Name);
return nqName == "if" || nqName == "elseif" || attr.Name == "unless";
Next, I got lazy, ran the code with an unless and found the ChunkBuilderVisitor class within the same namespace. This class threw an exception that "unless" wasn't defined. Within the class, we can see a mapping of tokens to actions. I added a new entry:
{"unless", VisitUnless},
VisitUnless, I looked at what VisitIf did - it creates a ConditionalChunk of type If and adds it to the Chunks member. I figured the simplest thing would be to call VisitIf and switch the type of the chunk to my own type. I added a new value of Unless to the ConditionalType enum, then implemented by simple VisitUnless:
private void VisitUnless(SpecialNode specialNode, SpecialNodeInspector inspector)
{
VisitIf(specialNode, inspector);
((ConditionalChunk) Chunks[Chunks.Count - 1]).Type = ConditionalType.Unless;
}
As you can see, it lets VisitIf do all the work, and then switches the type.
I found where ConditionalType.If was being used, and came across the a CSharp Code Generator (GeneratedCodeVisitorBase within Spark.Compiler.CSharp.ChunkVisitors) and JavaScript Code Generator (JavascriptGeneratedCodeVisitor within Spark.Compiler.Javascript.ChunkVisitors). This is the code that outputs the actual C# and JavaScript. Based on the case block for If, which is:
case ConditionalType.If:
{
CodeIndent(chunk)
.Write("if (")
.WriteCode(chunk.Condition)
.WriteLine(")");
}
break;
I wrote the case block for Unless:
case ConditionalType.Unless:
{
CodeIndent(chunk)
.Write("if (!(")
.WriteCode(chunk.Condition)
.WriteLine("))");
}
break;
The change was equally straightforward for the JavaScript generator.
That took care of supporting unless as an attribute (<p unless="CurrentUser == null">...</p>), but I still wanted to add support for unless as an element (<unless condition="...">...</unless>); For this I went to the SpecialNodeVisitor class within the Spark.Compiler.NodeVisitors namespace (I had looked briefly at it when getting started). I added "unless" to the _containingNames member in the constructor.
Next I did...nothing..that was all that was needed...aside from slightly different parsing rules, attributes and elements share the exact same code paths (which pleasantly surprised me at first, but is obviously the right implementation).
The moral of the story is that if you are looking for a view engine for ASP.NET MVC, one that, at first glance, looks better than WebForms and much easier to customize, I strongly suggest you check out the Spark View Engine.
We all know that you shouldn't test private methods, but the same can't be said about protected methods. Look at this slightly modified version of the UserBinder from the CodeBetter.Canvas
project:
public abstract class ModelBinder<T> : IModelBinder where T : class
{
//..all types of code
public object BindModel(NameValueCollection parameters, Action<string> addErrors)
{
var id = Get("id").ToInt(0);
T entity = null;
if (id == 0)
{
entity = BindNew();
}
else
{
entity = _repository.Find<T>(id);
BindExisting(entity);
}
if (entity == null)
{
AddError("Something bad happened");
return;
}
var errors = Validate(entity);
if (errors != null)
{
errors.Each(addErrors);
}
return entity;
}
protected abstract T BindNew();
protected abstract void BindExisting(T original);
protected virtual string[] Validate(T entity)
{
return null;
}
}
We are binding a new or existing user to our submitted data (that's what BindExisting and BindExisting do), and assuming that worked, calling Validate. Its up to each binder to implement the BindNew, BindExisting and Validate methods (the last one being optional). Our UserBinder looks something like:
public class UserBinder : ModelBinder<User>
{
protected override User BindNew()
{
var user = new User();
BindCommon(user);
return user;
}
protected override void BindExisting(User user)
{
BindCommon(user);
}
private void BindCommon(User user)
{
//Parameters is defined in the base binder, it just wraps Request.Form
user.Name = Parameters("Name");
user.Email = Parameters("Email");
return user;
}
protected override string[] Validate(User user)
{
if (user.Name == "Karl")
{
return new[] {"there can be only one"};
}
return null;
}
}
So far everything's great, our base ModelBinder class provides a generic binding framework, and subclasses specify the binding implementation for individual entities.
However, when we go to unit test our UserBinder's Validate method, we can't access it because its marked as protected. A simple solution might be to change it to public, or protected internal (and use the ugly InternalsVisibleTo attribute). I'm a big believer that real code should never be modified for the purpose of unit testing (including marking methods as virtual (I believe in virtual by default, but not because of unit testing)). Frankly, if you are having to change production code to make it testable, then you are doing something wrong.
The right solution (or at least the solution I've been using), is to simply subclass the UserBinder within your test project:
public class TestUserBinder : UserBinder
{
public string[] PublicValidate(User user)
{
//forward the call to the base implementation
return Validate(user);
}
}
We can now write a straightforward test:
[Fact]
public void ReturnsErrorWhenNameIsReserved()
{
var binder = new TestUserBinder();
var errors = binder.PublicValidate(new User{ Name = "Karl"});
Assert.Equal(1, errors.Length);
Assert.Equal("there can be only one", errors[0]);
}I was recently playing with the idea of implementing disk-base caching for pages that had a high read-to-write ratio. On top of being practically read-only, these pages also require quite of bit of database work to put together, so they are ideal candidates for caching. I dismissed the idea of using the built-in ASP.NET caching because of two reasons. First, the number of pages needing to be cached is in the thousands, making it impractical from a memory stand point. Secondly, since this is hosted in a web farm, each server would need to build-up its own cache, defeating the point of caching a little as well as introducing the possibility of different servers having out of sync cache.
The final solution may be to use memcached, but I really think that doing disk-based caching for these pages to a SAN (via iSCSI) is going to be more than good enough and simple to manage.
The code hasn't been deployed, nor has it been tested beyond the basic point of making sure that it runs as expected from VS.NET under the single load of a browser (and thus a single thread).
To make this work, we'll need three classes: an http module to hook into the page lifecycle, a custom filter to capture the output stream, and a configuration class to help us set the system up.
We'll take a quick first stab at our custom HttpModule:
public class DiskCacheModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.BeginRequest += ApplicationBeginRequest;
}
private void ApplicationBeginRequest(object sender, EventArgs e)
{
var context = HttpContext.Current;
var request = context.Request;
var url = request.RawUrl;
var response = context.Response;
var path = GetPath(url);
var file = new FileInfo(path);
if (DateTime.Now.Subtract(file.LastWriteTime).TotalMinutes < 5)
{
response.TransmitFile(path);
response.End();
return;
}
try
{
var stream = file.OpenWrite();
response.Filter = new CaptureFilter(response.Filter, stream);
}
catch (Exception)
{
//todo log this error
}
}
public void Dispose()
{
}
private static string GetPath(string url)
{
var hash = Hash(url);
//todo change the hardcoded value
return string.Concat("c:\\temp\\", hash);
}
private static string Hash(string url)
{
var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
var bs = md5.ComputeHash(Encoding.ASCII.GetBytes(url));
var s = new StringBuilder();
foreach (var b in bs)
{
s.Append(b.ToString("x2").ToLower());
}
return s.ToString();
}
}
As you can see, we cache based on the RawUrl property of the HttpRequest object. This is just a simple example, but you could cache on virtually anything - including QueryString or Form parameters, Cookies or Server Variables. The first thing we figure out is where the file should be stored (the GetPath and Hash methods). We take our RawUrl and MD5 it. We append that to our [ugly] hard-coded folder. Next we create a FileInfo based on the path and check whether the file was written less than 5 minutes ago. If it was, we use the highly efficient TransmitFile to send the file down to the browser. If the file wasn't there, or was stale, we let the normal ASP.NET process take place - with the only difference being that we inject our CaptureFilter into the HttpResponse.
HttpResponse Filters are nothing more than classes that inherit from System.IO.Stream, and the only important method is Write and Close (although we have to implement a bunch of other useless junk):
public class CaptureFilter : Stream
{
private readonly Stream _responseStream;
private readonly FileStream _cacheStream;
public override bool CanRead
{
get { return false; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return _responseStream.CanWrite; }
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public CaptureFilter(Stream responseStream, FileStream stream)
{
_responseStream = responseStream;
_cacheStream = stream;
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long length)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override void Flush()
{
_responseStream.Flush();
_cacheStream.Flush();
}
public override void Write(byte[] buffer, int offset, int count)
{
_cacheStream.Write(buffer, offset, count);
_responseStream.Write(buffer, offset, count);
}
public override void Close()
{
_responseStream.Close();
_cacheStream.Close();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_responseStream.Dispose();
_cacheStream.Dispose();
}
}
}
There really isn't much to it, our constructor expects the main Response's Stream as well as a FileStream to write to (which was created in the HttpModule by calling OpenWrite on our FileInfo). Whenever we are given a buffer, we write it to both streams.
In its simplest form, this is all we need, but let's add a configuration class to make it a little more practical. Our configuration class will define regular expression patterns which we'll match the RawUrl
against. If it's a match, we'll cache the file. If it isn't, we'll let ASP.NET do its thing.
public class DiskCacheConfiguration
{
private readonly IList<Regex> _patterns;
private static readonly DiskCacheConfiguration _instance = new DiskCacheConfiguration();
private DiskCacheConfiguration()
{
_patterns = new List<Regex>();
}
public static void Initialize(Action<DiskCacheConfiguration> action)
{
action(_instance);
}
public DiskCacheConfiguration AddPattern(string pattern)
{
_patterns.Add(new Regex(pattern, RegexOptions.Compiled));
return this;
}
public static bool IsMatch(string url)
{
foreach(var pattern in _instance._patterns)
{
if (pattern.IsMatch(url))
{
return true;
}
}
return false;
}
}
The two public methods, AddPattern and IsMatch are called from our modified HttpModule:
public void Init(HttpApplication application)
{
application.BeginRequest += ApplicationBeginRequest;
DiskCacheConfiguration.Initialize(c => c.AddPattern("^/Home/About$")
.AddPattern("^/default.aspx"));
}
private void ApplicationBeginRequest(object sender, EventArgs e)
{
var context = HttpContext.Current;
var request = context.Request;
var url = request.RawUrl;
if (!DiskCacheConfiguration.IsMatch(url))
{
return;
}
....
}
Again, you could do a lot more, such as adding ignore rules, of more detailed patterns or rules based on various headers. One of the things I'm worried about is how this will work under heavy-load. TransmitFile appears to use a FileShare.Read, and OpenWrite uses FileShare.None - which seems perfect for our cause, unless the heavy reads cause the writing thread to block (which won't happen if file access is queued, but I don't know if they are).
I've updated the Foundations of Programming license to a Share-Alike (as opposed to a no derivatives) license.
As always, you can download it from:
http://codebetter.com/media/p/179694.aspx
OR
http://openmymind.net/FoundationsOfProgramming.pdf
Additionally, I've released the raw docx file (under the same license):
http://openmymind.net/FoundationsOfProgramming.docx
I had a [crazy] idea. I have no way of making it happen, but maybe someone else could (maybe codebetter?).
The idea is inspired by the very cool annual Rails Rumble. From the RailsRumble 2009 website:
The Rails Rumble is a 48 hour web application development competition. As a contestant, your team gets one weekend to design, develop, and deploy the best web property that you can, using the awesome power of Ruby and Rails.
My variation is to pit teams of super-developers from different technologies against each other. Think of it: Scott Guthrie and Phil Haack vs David Heinemeier Hansson and Jeremy Kemper vs the team at the Lawrence Journal-World vs a facebook or digg team (for PHP). Prize money could go to a charity of the winning team's choice.
What are the benefits?
- It helps promote each framework,
- It helps each team learn more about their competition (where they are weak, where they are strong),
- It makes new frameworks more accessible to developers
What are the challenges?
- Keeping it light and fun
- Getting the super-stars involved
- Making it fair
What do you guys think?
In my last NHibernate post I mentioned that NHibernate is great because it does the heavy lifting for the common tasks that make up 95% of my data access layer, while letting me tweak the remaining 5% as needed.
Let's look at some of the ways you can tweak, or even circumvent NHibernate for those queries that require some extra attention. Do note that there are a lot of different solutions – we're only looking at a few.
The most obvious method is to use named queries and custom SQL statements in your mapping. This provides you a lot of flexibility without diverging from NHibernate's realm. The NHibernate reference has good documentation on this approach, so we won't cover it (check out section 14.2 "Named Queries", 14.3 "Custom SQL for create, update and delete" and 14.4 "Custom SQL for loading").
The next option is to use an ISqlQuery via the ISession.CreateSqlQuery. Unlike an IQuery which works against your domain using HQL, an ISqlQuery is closely tied to your database and SQL. However, you still get access to NHibernate parameter substitutions, paging capabilities and O/R mapping (if you want). Again, the NHibernate reference has a very detailed section, check out 14.1 "Using an ISQLQuery".
You can optimize your Deletes by supplying the ISession.Delete command some HQL rather than the instance you want to delete. For example, if you want to delete all comments belonging to a blog post, you can do:
session.Delete("from Comment where PostId = :id", postId, NHibernateUtil.Int32);
More recently the NHibernate team made it possible to execute any statement (insert/delete/update) from an IQuery. You can find out more from this post by Ayende.
When necessary (typically bulk imports or other bulk operations), you can circumvent NHibernate's first-level cache by using an IStatelessSession rather than an ISession. Rather than calling OpenSession on your ISessionFactory, simply call OpenStatlessSession. Keep in mind that the IStatelessSession doesn't have the exact same API as an ISession.
When things get really crazy, you can always get an ADO.NET connection from the ISession's Connection property. With a raw database connection, the world is your oyster. You can always write your code in such a way that you have direct access to our connection string to create your own connection, thus completely and fully circumventing NHibernate.
Finally, any of these methods can be combined with some simple designs in order to ensure that everything is neat and tidy:
public abstract class Repository : IRepository
{
public void Delete<T>(T entity)
{
WithinTransaction(s => s.Delete(target));
}
protected void WithinTransaction(Action action)
{
WithinTransaction(Session, action);
}
protected static void WithinTransaction(ISession session, Action action)
{
var transaction = session.BeginTransaction();
try
{
action();
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
}
}
}
public class PostRepository : Repositor, IPostRepository
{
public void Delete(int postId)
{
WithinTransaction(s =>
{
session.Delete("from Comment where PostId = :id", postId, NHibernateUtil.Int32);
session.Delete("from Post where Id = :id", postId, NHibernateUtil.Int32);
});
}
}
Once you understand NHibernate's flexibility and understand your code's hot spots, it generally takes a pretty trivial amount of tweaking to speed things up. While the changes you make, in the name of performance, to your models (both domain and data) may be disgusting rest assured that HHibernate will not stand in your way. Be warned though that many of the above solutions may have side effects with respect to NHibernate's 1st and 2nd level caches. However, if you were to custom write everything, including caching, you'd likely have similar synch issues.
Please, Just Remember One Thing
Just for a few seconds forget everything you know about NHibernate.
Good.
Now, before I snap my fingers and everything comes flooding back, the only thing you need to know about NHibernate is that whenever you need to, you can easily circumvent it and do whatever is needed to get the job done (like use an IDbConnection and IDbCommand to execute a stored procedure).
**snap**
I often hear people saying it, and often say it myself, but people just don't seem to get it. You use NHibernate to save yourself from having to write a ton of code for 95% of your data access, and then, after profiling, optimize the critical 5% with whatever other approach you prefer. Its stupidly easy. You can access an IDbConnection directly from an NHibernate's ISession, or subclass your NHibernateDataAccess class with an SqlServerDataAccess class and override those critical sections.
Instead of taking the above sane approach, I see people laboring through huge amounts of mundane code in order to keep control over that unique 5% (maybe a special report, or performance sensitive path). I see people concerned about performance, without understanding that for most of what an application does, the performance difference between method A, B and C are indistinguishable, whereas the maintenance and developer costs vary hugely.
Essentially I see people who are worried about not being able to tweak when necessary and people who simply don't want to change.
To the first group I say that your fears are understood, but not to worry. You can always drop down to the assembly of data access (SQL, Sprocs, ...).
Four Areas of Improvement
NHibernate makes my life easier and my code better, but it isn't without problems. A lot of these have to do with the fact that it's trying to solve a complex problem, of which numerous variations exist. As such, NHibernate's biggest problem is that it's hard to learn and harder still to master. There are some specific pain points I'd like to see addressed.
First, mapping needs to be simplified. It's extremely intimidating to a newcomer and requires that you understand most of the fundamentals. This is already being addressed with FluentNhibernate and hopefully the project will continue to mature.
Secondly, collections and references can be particularly puzzling. From the names used (bags, maps and sets are all foreign to most .NET developers), to advanced features like caching, lazy loading and cascading. I'm not sure if this is something that can actually be improved, or if this battle needs to be fought via documentation.
Thirdly, neither HQL nor Criterias feel like the most natural querying languages. Hopefully development on Linq to NHibernate will continue to be a priority.
Finally, NHibernate is an abstraction that will leak. This shouldn't be unexpected, but it would be nice to try and address some of the worst offenders. The leaks that continuously trip me up are related to management of ISessions. Lazy loading and session management is a black art – doubtlessly easy for masters, and frustrating for everyone else.
NHibernate and Microsoft
Microsoft has long been criticized for its attitude towards open source frameworks. The common approach appears to be to compete with established open source frameworks using inferior solutions. MSTest, MSBuild, Sandcastle, ASP.NET Ajax, ASP.NET MVC, Entity Framework, Unity, Logging Application Block, Velocity, Team Foundation SSC are all examples that come to mind.
It'd be nice if Microsoft took a different approach with NHibernate, by not only acknowledging its presence and using it in samples and demos, but also actively contributing to it. Remember, during NHibernate's lifetime, Microsoft has invented three distinct data access strategies (DataSets, Linq to SQL and the Entity Framework). So much effort, so much relearning by developers, to end up with a poor replica of NHibernate years later.
A couple years ago, Google contributed Hibernate Shards to the Hibernate project (and I'd bet that they've been involved in Hibernate in other ways since then). Surely Microsoft can help fund the project in a manner that works with the NHibernate team (money or resources).
NHibernate and You
I do think it's important for any developer to learn new things. Equally important is to learn them from different people. Its important that you learn Ruby or Python, not because you should use them, but because seeing MVC over ActiveRecord completely changes your perspective on how applications can be built. The more approaches that you know, the more likely you are to pick the right one for the job. If you only know one approach then you'll always use that one – and you better hope like hell that the person selling you that approach knows what he or she is talking about.
As for NHibernate specifically, most developers should learn about O/R mappers. You should know the advantages and disadvantages and therefore know when a situations calls for it or not.
A month ago I released CodeBetter.Canvas, a simple application with equally simple goals:
- Provide developers with a starting point for new ASP.NET MVC projects
- Provide developers with a learning tool for oft-talked about tools/patterns
Instead of building a full-fledge application (like another blog engine, or shopping site), I wanted to build something as close to a blank canvas as possible - not only because it made the task more managable for me, but also because I thought it would be a better way to learn, and a more practical application for developers to use as a launching pad for their own real projects. While being an MVC application, the focus is more general: namely proper use of dependency injection, leveraging NHibernate/FluentNhibernate/Linq2Nhibernate, unit testing and mocking, and jQuery.
Yesterday I released an updated version of CodeBetter.Canvas to Google Code. You can check it out at http://code.google.com/p/codebettercanvas/. There are a number of new things in this release, including a revamped repository model, the introduction of a model binding framework (these end up doing a lot of heavy lifting), an improved validation framework, and ideas on how to do paged lists.
Enjoy.
Almost two years ago, to the day, I completely gave up on Silverlight. How do I remember the day so clearly? Because every now and I again, I get an email notification that someone has replied to my following forum post: http://silverlight.net/forums/p/1334/216456.aspx. In it, I asked why the Shape classes are sealed. At the time I figured it might have just been a early-release kinda thing. However, since the issue continues to be raised I'll assume the problem still hasn't been fixed.
This whole thing reminds me of another common .NET problem - internal framework classes. This was a hot topic with the release of .NET 3.5 and the ExpressionVisitor code, so much so that a David Kean from Microsoft jumped in to explain the situation. For the most part though, all it did was highlight how inefficient and unwilling to change Microsoft was. Why are classes internal? Because the process to make them public involves 20 some people and a multi-layered review system. There's no "how do you guys handle these problems?" or "where can we improve" - merely a statement of fact of how things are (and therefore must be).
Back to the sealed silverlight classes. This to is a pretty old topic - remember Java is virtual by default, C# is not. What annoys me though, is the approach Microsoft has taken. I asked Why are the classes sealed, and the official response was Why do you want them unsealed? If .NET is built on the basis that developers need to justify how they use the framework, we're in deep shit. The default behavior of a framework developer should be to open components, and only close them off when necessary. Otherwise it limits what your customers can do with your product, places assumptions on how your framework will be used, and makes it clear that you think you know better than we do. Lets not forget that coming up with a use-case for inheriting Line isn't particularly difficult - maybe I want to create my own DottedLine class?
The specific wording used by Nick Kramer (Silverlight's Program Manager) was:
"We want to err on the side of less API surface area -- if you're missing something important someone will tell you, but if you include a feature that doesn't get used you'll never hear about it, and will pay the download cost for it forever. We weren't able to think of a compelling reason for arbitrary subclassing, but maybe we weren't creative enough -- what scenarios do you have in mind?"
It isn't Nick Kramer & Team's job to think of reasons for subclassing (its our job to do that). Nick Kramer and Team should only be concerned with reasons not to have an unsealed class. Nick did provide some of that reasoning, but I'm unconvinced. Essentially coming down to performance and wrapping unmanaged objects - issues simple enough to cover via documentation.








