NHibernate audit logging

Voorbereiding
In dit artikel gaan wij ervan uit dat je al beschikt over een Solution die is ingericht met NHibernate. Voor het voorbeeld gaan we ervan uit dat je solution beschikt over 3 projecten, namelijk:

  1. Domain (Class Library)
  2. Persistence (Class Library)
  3. Presentation (ASP.net MVC, WinForms, WPF)
Er dienen referentie toegevoegd te worden van onder naar boven, dus
Presentation -> Persistence -> Domain
 

Scenario
Binnen je applicatie wil je bijhouden welke wijzigen etc. worden doorgevoerd op je entiteiten. Deze wijzigen moeten automatisch door je applicatie geregistreerd worden.

Oplossing
De oplossing hiervoor kan je vinden in het Event model van NHibernate. Door gebruik te maken van deze events kan je bv. auditen welke entiteiten worden: toegevoegd, geupdate en verwijderd.
Wanneer een entiteit wordt geupdate kan je registreren welk veld er is gewijzigd en wat de oude en nieuwe waarde is.

Classes
Ons domein bestaat uit drie entiteiten, namelijk:

  • Entity (Base Class)
  • Customer (Afgeleid van Entity)
  • AuditLog (Afgeleid van Entity)
We willen graag de wijzigingen aan een Customer kunnen registreren zodat we bij problemen direct kunnen zien wie, en wat er precies gewijzigd is. 


Base

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Tcf.Framework.Domain.Entities
{
    public abstract class Entity
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        /// <value>
        /// The id.
        /// </value>
        public virtual int Id { get; private set; }
    }
}

Customer

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Tcf.Framework.Domain.Entities
{
    public class Customer : Entity
    {
        #region Properties
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>
        /// The name.
        /// </value>
        public virtual string Name { get; set; }
        /// <summary>
        /// Gets or sets the address.
        /// </summary>
        /// <value>
        /// The address.
        /// </value>
        public virtual string Address { get; set; }
        #endregion
    }
}

AuditLog

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Tcf.Framework.Domain.Logging
{
    public class AuditLog
    {
        /// <summary>
        /// Gets or sets the id.
        /// </summary>
        /// <value>
        /// The id.
        /// </value>
        public virtual int Id { get; set; }
        /// <summary>
        /// Gets or sets the username.
        /// </summary>
        /// <value>
        /// The username.
        /// </value>
        public virtual string Username { get; set; }
        /// <summary>
        /// Gets or sets the type of the audit entry.
        /// </summary>
        /// <value>
        /// The type of the audit entry.
        /// </value>
        public virtual string AuditEntryType { get; set; }
        /// <summary>
        /// Gets or sets the full name of the entity.
        /// </summary>
        /// <value>
        /// The full name of the entity.
        /// </value>
        public virtual string EntityFullName { get; set; }
        /// <summary>
        /// Gets or sets the short name of the entity.
        /// </summary>
        /// <value>
        /// The short name of the entity.
        /// </value>
        public virtual string EntityShortName { get; set; }
        /// <summary>
        /// Gets or sets the entity id.
        /// </summary>
        /// <value>
        /// The entity id.
        /// </value>
        public virtual int EntityId { get; set; }
        /// <summary>
        /// Gets or sets the name of the field.
        /// </summary>
        /// <value>
        /// The name of the field.
        /// </value>
        public virtual string FieldName { get; set; }
        /// <summary>
        /// Gets or sets the old value.
        /// </summary>
        /// <value>
        /// The old value.
        /// </value>
        public virtual string OldValue { get; set; }
        /// <summary>
        /// Gets or sets the new value.
        /// </summary>
        /// <value>
        /// The new value.
        /// </value>
        public virtual string NewValue { get; set; }
        /// <summary>
        /// Gets or sets the timestamp.
        /// </summary>
        /// <value>
        /// The timestamp.
        /// </value>
        public virtual DateTime Timestamp { get; set; }
    }
}


Implementatie
Binnen je persistence layer kan je diverse audit listeners aanmaken en later configureren in je NHibernate of Fluent NHibernate configuratie. Een audit listener class kan er als volgt uitzien:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate.Event;
using Tcf.Framework.Domain.Logging;
using Tcf.Framework.Domain.Entities;
using NHibernate;
using System.Globalization;

