RESTful Web Services Using ASP.NET Web API

등록 날짜: - 최종 수정 날짜:

REST (Representational State Transfer) is a widespread software architecture solution that uses the stateless HTTP protocol to manipulate and transfer data between systems, webpages, mobile applications and servers. RESTful services use HTTP verbs like GET, POST, PUT, DELETE for specifying the operation; data is usually transferred as JSON or XML. RESTful services conceptually are related to CRUD (Create, Read, Update and Delete) operations.

ASP.NET Web API is Microsoft’s platform to build RESTful (HTTP) services. In this article I create a RESTful service for Contact Cards stored in MongoDB database. The code presented here is hosted on GitHub (you can try it out, please don’t forget to update the MongoDB connection, database and collection details before running the application).

The project is ASP.NET MVC Web API application, which is using .NET Framework 4.5 and the official C# MonogDB driver and Foundation as UI Framework.

Data Repository

Before creating the API methods, I need a data repository, which does the data manipulation using the MongoDB driver and the data model.

Data Model

The data model consists of one class, Contact:

 public class Contact : BaseEntity
 {
        [BsonElement(elementName: "first_name")]
        [Required]
        [MaxLength(200)]
        public string FirstName { get; set; }

        [BsonElement(elementName: "last_name")]
        [Required]
        [MaxLength(200)]
        public string LastName { get; set; }

        [BsonElement(elementName: "birthday")]        
        public DateTime Birthday { get; set; }

        [BsonElement(elementName: "website")]
        [Required]
        [MaxLength(250)]
        public string Website { get; set; }

        [BsonElement(elementName: "email")]
        [Required]
        [EmailAddress]
        public string Email { get; set; }

        [BsonElement(elementName: "home_phone")]
        public string HomePhone { get; set; }

        [BsonElement(elementName: "work_phone")]
        public string WorkPhone { get; set; }

        [BsonElement(elementName: "mobile_phone")]
        public string MobilePhone { get; set; }
    }

Because I had the data in MongoDB before implementing the service, I had to map the database fields to the .NET like properties. The attribute [BsonElement] was added to map the class property and the field in the data table. The [Required] and [MaxLength (…)] attributes are for data validation, meaning, FirstName, LastName, Website and EmailAddress are mandatory fields and First and LastName can be 200 characters long, while Website can be 250.

Repository

The code for the repository uses the standard MongoDB C# driver. I am creating a connection to the database in the constructor and initializing the contacts collection too. In the Get(Guid id) method, I use LINQ to search for the contact in the contacts collection. The Update(Guid id, Contact c) method is the most complex -- there I define a query for identifying the entry in the collection, which has to be updated and I pass all the update queries for each field of the Contact class.

 public class ContactsRepository
 {
        //
        // Change these to your local settings
        //
        private const string CONNECTION_STRING = "mongodb://localhost";
        private const string DATABASE = "contact_db";
        private const string COLLECTION_CONTACTS = "contacts";

        private MongoClient client = null;
        private MongoServer server = null;
        private MongoDatabase db = null;
        private MongoCollection<Contact> contacts = null;

        public ContactsRepository()
        {
            client = new MongoClient(CONNECTION_STRING);
            server = client.GetServer();
            db = server.GetDatabase(DATABASE);
            contacts = db.GetCollection<Contact>(COLLECTION_CONTACTS);
        }


        public IEnumerable<Contact> GetAll()
        {
            List<Contact> result = new List<Contact>();
            result = this.contacts.FindAll().ToList();
            return result;
        }

        public Contact Get(Guid id)
        {
            Contact result = null;
            var partialResult = this.contacts.AsQueryable<Contact>()
                                    .Where(p => p.ID == id)
                                    .ToList();

            result = partialResult.Count > 0
                            ? partialResult[0]
                            : null;

            return result;
        }

        public Contact Save(Contact c)
        {
            var result = this.contacts.Save(c);
            if (result.DocumentsAffected == 0 && result.HasLastErrorMessage)
            {
                Trace.TraceError(result.LastErrorMessage);
            }

            return c;
        }

        public Contact Update(Guid id, Contact c)
        {
            var query = Query<Contact>.EQ(p => p.ID, id);
            var update = Update<Contact>.Set(p => p.Birthday, c.Birthday)
                                        .Set(p => p.Email, c.Email)
                                        .Set(p => p.FirstName, c.FirstName)
                                        .Set(p => p.HomePhone, c.HomePhone)
                                        .Set(p => p.LastName, c.LastName)
                                        .Set(p => p.WorkPhone, c.WorkPhone)
                                        .Set(p => p.MobilePhone, c.MobilePhone)
                                        .Set(p => p.Website, c.Website);

            var result = this.contacts.Update(query, update);
            if (result.DocumentsAffected == 0 && result.HasLastErrorMessage)
            {
                Trace.TraceError(result.LastErrorMessage);
            }

            return c;
        }

        public void Delete(Guid id)
        {
            var query = Query<Contact>.EQ(p => p.ID, id);
            var result = this.contacts.Remove(query);
            if (result.DocumentsAffected == 0 && result.HasLastErrorMessage)
            {
                Trace.TraceError(result.LastErrorMessage);
            }
        }
    }

