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.
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...
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.
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.
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.
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
}
Posts are hidden again, both in post queries and in the post collection on tags.
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.
Older
Tags
Newer
Pages
Older
Tags
Newer
Pages
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.