Archive for May, 2011

MVC – Getting User Roles From Multiple Sources (register and resolve arrays of dependencis using the fluent api)

(This post also appear in our team blog DotScrapBook)

In the last post I talked about encapsulating the authorisation and user credentials (i.e IPrincipal) logic in a separate, re-usable DLL (known as AuthServices).

Part 1 of that post discussed the DLL contents in detail. Part 2 focused upon using that DLL in an MVC application.

This post follows up on “Part 2″ specifically adding a user’s roles to the UserData objet prior to serialisation, on-route to becoming the auth cookie.

The code sample that I gave for using the AuthServices DLL is repeated below:

_authenticationService.Authenticate(userNameString, password);
if (_authenticationService.Result.Authenticated)
{
       _currentUserData = _authenticationService.Result.CurrentUserData;
       _currentUserData.Roles = GetRolesForUser(userName);//THE SUBJECT OF THIS POST
       _cookieManager.CreateUserCookie(userName, _currentUserData);
}

This post explores in more detail what lies behind the GetRolesForUser(string userName) method and how this can utilise more than one provider of roles.

Why bother?

The organisation in which I work is big. We have thousands of staff and many thousands more students. Applications written for an organisation of this size usually need to access different sources of data to provide a user’s roles relevant to the specific application. In projects like this you can’t easily use the standard RoleProvider stuff in a config file.

The Answer – Multiple Role Providers

To implement multiple roles I inject (in an IOC stylee) into my login service the interface IRolesMgr. IRolesMgr exposes the following:

public interface IRolesMgr
    {
        List GetAllRolesForUser(string userName);
    }

The interesting part is in the constructor of the implementation.

public class RolesMgr: IRolesMgr
    {
        private IRoleMgrComponent[] _roleManagers;

        public RolesMgr(IRoleMgrComponent[] roleManagers)
        {
            _roleManagers = roleManagers;
        }

        public List GetAllRolesForUser(string userName)
        {
            List roles = new List();
            foreach (var mgr in _roleManagers)
            {
                roles.AddRange(mgr.GetRolesForUser(userName));
            }
        }
    }

As can be seen the constructor has a dependency on an array of IRoleMgrComponents each of which will contain the access logic needed to identify a user’s roles from a different source.
Each IRoleMgrComponent implements the following:

public interface IRoleMgrComponent
    {
        List GetRolesForUser(string userName);
    }

That’s all very good, but the trick is getting the IOC container, in this case, Castle Windsor, to instantiate the appropriate implementations of IRoleMgrComponent in the roleManagers array.

This is done in the Component Registrar class used to register components to Castle by using the “ServiceOveride” method (more details of this can be seen on Mike Hadlow’s post – which was the original inspiration for the castle registration code – thanks Mike!). In effect this allows you to register and resolve arrays of dependencis using the fluent api.

container.Register(
                Component.For()
                    .ImplementedBy()
                    .ServiceOverrides(
                        ServiceOverride.ForKey("roleManagers").Eq(
                            "HumsAuthDbRoleManager",
                            "StudentAuthRoleMgr"
                        )
                    ),
                AllTypes
                    .FromAssemblyNamed("ApplicationServices")
                    .BasedOn()
                        .WithService.FromInterface(typeof(IRoleMgrComponent))
                        .Configure(c => c.Named(c.Implementation.Name))
                );

Once you have overriden this specific component you can continue to register all the other components in that assembly in the normal way, say:
container.Register(
                AllTypes.Pick()
                .FromAssemblyNamed("ApplicationServices")
                .WithService.FirstInterface());

Conclusion

Implementing RolesMgr(IRoleMgrComponent[]) in conjunction with the AuthServices assembly talked about in the last post, means that for any given new project I will only have to write minimal code yet benefit from User Credential information that:
a) Has the execution logic encapsulated in an (extensible) AuthServices library
b) Can uitilise dependency injection to configure how where and how the roles data comes from in the credential information.

Creating a resusable Authorisation/ User Principal library for MVC projects

Having been using SharpArchitecture/ MVC for a number of recent projects, I’ve finally made the effort to extract some common service features into a separate reusable project (with unit tests of course!). This is very much inkeeping with DRY principles and should shorten the time to (re)-implement common processes in new projects.

