Académique Documents
Professionnel Documents
Culture Documents
net/2013/11/25/detailed-tutorial-building-asp-net-web-apirestful-service/
Library and name your solution eLearning and your class library Learning.Data. You
can choose .NET framework 4 or 4.5.
Step 2: Install Entity framework using NuGet
We need to install Entity framework version 5 or 6 using NuGet package manager or NuGet
package console, the package well install is named EntityFramework. Our solution will
look as below after installing EntityFramework:
public Subject()
7
8
1
0
1
1
1
public Course()
1
6
8
1
2
0
2
1
2
public Tutor()
8
2
9
3
0
public Student()
3
3
3
9
4
0
4
public Enrollment()
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
6
3
6
4
6
5
6
6
6
7
6
8
6
9
7
0
7
1
7
2
7
3
7
4
7
5
7
6
7
7
7
8
7
9
8
0
8
1
8
2
8
3
As you noticed those classes do not derive from any base classes nor have any attributes,
having those standard classes give us more data access flexibility and allow us to focus on the
application needs without worrying about persistence implementation.
Entity framework Code First by default supports an approach called Convention over
Configuration for mapping your POCO classes to database objects (Tables, Table fields data
types, and FK Relations). I find this approach is useful in scenarios where you are building a
demo/simple applications. But in our case we need to override this conventions by providing
custom database mapping rules using Fluent API.
Step 4: Applying Custom Mapping Rules
Once we apply the custom mapping rules we will be able to define datatype for each column,
set null-ability, map FK relationships between tables, and specify PK and Identity columns.
To do this we need to create new folder named Mappers then add five classes which
derives from System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<T>
Classes are: CourseMapper, EnrollmentMapper, StudentMapper, SubjectMapper,
and TutorMapper.
public CourseMapper()
this.ToTable("Courses");
6
7
this.Property(c =>
9 c.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
}
}
25
26
27
28
public EnrollmentMapper()
29
30
this.ToTable("Enrollments");
31
32
33
this.Property(e =>
34 e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
35
36
37
38
39
40
41 s.MapKey("StudentID")).WillCascadeOnDelete(false);
42
43 c.MapKey("CourseID")).WillCascadeOnDelete(false);
44
45
}
}
46
47
48
49
public StudentMapper()
50
51
this.ToTable("Students");
52
53
54
this.Property(s =>
55 s.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
}
}
88
89
90
91
public SubjectMapper()
92
93
this.ToTable("Subjects");
94
95
96
this.Property(s =>
97 s.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
98
99
10
10
1
10
}
}
2
10
3
10
4
10
5
10
6
10 s.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
7
10
8
10
9
11
0
11
1
11
2
11
3
11
11
5
11
6
11
7
11
8
11
9
12
0
12
1
12
2
12
3
12
4
12
5
12
6
12
7
12
}
}
8
12
9
By looking at the code above you will notice that we are configuring each POCO class
property (Datatype, Null-ability, PK and identity columns, and FK relations). Those
configuration will be reflected on the database tables we are building. For more details about
mapping/configuring fluent API you can visit thislink.
The relationships between eLearning database tables are simple and described as the below:
Each Student can enroll in multiple Courses. So well have Many-to-Many table
to persist the relation called Enrollment.
{
public LearningContext() :
4
5
base("eLearningConnection")
{
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
8
9
Database.SetInitializer(new MigrateDatabaseToLatestVersion<LearningContext,
1 LearningContextMigrationConfiguration>());
0
1
1
1
4
modelBuilder.Configurations.Add(new StudentMapper());
modelBuilder.Configurations.Add(new SubjectMapper());
modelBuilder.Configurations.Add(new TutorMapper());
modelBuilder.Configurations.Add(new CourseMapper());
modelBuilder.Configurations.Add(new EnrollmentMapper());
1
8
base.OnModelCreating(modelBuilder);
1
9
2
0
2
1
2
2
2
3
2
4
2
5
2
6
2
}
}
7
2
8
The LearningContext class is responsible for three tasks which they are:
1. Exposing our POCO classes as public DbSet properties, this means that every POCO
class is transferred to a database table.
2. Overriding OnModelCreating procedure which is used to apply custom mapping
rules for each POCO class by adding the new configurations to
the DbModelBuilder configurations.
3. In LearningContext class constructor we have implemented two things:
Configured the initialization and migration strategy of the database to migrate to latest
version if a model has changed (i.e. new property has been added). To implement this
we need to add new class called LearningContextMigrationConfiguration which
derives from class
System.Data.Entity.Migrations.DbMigrationsConfiguration<TContext>. The code
listing as below:
public LearningContextMigrationConfiguration()
this.AutomaticMigrationsEnabled = true;
this.AutomaticMigrationDataLossAllowed = true;
7
8
9
1
0
1
1
1 #if DEBUG
2 protected override void Seed(LearningContext context)
1 {
3 new LearningDataSeeder(context).Seed();
1 }
4 #endif
1
5
1
6
1
7
Till this point weve implemented all the code needed to configure and create our eLearning
database depending on model objects weve defined, we can stop at this point and consider
our data layer completed, but we want to enhance it more and implement Repository
Pattern which facilitates data access and manipulation once we start building the Web API.
So in the next post well implement the Repository Pattern for our data access layer.
Listing all available subjects, and listing single subject by querying ID.
Listing all available courses including the sub-models (Subject, and Tutor).
Listing all available students including all sub-models (Enrolled in Courses, Course
Subject, and Tutor)
IQueryable<Subject> GetAllSubjects();
5
6
7
8
IQueryable<Course> GetAllCourses();
0
1
IQueryable<Student> GetAllStudentsWithEnrollments();
IQueryable<Student> GetAllStudentsSummary();
1
2
1
5
1
6
1
8
1
9
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
bool SaveAll();
}
3
1
3
2
3
3
3
4
The methods in the interface above covers all the operations needed in our Web API, note
how we are returning IQueryable<T> for any method returns a collection of objects, this will
facilitate the pagination, sorting, and results ordering in our Web API because IQueryable will
give us deferred execution and fully support for LINQ-to-SQL. In other words IQueryable
result will save us from returning too many unwanted rows from the database.
Now we need to add new class called LearningRepository which will implement the
interface ILearningRepository. Ill list partial implementation of the class here. Well be
able to see the full implementation by browsing the code on GitHub or by downloading the
source.
6
7
_ctx = ctx;
}
8
9
0
1
return _ctx.Subjects.AsQueryable();
}
1
1
2
1
3
1
4
1
5
You can notice that the constructor of the class LearningRepository accepts the database
context object LearningContext and there is a private member _ctx which is used to
interact with all DbSet objects without instantiating it. That design pattern is called
Dependency Injection, in simple words it means that objects do not create other objects on
which they rely to do their work. Instead, they get the objects that they need from an outside
source.
So in our situation the class LearningRepository depends on class LearningContext, but
it wont create an instance from it to complete its work, instead we will inject this object to it
using an external DI framework called Ninject. Well cover this deeply in the coming parts. If
you are new to Dependency Injection design pattern, I recommend you to read my
previous blog post about it.
Until this point our data access layer is complete and ready to be used with our Web API, so
lets jump to the next post where well start working on Web API.
Before talking about Web API configuration and how we can build our URIs to consume the
resources, we need to understand the relation between the HTTP verb and the resource well
consume, as example we will consider the Course domain model object as the resource, the
below table lists the usage of HTTP verbs with Course resource.
Action
HTTP Verb
Relative URI
GET
/api/courses
GET
/api/courses/id
POST
PUT or PATCH
Delete course
DELETE
/api/courses/id
1
2 public static class WebApiConfig
3
config.Routes.MapHttpRoute(
name: "Courses",
routeTemplate: "api/courses/{id}",
);
0
1
}
}
What weve done here is simple, we added new route named Courses, and mapped this
route to a URI template api/courses/{id}, and assigned two default values to it which they
are the name of controller it will use Courses, and set the id parameter to be optional.
Now the relative URI in the form /api/coursesor /api/courses/5 will be routed using this
route. If we didnt specify that id is optional then the URI /api/courses wont be valid.
Step 2: Adding First Controller (Courses Controller)
Controller in Web API is a class that handles HTTP requests from the client, now we have to
add a controller which will handle all HTTP verbs issued against URI /api/courses, to do this
right-click on Controllers folder->Select Add->Name the controller CoursesController and
choose Empty API Controller Template.
The controller weve added derives from ApiController, It is important to name the
controller as we mentioned before CoursesController because Web API controller selection
is implemented in a way which looks for all classes derives from ApiController then match
the first part of the class name Courses with the defaults route controller property we defined
in class WebApiConfig.
6
7
return repository.GetAllCourses().ToList();
9
1
1
1
return repository.GetCourse(id);
2
1
3
1
4
1
}
}
5
1
6
1 [{
2
"Id": 1,
"Duration": 5,
"Description": "The course will talk in depth about: History Teaching Methods 1",
"CourseTutor": {
"Courses": [],
"Id": 1,
"Email": "Ahmad.Joudeh@outlook.com",
"UserName": "AhmadJoudeh",
"Password": "EFZWOFEO",
"FirstName": "Ahmad",
"LastName": "Joudeh",
"Gender": 0
},
"CourseSubject": {
"Courses": [],
"Id": 1,
"Name": "History"
},
"Enrollments": []
1 },
6 {
1
"Id": 2,
"Duration": 4,
"Description": "The course will talk in depth about: History Teaching Methods 2",
"CourseTutor": {
"Courses": [],
"Id": 1,
"Email": "Ahmad.Joudeh@outlook.com",
"UserName": "AhmadJoudeh",
"Password": "EFZWOFEO",
"FirstName": "Ahmad",
"LastName": "Joudeh",
"Gender": 0
},
"CourseSubject": {
"Courses": [],
"Id": 1,
"Name": "History"
},
"Enrollments": []
2 }]
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
Self referencing when returning chain of objects. This can be solved using a design
pattern called theModel Factory.
We are returning all the fields from the domain model object and leaking sensitive
information to the client, for example if you take a look on Tutor object you will
notice that we are returning the password field which shouldnt be leaked to API
consumer. This can be solved using the Model Factory pattern.
Each resource returned in the response should be linked to a URI, this will simplify
resources query for the client. This can be solved using the Model Factory pattern.
We should return HTTP status code when returning single resource, i.e if the resource
was not found we should return 404 in response header, if it was found we should
return 200 OK, etc, this can be solved by returning HttpResponseMessage object.
Inside each method we are instantiating our repository, this operation is expensive as
it includes opening connection to the database, we need to implement Dependency
Injection pattern, this can be solved by using Ninject DI framework.
The format for JSON response objects are in Pascal Case i.e. FirstName, and most
probably our API will be consumed in client using JavaScript, it is easier for JS
developers to work with properties formatted in Camel Case firstName. this can be
solved by configuring JSON formatters of the response.
So in the next post well cover how to fix those flaws in our Web API.
Configuring Formatters
Web API provides media-type formatters for both JSON and XML. The framework inserts
these formatters into the pipeline by default. Clients can request either JSON or XML in the
Accept header of the HTTP request. In order to configure the JSON formatter we need to
implement the code below in class WebApiConfig:
What we did here that we looked at the built in formatters of type JSON, then we changed the
contract resolver of the serialization settings to use Camel Case resolver. Now all JSON
objects properties return in camel case.
Implement Dependency Injection using Ninject
If Dependency Injection concept is new to you, I recommend to read my previous post about
it.
Now to prepare our code for dependency injection we need to add new Base API controller
class named BaseApiController to folder Controllers. This class will derive from
APIController class and its constructor accepts the Repository Interface
ILearningRepository as parameter, were planing to implement
DI Constructor Injection Pattern. The code will look as below:
1
2
3
4
5 public class BaseApiController : ApiController
6
8
9
_repo = repo;
1
1
get
return _repo;
1
5
}
}
1
6
1
7
Now our CoursesController will derive from the BaseApiController, we need to use now
Ninject as DI framework to do the heavy lifting for us and resolve the dependencies between
our components, to install Ninject use NuGet package Manager and install the following
packages:
Ninject
Ninject.Web.Common
WebApiContrib.IoC.Ninject
Update (2014-04-21) Thanks to Thando Toto and Francesco to point out the issue related for
no generating the file NinjectWebCommon by default, this is due some changes done on
Ninject packages used here, so in order to follow along with dependency injection part we
need to install the same assemblies with the right version used in this tutorial, so open NuGet
Package Manager Console (View -> Other Windows -> Package Manager Console) and
install the following packages along the right version used in this tutorial:
After we install those package successfully a new file named NinjectWebCommon is added
to the App_Start folder, this file is responsible of the heavy lifting to configure the
dependencies in our project, now we need to specify the dependencies between our
components. As you remember in this post, when we created the LearningRepository its
constructor accepts the database context object LearningContext so the
LearningRepository class depends on the database context to work, we need register this in
Ninject Kernel.
So to configure Web API to use DI we need to add the following code to the class
NinjectWebCommon:
3
4
5
6
7
8
9
1
0
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
2
1
GlobalConfiguration.Configuration.DependencyResolver = new
1 WebApiContrib.IoC.Ninject.NinjectResolver(kernel);
4
1
RegisterServices(kernel);
return kernel;
6
1
kernel.Bind<LearningContext>().To<LearningContext>().InRequestScope();
kernel.Bind<ILearningRepository>().To<LearningRepository>().InRequestScope();
1
9
}
}
2
0
2
1
2
2
As you notice we are configuring Ninject to have a single instance of database context object
shared by all objects created via the kernel for that HTTP request. This is good technique for
sharing objects that are expensive to create. you can read more about Ninject Object
Scopes here.
Implement the Model Factory Pattern
The Model Factory Pattern will help us in shaping and controlling the response returned to
the client, so what we will do here is to create a simplified model for each domain object
model (entity) we have in the database. i.e. Course entity will map to CourseModel,
Tutor entity will map to TutorModel taking in consideration the relations between
models (Each course is taught by a Tutor and related to a Subject) etc
To implement this we need to add new folder named Models and add four classes
named SubjectModel, TutorModel, CourseModel, and EnrollmentModel. Those are
only simple POCO classes which will be used to return the data to the client, the code for
classes will be as below:
6
7
2
1
3
1
8
1
9
2
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
Now we need to use those classes to create the response for the client, we need a single class
which is responsible for creating those models, so well add a class named ModelFactory
as the code below:
public ModelFactory()
5
6
7
8
Id = course.Id,
Name = course.Name,
Duration = course.Duration,
Description = course.Description,
Tutor = Create(course.CourseTutor),
Subject = Create(course.CourseSubject)
1
4
};
}
1
5
Id = tutor.Id,
Email = tutor.Email,
UserName = tutor.UserName,
FirstName = tutor.FirstName,
LastName = tutor.LastName,
Gender = tutor.Gender
0
2
};
}
1
2
Id = subject.Id,
Name = subject.Name
2
5
};
}
2
6
EnrollmentDate = enrollment.EnrollmentDate,
Course = Create(enrollment.Course)
};
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
}
}
6
4
7
4
8
4
9
5
0
5
1
What weve done above is simple, weve overloaded the function named Create, so it
accepts domain object input i.e. Course and returns a new model of type CourseModel.
Notice how we can control the object graph and the chaining of objects and sub-objects i.e
(CourseModel -> TutorModel, and CourseModel -> SubjectModel).
By doing this weve fixed two important flaws we have identified in the previous post which
they are:
Controlling the fields returned to the client. (i.e. TutorModel is not returning the
password fields in the response).
Using the Model Factory in CoursesController and coming controllers well talk about is
fairly simple, thanks for the BaseApiController where all controllers will derive from it.
Open BaseApiController and add the code below:
3
4
5
6
7
8
protected ModelFactory TheModelFactory
9
{
1
get
0
{
1
if (_modelFactory == null)
1
{
1
_modelFactory = new ModelFactory();
2
}
1
return _modelFactory;
3
}
1
}
4
}
1
5
1
6
What we done here is adding read only property which is responsible to create an instance of
the model factory class.
Before introducing changes to the CoursesController we need to fix the last two flaws in
the previous implementation which they are:
4
5
8
9
}
}
{
private ModelFactory _modelFactory;
4
5
7
8
9
1
get
0
{
1
if (_modelFactory == null)
1
{
1
_modelFactory = new ModelFactory(Request);
2
}
1
return _modelFactory;
3
}
1
}
4
}
1
5
1
6
Id = course.Id,
};
0
1
1
}
}
1
2
There is a nice plural sight learning course produced by Shawn Wildermuth which discuss
deeply this Model Factory Pattern. I recommend watching this course.
Fix the flaw in returning HTTP status codes when returning single resource
Web API framework contains class named HttpResponseMessage which can be used to
return HTTP status code and data if needed, it is a good practice to return
HttpResponseMessage objects because it gives us the flexibility to set response code and
actual content, you will notice in the code below that well be returning object of type
HttpResponseMessage for action GetCourse(int id) instead of returning the
actualCourse domain object.
In the code listing below well implement all the fixes weve discussed:
{
public CoursesController(ILearningRepository repo)
: base(repo)
7
8
IQueryable<Course> query;
0
1
query = TheRepository.GetAllCourses();
1
1
.ToList()
3
1
return results;
1
5
try
if (course != null)
return Request.CreateResponse(HttpStatusCode.OK,
9 TheModelFactory.Create(course));
2
else
return Request.CreateResponse(HttpStatusCode.NotFound);
2
2
5
2
6
2
7
2
8
2
9
}
}
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
Using the model factory to create CourseModel and sub models TutorModel and
SubjectModel.
Returning HTTP status codes for action GetCourse(int id), so if the course was not
found well return 404, in case of an exception well return 400 (Bad request) along
with the exception message, and if the course is found well return 200 (OK) along
with a serialized representation of CourseModel object found.
To test the new changes lets issue a GET request the URI http://localhost:
{your_port}/api/courses, and notice how each resource returned has a URL property, as well
we have shaped the response returned and controlled the object graph to overcome circular
reference serialization issue.
1 [{
2
"id": 1,
"url": "http://localhost:8323/api/courses/1",
"duration": 5,
"description": "The course will talk in depth about: History Teaching Methods 1",
"tutor": {
"id": 1,
"email": "Ahmad.Joudeh@outlook.com",
"userName": "AhmadJoudeh",
"firstName": "Ahmad",
"lastName": "Joudeh",
"gender": 0
},
"subject": {
"id": 1,
"name": "History"
4 },
1 {
5
"id": 2,
"url": "http://localhost:8323/api/courses/2",
"duration": 4,
"description": "The course will talk in depth about: History Teaching Methods 2",
"tutor": {
"id": 1,
"email": "Ahmad.Joudeh@outlook.com",
"userName": "AhmadJoudeh",
"firstName": "Ahmad",
"lastName": "Joudeh",
"gender": 0
},
"subject": {
"id": 1,
"name": "History"
3
2 }]
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
So in the next post well cover how to implement the HTTP verbs (POST,PUT, and
DELETE).
{
try
6
7
return Request.CreateResponse(HttpStatusCode.Created,
1 TheModelFactory.Create(entity));
1
else
1 the database.");
4
6
1
7
1
8
1
9
2
0
2
1
2
2
2
}
}
Method named is Post, so the clients needs to issue HTTP Post request.
The method accepts parameter of type CourseModel, in Web API parameters with
complex types are deserialized from the request body. So the client has to send a
serialized representation of a CourseModel object in JSON format.
Weve returned proper HttpResponseMessange for all possible scenarios that might
happen when we execute this operation. In case the resource is created successfully,
server should return HTTP response 201(Resource Created) along with the resource
created. It is very important to return the resource created in response message
because it will contain CourseId generated at the server.
To test this we need to open fiddler and choose the Composer tab, well issue a POST request
to the URI:http://localhost:{your_port}/api/courses/ The request will look as the image
below:
The request body contains deserialized JSON data of the complex CourseModel, as
we mentioned before, each course has a tutor and related to subject, so sending the Ids
for subject and tutor is enough because the TheModelFactory.Parse function is
responsible to retrieve those objects from database and build valid domain object
model which can be used with our repository.
If this POST request executed successfully at the server, and the a new course is created well
receive status code 201 (Resource Created) in response header along with the new course
created on the server in response body. You can check the image blow:
1 [HttpPatch]
2
[HttpPut]
try
7
8
9
1
1
2
else
updatedCourse.Id = id;
6
1
return Request.CreateResponse(HttpStatusCode.OK,
8 TheModelFactory.Create(updatedCourse));
1
else
return Request.CreateResponse(HttpStatusCode.NotModified);
1
2
2
4
2
5
2
6
2
7
2
8
2
9
}
}
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
Method named PUT, so the clients needs to issue HTTP PUT request, but weve
added HttpPatch attribute to the Put method, so client can issue PUT or PATCH
request and both will be executed using Put method. The difference between Put and
Patch that if we want to update all fields of the CourseModel we need to use PUT, if
we want to update partial fields we need to use PATCH, in our implementation we do
not need to distinguish between those two actions.
Put method accepts two parameters, the Id of the updated resource which is set in
URI, and theupdated CourseModel which represents complex type deserialized in
the request body.
Weve returned proper HttpResponseMessange for all possible scenarios that might
happen when we execute this operation. In case the resource is updated successfully,
server should return HTTP response 200 (OK) along with the resource created. If the
resource is not modified the server should return HTTP response 304 (Not modified).
To test this we need to use fiddler and choose the Composer tab, well issue a PUT request to
the URI: http://localhost:{your_port}/api/courses/33 The request will look as the image
below:
The request body contains deserialized JSON data of the updated CourseModel.
If this PUT request executed successfully at the server, and the course is updated well
receive status code 200 (OK) in response header along with the updated course on the server
in response body.
Delete Course using HTTP DELETE action
Well add new method named Delete(int Id) to CoursesController, as the implementation
below:
try
6
7
if (course == null)
9
1
return Request.CreateResponse(HttpStatusCode.NotFound);
}
0
1
if (course.Enrollments.Count > 0)
3
1
return Request.CreateResponse(HttpStatusCode.OK);
else
6
1
7
1
8
1
9
2
0
2
1
{
2
return Request.CreateResponse(HttpStatusCode.BadRequest);
2
}
2
3
}
2
catch (Exception ex)
4
{
2
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.Message);
5
}
2
}
6
2
7
2
8
2
9
3
0
3
1
Method named DELETE, so the clients needs to issue HTTP DELETE request.
Delete method accepts the Id parameter of the deleted course, Id is set in URI, and
the request body is empty.
Weve returned proper HttpResponseMessange for all possible scenarios that might
happen when we execute this operation. In case the resource is deleted successfully,
server should return HTTP response 200 (OK). If the deletion of the resource failed
server should return HTTP response 400 (Bad request) with explanation content why
the request failed to process.
To test this we need to use fiddler and choose the Composer tab, well issue a DELETE
request to the URI: http://localhost:{your_port}/api/courses/33 Notice that the body of the
request is empty, the request will look as the image below:
I wont list the code for StudentsController here as it is some how identical to
CoursesController, you canbrowse it on GitHub. I will just list the new route configuration
weve added to the WebApiConfig class.
config.Routes.MapHttpRoute(
name: "Students",
routeTemplate: "api/students/{userName}",
);
1 config.Routes.MapHttpRoute(
2
name: "Enrollments",
routeTemplate: "api/courses/{courseId}/students/{userName}",
);
Notice how is courseId parameter is not optional and userName parameter is optional.
Now weve to add new controller named EnrollmentsController, well support two HTTP
verbs, GET to list all students in specific class, and POST to enroll student in specific calls,
youll notice that weve implemented pagination for GET method, well cover this topic in
the next post. The controller code listed as the below:
{
public EnrollmentsController(ILearningRepository repo)
: base(repo)
7
8
IQueryable<Student> query;
0
1
1 s.LastName);
1
2
1
3
System.Web.HttpContext.Current.Response.Headers.Add("X-InlineCount",
1 totalCount.ToString());
4
1
.Skip(pageSize * page)
.Take(pageSize)
.ToList()
7
1
return results;
8
1
9
2
0 [FromBody]Enrollment enrollment)
2
try
2
2
if (!TheRepository.CourseExists(courseId)) return
2
6
2
7
if (result == 1)
return Request.CreateResponse(HttpStatusCode.Created);
else if (result == 2)
3
2
return Request.CreateResponse(HttpStatusCode.BadRequest);
3
3
3
6
3
7
3
8
3
9
4
0
4
1
4
}
}
2
4
3
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
In the POST method notice how were using FromUri and FromBody attribute to indicate
from where were getting the values of the request, well get the userName from the URI
and the enrollment object from Request Body. The enrollment object will contain the
enrollment data only, we could send the CourseID and StudentID as well, but it makes
more sense to get both values from the URI.
To test the POST request well use fiddler to issue a POST request, we want to enroll student
TaiseerJoudeh in course with ID=5, the request will be as the image below:
As weve learned before, we will return HTTP status code as a result for this POST operation,
if the request is successful we will return 201 (Resource Created), if the student already
enrolled in this class well return status code 304 (Not modified).
In the next post well talk about pagination for large results, using manual pagination, and
how we return pagination meta-data.
In this post well discuss the different ways to implement results pagination, well implement
manual pagination then format the response in two different ways (having pagination metadata in an envelope, and in pagination header).
It is well known that overwhelming your server with a query which returns hundreds of
thousand of records is a bad thing, when we are designing an API, we should consider
returning the results of our GET methods in paginated way, i.e. providing 10 results on each
request, and giving the API consumer the ability to navigate through results, specify the size
of the page, and which page he wants.
Manual Pagination and Envelopes
Well modify the CoursesController to use pagination instead of returning the whole
courses at once.
Lets see the code below:
IQueryable<Course> query;
4
5
8
9
1 : "";
1
1
.Skip(pageSize * page)
1
3
1
4
1
5
1
6
1
7
.Take(pageSize)
.ToList()
1
9
return new
TotalCount = totalCount,
TotalPages = totalPages,
PrevPageLink = prevLink,
NextPageLink = nextLink,
Results = results
};
3
2
4
2
5
2
6
2
7
2
8
What weve done here is simple, weve introduced the below to CoursesController
Added two new optional parameters to the GET method with default values, those
optional parameters are translated to query string values, i.e. if we want to request the
second page of courses our GET request will be on the form: http://localhost:
{your_port}/api/courses/?page=1 Notice we didnt specify the pageSize parameter
and it took the default values 10. Sample of response will be on the form below:
1{
2
3
"totalCount": 33,
"totalPages": 4,
"prevPageLink": "http://localhost:8323/api/courses?page=0&pageSize=10",
"nextPageLink": "http://localhost:8323/api/courses?page=2&pageSize=10",
8
9}
Were using envelope to wrap our response, this envelope contains pagination metadata inside the JSON response such as: totalCount, totalPages, prevPageLink,
nextPageLink. It is important to return the total records count and total pages so API
consumer will be able to bind results and apply pagination on grid easily.
Returning the pagination meta-data in the response body is a common technique, there is
nothing wrong about it as everything is visible for the developer, the draw back of this
approach is that API consumer will dig into the response to extract data he was originally
asking for and maybe ignoring all the pagination meta data we returned if he do not need to
use it. So the other cleaner way to return pagination meta-data is to include them in response
header, so well keep the response body for the results only and well add new header called
X-Pagination which contains all pagination meta-data.
{
IQueryable<Student> query;
4
5
6
7
9
1
1 pageSize }) : "";
1
TotalCount = totalCount,
TotalPages = totalPages,
PrevPageLink = prevLink,
NextPageLink = nextLink
};
6
1
System.Web.HttpContext.Current.Response.Headers.Add("X-Pagination",
Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader));
1
8
.Skip(pageSize * page)
.Take(pageSize)
.ToList()
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
return results;
}
3
2
Notice how we added a new header to the response collection headers which contains a
serialized JSON object containing all pagination meta-data.
In the next post well talk briefly about web API security and how we can implement Basic
authentication.
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext
4 actionContext)
5
6
{
var request = actionContext.Request;
7
8
if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
0
1
if (request.Method.Method == "GET")
actionContext.Response = request.CreateResponse(HttpStatusCode.Found);
1 "text/html");
3
1
httpsNewUri.Scheme = Uri.UriSchemeHttps;
httpsNewUri.Port = 443;
5
1
actionContext.Response.Headers.Location = httpsNewUri.Uri;
else
actionContext.Response = request.CreateResponse(HttpStatusCode.NotFound);
1 "text/html");
9
2
0
2
1
2
2
2
3
2
4
2
5
2
}
}
6
2
7
2
8
2
9
3
0
By looking at the code above we are using actionContext parameter to get the request and
response object from it. What we are doing here is examining the URI scheme of the request,
so if it is not secure (http://) we need to return small html message in the response body
informing client to send the request again over https.
As well we are differentiating between the GET method, and other methods (POST, PUT,
DELETE); because in case the client initiated a GET request to existing resource over http we
need to construct the same request again using https scheme and use 443 SSL port then inject
this secure URI in response location header. By doing this the client (browser) will
initiate automatically another GET request using https scheme .
In case of non GET requests, we will return 404 status code (Not Found) and small html
message informing client to send the request again over https.
Now if we want to enforce this filter over the entire Web API we need to add this filter
globally in WebAPIConfig class as the code below:
3
4
config.Filters.Add(new ForceHttpsAttribute());
}
But if we want to enforce HTTPS for certain methods or certain controllers we can add this
filter attribute ForceHttps as the below:
1
2 //Enforce HTTPS on the entire controller
3
[Learning.Web.Filters.ForceHttps()]
[Learning.Web.Filters.ForceHttps()]
1
0
1
1
}
}
3
4
[Inject]
6
7
8 actionContext)
9
if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
1
2
return;
}
1
3
1
4
if (authHeader != null)
!String.IsNullOrWhiteSpace(authHeader.Parameter))
8
1
if (IsResourceOwner(userName, actionContext))
//for he sake of keeping example simple, we used out own login functionality
if (TheRepository.LoginStudent(userName, password))
2 null);
2
Thread.CurrentPrincipal = currentPrincipal;
return;
2
6
2
HandleUnauthorizedRequest(actionContext);
}
7
2
8 authHeader)
2
9
3
3
2
3
3
return credArray;
4
3
5 System.Web.Http.Controllers.HttpActionContext actionContext)
3
7
3
if (resourceUserName == userName)
return true;
return false;
4
1
4 actionContext)
2
actionContext.Response =
3 actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
4
4
actionContext.Response.Headers.Add("WWW-Authenticate",
5
4
6
4
7
4
8
4
}
}
9
5
0
5
1
5
2
5
3
5
4
5
5
5
6
5
7
5
8
5
9
6
0
6
1
6
2
6
3
6
4
6
5
6
6
6
7
6
8
6
9
7
0
7
1
7
2
7
3
7
4
7
5
7
6
7
7
In the code above weve overridden method OnAuthorization and implemented the below:
1. Getting the authorization data from request headers
2. Making sure that authorization header scheme is set to basic authentication and
contains base64-encoded string.
3. Converting the base64-encoded string to a string on the form username:password
and get the username and password.
4. Validating that username sent in authentication header is the same for username in
URI to ensure that resource owner only able to view his details.
5. Validating the credentials against our database.
6. If the credentials are correct we set the identity for current principal, so in sub
subsequent requests the user already authenticated.
7. If the credentials are incorrect the server sends an HTTP response with a 401 status
code (Unauthorized), and a WWW-Authenticate header. Web clients (browser) handle
this response by requesting a user ID and password from client.
Now to apply basic authentication on the two methods we mentioned earlier, all we need to
do is to add this attribute LearningAuthorizeAttribute to those methods as the code below:
[LearningAuthorizeAttribute]
6
7
8
}
}
[LearningAuthorizeAttribute]
5 [FromBody]Enrollment enrollment)
6
7
}
8
}
Note that this is not encryption at all, so as we stated earlier, using Basic Authentication
should be done over SSL only.
Now we will use the encoded string to pass it in authorization header Authorization: Basic
VGFpc2VlckpvdWRlaDpZRUFSVkZGTw== the request will be as the image below:
The response code will be 200 OK and will receive the specific data for this authenticated
user.
In the next post well talk about versioning Web API and how we can implement different
techniques to implement versioning.
Now in this controller well introduce our breaking changes on GET method and we keep
other HTTP verbs the same as we didnt introduce any change on them.
Special thanks goes to Shawn Wildermuth for describing this simple yet effective
technique for API versioning in his plural sight course.
Currently the JSON response for GET method in StudentsController as below:
1
2
3
4
[{
5
"id": 2,
6
"url": "http://localhost:8323/api/students/HasanAhmad",
7
"firstName": "Hasan",
8
"lastName": "Ahmad",
9
"gender": 0,
1
"enrollmentsCount": 4
0
},
1
{
1
"id": 3,
1
"url": "http://localhost:8323/api/students/MoatasemAhmad",
2
"firstName": "Moatasem",
1
"lastName": "Ahmad",
3
"gender": 0,
1
"enrollmentsCount": 4
4
}]
1
5
1
6
Now we want to achieve the below JSON response when calling the GET method in our new
StudentsV2Controller:
1
2
3
4
[{
5
"id": 2,
6
"url": "http://localhost:8323/api/students/HasanAhmad",
7
"fullName": "Hasan Ahmad",
8
"gender": 0,
9
"enrollmentsCount": 4,
1
"coursesDuration": 13
0
},
1
{
1
"id": 3,
1
"url": "http://localhost:8323/api/students/MoatasemAhmad",
2
"fullName": "Moatasem Ahmad",
1
"gender": 0,
3
"enrollmentsCount": 4,
1
"coursesDuration": 16
4
}]
1
5
1
6
Now lets copy and paste the existing controller StudnetsController and rename the new
one to StudentsV2Controller, we need to change the GET implementation as the code
below:
{
IQueryable<Student> query;
4
5
6
7
9
1
1 pageSize }) : "";
1
TotalCount = totalCount,
TotalPages = totalPages,
PrevPageLink = prevLink,
NextPageLink = nextLink
};
6
1
System.Web.HttpContext.Current.Response.Headers.Add("X-Pagination",
Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader));
1
8
.Skip(pageSize * page)
.Take(pageSize)
.ToList()
0
2
1
2
2
2
3
2
4
2
5
.Select(s => TheModelFactory.CreateV2Summary(s));
2
6
return results;
2
}
7
2
8
2
9
3
0
3
1
3
2
The code above is almost the same as the GET method in StudentsController, the only
change we did is returning a new response type StudentV2BaseModel by calling the new
method CreateV2Summary in the ModelFactory class. The code below lists the new Data
Model StudentV2BaseModel and the new function CreateV2Summary. Note
that V2 suffix in the model name and function name are not mandatory to implement
versioning, you can name those anything you want.
1
0
Id = student.Id,
Gender = student.Gender,
EnrollmentsCount = student.Enrollments.Count(),
};
1
7
1
8
1
9
2
}
}
0
2
1
2
2
2
3
2
4
2
5
Now we have prepared our Students resource to be versioned, in the next post we will start
implementing different techniques for versioning
1
2 config.Routes.MapHttpRoute(
3
name: "Students",
routeTemplate: "api/v1/students/{userName}",
);
7
8 config.Routes.MapHttpRoute(
9
name: "Students2",
routeTemplate: "api/v2/students/{userName}",
);
Notice in the code above how we added two new routes and mapped each route with it is
corresponding controller. i.e. Route named Students2 is by default mapped to controller
studentsV2. In future if we want to add version V3 then we need to add new route to
select the appropriate controller, this might get messy over time.
The drawbacks for this technique that it is not compliance with REST specification because
the URI will keep changing by time, as well we need to keep maintaining new routes once we
introduce a new version.
Before we jump and discuss the implementation of the other three techniques we need to take
a look on how Web API selects the appropriate controller based on the information sent with
the request.
Web API uses a method named SelectController in classDefaultHttpControllerSelector,
this method accept HttpRequestMessage object which contains a key/value pair of route
data including controllername which is defined in class WebApiConfig. Based on this
information and by doing reflection on all classes which derives from ApiController class,
Web API can match the controller name with the appropriate class, if there is a duplicate or
class is not found, then an exception will be thrown.
To override this default implementation we need to add new class named
LearningControllerSelector which derives from class
5
6
: base(config)
{
7
8
_config = config;
}
9
1
var controllers = GetControllerMapping(); //Will ignore any controls in same name even if
1
3
1
4
HttpControllerDescriptor controllerDescriptor;
1
5
6
1
7
1
8
HttpControllerDescriptor versionedControllerDescriptor;
if (controllers.TryGetValue(versionedControllerName, out
2 versionedControllerDescriptor))
0
return versionedControllerDescriptor;
2
2
return controllerDescriptor;
3
2
return null;
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
}
}
5
3
6
3
7
3
8
3
9
config.Services.Replace(typeof(IHttpControllerSelector), new
1
LearningControllerSelector((config)));
Now we will implement versioning by sending the version number with the request object:
1
2
3
private string GetVersionFromQueryString(HttpRequestMessage request)
4
{
5
var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
6
7
var version = query["v"];
8
9
if (version != null)
1
{
0
return version;
1
}
1
1
return "1";
2
1
}
3
1
4
Weve to call this method inside method SelectController to read the version number and
select the appropriate controller. The only drawback for this technique is that URI will keep
changing by time and this is not compliance with REST specifications.
API Versioning by Custom Header
Now will try another approach where the version is set in the header request not as a part of
the URI, we will add our custom header named X-Learning-Version and set the version
there. Well assume that clients who didnt provide the header value are requesting the oldest
version of the API (Version 1), to implement this we need to add new function named
GetVersionFromHeader to LearningControllerSelector class as the code below:
4
5
if (request.Headers.Contains(HEADER_NAME))
if (versionHeader != null)
return versionHeader;
1
1
2
1
3
1
4
return "1";
}
1
5
Basically what we have done here is simple, we hard-coded the custom header name, and
checked if the headers collection contains this name, if so we tried to get the value from this
header.
To test this out we need to issue GET request using fiddler as the image below, note how we
added the new header X-Learning-Version to request header collection:
The only drawback for this technique is that we are introducing new custom header to the
headers collection, we can version by headers using the Accept Header without introducing
new custom header as well see in fourth technique below.
API Versioning using Accept Header
In this approach well version by using the Accept header. Out GET request accept header
will be on form: Accept:application/json; version=2.
Well assume that clients who didnt provide the version value in accept header are requesting
the oldest version of the API (Version 1), to implement this we need to add new function
named GetVersionFromAcceptHeaderVersion to LearningControllerSelector class as the
code below:
4
5
if (mime.MediaType == "application/json")
0 StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
1
1
if (version != null)
return version.Value;
return "1";
return "1";
5
1
6
1
7
1
8
1
9
2
In the code above we are looping on all header values collection and examining if the media
type is application/json, if this is the case then we get the value of the parameter named
version.
To test this out we need to issue GET request using fiddler as the image below, note how we
added version parameter to the Accept header:
This is more standard way of API versioning because we didnt add new custom header, and
we didnt version the API by changing the URI.
If you have plural sight subscription; I highly recommend watching this course where Shawn
Wildermuth covers another ways for API versioning.
In the next post well talk about resources cashing using Entity Tags.
In this post well discuss how we can implement resource caching by using an open source
framework for HTTP caching on the client and server, this framework is called CacheCow. It
is created by Ali Kheyrollahi, well cover in this post the server side caching.
API Source code is available on GitHub.
Using resource caching will enhance API performance, reduce the overhead on the server,
and minimize the response size.
The form of caching we want to achieve here called Conditional Requests, the idea is pretty
simple; the client will ask the server if it has an updated copy of the resource by sending
some information about the cached resources it holds using a request header called ETag,
then the server will decide weather there is an updated resource that should be returned to the
client, or the client has the most recent copy, so if the client has the most recent copy the
server will return HTTP status 304 (Not modified) without the resource content (empty
body). By using Conditional Requests the client will send HTTP requests always to the server
but the added value here that the server will only return full response 200 (OK) including
resource content within the body only if the client has stale copy of the resource.
Now the client wants to request the same resource again (Course id: 4) by sending another
HTTP GET, assuming that the client is interested in using caching, then the GET request
initiated will include a header called If-None-Match with the value of the ETag for this
resource, once the server receives this request it will read the ETag value and compare it with
the ETag value saved on the server, if they are identical then the server will send HTTP status
304 (Not modified) without the resource content (empty body) and the client will know that it
has the most recent copy of the resource.
For HTTP GET and DELETE we can use the header If-None-Match, but when updating a
resource and we want to use ETags we have to send the header If-Match with the HTTP
PUT/PATCH request, so the server will examine the ETag and if they are different; the server
will respond with HTTP Status 412 (Precondition Failed) so the client knows that there is a
fresher version of the resource on the server and the update wont take place until the client
has the same resource version on the server.
After we had a brief explanation of how conditional requests and ETags work lets implement
this feature in our Web API.
Now we need to Install CacheCow server from NuGet, so open NuGet package manager
console and type Install-Package CacheCow.Server -Version 0.4.12 this will install two
dlls, the server version and common dll.
Configuring CacheCow is pretty easy, all we need to do is to create Cache Handler and inject
it into the Web API pipeline, this this handler will be responsible to inspect each request and
response coming to our API by looking for ETags and Match headers and do the heavy lifting
for us.
To implement this open the file WebApiConfig.cs and at the end of the file add the below
line of codes:
Till this moment we have configured our API to use In-memory caching; this is the default
configuration for CacheCow, this will be fine if you have a single server or for demo
purposes, but in case of load balancers and web farms, the cache state should be persisted for
the whole farm in single place and all web servers should be aware of this cache. In this case
we need to configure CaschCow to use persistence medium (SQL Server, MongoDB,
MemCache). But before moving to persistence store let we test the in-memory caching.
The HTTP status response is 200 OK which means that server returned the resource
content in the response body.
New two headers are added to the response which they are eTag and Last-Modified,
we are interested here on the eTag value and well get rid off the Last-Modified
header later on as well send conditional requests using eTag only.
The value of the eTag is weak (Prefixed with W) because we are using in-memory
caching for now, in other words if we restarted IIS or shutdown its worker process all
eTag values the client saving are useless.
Now after receiving the eTag header value, the client is responsible to send this value along
with any subsequent requests for the same resource, the client will use the header If-NoneMatch when sending the request and the server will respond by HTTP status 304 with empty
response body if the requested resource has not changed, other wise it will return 200 OK
with new eTag value and resource content in the response body. To examine this let we open
fiddler and issue GET request to the same resource (Course with Id: 4) as the image below:
The HTTP status response is 304 Not modified which means that server returned
empty body and the copy of the resource at client side is the latest.
Now lets move to configure CacheCow to use persistence caching medium (SQL Server)
instead of in-memory caching.
As you notice from the above image, the ETag value now is not weak, it is strong because we
persisted it on SQL server, so if we opened the table CacheState you will notice that
CacheCow has inserted new record including the ETag value, Route Pattern, Last Modified
date, and binary hash key as the image below:
Now if the client sends any subsequent requests to the same resource, the same ETag value
will be returned as long as no other clients updated the resource by issuing HTTP
PUT/PATCH on the same resource.
So if the client includes this ETag value within the If-None-Match header then the server will
respond by 304 HTTP status.
Now lets simulate the update scenario, the client will issue HTTP PUT on the resource
(Course with Id 4) including the ETag value in the header If-Match along with the request as
the image below:
The HTTP status response is 200 OK which means that client has the most recent
copy of the resource and the update of the resource has took place successfully.
New ETag has been returned in the response because the resource has been changed
on the server, so the client is responsible to save this new ETag for any subsequent
requests for the same resource.
The new ETag value and last modified date has been updated in table CacheStore
for this resource.
Now if we directly tried to issue another PUT request using the old ETag
(8fad5904b1a749e1b99bc3f5602e042b) we will receive HTTP Status 412 (Precondition
Failed) which means that updating the resource has failed because the client doesnt have the
latest version of the resource. Now client needs to issue GET request to get the latest version
inShare2
Share on Tumblr
Asp.Net Web API 2 has been released with the release of Asp.Net MVC 5 since 5 months
ago, Web API 2 can be used with .NET framework 4.5 only, the Web API 2 template is
available by default on VS 2013 and you can install Web Tools 2013.1 for VS 2012 to have
this template as well by visiting this link.
In my previous tutorial on Building ASP.Net Web API RESTful service, Ive covered
different features of the first release of ASP.Net Web API, in this two series post (part 2 can
be found here) I will focus on the main features in version 2, Ill be using the same project
Ive used in the previous tutorial but I will start new Web API 2 project then well implement
and discuss the new features. The source code for the new Web API 2 project can be found on
my GitHub repository.
Note: I will not upgrade the existing eLearning Web API to version 2, but if you are
interested to know how to upgrade version 1 to version 2 then you can follow the steps in
ASP.Net official post here.
ASP.Net Web API 2 comes with a couple of nice features and enhancements, the most four
important features in my opinion are:
IHttpActionResult:
o In the first version of Asp.Net Web API we always returned HTTP response
messages for all HTTP verbs using the extension
method Request.CreateResponse and this was really easy, now with version
2 of ASP.Net Web API this is simplified more by introducing a new interface
namedIHttpActionResult, this interface acts like a factory
for HttpResponseMessage with a support for custom responses such as (Ok,
BadRequest,Notfound, Unauthorized, etc).
o Multi part series tutorial for Building OData Service using ASP.Net Web
API is published now.
The ASP.Net Web API 2 template we used has by default a class named WebApiConfig
inside the App_Start folder, when you open this class you will notice that there is new
line config.MapHttpAttributeRoutes(); this didnt exists in Web API version one, the
main responsbility for this line of code is to enable Attribute Routing feature in Web API
version 2 where we can define routes on the controller directly. In this tutorial we want to use
routing by attributes only to route all requests, so feel free to delete the default route named
DefaultApi.
The other important change introduced in Web API 2 is how the WebApiConfig file is
registered, go ahead and open class Global.asax and you will notice that there is new line
used for configuring the
routes GlobalConfiguration.Configure(WebApiConfig.Register); this line is
responsible for registering the routes when the global configuration class is initiated and
ready to be called.
[Route("api/courses/{id:int}")]
8
9
try
1
1
if (course != null)
else
return Request.CreateResponse(HttpStatusCode.NotFound);
6
1
finally
ctx.Dispose();
2
1
}
}
2
2
2
3
2
4
2
5
2
6
2
7
2
}
8
2
9
3
0
3
1
3
2
3
3
3
4
What we have done here is simple, if you look at the highlighted line of code above you will
notice how we defined the route on action level by using
attribute System.Web.Http.Route where we specified the request URI and stated that id
parameter should be integer, if you tried to issue GET request to URI /api/courses/abc you
will get 404 response.
In Web API version 1 we used to define names for routes inside WebApiConfig class, those
names were useful as we used them to link each resource to URI or for results navigation
purpose, in our case we still need to return PrevPageLink and NextPageLink in the
response body when user issue HTTP GET request to URI /api/courses. This still available
in Web API version 2 but defining route names will take place on the attribute level, lets
implement the code below:
IQueryable<Course> query;
7
8
9
1
0
1
2
1
.Take(pageSize)
.ToList();
1
8
TotalCount = totalCount,
TotalPages = totalPages,
PrevPageLink = prevLink,
NextPageLink = nextLink,
Results = results
};
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
}
}
3
3
4
3
5
3
6
3
7
In the code above notice how we defined the route name on attribute level, you can
not define route names on Controller level so if you have another actions need route names
then you have to define them on each attribute.
2. Define attributes on controller level
Now instead of repeating the prefix /api/courses on each action, we can add this prefix on
controller level and define the specific route attributes information on each action, the change
is fairly simple as the code below:
1 [RoutePrefix("api/courses")]
2 public class CoursesController : ApiController
3
[Route(Name = "CoursesRoute")]
7
8
9
1
[Route("{id:int}")]
1
1
2
1
3
1
}
}
4
1
5
1 [Route("~/api/subject/{subjectid:int}/courses")]
2 public HttpResponseMessage GetCoursesBySubject(int subjectid)
3 {
4
IQueryable<Course> query;
7
8
try
1
1
query = repo.GetCoursesBySubject(subjectid);
if (coursesList != null)
else
return Request.CreateResponse(HttpStatusCode.NotFound);
6
1
finally
0
2
1 }
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
ctx.Dispose();
}
9
3
0
3
1
3
2
3
3
Now how we defined the routing attribute and prefixed it with ~ so Web API will route any
HTTP GET request coming to the URI api/subject/8/courses to this route, this is really nice
feature when you want to introduce exceptional routing in the same controller.
In the next post Ill cover the IHttpActionResult return type as well the support for CORS in
ASP.Net Web API 2.
inShare
Share on Tumblr
In the previous post weve covered ASP.Net Web API 2 attribute routing, in this post well
complete covering new features, well start by discussing the new response return type
IHttpActionResult then cover the support for CORS.
Source code is available on GitHub.
1 [Route("{id:int}")]
2 public IHttpActionResult GetCourse(int id)
3 {
4
try
1
0
if (course != null)
1
2
return Ok<Learning.Data.Entities.Course>(course);
}
else
return NotFound();
return InternalServerError(ex);
finally
8
1
9 }
2
0
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
ctx.Dispose();
}
Notice how were returning Ok (200 HTTP status code) with custom negotiated content, the
body of the response contains JSON representation of the returned course. As well we are
returning NotFound (404 HTTP status code) when the course is not found.
But what if want to extend the NotFound() response and customize it to return plain text
message in response body? This is straight forward using IHttpActionResult interface as the
below:
Construct our own Action Result:
We want to build our own NotFound(Your custom not found message) action result, so we
need to add a class which implements IHttpActionResult, lets add new file
named NotFoundPlainTextActionResult as the code below:
5
6
this.Request = request;
this.Message = message;
0
1
1
2
return Task.FromResult(ExecuteResult());
}
1
3
1
5
response.RequestMessage = Request;
return response;
7 }
1
8 public static class ApiControllerExtension
1 {
9
2 message)
0
2
1
2 }
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
By looking at the code above you will notice that the class
NotFoundPlainTextActionResult implements interface IHttpActionResult, then weve
added our own implementation to the method ExecuteAsync which returns a Task of type
HttpResponseMessage. This HttpResponseMessage will return HTTP 404 status code along
with the custom message well provide in the response body.
In order to be able to reuse this in different controllers we need and new class
named ApiControllerExtension which contains a method returns this customized Not Found
response type.
Now back to our CoursesController we need to change the implementation of the standard
NotFound() to the new one as the code below:
if (course != null)
1
return Ok<Learning.Data.Entities.Course>(course);
else
return
You can read more about extending IHttpActionResult result by visiting Filip W. post.
1 [RoutePrefix("api/courses")]
2 [EnableCors("*", "*", "GET,POST")]
3 public class CoursesController : ApiController
4{
5
6}
The EnableCors attribute accept 3 parameters, in the first one you can specify the origin of
the domain where requests coming from, so if you want to allow only domain
www.example.com to send requests for your API; then you specify this explicitly in this
parameter, most of the cases you need to allow * which means all requests are accepted from
any origin. In the second parameter you can specify if you need a certain header to be
included in each request, so you can force consumers to send this header along with each
request, in our case we will use * as well. The third parameter is used to specify which HTTP
verbs this controller accepts, you can put * as well, but in our case we want to allow only
GET and POST verbs to be called from requests coming from different origin.
In case you want to exclude a certain action in the controller from CORS support you can add
the attributeDisableCors to this action, let we assume we want to disable CORS support on
method GetCourse so the code to disable CORS support on this action will be as the below:
1 [Route("{id:int}")]
2 [DisableCors()]
3 public IHttpActionResult GetCourse(int id)
4{
5
6}
2. On action level
Enabling CORS on certain actions is fairly simple, all you want to do is adding the attribute
EnableCors on this action, as well EnableCors accepts the same 3 parameters we discussed
earlier. Enabling it on action level will be as the below code:
1 [Route(Name = "CoursesRoute")]
2 [EnableCors("*", "*", "GET")]
3 public HttpResponseMessage Get(int page = 0, int pageSize = 10)
4{
5
6}
In some situations where you have many controllers and you want to allow CORS on your
entire API, it is not convenient to visit each controller and add EanbleCors attribute to it, in
this situation we can allow it on the entire API inside the Register method in
Class WebApiConfig, so open your WebApiConfig class and add the code below:
By adding this we enabled CORS support for every controller we have in our API, still you
can disable CORS support on selected actions by adding DisableCors attribute on action
level