This is post #20 in the Building Neno series. Please click here for a description of the Building Neno project and instructions on how to access the source code for this post.
I want to add comments to my posts. The easiest way I can think of to do that, in a disabled JavaScript friendly way, is to have a form on the individual post screen. That screen currently accepts a post domain object as its post model. While great for showing the details of a post, it's not going to easily handle my comment postback without some ugly workarounds. (I know, I spent a couple of hours trying.)
I need to acknowledge the fact my domain objects are not going to make very good view models going forward. I need to create space in my web application for explicit view models, and that includes refactoring the confused, de facto, implicit view models I already have.
If you like elegant code, then everything.
The first thing I'm going to do is rename my Models
namespace to ViewModels
. Models is too ambiguous for me -- I already have a domain model in my application assembly -- I want to be explicit about what kind of models these will be.
Why ViewModels
and not ViewModel
, like the DomainModel
namespace? A domain model is just one model, made up of many classes. View models on the other hand, are (usually) independent models for single views. It's personal preference in the end, but I prefer to acknowledge the collection in the namespace.
Next, I'm going to move the PublishDate
family of classes to a new namespace, RouteModels
. That's really what those are, some helper classes for route data. They're not view models.
Some other classes don't belong in the ViewModels
namespace either. the IIdentityExtensions
class can go in the application assembly since it has no web dependency and could conceivably be reused in another authentication environment (like Windows authentication).
The AuthCookie
class is also not a view model. I'll move that up into the parent WebApplication
namespace. It could be reused if I build another forms authentication UI, but that's unlikely at the moment, so it can stay in this UI for now.
My terrible cockney accent doesn't convey, but the general gist is I'm going to rename my view model classes too. I'm using a bunch of de facto view models right now with names like...
Archive
ArchiveYear
ArchiveMonth
OlderNewerArchive
OlderNewer
OlderNewerPost
OlderNewerPosts
Not very well named view models. What do they mean is probably the biggest question. Who is using them is probably the next.
I'm going to refactor them into view models named for the views that use them. Some view models will be reused, so I'll either choose a name that makes sense to both, or name it after the first view to use it. Naming view models after views is going to lead to some duplication, so I'll use extra namespaces based on controller names (the same way views are broken up into folders).
The gizzards are being refactored too. The smelly Payload
property is gone, replaced by better named properties. The nonsensical inheritance chain of OlderNewer
is also gone.
While I'm refactoring, I'm going to address the code smell in one of the view models. The Archive
view model, now the Posts
and PostsByPublishYear
view models, was accepting large lists of publish dates and grouping them in memory using Linq. I'm going to make the database do the grouping work for me and cut down on the amount of data being passed around.
I don't normally refactor for performance when there's no performance issue, but this code smell is offending my RDBMS sensibilities.
I'm having trouble getting grouping to work in NHibernate Linq, so in the interest of just getting this done I'm going to fall back to ICriteria
, specifically the new wrapper for it, QueryOver.
public IEnumerable<object[]> GetYearlyCounts()
{
return Session.QueryOver<Post>()
.Select(
Projections.SqlGroupProjection("DATEPART(year, {alias}.[PublishDate]) AS [PublishYear]", "DATEPART(year, {alias}.[PublishDate])", new[] { "PublishYear" }, new[] { NHibernateUtil.Int32 }),
Projections.SqlGroupProjection("DATEPART(month, {alias}.[PublishDate]) AS [PublishMonth]", "DATEPART(month, {alias}.[PublishDate])", new[] { "PublishMonth" }, new[] { NHibernateUtil.Int32 }),
Projections.Count<Post>(post => post.PublishDate))
.List<object[]>();
}
While a reasonably light query, I don't like the T-SQL dependency. And it gives me a small dataset of years, months and post counts that is still not easy to consume. I may look at this again, but in the meantime I'll plough ahead and address the consumability issue at least.
public IEnumerable<YearlyPostCount> GetYearlyCounts()
{
return Session.QueryOver<Post>()
.Select(
Projections.SqlGroupProjection("DATEPART(year, {alias}.[PublishDate]) AS [PublishYear]", "DATEPART(year, {alias}.[PublishDate])", new[] { "PublishYear" }, new[] { NHibernateUtil.Int32 }),
Projections.SqlGroupProjection("DATEPART(month, {alias}.[PublishDate]) AS [PublishMonth]", "DATEPART(month, {alias}.[PublishDate])", new[] { "PublishMonth" }, new[] { NHibernateUtil.Int32 }),
Projections.Count<Post>(post => post.PublishDate))
.List<object[]>()
// End of NHibernate call, now transform in memory.
.GroupBy(row => (int)row[0], row => new[] { (int)row[1], (int)row[2] })
.Select(year => new YearlyPostCount(year.Key, year
.GroupBy(row => row[0], row => row[1])
.Select(month => new MonthlyPostCount(year.Key, month.Key, month.First()))));
}
This gives me some nested lists I can just iterate through in my UI. That will do for now.
After all that my application looks exactly the same as it did yesterday, but at least I'm happier about the direction my code is going. Staying clear, flexible, and easy to maintain can be just as important as adding features.
There are 0 comments.
Older
Post Screens
Newer
Comments
Older
Post Screens
Newer
Comments
browse with Pivot
Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue
Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
C# (4)
Challenges (3)
Collections (1)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Kata (2)
Linq (7)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
OS X (1)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)
Comments
Leave a Comment
Please register or login to leave a comment.