namespace Tcf.Framework.Persistence.NHibernate.Logging
{
    public class AuditListener : IPostInsertEventListener, IPostUpdateEventListener, IPostDeleteEventListener
    {
        public void OnPostInsert(PostInsertEvent @event)
        {
            if (@event.Entity is AuditLog)
                return;

            if (@event.Entity is Entity)
                CreateAuditRecord(@event, ListenerType.PostInsert);
        }
        public void OnPostUpdate(PostUpdateEvent @event)
        {
            if (@event.Entity is AuditLog)
                return;

            if (@event.Entity is Entity)
                CreatePostUpdateAuditRecord(@event);
        }
        public void OnPostDelete(PostDeleteEvent @event)
        {
            if (@event.Entity is AuditLog)
                return;

            if (@event.Entity is Entity)
                CreateAuditRecord(@event, ListenerType.PostDelete);
        }
        private void CreateAuditRecord(AbstractPostDatabaseOperationEvent e, ListenerType listenerType)
        {
            var session = e.Session.GetSession(EntityMode.Poco);

            session.Save(new AuditLog()
            {
                EntityShortName = e.Entity.GetType().Name,
                EntityFullName = e.Entity.GetType().FullName,
                EntityId = (int)e.Id,
                Username = Environment.UserName,
                AuditEntryType = listenerType.ToString(),
                Timestamp = DateTime.Now
            });

            session.Flush();
        }
        private void CreatePostUpdateAuditRecord(PostUpdateEvent @event)
        {
            var entityFullName = @event.Entity.GetType().FullName;

            if (@event.OldState == null)
                throw new ArgumentNullException(string.Format(CultureInfo.CurrentCulture, Language.OldStateIsNullException, entityFullName));

            var dirtyFieldIndexes = @event.Persister.FindDirty(@event.State, @event.OldState, @event.Entity, @event.Session);

            var session = @event.Session.GetSession(EntityMode.Poco);

            foreach (var dirtyFieldIndex in dirtyFieldIndexes)
            {
                var oldValue = getStringValueFromStateArray(@event.OldState, dirtyFieldIndex);
                var newValue = getStringValueFromStateArray(@event.State, dirtyFieldIndex);

                if (oldValue == newValue)
                    continue;

                session.Save(new AuditLog
                {
                    EntityShortName = @event.Entity.GetType().Name,
                    FieldName = @event.Persister.PropertyNames[dirtyFieldIndex],
                    EntityFullName = entityFullName,
                    OldValue = oldValue,
                    NewValue = newValue,
                    Username = Environment.UserName,
                    EntityId = (int)@event.Id,
                    AuditEntryType = ListenerType.PostUpdate.ToString(),
                    Timestamp = DateTime.Now
                });
            }

            session.Flush();
        }
        private static string getStringValueFromStateArray(object[] stateArray, int position)
        {
            var value = stateArray[position];

            return value == null || value.ToString() == string.Empty
                    ? string.Empty
                    : value.ToString();
        }  
    }
}


Deze listener implementeert drie interfaces die allemaal 1 methode vereisen. In dit voorbeeld worden er drie momenten geaudit, namelijk:

  1. Als een entiteit is opgeslagen 
  2. Als een entiteit gewijzigd is 
  3. Als een entiteit verwijdert is
public void OnPostInsert(PostInsertEvent @event)
        {
            if (@event.Entity is AuditLog)
                return;

            if (@event.Entity is Entity)
                CreateAuditRecord(@event, ListenerType.PostInsert);
        }


Deze methode controleert of de entiteit die toegevoegd wordt van het type AuditLog is, en zo ja dan wordt er geen record aangemaakt, dit zou namelijk een oneindige loop veroorzaken. Als de entiteit afgeleid is van de base class Entity, zal er een audit record aangemaakt worden en opgeslagen in de database.

Registratie
De registratie van listeners met Fluent NHibernate gaat als volgt:

return Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString))
                .Mappings(mappings => mappings.AutoMappings.Add(PersistenceConfigurator.Generate()))
                .ExposeConfiguration(cfg =>
                {
                    cfg.SetListener(ListenerType.PostInsert, new AuditListener());
                    cfg.SetListener(ListenerType.PostUpdate, new AuditListener());
                    cfg.SetListener(ListenerType.PostDelete, new AuditListener());
                    cfg.SetProperty(nconfig.Environment.GenerateStatistics, "true");
                    cfg.SetProperty(nconfig.Environment.CurrentSessionContextClass, sessionContextType);

                    if(exportSchema)
                        new SchemaExport(cfg)
                            .SetOutputFile(@"c:\\schema.sql")
                            .Execute(true, true, false);
                })
                .Cache(cache => cache.UseQueryCache())
                .BuildSessionFactory();


Einde

Voor meer informatie over NHibernate verwijs ik jullie naar de volgende websites:

NHibernate Forge
NHibernate in Action


Torrentson is free!!

I have decided to release Torrentson for free, because to many people were complaining about the price (really, about E1.49 ?!?!?).

