Creating RESTful Web API

By | October 20, 2015

Introduction

This post is about creating a RESTful web API using Microsoft technology stack. We will be using Visual Studio 2012 to build this REST service in C#. Entity Framework will be used for persistence in SQL Server database.

REST Project

Structure

Open Visual Studio and click on File –> New –> Project (Shortcut : Ctrl + Shift + N). The below given dialog box will appear. Select Web under Templates –> Visual C#. Select “ASP.NET Web API 2 Empty Project

REST New Project Dialog

New Project Dialog

Once OK button is clicked after providing a suitable project and solution name the below given structure will appear in solution explorer.

REST Web API Solution Structure

REST Web API Solution Structure

Now that we have structure in place, we will start by adding a Data folder (Shown in above image for project structure). Lets start adding code under data to interact with SQL database using Entity Framework.

Code

Data components

Let’s start with adding an Interface to the project. We will name it as IContactRepository. The code for the same is given below. This repository contract has methods for getting all contact, contact by Id, Inserting new contact, updating an existing and deleting contact

IContactRepository Interface

using RestWebService.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RestWebService.Data
{
    public interface IContactRepository
    {
        IQueryable<Contact> GetAllContacts();
        Contact GetContactById(string Id);
        bool Insert(Contact contact);
        bool Update(Contact originalContact, Contact updatedContact);
        bool DeleteContact(string Id);
    }
}

Further we will need to create a database context class. This will be used to interact with SQL Server database using Entity Framework. Let’s create a context class and name it as RestWebServiceContext. The code for the same is given below;.

RestWebServiceContext class

using RestWebService.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace RestWebService.Data
{
    public class RestWebServiceContext : DbContext
    {
        public RestWebServiceContext()
            : base("name=RestWebServiceContext")
        {
            Database.SetInitializer<RestWebServiceContext>(null);
            this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
        }

        public System.Data.Entity.DbSet<Contact> Contacts { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Contact>().ToTable("TBL_CONTACT", schemaName: "dbo");

            modelBuilder.Entity<Contact>()
                .Property(x => x.ContactId).HasColumnName("CONTACTID").IsRequired();
 
            modelBuilder.Entity<Contact>().Property(x => x.FirstName).HasColumnName("FirstName");
            modelBuilder.Entity<Contact>().Property(x => x.LastName).HasColumnName("LastName");
            modelBuilder.Entity<Contact>().Property(x => x.Department).HasColumnName("Department");
            modelBuilder.Entity<Contact>().Property(x => x.JobTitle).HasColumnName("JobTitle");
            modelBuilder.Entity<Contact>().Property(x => x.TypeNum).HasColumnName("TypeNum");

            modelBuilder.Entity<Contact>().Property(x => x.IsPrivate).HasColumnName("IsPrivate");
            modelBuilder.Entity<Contact>().Property(x => x.IsImported).HasColumnName("IsImported");
            modelBuilder.Entity<Contact>().Property(x => x.ManageUserId).HasColumnName("ManageUserId");
            modelBuilder.Entity<Contact>().Property(x => x.CreateUserId).HasColumnName("CreateUserId");
            modelBuilder.Entity<Contact>().Property(x => x.CreateDate).HasColumnName("CreateDate");
            modelBuilder.Entity<Contact>().Property(x => x.AemOptOut).HasColumnName("Aem_OptOut");
            modelBuilder.Entity<Contact>().Property(x => x.AemBounceBack).HasColumnName("Aem_BounceBack");

        }
    }
}

The context class extends from DBContext class from System.Data.Entity. Line 14 is one way to provide name of the context provider that will be configured in web.config file. The other way to pass this information can be by having a constructor with this parameter.

Line 16 tells Entity Framework not to create a database model and use the one available. The database initializer is called when a the given DbContext type is used to access a database for the first time. The default strategy for Code First contexts is an instance of CreateDatabaseIfNotExists. As we are using an existing database we should set Database.SetInitializer of type RestWebServiceContext to nullLine 20 is declaring a property to return a Contacts collection.

The override method OnModelCreating(DbModelBuilder modelBuilder) provides a mapping of entity in the model builder. It provides a mapping between table and entity object properties. Line 27-29 declares a primary column attribute on entity and sets it to be required.

Not all column and attribute mapping are needed in OnModelCreating method override. It is only required if you have mismatch between column name and attribute name. In the above code the only required mapping is Aem_OptOut and Aem_BounceBack

Add an implementation for the above created contact repository interface. Let’s name it as ContactRepository. The code for the same is given below.

ContactRepository class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RestWebService.Data
{
    public class ContactRepository : IContactRepository
    {
        private RestWebServiceContext _ctx;

        public ContactRepository(RestWebServiceContext ctx)
        {
            _ctx = ctx;
        }

        public IQueryable<Models.Contact> GetAllContacts()
        {
            return _ctx.Contacts.AsQueryable();
        }

        public Models.Contact GetContactById(string Id)
        {
            return _ctx.Contacts.Find(Guid.Parse(Id));
        }

        public bool Insert(Models.Contact contact)
        {
            try
            {
                _ctx.Contacts.Add(contact);
                _ctx.SaveChanges();
                return true;
            }
            catch (Exception ex)
            {
                //Log exception 
                return false;
            }
        }

        public bool Update(Models.Contact originalContact, Models.Contact updatedContact)
        {
            try
            {
                _ctx.Entry(originalContact).CurrentValues.SetValues(updatedContact);
                _ctx.SaveChanges();
                return true;
            }
            catch (Exception ex)
            {
                //Log exception
                return false;
            }
        }

        public bool DeleteContact(string Id)
        {
            bool isDeleted = false;
            try
            {
                var entity = _ctx.Contacts.Find(Id);
                if (entity != null)
                {
                    _ctx.Contacts.Remove(entity);
                    _ctx.SaveChanges();
                    isDeleted = true;
                }
            }
            catch (Exception ex)
            {
                //Log exception
                isDeleted = false;
            }
            return isDeleted;
        }

    }
}

