Introduction

This article explains the Representational State Transfer (REST) services with the attribute routing. REST is based on the client/server architecture in which both communicate with each other.

We know that the attribute routing is the new feature of the Web API2 that uses attributes for defining the routes.

Let's see an example.

Step 1

First we create an application:

Step 2

Create Two Model Classes. The first is "novel.cs" and the second is "Writer.cs" as in the following:

Add the following code in the Novel.cs class:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.ComponentModel.DataAnnotations.Schema;
  5. using System.Linq;
  6. using System.Web;
  7. namespace BooksRESTAPI.Models
  8. {
  9. public class Novel
  10. {
  11. public int NovelId { get; set; }
  12. [Required]
  13. public string N_Title { get; set; }
  14. public decimal Cost { get; set; }
  15. public string N_Genre { get; set; }
  16. public DateTime N_PublishDate { get; set; }
  17. public string N_Desc { get; set; }
  18. public int WriterId { get; set; }
  19. [ForeignKey("WriterId")]
  20. public Writer Writer { get; set; }
  21. }
  22. }

Add the following code in the Writer.cs class:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Linq;
  5. using System.Web;
  6. namespace BooksRESTAPI.Models
  7. {
  8. public class Writer
  9. {
  10. public int WriterId { get; set; }
  11. [Required]
  12. public string WriterName { get; set; }
  13. }
  14. }

Step 3

Now add a OData Controller as in the following:

If there is an error message generated then before creating the OData controller build the project and then create the controller.

Here create two code files as in the following:

Step 4

Now we seed the database as in the following:

It adds a Migration folder in the application with the configuration file.

migrations folder

Now we add some code in this file as in the following:

  1. namespace BooksRESTAPI.Migrations
  2. {
  3. using BooksRESTAPI.Models;
  4. using System;
  5. using System.Data.Entity;
  6. using System.Data.Entity.Migrations;
  7. using System.Linq;
  8. internal sealed class Configuration : DbMigrationsConfiguration<BooksRESTAPI.Models.BooksRESTAPIContext>
  9. {
  10. public Configuration()
  11. {
  12. AutomaticMigrationsEnabled = false;
  13. }
  14. protected override void Seed(BooksRESTAPI.Models.BooksRESTAPIContext context)
  15. {
  16. context.Writers.AddOrUpdate(new Writer[] {
  17. new Writer() { WriterId = 1, WriterName = "A.M. Homes" },
  18. new Writer() { WriterId = 2, WriterName = "Mark Danielewski" },
  19. new Writer() { WriterId = 3, WriterName = "Cormac McCatrhy" },
  20. new Writer() { WriterId = 4, WriterName = "Brett Easton Ellis" }
  21. });
  22. context.Novels.AddOrUpdate(new Novel[] {
  23. new Novel() { NovelId = 1, N_Title= "Music For Torching", N_Genre = "Fantasy",
  24. N_PublishDate = new DateTime(2000, 12, 16), WriterId = 1, N_Desc ="It is after midnight on one of those Friday nights when the guests have all gone home and the host and hostess are left in their drunkenness to try and put things right again.", Cost = 14.95M },
  25. new Novel() { NovelId = 2, N_Title = "House or Leaves", N_Genre = "Horrorable",
  26. N_PublishDate = new DateTime(2000, 11, 17), WriterId = 2, N_Desc = "While enthusiasts and detractors will continue to empty entire dictionaries attempting to describe or deride it, "authenticity" still remains the word most likely to stir a debate.", Cost= 12.95M },
  27. new Novel() { NovelId = 3, N_Title = "The Road", N_Genre = "Fantasy", N_PublishDate = new DateTime(2001, 09, 10), WriterId = 2, N_Desc ="When he woke in the woods in the dark and cold of the night he'd reach out to touch the child sleeping beside him.", Cost = 12.95M },
  28. new Novel() { NovelId = 4, N_Title = "Rules of Attraction", N_Genre = "Romance",
  29. N_PublishDate = new DateTime(2000, 09, 02), WriterId = 3, N_Desc =
  30. "And it's a story that might bore you, but you don't have to listen, she told me, because she always knew it was going to be like that", Cost = 7.99M },
  31. new Novel() { NovelId = 5, N_Title = "Splish Splash", N_Genre = "Romance",
  32. N_PublishDate = new DateTime(2000, 11, 02), WriterId = 4, N_Desc = "A deep sea diver finds true love 20,000 leagues beneath the sea.", Cost = 6.99M},
  33. });
  34. }
  35. }
  36. }

Now for creating the local database we need to use this command that we enter into the console window. The commands are "Add-migration Initial" and then "Update-database".

Step 5

Execute the application:

Response

Here we see that it does not return the Write name. If we want it to return the Writer Name instead of the Writer ID, we use the DTO (Data Transfer Object).

Step 6

In the Solution Explorer add the DTOs folder:

Add the following code to the "NovelDetailDto.cs" file:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data.Entity;
  4. using System.Linq;
  5. using System.Web;
  6. namespace BooksRESTAPI.Models
  7. {
  8. public class BooksRESTAPIContext : DbContext
  9. {
  10. public BooksRESTAPIContext() : base("name=BooksRESTAPIContext")
  11. {
  12. }
  13. public System.Data.Entity.DbSet<BooksRESTAPI.Models.Novel> Novels { get; set; }
  14. public System.Data.Entity.DbSet<BooksRESTAPI.Models.Writer> Writers { get; set; }
  15. }
  16. }

Add the following code code to the "NovelDto.cs" file:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.ComponentModel.DataAnnotations.Schema;
  5. using System.Linq;
  6. using System.Web;
  7. namespace BooksRESTAPI.Models
  8. {
  9. public class Novel
  10. {
  11. public int NovelId { get; set; }
  12. [Required]
  13. public string N_Title { get; set; }
  14. public decimal Cost { get; set; }
  15. public string N_Genre { get; set; }
  16. public DateTime N_PublishDate { get; set; }
  17. public string N_Desc { get; set; }
  18. public int WriterId { get; set; }
  19. [ForeignKey("WriterId")]
  20. public Writer Writer { get; set; }
  21. }
  22. }

Step 7

Update the code of the "NovelsControler" class for returning the NovelDto instances as in the following:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Data.Entity;
  5. using System.Data.Entity.Infrastructure;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Net.Http;
  9. using System.Threading.Tasks;
  10. using System.Web.Http;
  11. using System.Web.Http.Description;
  12. using BooksRESTAPI.Models;
  13. using BooksRESTAPI.DTOs;
  14. using System.Linq.Expressions;
  15. namespace BooksRESTAPI.Controllers
  16. {
  17. [RoutePrefix("api/novels")]
  18. public class NovelsController : ApiController
  19. {
  20. private BooksRESTAPIContext db = new BooksRESTAPIContext();
  21. private static readonly Expression<Func<Novel, NovelDto>> AsBookDto =
  22. x => new NovelDto
  23. {
  24. N_Title = x.N_Title,
  25. Writer = x.Writer.WriterName,
  26. N_Genre = x.N_Genre,
  27. };
  28. // GET api/Novels
  29. [Route("")]
  30. public IQueryable<NovelDto> GetNovels()
  31. {
  32. return db.Novels.Include(b=>b.Writer).Select(AsBookDto);
  33. }
  34. // GET api/Novels/5
  35. [Route("{id:int}")]
  36. [ResponseType(typeof(NovelDto))]
  37. public async Task<IHttpActionResult> GetNovel(int id)
  38. {
  39. NovelDto novel = await db.Novels.Include(p => p.Writer)
  40. .Where(p => p.NovelId == id)
  41. .Select(AsBookDto)
  42. .FirstOrDefaultAsync();
  43. if (novel == null)
  44. {
  45. return NotFound();
  46. }
  47. return Ok(novel);
  48. }
  49. protected override void Dispose(bool disposing)
  50. {
  51. db.Dispose();
  52. base.Dispose(disposing);
  53. }
  54. }
  55. }

In the code above we can see that we used the following routes:

[RoutePrefix("api/novels")]
[Route("")]
[Route("{id:int}")]

Execute the application:

Response with DTO

Step 8

If you want to get the details then add this code in the "NovelsController". With the URL "http://localhost:26436/api/novels/1/details".

  1. [Route("{id:int}/details")]
  2. [ResponseType(typeof(NovelDetailDto))]
  3. public async Task<IHttpActionResult> GetNovelDetail(int id)
  4. {
  5. var novel = await (from p in db.Novels.Include(p => p.Writer)
  6. where p.WriterId == id
  7. select new NovelDetailDto
  8. {
  9. N_Title = p.N_Title,
  10. N_Genre = p.N_Genre,
  11. N_PublishDate = p.N_PublishDate,
  12. Writer = p.Writer.WriterName,
  13. N_Desc = p.N_Desc,
  14. Cost = p.Cost
  15. }).FirstOrDefaultAsync();
  16. if (novel == null)
  17. {
  18. return NotFound();
  19. }
  20. return Ok(novel);
  21. }

Now execute the application; the output will be:

Detail of Novel

Step 9

If you want to see the details of novels by the "N_Genre" then add the following code in the "NovelsController".

  1. [Route("{genre}")]
  2. public IQueryable<NovelDto> GetNovelsByGenre(string genre)
  3. {
  4. return db.Novels.Include(p=> p.Writer)
  5. .Where(p => p.N_Genre.Equals(genre, StringComparison.OrdinalIgnoreCase))
  6. .Select(AsBookDto);
  7. }

Again execute the application: with URL "http://localhost:26436/api/novels/fantasy".

Output by N_Genre