Atom feed

18 August, 2006 / Entity Life-Cycle in NHibernate: IInterceptor Interface

So what NHibernate provides you to manage entity state? At first sight all you need: interceptor concept via IInterceptor interface and persistent entity via ILifeCycle interface.

Let's recall our needs. First of all, we want to validate entity before it saved into database and then we want to set some properties to entity (for example, last modified date, last editor, entity owner on first save and so on).

Interceptor

I'll start from interceptor, it is bigger and more complex (good thing to start from, simple things in the end). OK, the first thing about interceptor is that you can have only one interceptor in ISession. It means you can't create two interceptors with different responsibility in one session and should place all roles into one interceptor. Interceptor binds to session when it opened.

private void OpenSession()
{
 _session = _factory.OpenSession(new TriggerInterceptor());
 _session.FlushMode = FlushMode.Never;
}

IInterceptor has pretty much methods, here is brief explanation.

For example, when you add completely new object, workflow will be as on picture below:

Project project = new Project();
session.Save(project);
session.Flush();

OnSave. Called only when new entity saved into database (it means NHibernate thinks that entity is new, but you can trick NHibernate, it will be shown later).

IsUnsaved. Called only when new entity saved into database. In this method you can control whether entity will be updated or inserted, but in general this is not required.

PreFlush. Called each time before objects saved into database.

PostFlush. Called each time after objects saved into database. In this method you can execute some actions on saved objects. For example, invoke triggers. In general, if you want to support several databases, it is better to implement triggers in business logic layer.

Not surprisingly, when you change entity, workflow will be different.

project.Name = "new name";
session.Flush();

OnFlushDirty. Called when entity was changed (and NHibernate managed to check that) and saved into database. Pay attention that you can't just set properties here, they will not be saved into database, so the following code will not save new ModifiedDate into database:

public bool OnFlushDirty(
 object entity, object id, object[] currentState,
 object[] previousState,  string[] propertyNames, IType[] types)
{
 (entity as EntityObject).ModifiedDate = DateTime.Now;
 return false;
}

Armed with such powerful knowledge, we can try to start custom entity life-cycle management implementation.

Validation

This is the simplest part. Each entity should validate itself, so it is naturally to have Validate() method in base class and implement custom validation rules for entities. Validate method will invoke all required assertions and throw exception if something will be incorrect. To validate entity, we should just call entity.Validate() in appropriate place. The main problem is to find such pleasant place.

In general, we'd like to know about entity validity as early as possible, at least somewhere before session flush. If we check pictures above, we have no problem for new entities. For example, we can put code into OnSave method

public bool OnSave(
 object entity, object id, object[] state,
 string[] propertyNames, IType[] types)
{
 (entity as EntityObject).Validate();
}

For changed entities we have fewer options. Unfortunately, there is no such method as OnUpdate in interceptor, all methods called when flush initiated. So we have to put validation into OnFlushDirty method.

public bool OnFlushDirty(
 object entity, object id, object[] currentState,
 object[] previousState,  string[] propertyNames, IType[] types)
{
 (entity as EntityObject).Validate();
 return false;
}

Custom OnSave Rules

So far so good. Let's complicate things a bit. Very often you need to execute some rules before entity is saved. For example, we want to add logged user into project team when user creates new project. Another example, we want to mark user story as done when all it tasks marked as done. All these operations modify entity and that is the real problem.

Well, it looks simple. We can add ExecuteCustomOnSaveRules() method into EntityObject and override it in each entity.

public bool OnSave(
 object entity, object id, object[] state,
 string[] propertyNames, IType[] types)
{
 (entity as EntityObject).Validate();
 (entity as EntityObject). ExecuteCustomOnSaveRules ();
}

Then by analogy we can put method invocation in OnSave method, but we can't invoke it in OnFlushDirty, as you already know, changes will not be applied. Are we stuck? Yes, we are. In next post I'll bring ILifeCycle on board.

kick it on DotNetKicks.com

14 August, 2006 / Entity Life-Cycle in NHibernate Based Application

During last few days we are performing code reviews and making refactoring. Also we are working on TargetProcess:Migration tool that will simplify migration from TP 1.7 to TP 20. I think migration tool is not something worth to talk about, but some architecture problems might be interesting. The main point of review is Business Logic - Data Layer interactions. As you maybe know, TP 2.0 built on top of NHibernate, so we are trying to improve entity life-cycle by utilizing NHibernate power (that is why no posts during last week, we are sorry, the problem takes almost all time).

What we have right now.

As you see from diagram, the most important class in data layer is Portal (yes, it is biggest and thus looks pretty haughty). Portal is a single point of data access. It stands between business layer and NHibernate. Some things about Portal:

  1. It is a singleton
  2. It stores NHIbernate ISession
  3. It provides all operations for entities manipulation: Save, Delete, Retrieve.

Usage is pretty simple though:

UserStory story = UserStory.Create();
story.Name = "This is name " + index.ToString();
story.Description = "This is description " + index.ToString();
Portal.Instance.Save(entity);

The other important class is EntityObject - root class for all persistent entities in TP. It implements ILifeCycle interface and executes common operations on entities like validation.

Also we have TriggerInterceptor class that handles triggers invocations.

What else? We use common "session-per-request" pattern implemented via HttpModule. That's all about classes.

So, what we want to do is simplify entity life-cycle, since in current state it becomes quite weird.

What should we do with new entity before save into database? In general, several actions:

  1. Validate
    Check if entity is valid. For example, project should always have Name or EntityState object should not be final and initial at the same time. This performed in EntityObject.Validate() method which overloaded in every entity.
  2. Set Default/Known Values
    If entity is new we should set CreatedDate, if entity is modified we should set ModifiedDate. This performed in Entity.OnUpdate() method which overloaded in every entity. For example, in General entity we have:
    if (this.IsNew) 
     this.CreateDate = DateTime.Now;
    
    if (!this.IsNew) 
        this.ModifyDate = DateTime.Now;
    
  3. Execute Rules
    Yes, we should execute some business rules in rare cases. For example, it is not possible to have two initial states for user story (this is business restriction at the moment). So if user add new initial state, we should find old initial state and set Initial = false; This performed in Entity.OnUpdate() as well.
  4. Execute Triggers
    They are required for data integrity, but implemented in business layer to eliminate database dependency. For example, user story may contain several tasks and user story effort is a sum of all tasks effort. So when you add new task, you should recalculate user story effort. Triggers invoked in IInterceptor implementation in PostFlush method.

First of all, we want to make entity flow more explicit and transparent. We have both mechanisms for entity life-cycle management: ILifeCycle and IInterceptor, we want to leave only one mechanism.

Currently, we see several things that will simplify architecture. Remove ILifeCycle:

  • Completely remove OnUpdate method. It takes too much responsibility.
    • Move default values setting into entity constructor
    • Move knows values setting into IInterceptor's OnFlushDirty method (in fact it is only two properties: ModifiedDate and LastEditor)
    • Move rules invocation into IInterceptor's PostFlush if possible
  • Move Validate invocation into IInterceptor

As a result, ILifeCycle interface implementation will be removed. The other solution is to remove IInterceptor implementation, but in this case we have to call triggers execution from Portal directly, which means that if entity will be saved by cascade by NHibernate, triggers will not be fired.

OK, it is midnight and I am slightly tired, so to be continued…

06 August, 2006 / Implementing TextBox With Tool Tip Control (ASP.NET C#)

Almost any web application should guide user and help him/her reach major goals. It should provide advices, next possible steps and so on. In the other words it should provide on demand help. For example, user doesn't know what "Importance" field means. You may help the user by showing tool tip. This article describes creation of such tool tip control integrated with text box on ASP.NET 2.0 + C#.

Here is the idea. We have form with several fields.

We want to show tool tip when user about to type something.

It means tool tip should be visible on onfocus event. Also we want to highlight current field, this is just small UI improvement that helps user to feel current state and faster recover from interrupts.

OK, let's think about how we can implement such control. What we need is additional layer that will be visible onfocus and hidden onblur. This is not a problem, we can easily show/hide layer with javascript. The problem is in position. How can we show tool tip layer right after input field? One possible way is to define x,y coordinates of input field and set these coordinates to tool tip layer.

function findPosX(obj)
{
var curleft = 0;
if (obj.offsetParent)
{
     while (obj.offsetParent)
     {
          curleft += obj.offsetLeft
          obj = obj.offsetParent;
     }
}
else if (obj.x)
     curleft += obj.x;
return curleft;
}

So, to implement what we want the following steps required:

  1. Inherit from TextBox control
  2. Add ToolTipText field that will store tool tip text
  3. Create tool tip layer and hide it by default (in Render method)
  4. Define x,y coordinates, show tool tip layer onfocus

From control user perspective we need the simplest solution: just put control on the page and specify tool tip text. That's it.

<tp:TpTextBox MaxLength="255" ToolTipText="Put any text you want here..." ID="TpTextBox2"
CssClass="input" Width="300" runat="server" Text=''>
</tp:TpTextBox>

And here is the solution step by step.

Step 1-2. Inherit from TextBox control, Add ToolTipText field that will store tool tip text

public class TpTextBox : TextBox
 {
  private string toolTipText = null;

  public string ToolTipText
  {
   get { return toolTipText; }
   set { toolTipText = value; }
  }

Step 3. Create tool tip layer and hide it by default (in Render method)

The most interesting things are in Render method of TpTextBox class. See comments in code:

protected override void Render(HtmlTextWriter writer)
{
 FormTipAndFocus(writer);
 base.Render(writer);
}


private void FormTipAndFocus(HtmlTextWriter writer)
{
 string tipFunction = "";
 
 // Show tool tip only if ToolTipText has some text
 if (!ReferenceEquals(ToolTipText, null))
 {
  // to have several controls on page, we should make unique ids for tool tip layers
  // javscript function will use this id to show/hide layer
  string id = Guid.NewGuid().ToString();
  
  // create tool tip layer. Resolve drop down and z-index problem for IE as well
  string toolTipLayer = String.Format(
    "<div class='toolTip selectFree' id='{0}'><div class='content'>{1}</div>" + 
    "<!--[if lte IE 6.5]><iframe></iframe><![endif]--></div>",
    id, ToolTipText);
  Literal lit = new Literal();
  lit.Text = toolTipLayer;
  lit.RenderControl(writer);
 }
}

Step 4. Define x,y coordinates, show tool tip layer onfocus

And here is the code of showTip javascript function. It shows/hides tool tip and position it on the right place - after input field.

function showTip(id, inputId) {
    var panel = document.getElementById(id);
    var inputField = document.getElementById(inputId);
                   
    // show/hide tool tip layer
    if (panel.style.display != 'block') {
        panel.style.display = 'block';
    }
    else {
        panel.style.display = 'none';
    }
    
    //positioning
    panel.style.position = 'absolute';
    var width = inputField.style.width.toString();
    var w = findPosX(inputField) + parseInt(width.substring(0, width.length - 2));
    var h = findPosY(inputField);
    panel.style.left = w + 3 + 'px';
    panel.style.top = h + 'px';
}

Now we have to invoke showTip function onfocus and onblur. Just add these attributes to TpTextBox in Render method:

private void FormTipAndFocus(HtmlTextWriter writer)
{
 string tipFunction = "";
 
 if (!ReferenceEquals(ToolTipText, null))
 {
  ...

  tipFunction = String.Format("showTip('{0}','{1}');", id, ClientID);
 }

 // add showTip function onfocus event, also change styles to highlight input field
 Attributes.Add("onfocus", tipFunction + 
  String.Format(FocusBlurStyles, focusBackground, focusBorderColor));

 Attributes.Add("onblur", tipFunction + 
  String.Format(FocusBlurStyles, blurBackground, blurBorderColor));
}

That's it. Control is ready to use. You may download source code here.

03 August, 2006 / TP 2.0 "Minima"

We are about to release TP 2.0 "Minima". It is a "prove-of-concepts" release in fact with some important features and overall application framework, navigation patterns, layout, etc. We are not going to sell it in current state, since it has limited set of features, but rather check and correct our direction based on early users feedback.

Features list:

Planning

  • Release, User Stories, Iterations, Tasks support
  • XP planning
  • Drag and Drop Iteration planning
  • Drag and Drop developers assignment
  • ToDo list
  • User dashboard
  • Time tracking
  • Release burn down chart
  • Iterations velocity progress chart

General

  • Role-based permissions
  • Customizable workflow for user stories and tasks
  • Attachments and Comments for all entities
  • Customizable lists (visible columns, page size, filters)
  • Context help

 

We are developing new version of TargetProcess and blogging about our progress.

TP 2.0 online demo
TP 2.0 quick tour