Showing posts with label Booky. Show all posts
Showing posts with label Booky. Show all posts

Sunday, November 3, 2013

Building Booky SPA - Building a RESTFul API for Booky using ASP.NET Web API

REST stands for Representational State Transfer.
  • REST is an architectural style that runs over HTTP. 
  • REST is used for exchanging data over HTTP. 
  • REST is simple and stateless
  • REST is not an official protocol or standard.

The core idea behind the creation of REST based services is that we should treat our distributed services as a resource and we should be able to use simple HTTP verbs to perform various operations on that resource.


In booky we have the following resources:
  • users
  • bookmarks
  • boards - not implemented
  • tags - not implemented

Each resource is available from a URI. booky REST URI's is created with the following URI http://domain.tld/rest/. Each booky resource will take on a URI such as:
  • users - http://domain.tld/rest/user
  • bookmarks - http://domain.tld/rest/bookmark
  • boards - http://domain.tld/rest/board
  • tags - http://domain.tld/rest/tag

In cases where REST-ful style doesn't fit with the application requirements, a REST-hybrid URI design can be used, such as with server side paging and application design

In booky a REST-hybrid URI http://domain.tld/actions is created so that there is an area for REST only URI's and REST-hybrid URI's. 
  • http://domain.tld/rest/ -  booky common REST URI's
  • http://domain.tld/actions/ - booky common REST-hybrid URI's

Example - booky common REST-hybrid URI's:
  • Server side paged bookmarks - http://domain.tld/actions/bookmarkaction/GetPagedBookmarks/
  • Updating a single bookmark property/attribute that indicates whether a bookmark has been read. - http://domain.tld/actions/bookmarkaction/toggleread/

HTTP Verbs

On the server each server side operation (Create, Read, Update, DELETE) maps to an HTTP verb:
  • GET  
  • POST 
  • PUT 
  • DELETE 
GET

Gets a listing of resources. When no resource id is specified then all resources are returned. When a resource id is specified then a single resource is returned. 

Example: 

  • GET http://domain.tld/rest/bookmark/1 - single resource 
  • GET http://domain.tld/rest/bookmark - listing of resources

POST

Creates a new resource. On successful creation, return HTTP status 201, returning a Location header with a link to the newly-created resource with the 201 HTTP status.When the resource can't be found return a 404 not found HTTP Response Code. POST is neither safe nor idempotent. It is therefore recommended for non-idempotent resource requests. Making two identical POST requests will most-likely result in two resources containing the same information.

Example: 
  • POST http://domain.tld/rest/bookmark

PUT

Updates an existing resource. On successful update, return HTTP status 200, returning a Location header with a link to the updated resource with the 201 HTTP status.When the resource can't be found return a 404 not found HTTP Response Code.

Example: 

  • PUT http://domain.tld/rest/bookmark/1 

DELETE

- Deletes an existing resource.  On successful delete, return HTTP status 200, and in the response body the representation of the deleted item.

Example: 

  • DELETE http://domain.tld/rest/bookmark/1

HTTP Response Codes for use in response headers

The listing below outlines the typical HTTP Response Code to use for each server operation/HTTP verb combo:
  • GET - 200 OK
  • POST -  201 Created 
  • PUT - 200 OK
  • DELETE - 200 OK
A standard that I follow for PUT/DELETE/GET is when the resource can't be found is to return a 404 not found HTTP Response Code.

A listing HTTP Response Codes for reference:
  • 200 OK: Success
  • 201 Created - Used on POST request when creating a new resource.
  • 304 Not Modified: no new data to return.
  • 400 Bad Request: Invalid Request.
  • 401 Unauthorized: Authentication.
  • 403 Forbidden: Authorization
  • 404 Not Found – entity does not exist.
  • 406 Not Acceptable – bad params.
  • 409 Conflict - For POST / PUT requests if the resource already exists.
  • 500 Internal Server Error
  • 503 Service Unavailable

Additional resources that provide expanded HTTP Response Codes information:

REST was introduced and defined by Roy Fielding in 2000.


This completes an overview on REST basics. ASP.NET Web API is up next followed by booky ASP.NET Web API Design.

What is ASP.NET Web API?