The project – AuthUserServices provides several useful interface/implementations that can be used in any (MVC/ or Non-MVC) .NET project. It aint rocket science but provides a number of neat and unit tested resources around Authorisation and GenericPrinicpal.

Part 1 – The AuthServices DLL

The IAuthenticationService

This interface exposes two methods:

void Authenticate(string userName, string password)// Authenticates against a user directory
void FindUser(string userName)// Ascertains whether a user is contained in a user directory

And an AuthenticationResult propterty…

AuthenticationResult Result { get; }

… which is a struct containing result information supplied from “Authenticate” or “FindUser”.

The current implementation(s) of this interface are for LDAP and CAS. Our MVC projects implement dependency injection using Castle Windsor. It is therefore fairly trivial to choose the IAutheniticationService implementation in the component register (specific examples to follow).

AuthenticationResult

The AuthenticationResult struct exposes the following properties:

public bool Authenticated {get;}// bool describing whether a user is authenticated
public string ConnectionMessage {get;}// any message that comes from connecting with/ or communicating with a user directory
public string SearchString {get;} // the string used to search the user directory, i.e. the username
public IUserData UserData {get;}// An object containing user details gleaned from searching the user directory

IUserData

IUserData exposes the following properties:

string Email { get; }
string FName { get; }
string SomeOtherProperty { get; }
List Roles { get; set; }
string SName { get; }
string ToString();

In the AuthServices project there is only one implementation of this interface (cunningly) called UserData. I implemented this as an interface so that it is possible to provide other implementations in the future. For example the

ToString()

implementation is used to serialise the object for use in a cookie. It is possible that a developer may with to implement a different serialisation algorithm.

UserData

UserData object data is built from a directory search or authentication attempt. It has two constuctors.
The first is used to create the UserData from properties gleaned from the user directory

public UserData(string email, string fName, string sName, string someOtherProperty)

