Register | Login

Stacking Code

public interface IBlog { string Dump(Stream consciousness); }

NHibernate Filters

Thursday, 16 December, 2010 @ 8:47 PM < Adam Boddington
Tags: Building Neno, NHibernate

This is post #23 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.

There's a bug in my application. I'm sure there is more than one, but the one I've noticed involves the posts attached to my tags. They’re not filtered correctly by user.

First a bit of background on the rules regarding posts.

  1. Posts can be published and unpublished.
  2. Unregistered and registered users can normally see only published posts.
  3. Users who are authors can see the unpublished posts they are authors of.
  4. Administrators can see all posts regardless of publish status.

When I say "see", I mean unpublished posts are mixed in with the published posts on the site –- including the archives and the tag views.

Let me give you an example. Say I have one post in my application, unpublished, with a tag...

Post Edit

When I log out, the application will treat me as an unregistered user and I should see no posts in the application, and no tags either -- no tags because I only show tags that have a (visible) post count greater than zero.

Unfiltered Post

And here's the problem. I've logged out and navigated to a tag view where I can see the unpublished post. If I click on that post, the application tells me it can't find it -- which is correct, but it shouldn't be showing me the post link at all. It shouldn't even be showing me the tag!

The problem is only in the tag views and the tag partial view, which gives me a pretty big hint as to where the problem is. When I implemented my rules way back when, I did it with an IQueryable filter on all post queries. Because the tag views and the tag partial view access posts through a lazy loaded collection on the Tag class instead, the filter, and the rules, are being bypassed completely.

I need a way to filter all my posts, not just the ones pulled from my post queries.

Filter at the Source

Enter NHibernate filters. These suckers can be applied everywhere, but the caveat is I have to translate my rules into a simple SQL statement -- which isn't always easy.

Since I'm going to the trouble of making an NHibernate filter, I'm going to add it to the Post class as well and ditch my original IQueryable filter.

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
    <class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="StackingCode.Neno.DomainModel.Post, StackingCode.Neno" table="Neno_Post">
        <!-- ... -->
        <filter name="postsByCurrentUser" condition="(IsPublished = 1 OR :currentUserIsAnAdministrator = 1 OR (:currentUserIsAnAuthor = 1 AND :currentUserId = Author))" />
    </class>
    <filter-def name="postsByCurrentUser">
        <filter-param name="currentUserIsAnAdministrator" type="System.Boolean" />
        <filter-param name="currentUserIsAnAuthor" type="System.Boolean" />
        <filter-param name="currentUserId" type="System.Int32" />
    </filter-def>
</hibernate-mapping>

The filter gets a name and a condition with as many parameters as needed. The filter-def defines the parameters by name and type.

To apply the filter to the post collection on the Tag class as well, I copy the filter element over to the many-to-many element in the collection.

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
    <class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="StackingCode.Neno.DomainModel.Tag, StackingCode.Neno" table="Neno_Tag">
        <!-- ... -->
        <set name="Posts" mutable="false" table="Neno_Post_Tags">
            <key column="Tag" />
            <many-to-many class="StackingCode.Neno.DomainModel.Post, StackingCode.Neno" column="Post">
                <filter name="postsByCurrentUser" condition="(IsPublished = 1 OR :currentUserIsAnAdministrator = 1 OR (:currentUserIsAnAuthor = 1 AND :currentUserId = Author))" />
            </many-to-many>
        </set>
    </class>
</hibernate-mapping>

Placing the same filter on the Post class and on the post collection in the Tag class covers every avenue for retrieving posts.

Using the Filter

NHibernate filters have to be specifically turned on in the ISession before they will apply. A quick extension method will help out there.

public static class SessionExtensions
{
    public static ISession FilterPostsByCurrentUser(this ISession session)
    {
        User currentUser = User.Current;

        session.EnableFilter("postsByCurrentUser")
            .SetParameter("currentUserIsAnAdministrator", currentUser != null && currentUser.IsAnAdministrator)
            .SetParameter("currentUserIsAnAuthor", currentUser != null && currentUser.IsAnAuthor)
            .SetParameter("currentUserId", currentUser != null ? currentUser.Id : 0);

        return session;
    }
}

The final thing to do is make sure the filter is turned on before any queries of posts or tags are executed.

public class PostRepository : Repository<Post, int>, IPostRepository
{
    public Post GetByPublishDateAndSlug(DateTime publishDate, string slug)
    {
        Session.FilterPostsByCurrentUser();

        return Session.Query<Post>()
            .Where(post => post.PublishDate == publishDate)
            .Where(post => post.Slug == slug)
            .SingleOrDefault();
    }

    // ... and so on
}

public class TagRepository : Repository<Tag, int>, ITagRepository
{
    public Tag GetByName(string name)
    {
        Session.FilterPostsByCurrentUser();

        return Session.Query<Tag>()
            .Where(tag => tag.Name == name)
            .SingleOrDefault();
    }

    // ... and so on
}

Result

Posts are hidden again, both in post queries and in the post collection on tags.

Filtered Post

The downside is I've buried an important piece of logic in my NHibernate mapping files. Conceded the original location wasn't that great either, since it was in an NHibernate specific post repository and not the domain model or the service layer. In either place the logic could be potentially lost if NHibernate is ever swapped out for something else.

It will have to do for now.

There are 0 comments.


Comments

Leave a Comment

Please register or login to leave a comment.


Older
Tags

Newer
Pages

Older
Tags

Newer
Pages

browse with Pivot


About


Projects

Building Neno


RSS
Recent Posts

Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue


Tags

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)


Archives


Powered by Neno, ASP.NET MVC, NHibernate, and small furry mammals. Copyright 2010 - 2011 Adam Boddington.
Version 1.0 Alpha (d9e7e4b68c07), Build Date Sunday, 30 January, 2011 @ 11:37 AM