ASP.NET Web API is a framework for building and consuming HTTP services that can reach a broad range of clients including browsers, phones and tablets. You can use XML or JSON or something else with your API. JSON is nice for mobile apps with slow connections, for example. You can call an API from jQuery and better utilize the client's machine and browser.

Why use ASP.NET Web API?

ASP.NET Web API provides the following benefits: 

  • From a focus standpoint MS Web API doesn't try to be all things; Web API is designed and focuses on HTTP
  • Easier to secure than other ASP.NET web service options (WCF, ASMX)
  • Has close integration of MS Web API with ASP.NET MVC; Uses the controller (Controller class) model found in ASP.NET MVC which developers may already be familiar with.

booky ASP.NET API Design

booky contains the following ASP.NET Web API controllers for each REST resource.

booky Controller Classes for Common REST URI's:

  • Bookmark resource - BookmarkController Class - Get Bookmark 
  • User resource - UserController Class - Update User (PUT)

booky Controller Classes for Common REST-hybird URI's:

  • User Action Controller - Authenticate user and Register user
  • Bookmark Action Controller - Get Paged Bookmarks, Get Bookmark By Url, Toggle Favorite, Toggle Read.

Bonus: CORS

CORS - Cross Origin Resource Sharing

Implementation of CORS via Web.config

<system.webServer>
 <httpProtocol>
  <customHeaders>
    <add name="Access-Control-Allow-Origin" value="*" />
    <add name="Access-Control-Allow-Headers" value="Content-Type" />
    <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
  </customHeaders>
 </httpProtocol>
</system.webServer>


<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <remove name="WebDAVModule"/>
  </modules>
  <handlers>
    <remove name="WebDAV" />
  </handlers>
</system.webServer>

You can also implement CORS via code by creating a Message Handler:

In Global.asax add the CorsHandler.

GlobalConfiguration.Configuration.MessageHandlers.Add(new CorsHandler());

 public class CorsHandler : DelegatingHandler
    {
        const string Origin = "Origin";
        const string AccessControlRequestMethod = "Access-Control-Request-Method";
        const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
        const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
        const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
        const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";

        protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            bool isCorsRequest = request.Headers.Contains(Origin);
            bool isPreflightRequest = request.Method == HttpMethod.Options;
            if (isCorsRequest)
            {
                if (isPreflightRequest)
                {
                    return Task.Factory.StartNew(() =>
                    {
                        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
                        response.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());

                        string accessControlRequestMethod = request.Headers.GetValues(AccessControlRequestMethod).FirstOrDefault();
                        if (accessControlRequestMethod != null)
                        {
                            response.Headers.Add(AccessControlAllowMethods, accessControlRequestMethod);
                        }

                        string requestedHeaders = string.Join(", ", request.Headers.GetValues(AccessControlRequestHeaders));
                        if (!string.IsNullOrEmpty(requestedHeaders))
                        {
                            response.Headers.Add(AccessControlAllowHeaders, requestedHeaders);
                        }

                        return response;
                    }, cancellationToken);
                }
                else
                {
                    return base.SendAsync(request, cancellationToken).ContinueWith(t =>
                    {
                        HttpResponseMessage resp = t.Result;
                        resp.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());
                        return resp;
                    });
                }
            }
            else
            {
                return base.SendAsync(request, cancellationToken);
            }
        }
    }




Sunday, October 27, 2013

Building Booky SPA - A quick tour of AngularJS and Booky Angular Design


In this post I give a quick tour of AngularJS concepts and touch on some of the AngularJS Design for Booky.

What is AngularJS?

"AngularJS is a new, powerful, client-side technology that provides a way of accomplishing really powerful things in a way that embraces and extends HTML, CSS and JavaScript, while shoring up some of its glaring deficiencies. It is what HTML would have been, had it been built for dynamic content." - Reference

Two way Data Binding

Besides being the coolest feature to come out of AngularJS, two way data binding provides us with:

  • Any changes to the view update the model
  • Any changes to the model update the view


Time saving benefits:

  • Saves us from writing boiler plate code that is typically written to traverse, manipulate and listing to the DOM.


Two Way Data Binding Plunker Sample

Model