The second is used to deserialise a userDataString contained in the UserData property in an auth cookie (in effect the reverse of the

ToString()

implementation.

public UserData(string userDataString)

The UserData class encapsulates the logic involved in managing user data across the Authorisation –> Cookie serialisation/deserialisation –> Building the User Principal process.

ICurrentUserCookieMgr

The ICurrentUserCookieMgr interface doesn’t expose a huge amount of functionality.

void CreateUserCookie(string userName, IUserData userData);
bool HttpOnly { get; set; }
void RemoveUserCookie();

The current implementation implements FormsAuthentication. It is the only part of the service that doesn’t really have adequate unit tests due to it’s (necessary) reliance upon HttpContext and the Response therein. Still the implemenation encapsulates this functionaly and can easily be stubbed/ mocked in application unit tests.

IHumsPrincipal

The final item of the service is the IHumsPrincipal interface. This exposes the the UserData and UserName properties as well as the IIdentity and property and IsInRole method available in the standard MS IPrincipal interface.

IIdentity Identity { get; }
bool IsInRole(string role);
IUserData UserData { get; }
string UserName { get; }

Part 2 – Example Uses in an (MVC) app

Using AuthServices in the Authorisation logic of an application

I use the AuthServices implementation in a LoginService within an ApplicationServices project.
The LoginService has the following private members

private ICurrentUserMgr _userManager;
private IAuthenticationService _authenticationService;
private ICurrentUserCookieMgr _cookieManager;

You can then manage the whole cookie building/ roles setting process with only a few lines of code

_authenticationService.Authenticate(userNameString, password);
if (_authenticationService.Result.Authenticated)
{
       _currentUserData = _authenticationService.Result.CurrentUserData;
       _currentUserData.Roles = GetRolesForUser(userName);//Some logic to determine the roles (SEE NEXT BLOG POST ABOUT THIS)
       _cookieManager.CreateUserCookie(userName, _currentUserData);
}

Accessing User Info

Post authentication, developeres can use the implementations of IHumsPrincipal and IUserData in the Application_OnAuthenticateRequest in the Global.asax file of their apps to expose UserData properties to the application. e.g.

FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
string userId = id.Ticket.Name;
IUserData uData = new UserData(id.Ticket.UserData);
HttpContext.Current.User = new HumsPrincipal(id.Name, uData, id);

You could then expose the IHumsPrincipal members in the application (such as a controller or application by casting the CurrentPrincipal to the IHumsPrincipal as follows:

IHumsPrincipal p = Thread.CurrentPrincipal as IHumsPrincipal;

Better still would be to encapsulate this cast behind an “ICurrentUserManager” interface to remove the dependency within controller/app service methods upon the Thread and therefore make such methods easy to unit test. For example:

public class CurrentUserMgr: ICurrentUserMgr
    {
        IHumsPrincipal _principal
        {
            get
            {
                try
                {
                     return Thread.CurrentPrincipal as HumsPrincipal;
                }
                catch
                {
                    return null;
                }
            }
        }

        public CurrentUserMgr()
        {

        }

        public bool IsStudent()
        {
            if (_principal == null)
                return false;
            else return _principal.IsInRole("student");
        }

        public bool IsSysAdmin()
        {
            if (_principal == null)
                return false;
            else return _principal.IsInRole("sysadmin");
        }

        public bool IsLoggedIn()
        {
            try
            {
                return _principal.Identity.IsAuthenticated;
            }
            catch
            {
                return false;
            }
        }
    }

Castle Wiring of AuthServices DLL in the Component Register

Finally, in an MVC, project you have to register AuthServices library in an MVC project.

container.Register(
     AllTypes.Pick()
     .FromAssemblyNamed("Humanities.Reusable.Components.AuthUserServices")
     .WithService.FirstInterface());

MVC/ NHibernate vs WebForms/ Nettiers

Not so long ago my boss asked me to write a report to analyse the use of an MVC/NHibernate stack in a comparison to a Webforms/Nettiers stack. This is a version of the report I wrote to propose that we adopt MVC/NHibernate as standard, across the team, in future developments. I made an attempt at impartiality but failed miserabley (it’s hard to be impartial when I am clearly partial!)

I thought I’d share the contents of this report for more general perusal. The report is my take on why I feel that MVC/NHibernate/SharpArchictecture is a “better” platform for devlopment than good ol’ webforms. The report is aimed at developments tailored towards humanities/academic research projects but with a little tailoring could be relevant more widely. There are likely to be errors or misinterpretations in my analysis so any thoughts/ corrections/ different opinions would be ace.

1. Overview

MVC and Web Forms are the two development frameworks offered by Microsoft. Both share the core ASP.NET platform features including authentication, authorization, configuration, compilation and deployment. Both are developed using any of the core languages such as C#. Both are officially Microsoft supported and developed frameworks.

This report compares using MVC and NHibernate to Web Forms and Nettiers. Firstly it compares the web frameworks and then goes on to look at the data access strategies.

2. Framework Comparison

2.1.1. Benefits of MVC

ASP.MVC was written in response to criticisms of Web Forms in relation to other development platforms. Whilst Web Forms gives developers a Rapid Application Development framework and was written to ‘mimic’ the development methods used in Win Forms, it made it difficult to implement certain practices implemented in other platforms and seen as fundamental to good development.

2.1.1.1.Testable

MVC allows automated unit and integration tests to be written. This has a number of distinct advantages.

1)     Greatly reduces the need for manual testing; the upfront cost of creating unit tests is paid back downstream by significantly reducing the amount of manual testing.

2)     Can be run throughout the development; this is invaluable as it allows the developer to be sure they aren’t introducing breaking changes to the code base.

3)     Gives the developer the confidence to implement changes to the code base as tests will show any bugs.

4)     Leads to a much more rigorous code base as the code has been testing numerous times throughout the development rather than that afforded by sporadic manual testing.

5)     Each test is a FUNCTIONAL requirement encapsulated in code. If there is a requirement that only users with role X can perform activity Y, the test is evidence that this requirement is being met. This becomes invaluable should a developer return to a code base after a period of time as the requirements are directly viewable by running the tests.

A developer can write as little or as much test code based upon the requirements of the project. If a project is exposed to changes in specification then having tests in place significantly speeds up the implementation of changes. For small lower impact developments, automated testing could be scaled back to cover mainly Create/ Update and Delete scenarios rather than Read scenarios.

Writing tests is an art in its own right and one that a developer becomes better at over time. Without writing any test code an experienced developer could create a project using MVC or Web Forms in a similar time. Writing tests would add perhaps 20% to development time, but reduce any time for system testing considerably.

Having tests in place also considerably reduces the time taken to implement changes and if a change is significant may actually allow that change to take place where it would otherwise be seen as either too risky or too time consuming.

2.1.1.2.Adheres to principles of good software design