We hope you enjoy the free version and are looking forward to your replies!

You can download it from Google Play here 

Torrentson update 1

We are working on the first update for Torrentson. This update includes:

  • Multiple device support (download)
  • Result filtering
  • Styling improvements
  • More feeds, so more search results
  • Security improvements
Keep the idea's and messages coming!
You can download Torrentson in Google Play here
 
There's a pirate inside everyone! TAPIE

Torrentson available now!

Torrentson is available now in Google Play, you can download it from this URL:

https://play.google.com/store/apps/details?id=decodefabriek.torrentson

There's a pirate inside everyone! TAPIE

Torrentson official release!

Tomorrow is the big day, because we are releasing Torrentson for Android and the official website. We are making some last improvements and are adding more RSS feeds. We hope you will enjoy Torrentson and are looking forward to your comments!

The official websites are:

www.torrentson.com

www.torrentson.nl

Torrentson - There's a pirate inside everyone

 

Why?

I had a drink with a friend in some bar and he told me about a cool serie he downloaded from TPB. I woke up the day after with a massive hangover and obviously i forgot the name of the serie. These kind of situations are history if you use my new mobile app called Torrentson. With Torrentson you can search for torrents on your smartphone and download them to a remote location that is running Torrentson Server.

How?

The mobile clients use a super fast RSS processing service that runs on my server. This service scans over 200+ RSS feeds for your keywords and returns the result, wich is cached on my server so the performance is even better for the second person that is looking for the same torrent.

The Torrentson Server that you install on your home computer(for example) listens for download requests coming from the mobile client and starts the download with your own torrent client (utorrent for example). The service communicates over HTTP, so no extra configuration is needed.

When?

I'm finishing up some last modifications and improvements on the website and the mobile client, so i think i'm going to release the first beta version in about 2-3 weeks from now. In the meantime you can follow the last updates on Twitter by following @torrentson (https://twitter.com/torrentson) or on Facebook https://www.facebook.com/torrentson

Aloha Editor - HTML5 WYSIWYG Editor

It has been a while, sorry for that! I'm working on a couple of projects and don't have enough time to update my blog.

One project i'm working on needed a simple HTML editor that has the ability to include a image. After doing some research it wasn't as easy as i thought. Most of the editors available contain to much features that our customers will never use and take a hell lot of time to start up.

After spending some hours looking for a decent implementation, i stumbled upon the Aloha HTML5 Editor and it's that good that i needed to share this. The good thing about is that it's open-source, lightweight and extremely simple to use. You can find the editor on this website.

The only problem that i have is that it doesn't support video's, but i'm currently writing a plugin that uses oEmbed (link) to include video. The plugin will check if HTML 5 is supported by the client's browser and will add a video tag or an iframe, depending on support.

I will provide a link to plugin when it's finished, i hope this or next week. If you have any question or suggestions, feel free to contact me.

FluentChart Cool Wall

The first steps in the logo design proces are a fact. Lu Yu is the guy that is designing the logo and these are the first Cool Wall results. The first logo drafts will be posted soon...

FluentChart_CoolWall.pdf (442.83 kb)

First FluentChart API peek!

For the people that can't wait, here's a small preview on the FluentChart API that i'm currently working on. The following syntax is used to configure and create your kick-ass charts.

FluentChart API

chart = FluentChart.Configure()
          .Template(new DefaultTemplate())
          .Create();

Templates

You can create your own look and feel by using the build-in template mechanism like this:

namespace FluentChart.FusionCharts.Templates
{
    public class DefaultTemplate : FusionChartTemplate
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultTemplate" /> class.
        /// </summary>
        public DefaultTemplate()
        {
            Set(x => x.ShowLegend, true);
        }
    }
}

I'm looking forward to your comments and what you think of this first sneek peek into the FluentChart API. Keep posted for more updates!

fluentchart.com is coming soon!

I was thinking about this idea for a couple of years now. When you work a lot with charts, i thought it would be nice to have a simpel API which you can use to create your dashboards and other chart driven applications. I'm currently working on the first API for the FusionCharts suite. This takes time, so keep posted for more info! 

Me, Myself and I

Hello, I'm Rob Angelier and i'm the author of this blog about .NET development (mostly). I'm a professional software developer on the .NET platform. I focus on ASP.NET (MVC) development and related technics.

I hope this blog can help you in a certain way, but i'll let you decide that! Feel free to contact me if you have any questions, suggestions or something else!

profile for Rob Angelier at Stack Overflow, Q&A for professional and enthusiast programmers

Months

Widget TorrentSearch not found.

Unable to resolve http://rss.thepiratebay.sx/201. Please provide a working url and try again.X