In AngularJS, the model contains plain old javascript objects such as string, booleans, arrays that represent the objects viewable in the view and those which are manipulated in AngularJS controller. The model is contained in AngularJS scope.

View

In AngularJS, the view is the HTML that is rendered after AngularJS has parsed and compiled the HTML to include rendered markup and bindings.

Controller

In AngularJS, a Controller is the glue between an the view and model. A controller contains the business logic for the view. For example, if you had search form with a search box and search button, you could have a function in your controller that handles executing a call to a search service each time the search button is clicked.

There is one cardinal rule with an AngularJS Controller:

- The controller does not manipulate the DOM. Directives should be created to manipulate the DOM.

Controller Plunker Sample

Filters

In AngularJS, Filters allow us to format data in some way. For example, a built in Filter from AngularJS called uppercase would make all characters UPPERCASE. Filters cleanup our HTML code by removing the formatting from the view. A filter is created in AngularJS Module much like the other AngularJS concepts such as controllers, directives, etc.

Filter Plunker Sample

Boolean Yes/No Custom Filter Plunker Sample

Directives

AngularJS Directives are the core functions that get run when the DOM is compiled by the AngularJS compiler.

In AngularJS, Directives allow us to build reusable UI components by creating new directives that encapsulate the different application UI aspects such as logo, navigation, etc. With this concept each Directive encapsulate a single UI component, and provide simple DOM manipulation that modify or even create totally new behavior in HTML.

Custom Directive Plunker Sample

Modules

In AngularJS, Modules are the main way to define our AngularJS app. Each application can have multiple modules.

There are different modules in an app such as:
main app module
controller module
directive module
filter module
service module

We logically organize each area of the app into a module. For example, we create a module for controllers and all individual controllers go in the controller module.

Booky AngularJS Design

  • booky has the following modules:
  • booky app module - booky main ng-app 
  • booky filter module - booky.filters - contains all Angular filters
  • book directive module - booky.directives - contains all Angular directives
  • booky controller module - booky.controllers - contains all Angular controllers
  • booky authentication factory - authservice - Angular factory for communicating
  • booky restservice - Angular factory for communicating with booky WebAPI REST service
  • booky bkyviewmodel - Angular factory that is a layer of abstraction between the booky viewmodel and booky server side MS Web API REST service. All booky controllers work with the bky viewmodel factory rather than directly communicating with the booky restservice.
  • booky scrnur rest factory - scrnurservice - Screen Shot service for communicating with Scrnur

Thursday, August 22, 2013

Building Booky SPA - Building Booky Entity Framework Code First

The main steps to building Entity Framework Code First is to:

  1. Create POCO model classes 
  2. Create a derived type that inherits from DbContext which includes DbSet properties 

(System.Data.Entity.DbSet<TEntity>) for each POCO model class, and optionally override the methods we want to customize to create our own DbContext implementation. We will use the domain model from booky. Booky has the concept of Bookmarks, Tags, Users and Boards.

Creating a Domain Model

Booky requires the following relationships based on how the data is best modeled. Booky has the concept of a user where each User has one to many Bookmarks. This is written as One User to many Bookmarks (one to many). Booky has the concepts of Boards which are like groups that allow you to group a list of Bookmarks that are associated with a Board. Booky has one Board to many Bookmarks which is written as One Board to many Bookmarks (one to many). Booky has the concept of each Bookmark being assigned many Tags which are shared among all Bookmarks. Booky has a Many to Many relationship between Tags and Bookmarks (Many to Many).

Booky Code First Class Diagram



Let's review a code example of the booky domain model.

Bookmark POCO Sample:

 public class Bookmark  
 {  
   #region Fields  
   [Key]  
   public int BookmarkId { get; set; }  
   [Required, MaxLength(128)]  
    public string Title { get; set; }  
   [Required, MaxLength(512)]  
   public string Url { get; set; }  
   public DateTime DtCreated { get; set; }  
   public bool Read { get; set; }  
   public bool Starred { get; set; }  
   [Required, MaxLength(128)]  
   public string Description { get; set; }  
   public int UserId { get; set; }  
   public User User { get; set; }  
   public virtual ICollection<Tag> Tags { get; set; }  
   public Board Board { get; set; }  
   #endregion  
   #region ctor  
   public Bookmark()  
   {  
     Tags = new HashSet<Tag>();  
   }  
   #endregion  
 }  