ASP.MVC follows certain well trodden principles of designing good and maintainable software, such as:

  • “Don’t Repeat Yourself (DRY)
  • “Separation of Concerns” (SoC)

Separation of concerns (not mixing UI logic with underlying behaviour) is a fundamental principle that exists to help developers deal with complexity. Mixing different responsibilities within the same object or files (like putting most of your logic in code behind files such as handling edit, cancel and edit post back events) invites maintenance problems and makes that code difficult to change and difficult to read. MVC prescribes where certain types of objects should be written making the resulting code base much cleaner and easier to follow.

2.1.1.3.MVC is prescriptive

Because MVC follows good software design principles it is prescriptive about where certain types of code should be developed. This is useful in a team environment in that it standardises project structures much more than in Web Forms. Once developers are familiar with an MVC project structure it becomes obvious where to look for certain types of code. This will make it easier to work on other developer’s code; a) because of the prescription and b) because running the unit tests will ensure that additional developers aren’t breaking existing functionality. It also helps developer make decisions about where certain types of code should be developed and how code should be separated to allow for easier maintenance. The prescriptive nature an MVC project has also proved useful by allowing the developers in this team to talk about issues and problems they face without having to know the detailed implementation of the project.

2.1.1.4.MVC enables easy integration with JavaScript frameworks

Since the launch of Google Suggest in 2004 there has been a sea change in client side web development. This change has been about the implementation of AJAX functionality. AJAX (shorthand for Asynchronous JavaScript and XML) is a group of related techniques used on the client side, to create rich interactive and responsive web applications. With AJAX, web applications can retrieve data from the server asynchronously in the background without interfering with the display and behaviour of the existing page. AJAX uses a combination of HTML and CSS to mark up and style information. The DOM is accessed with JavaScript to dynamically display, and to allow the user to interact with, the information presented.

This type of functionality used to be a fairly difficult to programme due to the varying implementation of standards compliance across browsers. This has now become less of an issue a) due to better standards compliance and b) more significantly due to the development of JavaScript frameworks such as JQuery that allow you write code that works on every browser.

The ‘V’ in MVC, stands for ‘View’ and deals only with the front end delivery of HTML and JavaScript. MVC allows the easy integration with JS frameworks such as JQuery and greatly simplifies the creation of rich client side experiences using such frameworks. (Note JQuery now ships as standard with ASP.NET 2010 and Microsoft are now actively and perhaps belatedly contributing to the JQuery code base).

This simplified integration makes it easier to develop user experiences that match the performance users expect on a modern web site. This is a subtle point but is one that I believe we shouldn’t ignore. Our clients will increasingly develop implicit expectations about how they interact with websites based on their use of the wider internet. There are now very few commercial sites that don’t implement AJAX functionality and the slick responsive interface that it affords. Not looking at developing rich front ends, will increasingly make our output compare disfavour-ably with our clients expectations.

2.1.1.5.Consumption of the web is changing (fast)

Morgan Stanley predicts that more people will access the web using mobile devices than using fixed internet / desktop by the middle of 2013 (i.e. within 2 1/2 years).

(http://gigaom.files.wordpress.com/2010/04/mobile-chart2.png)

What’s this got to do with MVC? I believe that MVC is a better platform to deliver content to a variety of devices due to its easier implementation of JS libraries and the ease with which it allows the developer to control the contents of the HTTP response (explained in detail below).

1)     Easier implementation of AJAX – a 3G connection on a mobile device doesn’t currently have bandwidth comparable with broadband. AJAX greatly improves the performance of pages over slower connections as you only return the required data (which is formatted by the browser) rather than the data and the entire page.

2)     MVC gives the developer much greater control over the data returned from a request. This is best seen with actual code examples but within two lines of code a developer can return an entire page (with Master Page etc), JSON (JavaScript Object Notation) which is the data format of choice in AJAX requests, XML or any other format required.

Code sample:

//Try doing this in webforms

If (IsJson)

{

return this.JSON(model);

}

Else if (IsXml)

{

return new XMLResult(model);

}

else

{

return View(model)l;

}

This level of control is much more difficult to achieve using Web Forms. Having tight control over what is returned in the HTTP response simplifies development of content for a range of devices such as for traditional browsers or Smart phone apps, or for applications that sit within other platforms such as SharePoint.

2.1.1.6.MVC uses RESTFul URL’s

MVC gives the developer full control over the URL. With WebForms the URL matches the underlying file structure of the code. With MVC you have what’s known as a ‘Route Registrar’ which is a file that matches URL’s to the resources that will deal with the request.

There are two distinct advantages of having full control over the URL:

i)      If a project has a requirement for human readable URL’s or search engine optimisation then URL control is a big advantage. Consider the following:

www.mywebapplication.com/users/alex.hardman  (i.e. Retrieve user with name alex.hardman)

as opposed to

www.mywebapplication/users/getuser.aspx (name passed in session) or www.mywebapplication/users/getuser.aspx?username =alex.hardman

The first URL (via MVC) is more human readable and search engine friendly (e.g. all things equal would be ranked higher by all the main search engines).

ii)     The second advantage with having full control over url’s relates to the current drive in Digital Humanities research for data preservation. Without going into too much detail one aspect of data preservation involves the creation of “permanent url’s” and “unique resource identifiers” (uri’s).  The standard use of session variables and view state in web forms means that the same url can refer to a multiple items of data (where query parameters are input via view state). The result is that data accessed in this manor data doesn’t have a unique resource identifier (i.e. a unique address on the web). This means data items can’t be referenced as it doesn’t have a unique address on the web and prevents its inclusion in Linked Data initiatives and other research initiatives requiring a URI.

The combination of URI and the ease at which a system can return computer readable data formats such as XML (see above) puts a developer at an advantage when it comes to best practice in Humanities data curation and preservation techniques. At the recent Oxford ‘Research databases in the humanities – where next?’ workshop there was a strong emphasis upon curating Humanities research data with uri’s, and computer readable data formats. This is something that we could ‘sell’ as distinctly advantageous to our research clients.

2.1.2.    Benefits of Web Forms

ASP.NET Web Forms is the more mature of the two methodologies (from a Microsoft perspective) and is currently the more widely adopted.

In terms of development, there are two main things to consider when comparing Web Forms with MVC:

1)     Web Forms has a larger control toolbox (although how many of these a developer uses in their projects is a discussion point in its own right). As a result, it allows developers to develop without needing to be particularly knowledgeable about HTML, CSS and JavaScript. This is flip side of the ‘MVC gives you greater control over the output’ argument. It does but Web Forms automatically renders much of the raw html such as tables, buttons and input boxes (however an MVC enthusiast would respond to this by pointing out that rendering things like tables, buttons and input boxes isn’t a particularly difficult, nor time consuming process. They would also point out that Web Forms developers render tables and other html nodes for general page layout and that such activity is a core competency of the job).

2)     ASP.NET Web Forms was originally designed to mimic the design methods used in the creation of Win Forms and give the developer a Rapid Application Development tool by shielding the developer from the HTTP response and request architecture.  It is likely that this shielding formed part of Microsoft’s strategy for converting desktop (used to winforms) more easily into Web developers (using the analagous webforms) as web development became the dominant development requirement.

In theory this means that a developer can jump straight in by dragging controls onto a page, handling their events and setting values for background colour and ‘bobs your uncle’ – database driven website. In reality, for all but the simplest sites, significant thought is needed, even with Web Forms, when developing both front and backend code.

Desktop development, however, is stateful whereas the Web is inherently stateless. The Web Forms model essentially abstracted a number of features to provide a simulated stateful model for Web developers (aka View State). Despite being one of the development world’s biggest criticisms in relation to Web Forms, there are times when it is useful to provide the illusion that a web application is aware of what a user has been doing. ‘Wizard’ functionality, where data input is spread over several pages before it is committed, is one such example. This ‘stateful’ illusion is provided out of the box with Web Forms unlike with MVC. However in situations where the illusion of state is required it isn’t a difficult exercise to implement in MVC.

In terms of adopting MVC is it worth noting the following. Just as with learning Web Forms, there is a learning curve to MVC. It has a different response/ request pipeline and different ways of structuring application code (no code behind files for example). There is also a knack of writing code that is testable and getting used to the rigour of writing testable code requires practice. MVC requires that the developer gets a rudimentary understanding some new C# code libraries.  These include Mocking frameworks such as Rhino Mocks (which can be used in testing scenarios), Html helpers (used to render html) and the core MVC libraries that deal with ModelState and Routing. There are also concepts like dependency injection/inversion of control to get familiary with.