The API

The API’s implementation is in the ContactsController.cs file. ASP.NET Web API prefers convention over configuration. This means if I name the methods properly, the routing and parameter parsing/passing will be done automatically by the Web API framework.

Contact Card Web API methods

GET methods

There are two HTTP GET methods defined in the API. One does not receive any parameter and returns a list of items as response. The other receives an id of a contact and returns only one contact object. If there is no entry in the database for the given id I return the HTTP 404 Status Code – Not Found.

        //
        // GET api/contacts
        //
        public IEnumerable<Contact> Get()
        {
            return repo.GetAll();
        }

        //
        // GET api/contacts/guid
        //
        public Contact Get(Guid id)
        {
            var contact = repo.Get(id);
            if (contact == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            return contact;
        }

Since all of the above methods start with Get, the Web API routes these as methods which can be invoked using HTTP GET requests. The type of id field is GUID, luckily .NET Framework and Web API resolves the conversion between string and GUID from the request.

POST method

The POST HTTP request (usually) is used when there are forms submitted to a Web server. The request has to contain Form Data, which is parsed by Web API and mapped to a new Contact object.

The framework validates the model (based on the attributes – Required, MaxLength, EmailAddress – specified before). In case the model is valid I try to save the new contact. If there are no exceptions, a new HTTP 201 – Created – response is sent back along with the route to the newly created contact. This is a good practice for POST methods when implementing RESTful services. In case the model is invalid, I return HTTP 400 – Bad Request – along with the validation error messages. 

        //
        // POST api/contacts
        //
        public HttpResponseMessage Post(Contact value)
        {
            HttpResponseMessage result = null;

            if (ModelState.IsValid)
            {
                try
                {
                    var contact = repo.Save(value);
                    result = Request.CreateResponse<Contact>(HttpStatusCode.Created, contact);
                    string newItemURL = Url.Link("DefaultApi", new { id = contact.ID });
                    result.Headers.Location = new Uri(newItemURL);
                }
                catch (Exception ex)
                {
                    Trace.TraceError(ex.Message, ex);
                    result = Request.CreateResponse<string>(HttpStatusCode.InternalServerError, ex.Message);
                }
            }
            else
            {
                result = GetBadRequestResponse();
            }

            return result;
        }

PUT method

The PUT HTTP verb is used when the data needs to be updated (in some APIs it is used for adding new data too). The logic in the code is similar to the POST method, the only difference is, I invoke the Update(id, value) method from the repository. If the update was successful I return a HTTP 204 – No content; if there were errors during update, a HTTP 500 – Internal Server Error – is sent back with the error message.

        //
        // PUT api/contacts/guid
        //
        public HttpResponseMessage Put(Guid id, Contact value)
        {
            HttpResponseMessage result = null;

            if (ModelState.IsValid)
            {
                try
                {
                    repo.Update(id, value);
                    result = Request.CreateResponse(HttpStatusCode.NoContent);
                }
                catch (Exception ex)
                {
                    Trace.TraceError(ex.Message, ex);
                    result = Request.CreateResponse<string>(HttpStatusCode.InternalServerError, ex.Message);
                }
            }
            else
            {
                result = GetBadRequestResponse();
            }

            return result;
        }

DELETE method

The DELETE HTTP request is used to remove data from the database for the given id. If the delete was successful, HTTP 204 – No content – is sent back, otherwise HTTP 500 – Internal Server Error – along with the error message.

        //
        // DELETE api/contacts/guid
        //
        public HttpResponseMessage Delete(Guid id)
        {
            HttpResponseMessage result = null;

            try
            {
                repo.Delete(id);
                result = Request.CreateResponse(HttpStatusCode.NoContent);
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.Message, ex);
                result = Request.CreateResponse<string>(HttpStatusCode.InternalServerError, ex.Message);
            }

            return result;
        }

RESTful services provide a convenient and easy way to manipulate and access data over the Internet, independently from the platform which you are using, this is one of the reasons this architectural solution became so popular nowadays.

게시 26 1월, 2015

Greg Bogdan

Software Engineer, Blogger, Tech Enthusiast

I am a Software Engineer with over 7 years of experience in different domains(ERP, Financial Products and Alerting Systems). My main expertise is .NET, Java, Python and JavaScript. I like technical writing and have good experience in creating tutorials and how to technical articles. I am passionate about technology and I love what I do and I always intend to 100% fulfill the project which I am ...

다음 문서

Tips for Creating a Great Landing Page