User POCO Sample:

 public class User  
 {  
   [Key]  
   public int UserId { get; set; }  
   [Required, MaxLength(16)]  
   public string Username { get; set; }  
   [Required]  
   public string PasswordHash { get; set; }  
   [Required, MaxLength(256)]  
   public string Email { get; set; }  
   public DateTime DtCreated { get; set; }  
   public DateTime? DtLastUpdated { get; set; }  
   public virtual ICollection<Bookmark> Bookmarks { get; set; }  
   public User()  
   {   
     Bookmarks = new HashSet<Bookmark>();      
   }  
 }  

Board POCO Sample:

 public class Board  
 {  
   [Key]  
   public int BoardId { get; set; }  
   [Required, MaxLength(128)]  
   public string Title { get; set; }  
   public virtual ICollection<Bookmark> Bookmarks { get; set; }  
   public Board()  
   {  
     Bookmarks = new HashSet<Bookmark>();  
   }  
 }  

Tag POCO Sample:

 public class Tag  
 {  
   [Key]  
   public int TagId { get; set; }  
   [Required, MaxLength(64)]  
   public string Title { get; set; }  
   public virtual ICollection<Bookmark> Bookmarks { get; set; }  
   public Tag()  
   {  
     Bookmarks = new HashSet<Bookmark>();  
   }  
 }  

}

Note there are certain fields above which are Required; this allows for validation via attribute decoration. If you require a different primary key field name you can decorate your primary key with the [Key] attribute. You have the option to have a custom name or use the default convention which is done by specifying Id as the primary key.

Create a derived type BookyDbContext 

Creating a derived type BookyDbContext is as simple as inheriting from DbContext, creating a constructor that specifies a connection string ("Sql_CE" in our case) passing this parameter to the base class. Next the requirement is for database table names to not be pluralized and when the model changes I want to Drop and the Create Database. There are model builder configurations being added that will be touched upon later. 

BookyDbContext Code Sample:

public class BookyDbContext : DbContext 
{
    public BookyDbContext()
    : base(nameOrConnectionString: "Sql_CE") { }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
            // Use singular table names
       modelBuilder.Conventions.Remove();
       Database.SetInitializer(new DropCreateDatabaseIfModelChanges());

       modelBuilder.Configurations.Add(new BookmarkConfiguration());
       modelBuilder.Configurations.Add(new TagConfiguration());
    }

    public DbSet Bookmarks { get; set; }

    public DbSet Users { get; set; }

    public DbSet Tags { get; set; }

    public DbSet Boards { get; set; }
}

You need a DbSet Property for each model class.

Using EntityTypeConfiguration to Create Model Relationships 


class BookmarkConfiguration : EntityTypeConfiguration
{
    internal BookmarkConfiguration()
    {
        this.HasMany(b => b.Tags)
            .WithMany(t => t.Bookmarks)
            .Map(mc =>
            {
                mc.MapLeftKey("BookmarkId");
                mc.MapRightKey("TagId");
                mc.ToTable("BookmarkTag");
            });

        this.HasOptional(bd => bd.Board)
            .WithMany(b => b.Bookmarks)
            .Map(x => x.MapKey("BoardId"));
    }

}

internal class TagConfiguration : EntityTypeConfiguration
{
    internal TagConfiguration()
    {
        this.HasMany(t => t.Bookmarks).WithMany(b => b.Tags).Map(
            mc =>
                {
                    mc.MapLeftKey("TagId");
                    mc.MapRightKey("BookmarkId");
                    mc.ToTable("BookmarkTag");
                });
    }
}

Assure you have these classes configured in BookyDbContext

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // Use singular table names
    modelBuilder.Conventions.Remove();
    Database.SetInitializer(new DropCreateDatabaseIfModelChanges());

    modelBuilder.Configurations.Add(new BookmarkConfiguration());
    modelBuilder.Configurations.Add(new TagConfiguration());

}

Sample Code to Add/Update/Delete/Read data