3. Data Access Framework Comparison

In the past the IT Research Development team used the Nettiers template and CodeSmith to generate data access code. This generates the data access code and if desired some rudimentary data access controls, one for each table. The code generated is robust and affords the developer standard CRUD queries. The developer can extend the range of queries by including stored procedures that can be called using the data access layer.

Prior to generating the data access there are some minor configuration settings to set up, including the default namespace and the connection string. CodeSmith generates the code replacing any existing code files in that namespace.

In the current IBusiness application, data access is handled by NHibernate. NHibernate is a C# port of the popular Java object relational mapping (ORM) tool Hibernate. Its primary features are to map .NET classes to database tables and to provide data query and retrieval facilities. In terms of data query and retrieval it performs a similar role as Nettiers, affording the developer standard CRUD queries.

You don’t need to generate code with NHibernate as you can use its auto mapping facility straight out of the box. NHibernate is however, much more flexible that Nettiers in that you can fully control how values in tables are associated with properties in C# by creating Mapping files. To use NHibernate with automapping requires only that you provide a connection string and that the C# classes to be mapped inherit from an “Entity” class and have the same names as the table columns.

As well as the standard CRUD, you can write complex queries in a few lines of C# using NHiberate criteria.

NHibernate implements the repository pattern. A repository can be understood as a mechanism for encapsulating the storage, retrieval, and searching of objects. The repositories are implemented via repository interfaces, meaning that the code that uses the repositories it amenable to automated unit testing. The repositories themselves are amenable to automated integration tests if that is applicable to the project.

4 Additional Points

(From My Colleague Andrew Jerrison)
SharpArchitecture adds a lot of extras that make MVC development a lot easier and quicker for an experienced developer, plus it automatically generates a very good initial solution template which speeds up development initially.

I feel that MVC is a much better tool from a software engineering point of view, for all the reasons Alex mentions above. It forces the developer to consider the structure of the data and site in a much more rigorous manner than Webforms. This means another developer can come to a project without any prior knowledge and figure out what it is doing quickly. However for the same reasons those not used to an OO approach might find it difficult to get a good understanding of the framework without first learning proper software engineering techniques, thus increasing their learning curve. Webforms was of course Microsoft’s solution to this by making it easier to get simple sites off the ground – however the Webforms approach would not be considered best practice in software engineering courses at Uni.

Webforms is by its nature a rapid development tool. This means that it is quick to get a few pages off the ground. However larger projects quickly become more muddled and less maintainable, especially when less experienced developers are working on the project. MVC has a longer time to set up initial project, but in more complex projects quickly becomes a lot more productive and manageable than Webforms. I would say that Webforms is probably suitable for small projects of approximately 3 weeks or less in duration, and that will not require much maintenance, whereas MVC is better for bigger projects and those that need constant support.

Unit testing is a double edged sword; it certainly increases initial development time, but can greatly reduce testing time and also the time to make and test any future changes and additional features to the project. It can lead you into a false sense of security though, as it is possible to think that just because a test passes then everything is OK, but of course you have to make sure that the test itself covers all scenarios in the first place (Test Driven Design, i.e. writing tests first rather than guiltily adding tests at the end ;) – Alex). So it is important not to rely solely on programmed unit tests.

Fluent nHibernate is the most flexible object-relational mapping tool I have used so far in my career.

I think that it is also important to mention that the Javascript library jQuery is important to producing good MVC sites. We don’t really use this directly for Webforms as it is more difficult to latch into the HTML generated by the Webforms controls – and Webforms has its own internal AJAX functionality that developers don’t see (and can’t change). jQuery is a very terse syntax which can be a bit obscure at times (similar to Perl), but despite the initial thought of ‘ugh’ when you see it, it is not hard to learn and it is actually much nicer to use than JavaScript itself.

MVC makes it very easy to write RESTful web services that are part of the same application. In Webforms services are not RESTful and are usually a separate application, making testing and maintenance more difficult and time consuming. RESTful interfaces for services are possibly even more useful for our team’s purposes than for the SEO reasons mentioned above as it makes inter-application communication much simpler and easier to implement.


Topics/ Categories

My (Live) Twitter stream

Archives

My Recent Bookmarks

 

May 2011
M T W T F S S
« Aug   Sep »
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Follow

Get every new post delivered to your Inbox.