Entity

We are referring Contact class in the repositories above. This class is acting as an Entity in the example. For the purpose of demonstration only we are sharing entity across REST interface.

In real world scenario we restrain from sharing the underline database model. The served ones are light weight container classes with no business functions. They are called DTOs (Data Transfer Objects). Refer to DTOs advantages and disadvantages on web for more details.

Contact class

Entity classes maps to underline data source. These classes encapsulates business model, including validations, data, relationships and persistence behavior. The Contact class is an anemic domain model.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RestWebService.Models
{
    public class Contact
    {
        public Guid ContactId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool TypeNum { get; set; }
        public string JobTitle { get; set; }
        public string Department { get; set; }
        public bool IsPrivate { get; set; }
        public bool IsImported { get; set; }
        public Guid ManageUserId { get; set; }
        public Guid CreateUserId { get; set; }
        public DateTime CreateDate { get; set; }
        public bool AemOptOut { get; set; }
        public bool AemBounceBack { get; set; }

        public override string ToString()
        {
            return "Contact Id: " + ContactId.ToString() + " FirstName " 
                + FirstName + " LastName " + LastName + " JobTitle " + JobTitle + " Department" + Department;
        }
    }
}

Controller

This section defines controller. We will create a new controller by name ContactController.

ContactController class

using RestWebService.Data;
using RestWebService.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace RestWebService.Controllers
{
    public class ContactController : ApiController
    {
        private IContactRepository _repo;

        public ContactController()
        {
            _repo = new ContactRepository(new RestWebServiceContext());
        }

        public HttpResponseMessage Get()
        {
            try
            {
                var contact = _repo.GetAllContacts();
                if (contact != null)
                {
                    return Request.CreateResponse(HttpStatusCode.Found, contact.ToList());
                }
                else
                {
                    return Request.CreateResponse(HttpStatusCode.NotFound);
                }
            }
            catch (Exception ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
            }
        }

        public HttpResponseMessage Get(string id)
        {
            try
            {
                var contact = _repo.GetContactById(id);
                if (contact != null)
                {
                    return Request.CreateResponse(HttpStatusCode.Found, contact);
                }
                else
                {
                    return Request.CreateResponse(HttpStatusCode.NotFound);
                }
            }
            catch (Exception ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
            }
        }

        public HttpResponseMessage Post([FromBody] Contact contact)
        {
            try
            {
                if (_repo.Insert(contact))
                {
                    return Request.CreateResponse(HttpStatusCode.Created, contact);
                }
                else
                {
                    return Request.CreateResponse(HttpStatusCode.BadRequest, contact.ToString());
                }
            }
            catch (Exception ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
            }
        }

        [HttpPatch]
        [HttpPut]
        public HttpResponseMessage Put(string Id, [FromBody] Contact contact)
        {
            try
            {
                var originalContact = _repo.GetContactById(Id);
                if (originalContact == null)
                {
                    return Request.CreateResponse(HttpStatusCode.NotFound, "Contact not found");
                }
                if (_repo.Update(originalContact, contact))
                {
                    return Request.CreateResponse(HttpStatusCode.OK, contact);
                }
                else
                {
                    return Request.CreateResponse(HttpStatusCode.NotModified);
                }
            }
            catch (Exception ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
            }
        }

        public HttpResponseMessage Delete(string id)
        {
            try
            {
                var contact = _repo.GetContactById(id);
                if (contact == null)
                {
                    return Request.CreateResponse(HttpStatusCode.NotFound, "Contact not found");
                }

                if (_repo.DeleteContact(contact.ContactId.ToString()))
                {
                    return Request.CreateResponse(HttpStatusCode.OK);
                }
                else
                {
                    return Request.CreateResponse(HttpStatusCode.NotModified);
                }
            }
            catch (Exception ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
            }
        }
    }
}

There are five methods in controller, Get, Get by an Id, Post, Put, and Delete.

Get

Get method will return all contacts from database. It calls repositories GetAllContacts method and returns a list of the same.

GET

Get(string id)

This Get takes an id (GUID) string in our case and returns one single contact matching the Id.

GET by Id

Post

Post method takes Contact in Request body. This method is called for creation of a new contact. This is the recommended usage of Post verb however, it can be used for different purpose also.

POST

POST

Put

Put method takes Contact in Request body along with an Id. Put is used for updating an existing method. This method is loading an existing contact and then updating its attributes with the new contact that was passed to the controller. Entity Framework provides an easy way to update existing contact with the new data received by the service.

PUT

Delete

Delete method is used to delete a given contact. It uses Id passed to controller for deleting. It fetches existing contact by id and deletes it if found.

DELETE

Please note that the response messages are just for illustration and were not written to be perfect.

Web.Config

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=301879
  -->
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="RestWebServiceContext" 
              connectionString ="Data Source=.\LocalContact;Database=ActDatabase;Integrated Security=True;"
              providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <appSettings></appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v11.0" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
    
  </entityFramework>
</configuration>

WebApiConfig class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;

namespace RestWebService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *