Who updated my cheese?

LightSpeed has always had the ability to track when entities were created and when they were last updated — just define fields named CreatedOn and UpdatedOn, and LightSpeed will populate them automatically as the entity is created and modified. Or if you’re using the designer you can just turn on the ‘Track Create Time’ and ‘Track Update Time’ options and experience the magic.

In recently nightly builds, we’ve added the ability to track who created and who last updated the entity — and, for soft-deleted entities, who deleted it. To use this feature requires two simple steps.

The tracking fields

The first step is to provide somewhere to store the tracking information. As with CreatedOn and UpdatedOn, all you need to do is create fields with names that adhere to a LightSpeed convention (and, of course, the corresponding database columns). The field names LightSpeed recognises are:

  • CreatedBy (string)
  • UpdatedBy (string)
  • DeletedBy (string, should allow NULLs in database)

You don’t need to have all of these (and DeletedBy will be ignored unless you’re also using soft delete): you can pick and choose which actions you want to track. As with the time tracking fields, you’ll usually want to make these fields read-only to prevent other people messing with them.

public class Cheese
{
  private readonly string _createdBy;
 
  public string CreatedBy
  {
    get { return _createdBy; }
  }
}

At the moment, there’s no special support in the designer similar to the ‘Track Create Time’ option. You’ll need to create the special fields as normal properties (with LoadOnly to avoid people messing with them). We’ll add designer support in a future update once we’ve had a bit more user feedback.

The user information

Applications have all sorts of crazy ways of identifying users. Web applications typically use the ASP.NET HttpContext.User; WinForms and WPF applications might use the Windows login identity, but often have their own internal identity systems instead. LightSpeed doesn’t know what kind of user identity makes sense for your app.

So the second step is to tell LightSpeed how to get the user name to put into the special fields you just created. To do this, set the LightSpeedContext.AuditInfoMode in code or configuration. (If you don’t do this, LightSpeed won’t populate the special fields at all. This, by the way, means existing applications which happen to use fields with the same names don’t need to worry about LightSpeed mucking them around. You only get the special behaviour if you opt into it.) There are two built-in modes:

  • The WindowsIdentity mode takes the user name to be the name of the logged-in Windows user. This is suitable for many rich client applications especially on corporate domains.
  • The HttpContext mode takes the user name from the User property of the current HttpContext. (It defaults to “Anonymous” if the user isn’t authenticated. But it’s probably not a great idea to turn on user tracking and not bother authenticating your users.) This is suitable for most Web applications.
<lightSpeedContexts>
  <add name="Test" auditInfo="HttpContext" />
</lightSpeedContexts>
LightSpeedContext context = /* ... */;
context.AuditInfoMode = AuditInfoMode.HttpContext;

That’s it

With these two changes in place LightSpeed will automatically record the user name as well as the time of whatever actions you’ve set up! This is all in the current–

Hey, I have my own identity system! What about me?

WindowsIdentity and HttpContext cover the majority of Web and Windows scenarios. However, if you’ve got your own identity system, for example based on an internal database or a third-party system like an ERP or CRM system, then you can hook LightSpeed up to that using the Custom mode. If you set AuditInfoMode to Custom, you must also set LightSpeedContext.CustomAuditInfoStrategy to an implementation of the IAuditInfoStrategy interface.

internal class BlameAuditInfoStrategy : IAuditInfoStrategy
{
  private readonly string _whoToBlame;
 
  public BlameAuditInfoStrategy(string whoToBlame)
  {
    _whoToBlame = whoToBlame;
  }
 
  public AuditInfoMode Mode
  {
    get { return AuditInfoMode.Custom; }  // required under New Zealand law
  }
 
  public string GetCurrentUser()
  {
    return _whoToBlame;
  }
}
 
// Usage:
// context.AuditInfoMode = AuditInfoMode.Custom;
// context.CustomAuditInfoStrategy = new BlameAuditInfoStrategy("jd");

Getting the tracking info

LightSpeed loads entity tracking info just as it does any other field, so you can access it via a property, query on it, and so on:

public void WhoMovedMyCheese()
{
  var mine = _unitOfWork.Cheeses
                        .Where(c => c.CreatedBy == "ivan" && c.UpdatedBy != "ivan");
  foreach (var cheese in mine)
    Console.WriteLine(cheese.UpdatedBy + " moved my cheese");
}

That really is it

User tracking is in the current LightSpeed 3.11 nightly builds. (It’s not in the 4.0 beta yet; give us a few days to merge it across.) You can get the free Express edition from the Downloads page or the Professional edition from the store.

Tagged as LightSpeed

5 Responses to “Who updated my cheese?”

  • This sounds great — for those who are building single-tier applications, that is. What about those of us who build n-tier apps, where LightSpeed is hidden away behind a web service?

    The only way I have done this successfully in the past is to supply the username as a separate parameter in the web service call. I then filled in whatever tracking fields I needed to as part of the process of saving the data, usually in a stored procedure. I think the Custom AuditInfoMode you describe above is the way I need to go with LightSpeed, but how can I do that on a per-call basis? I want it to still work when you fully integrate this functionality with the designer and hide it away under the covers, so to speak.

    I love the way your Beta 4 version allows me to work with the entities and just pass them back up the chain to my web service. This is by far the easiest way to build n-tier apps I know of (not that I have vast knowledge of other methods, but LS Beta 4 is drop-dead simple).

    Thanks,
    Dave

  • That’s right, you would need to use Custom mode, and the service would need to somehow identify the user. Sending a username via a Web service parameter is possible though if you wanted to verify the username then you would be better off using WCF security (otherwise a malicious client could send a bogus username in the parameter). I’m not a WCF expert but I believe you could then get the caller identity from ServiceSecurityContext.Current. So your IAuditInfoStrategy implementation would just consult ServiceSecurityContext.Current and return the PrimaryIdentity.Name. And this would be on a per-call basis. I’ll ask Jeremy to jump in on this though because I may be talking out of my hat!

    Anything you do with custom strategies should not be affected by designer integration; the only integration work we have planned would be to provide entity-level settings for user tracking similar to the way we offer Track Create Time and Track Update Time instead of requiring people to declare CreatedOn and UpdatedOn fields.

  • Dave – have a look at this for some more details: http://msdn.microsoft.com/en-us/library/ff647503.aspx but in a nutshell what Ivan has suggested is spot on, assuming you are using Windows Auth around your service via IIS you will be able to get access to the principal object through ServiceSecurityContext.Current.

    If you are implementing your own authentication extension (say for custom username/password authentication against a local database store) then just make sure you set Thread.CurrentPrincipal as part of that.

    Jeremy

  • Please post when you have this ported over to the Beta, I have a need for it in my project. Thanks!

    Dave

  • Never mind, it seems to work just fine in the Beta. Thanks again!

  • Leave a Reply

Archives

Join our mailer

You should join our newsletter! Sent monthly:

Back to Top