public Bookmark GetById(int id)
{
    using (var context = new BookyDbContext())
    {
        var bookmark = context.Bookmarks.FirstOrDefault(o => o.BookmarkId == id);
        return bookmark;
    }
}

public IEnumerable GetAll(int userId)
{
    using (var context = new BookyDbContext())
    {
        return context.Bookmarks.Where(o => o.UserId == userId).ToList();
    }
}

public List GetPagedEntity(int skip, int take, int userId)
{
    using (var context = new BookyDbContext())
    {
        return context.Bookmarks.Where(o => o.UserId == userId).OrderBy(o => o.Title).Skip(skip).Take(take).ToList();
    }
}

public Bookmark GetBookmarkByUri(string Uri)
{
    using (var context = new BookyDbContext())
    {
        var bookmark = context.Bookmarks.FirstOrDefault(o => o.Url == Uri);

        return bookmark;
    }
}

public Bookmark Delete(int id, int userId)
{
    //var bookmark = GetById(id, userId);

    using (var context = new BookyDbContext())
    {
        var bookmark = context.Bookmarks.FirstOrDefault(o => o.BookmarkId == id);
               
            context.Bookmarks.Remove(bookmark);
            context.SaveChanges();  
         
        return bookmark;
    }
}

public Bookmark Add(Bookmark item, int userId)
{
    using (var context = new BookyDbContext())
    {
        Tag tag = SaveDefaultTagIfNotExists(context);
        item.DtCreated = DateTime.Now;
        item.UserId = userId;
        item.Tags.Add(tag);
        context.Bookmarks.Add(item);
        context.SaveChanges();

        Board board = this.SaveDefaultBoardIfNotExists(context, item);
    }

    return item;
}

private Tag SaveDefaultTagIfNotExists(BookyDbContext context)
{
    //Check if the tag exists first, if not then add the tag
    var existTag = context.Tags.FirstOrDefault(o => o.Title == "General");

    if (existTag != null)
    {
        return existTag;
    }
    else
    {
        var tag = new Tag();
        tag.Title = "General";
        context.Tags.Add(tag);
        context.SaveChanges();
        return tag;
    }
}

private Board SaveDefaultBoardIfNotExists(BookyDbContext context, Bookmark bookmark)
{
    //Check if the tag exists first, if not then add the tag
    var existBoard = context.Boards.FirstOrDefault(o => o.Title == "General");

    if (existBoard != null)
    {
        existBoard.Bookmarks.Add(bookmark);
        context.SaveChanges();
        return existBoard;
    }
    else
    {
        var board = new Board();
        board.Title = "General";
        board.Bookmarks.Add(bookmark);
        context.Boards.Add(board);
        context.SaveChanges();
        return board;
    }
}

public Bookmark Update(Bookmark item, int userId)
{
    var bookmark = GetById(item.BookmarkId, userId);
    using (var context = new BookyDbContext())
    {
        if (bookmark != null)
        {
            bookmark.Title = item.Title;
            bookmark.Description = item.Description;
            bookmark.Read = item.Read;
            bookmark.Starred = item.Starred;
            bookmark.Url = item.Url;
       
            context.SaveChanges();
        }
    }

    return item;
}


Sunday, August 18, 2013

Building Booky - A look at developing Booky; An online service to manage, share and find your bookmarks.

Booky is an online service that allows you to manage, share and find bookmarks. Development of booky code base has gone through two phases of development. Each phase includes the following architectural layers:

Phase 1 - booky feature development - Prototyping

  • Data Layer (Entity Framework Code First with SqlCE RDBMS for prototyping 
  • Business Layer
  • RESTFul API Layer - MS Web API
  • User Interface Layer (AngularJS)

Phase 2 - Alpha version

  • Azure Table Storage
  • Business Layer
  • RESTFul API Layer - MS Web API
  • User Interface Layer (AngularJS)

The alpha version of booky is using Azure Table Storage for the backend. To speed development, a backend implementation was done in Entity Framework Code First with SqlCE RDBMS. This was done so that I could focus on a primary problem domain: booky feature development. The secondary problem domain was development of the User Interface layer with AngularJS.


In a three part blog series I discover and outline some of the aspects that went into developing booky and some of the aspects I learned while developing Booky: