Friday, August 28, 2015

This little thing called this

While learning the Basic Authentication on node express, I encountered the need to learn javascript more. One thing I learned today is the use of .bind function of javascript. I seen it being used most on javascript UI frameworks, I didn't bother to learn what's the use of that bind function, until today.

On Basic Authentication of node express, I need to pass an extra parameter to node express callback, that parameter is role. That role parameter is akin to Authorize attribute of ASP.NET MVC.

Here's a rough signature of node express callback:

interface Func {
    (random: number, basicAuthUser: string) : boolean;
}


function doSomething(...actions: Func[]) : void {
    
    var basicAuthUser = "davegrohl"; // obtained from browser's Basic Authentication    
    
    for(var i = 0; i < actions.length; ++i) {
        var a = actions[i];
        var okToContinue = a(Math.random() * 100, basicAuthUser);
        if (!okToContinue) 
            break;
    }
}

console.log('');
console.log('without authorization');
doSomething(
    (i,u) => { console.log('Alpha ' + i); return true; },
    (i,u) => { console.log('Beta ' + i); return true; }
    );


Then I think, authentication and authorization role mechanism could be slotted on the first parameter of the array. But how would I pass an extra parameter on the callback? Then an enlightenment from C# came. On C# object, the properties can be accessed even if the method reference is passed to callback. The solution could be written in C# like the following:


Live Code: https://dotnetfiddle.net/tSenYK
using System;
using System.Linq;
                    
public class Program
{
    public static void Main()
    {
        Console.WriteLine("Without authorization:");
        DoSomething(            
            (i,u) => { Console.WriteLine("Alpha " + i); return true; },
            (i,u) => { Console.WriteLine("Beta " + i); return true; }            
            );    
                
        Console.WriteLine();
        Console.WriteLine("Rockstar activity:");
        DoSomething(
            new Authorizer("rockstar").ActionToDo, // just pass the reference of method
            (i,u) => { Console.WriteLine("Alpha " + i); return true; },
            (i,u) => { Console.WriteLine("Beta " + i); return true; }            
            );                        
        
        Console.WriteLine();
        Console.WriteLine("Guest activity:");        
        DoSomething(
            new Authorizer("guest").ActionToDo,
            (i,u) => { Console.WriteLine("Alpha " + i); return true; },
            (i,u) => { Console.WriteLine("Beta " + i); return true; }            
        );    
    }
    
    
    // third-party framework. cannot change the signature
    public static void DoSomething(params Func<int, string, bool>[] actions)
    {
        var basicAuthUser = "davegrohl"; // obtained from browser's Basic Authentication        
        for(int i = 0; i < actions.Length; ++i)
        {
            var a = actions[i];
            bool okToContinue = a(new Random().Next(10), basicAuthUser);
            if (!okToContinue)
                break;
        }
        
    }
    

}



class Authorizer
{
    public string RoleAllowed { get; set; }
    
    public Authorizer(string roleAllowed)
    {
        this.RoleAllowed = roleAllowed;
    }
    
    string GetRole(string basicAuthUser)
    {
        return "rockstar"; // davegrohl's role fetched from database
    }
    
    public bool ActionToDo(int i, string basicAuthUser) 
    {
        // Database operation here.
        // Get the role of basicAuthUser
        var role = this.GetRole(basicAuthUser);
        
        
        
        return role == this.RoleAllowed;    
    }
}

Output:
Without authorization:
Alpha 4
Beta 4

Rockstar activity:
Alpha 4
Beta 4

Guest activity:


So I just need write the equivalent in TypeScript/JavaScript:

Live Code at TypeScriptLang


class Authorizer
{
    roleAllowed : string;
    
    constructor(roleAllowed: string)
    {
        this.roleAllowed = roleAllowed;
    }
    
        
    private getRole(basicAuthUser: string) : string {
        return "rockstar"; // davegrohl's role fetched from database
    }
    
    actionToDo(i: number, basicAuthUser: string) : boolean 
    {
        // Database operation here.
        // Get the role of basicAuthUser
        
        var role = this.getRole(basicAuthUser); // davegrohl's role fetched from database
                
        return role === this.roleAllowed;    
    }
}



    


interface Func {
    (random: number, basicAuthUser: string) : boolean;
}


function doSomething(...actions: Func[]) : void {
    
    var basicAuthUser = "davegrohl"; // obtained from browser's Basic Authentication    
    
    for(var i = 0; i < actions.length; ++i) {
        var a = actions[i];
        var okToContinue = a(Math.random() * 100, basicAuthUser);
        if (!okToContinue) 
            break;
    }
}

console.log('');
console.log('without authorization');
doSomething(
    (i,u) => { console.log('Alpha ' + i); return true; },
    (i,u) => { console.log('Beta ' + i); return true; }
    );
    


console.log('');
console.log("Rockstar activity:");
doSomething(
    new Authorizer("rockstar").actionToDo,
    (i,u) => { console.log("Alpha " + i); return true; },
    (i,u) => { console.log("Beta " + i); return true; }            
    );                        


console.log('');
console.log("Guest activity:");        
doSomething(
    new Authorizer("guest").actionToDo,
    (i,u) => { console.log("Alpha " + i); return true; },
    (i,u) => { console.log("Beta " + i); return true; }            
);    


However, it didn't work. The this object looks like it's not an instance af Authorizer class. The error says:





Then I add a console.log of the this object.
actionToDo(i: number, basicAuthUser: string) : boolean 
    {
        // Database operation here.
        // Get the role of basicAuthUser
        
        console.log(this);
        
        var role = this.getRole(basicAuthUser); // davegrohl's role fetched from database
                
        return role === this.roleAllowed;    
    }


Here's the output, oops it looks like the this object is not carried when passing the reference of the method to callbacks.



To make the long story short, the this object is not included when accessing the reference of the method. Not similar to C#

The solution is to bind the this object before passing the reference of the method to callback.

Correct:

Live Code at TypeScriptLang

class Authorizer
{
    roleAllowed : string;
    
    constructor(roleAllowed: string)
    {
        this.roleAllowed = roleAllowed;
    }
    
    getBindedActionToDo() : Func {
        return this.actionToDo.bind(this);
    }
    
    private getRole(basicAuthUser: string) : string {
        return "rockstar"; // davegrohl's role fetched from database
    }
    
    actionToDo(i: number, basicAuthUser: string) : boolean 
    {
        // Database operation here.
        // Get the role of basicAuthUser
        
        console.log(this);
        
        var role = this.getRole(basicAuthUser); // davegrohl's role fetched from database
                
        return role === this.roleAllowed;    
    }
}



interface Func {
    (random: number, basicAuthUser: string) : boolean;
}


function doSomething(...actions: Func[]) : void {
    
    var basicAuthUser = "davegrohl"; // obtained from browser's Basic Authentication    
    
    for(var i = 0; i < actions.length; ++i) {
        var a = actions[i];
        var okToContinue = a(Math.random() * 100, basicAuthUser);
        if (!okToContinue) 
            break;
    }
}

console.log('');
console.log('without authorization');
doSomething(
    (i,u) => { console.log('Alpha ' + i); return true; },
    (i,u) => { console.log('Beta ' + i); return true; }
    );
    


console.log('');
console.log("Rockstar activity:");
doSomething(
    new Authorizer("rockstar").getBindedActionToDo(),
    (i,u) => { console.log("Alpha " + i); return true; },
    (i,u) => { console.log("Beta " + i); return true; }            
    );                        


console.log('');
console.log("Guest activity:");        
doSomething(
    new Authorizer("guest").getBindedActionToDo(),
    (i,u) => { console.log("Alpha " + i); return true; },
    (i,u) => { console.log("Beta " + i); return true; }            
);    


Output:





Happy Coding!

No comments:

Post a Comment