Simplify Coding with Caveman Tools

By Mike on 9 February 2013

I think I've started Caveman Tools around 2009 or so as the library where I'd dump extension methods and other helpers for common tasks. In the mean time it grew to contain lots of big and small utils and now I think my productivity would drop (and the boring factor would rise like inflation in times of depression) without it. I want to share with you a few of the helpers, little bits that simplify some tasks.

Guard Clauses

Nothing magical, just semantic shortcuts to avoid the boringness of 'if (!argument condition) throw Exception()'.

user.MustNotBeNull();
title.MustNotBeEmpty();
tile.MustMatch(@"[a-z]+");
list.MustNotBeEmpty();
value.MustBe<int>();

//exotic
strategy.MustImplement<IStrategy>();

 

LogHelper

Even when doing simple apps I want some logging, at least to show some stuff in Diagnostics window or to Console. Doing Debug.WriteLine is not that fun, especially if I want to 'upgrade' later to a real logging solution. With LogHelper things are simple:

//Setup - yes, one line
LogHelper.OutputTo(s=>Debug.WriteLine(s));
//or
LogHelper.OutputToConsole();


//Usage 
LogHelper.DefaultLogger.Info("something");


//as a dependency
public class MyClass
{
    public MyClass(ILogWriter log){}
}

var c=new MyClass(LogHelper.DefaultLogger);

All I have to do when I want to 'upgrade' to using NLog (for example) is to wire-up NLog to LogHelper and I'm good to go.

 

Fun with Lists

var old = new List<int>();
 var fresh = new[] {1, 2};
 
 var diff=fresh.Compare(old); // diff will contain what items were added or removed
 
//new items from fresh will be added to old
//items from old, but not available in fresh will be removed
 old.Update(fresh); 
 
old.AddIfNotPresent(someItem);//self explaining

//RemoveAll() method added to IList
Ilist<int> list;
list.RemoveAll(predicate);

 

Working with Percentages

Every time when I was dealing with something involving percentages I was confused about that decimal: is it already divided by 100 or I have to divide it? Percentage struct to the rescue.

var taxRate=new Percentage(3);//3%
Percentage salesTaxRate = 2;//2% , implicit from decimal

var tax=taxRate.ApplyTo(1000); //tax=30

//operator overload
var totalTaxRate=taxRate + salesTaxRate; 
Console.WriteLine(totalTaxRate.ToString());// outputs 5%

 

Reflection

//fun with attributes
type.GetSingleAttribute<MyAttribute>();
type.HasCustomAttribute<MyAttribute>();
type.GetCustomAttributes<MyAttribute>();

//type utils
if (type.Implements<Interface>()) { };

var argType=type.GetGenericArgument(0);//gets first generic argument

if (type.IsNullable()){};

if (type.IsNullableOf<int>()) {};

if (type.CanBeCastTo<int>()) {};

 

Time Utils

TimeSpan ts = 1.ToSeconds();
var half = ts.Multiply(.5f);
Console.WriteLine(TimeSpan.FromDays(2).ToHuman());//outputs "2 days ago" . english only

 

Other

//instead of the ugly: (expression.Body as MemberExpression).Member.Name
expression.Body.As<MemberExpression>().Member.Name;

//instead of (int)object
object.Cast<int>()

//useful when you need the id-name combo, common in view models
IdName topic=new IdName(){Id=1,Name="Tools"}

There are other interesting helpers but they're a bit specific, dealing with LCG (Light Code Generation) or Expressions Trees, so that's all for now.

Domain Events Toolkit Released And My Git Experience

By Mike on 4 April 2012

So, I decided that my Domain Events Framework was a bit to dependent on Rx, not only as a library but also as a mindset. The DomainEvents pattern is pretty straightforward and I thought that a simplified and lighter alternative is better. I mean you don't have to understand the Rx principles in order to use it. Rx is an amazing library but way to overkill for this.

After a bit of thinking,I decided that is also better to come up with a different name, rather than change the version and break pretty much everything. Besides, 'Toolkit' is a better description, it's something small and quick. 'Framework' usually makes you think of something big, complex, clunky.

A quick tutorial

//define an event
public class OrderPlacedEvent:DomainEventBase<Order>
{
    public OrderPlacedEvent(Order data) : base(data)
   {
   }
 }

//define a handler
 public class OrderPlacedEventHandler:DomainEventHandlerBase<OrderPlacedEvent>
  {
    public override void Handle(OrderPlacedEvent ev)
    {
      //handle event
    }
  }

//create the events manager
static IApplicationDomainEventsManager DomainEvents=new ApplicationDomainEventsManager()

//register a handler for the event
var subscription=DomainEvents.RegisterHandler(new OrderedPlacedEventHandler());

//publish events
DomainEvents.Publish(new OrderPlacedEvent(myOrder))

//unregister the handler
subscription.Dispose();

Pretty simple, heh? But you can find more details on Github .

Speaking of Github, this is also the day I joined the cool kids and made an account on github and actually using Git. My experience was pretty much ugly. Yes, Git is known as being hard to use on Windows and it lives up to the expectations. The Github site has more features than BitBucket but also I found it more cumbersome to write the docs. I write Markdown every day on Stackoverflow so I'm used to the syntax, but it was painful to write it on Github.

I prefer Mercurial every day (I learned it quite fast), but unfortunately Github is all the rage these days and Github uses only Git. Well more knowledge means more power after all.

Easy Authorization For Multi Tenant Asp.Net Mvc Applications

By Mike on 23 February 2012

The latest (1.3) release of Caveman Tools toolkit, includes functionality to make authorization with groups and rights a breeze (take a look at the tutorial). However for the next release, I've added support for authorization in multiple scopes, which will make it much easier for multi tenant applications to deal with request authorization. This post refers to the version 1.3.5 (unreleased yet) of CavemanTools.

I'm talking about web application hosting scenario, similar to what wordpress.com, bloger, getsatisfaction, github or bitbucket do, where people register with them to have their own instance of the specific service. However, every member is also a user of the global service (not limited to an account only) but their rights are specific to an account. So you can be the admin of your account, but you're a simple user for the other accounts.

Let's say for example, you're a github user browsing https://github.com/SamSaffron/dapper-dot-net . The account name is SamSaffron and let's suppose its id is 497. You, as a github user can browse the dapper-dot-net repository but you can't do much, your rights don't allow it. However, Sam has admin rights on this account, but if he goes to https://github.com/restsharp/RestSharp , he's just a simple user there. So Sam is a global github user with specific rights depending on what github location he's visiting.


If you're building such an application, Caveman Tools makes it a breeze to handle the above scenario. Scoped authorization means that the authorization is handled according to an active scope (or context). If there is no scope set, then it's assumed there is a global scope.

Your application membership schema needs to implement the concepts of users belonging to groups (or roles, but I call them groups in Caveman Tools) which can be associated or not with an account . A group holds rights valid for the account associated with it. If no account exists, it's assumed the group is global and the rights apply in every scope.

First, Caveman Tools provides an interface for you to implement.

In this is example, Sam is member of the global group with id 1 and of the group with id 2 which is associated with the 'SamSaffron' account id 497. The DefaultAuthorizationScopeId suports integers as ids, but you can implement your own type of scope id by extending the AuthorizationScopeId abstract class. The GetDefaultGroup methods returns the group for anonymous users.

public class TestUserRepo:interface IUserRightsRepository
{
    public IEnumerable<IUserContextGroup> GetGroupsById(IEnumerable<int> ids)
        {
            return new[]
                       {
                           new UserContextGroup(1, new ushort[] (UserRights.Browse),  //can browse public repositories
                           new UserContextGroup(2,new ushort[]{UserRights.DoEverything}){ScopeId = new DefaultAuthorizationScopeId(497)}, //admin for account 497
                       };
        }

        public IUserContextGroup GetDefaultGroup()
        {
            return new UserContextGroup(0,new ushort[0]);
        }

}


When a user logs in, you should retrieve his id and the groups the user belongs to. Next, you setup the authentication cookie (you must use this helper)

Response.SetAuthCookie(userId, name, groups);

Enable the CavemanUserContext in order for all the good stuff to happen.

GlobalFilters.Filters.Add(new CavemanUserContext());

 It's important to always establish the scope of a request i.e what account the user is browsing. When someone goes  to the GitHub's homepage, that's the global scope, but when browsing https://github.com/restsharp/RestSharp, the scope is restsharp. Usually you get the scope name from the url, then look for its corresponding id in the database (in this example the id of restsharp is 508) .  Once you know the scope id, you just need to set it.    

AuthenticationUtils.SetAuthScope(new DefaultAuthorizationScopeId(508));                 

That's all the setup needed.     
     
To require authorization, you  can use the DemandRightAttribute (on the controller or on a method)

[DemandRight(UserRights.DoEverything)]
public class AdminController:Controller {}

Or manually

this.GetUserContext().HasRightTo(UserRights.DoEverything)

The authorization takes into account the current scope: if no scope if set, then only the global rights are checked, but if there is a scope, then both the rights associated with the scope and global rights are checked.  So Sam is allowed to access https://github.com/SamSaffron/Admin , but he's denied access to https://github.com/restsharp/Admin.

If you're interested in the internals of how CavemanTools handles authorization, check out the source code.