ASP.NET Web API (.NET Framework) Tutorial
Classic ASP.NET Web API 2 on .NET Framework — HTTP services hosted on IIS.
- Topics
- 36
- Full lessons
- 36
- Est. hours
- ~7
- Interview Qs
- 23
Overview
ASP.NET Web API 2 is the classic framework for building HTTP services on .NET Framework 4.x. Controllers derive from ApiController, configuration lives in WebApiConfig, and apps are hosted on IIS.
In ASP.NET Core, MVC and Web API were unified into a single framework. Learn this track to maintain existing services; build new APIs with ASP.NET Core Web API.
What you will learn
- Design RESTful endpoints with ApiController
- Use attribute routing and IHttpActionResult
- Access SQL Server with ADO.NET and stored procedures
- Secure and document classic APIs
Prerequisites
You will get the most from this track if you already know:
Learning path
The recommended sequence, from beginner to advanced. Each stage builds on the one before.
- 1REST Foundations · 4 topics · BeginnerIntroduction to Web APIs · REST Principles · HTTP Methods · HTTP Status Codes
- 2Building APIs · 7 topics · Beginner → IntermediateControllers and Endpoints · Routing · Request and Response Models · DTOs · Model Validation · Dependency Injection · Service Layer
- 3Data · 8 topics · IntermediateADO.NET Integration · Entity Framework 6 · SQL Server · Stored Procedures · CRUD API · Pagination · Filtering and Sorting · Searching
- 4Cross-Cutting · 9 topics · Intermediate → AdvancedGlobal Exception Handling · Logging · Swagger with Swashbuckle · Token Authentication · Role-Based Authorization · CORS · API Versioning · Message Handlers · File Upload APIs
- 5Quality & Delivery · 8 topics · AdvancedAPI Testing · Unit Testing · Integration Testing · Performance · Secure API Design · IIS Deployment · Azure App Service Deployment · Migrating to ASP.NET Core
Complete topic list
36 topicsREST Foundations
4 topics- Introduction to Web APIsBeginner6m
- REST PrinciplesBeginner6m
- HTTP MethodsBeginner6m
- HTTP Status CodesBeginner6m
Building APIs
7 topics- Controllers and EndpointsBeginner6m
- RoutingBeginner5m
- Request and Response ModelsBeginner6m
- DTOsBeginner6m
- Model ValidationBeginner6m
- Dependency InjectionIntermediate6m
- Service LayerIntermediate6m
Data
8 topics- ADO.NET IntegrationIntermediate6m
- Entity Framework 6Intermediate7m
- SQL ServerIntermediate6m
- Stored ProceduresIntermediate6m
- CRUD APIIntermediate7m
- PaginationIntermediate6m
- Filtering and SortingIntermediate6m
- SearchingIntermediate6m
Cross-Cutting
9 topics- Global Exception HandlingIntermediate7m
- LoggingIntermediate6m
- Swagger with SwashbuckleIntermediate5m
- Token AuthenticationIntermediate7m
- Role-Based AuthorizationIntermediate6m
- CORSIntermediate5m
- API VersioningAdvanced5m
- Message HandlersAdvanced6m
- File Upload APIsAdvanced6m
Quality & Delivery
8 topics- API TestingAdvanced5m
- Unit TestingAdvanced6m
- Integration TestingAdvanced6m
- PerformanceAdvanced6m
- Secure API DesignAdvanced7m
- IIS DeploymentAdvanced5m
- Azure App Service DeploymentAdvanced6m
- Migrating to ASP.NET CoreAdvanced6m
Code example
A taste of the track, from Introduction to Web APIs.
// Global.asax.cs — runs once when IIS starts the app
using System.Web;
using System.Web.Http;
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
// App_Start/WebApiConfig.cs — routes and settings
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
}
}
// Controllers/ProductsController.cs
using System.Collections.Generic;
using System.Linq;
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class ProductsController : ApiController
{
private static readonly List<Product> Products = new List<Product>
{
new Product { Id = 1, Name = "Pen", Price = 20m },
new Product { Id = 2, Name = "Notebook", Price = 60m },
};
// GET api/products
public IEnumerable<Product> Get()
{
return Products;
}
// GET api/products/2
public IHttpActionResult Get(int id)
{
var product = Products.FirstOrDefault(p => p.Id == id);
if (product == null) return NotFound();
return Ok(product);
}
}
GET /api/products
200 OK
Content-Type: application/json; charset=utf-8
[{"Id":1,"Name":"Pen","Price":20.0},{"Id":2,"Name":"Notebook","Price":60.0}]
GET /api/products/2
200 OK
{"Id":2,"Name":"Notebook","Price":60.0}
GET /api/products/99
404 Not FoundPractice exercises
Add a CategoriesController with Get() returning ["Stationery", "Books", "Bags"] and Get(int id) returning the category at that index or 404.
Design REST endpoints (method + URL) for a blog: list posts, read one post, create a post, update a post, delete a post, list comments of a post, add a comment to a post.
Write a TodosController with GET api/todos, POST api/todos (201) and DELETE api/todos/{id} (204 or 404), storing todos in a static Dictionary<int, string>.
Write a RegisterController.Post(RegisterRequest req) that returns 400 if Email is empty, 409 if the email is "taken@example.com", and 201 otherwise.
Write an async action GET api/health that awaits Task.Delay(5) and returns 200 with { status = "ok", time = DateTime.UtcNow }.
Add three routes to an OrdersController: GET api/orders/{id} for numeric ids, GET api/orders/{orderNumber} only for values like "ORD-1234", and GET api/customers/{customerId}/orders.
Show all 36 exercises
Write an action GET api/reports/sales that binds a [FromUri] SalesQuery with From and To (DateTime) and Region (string, optional), and returns them back. What query string calls it?
Write an UpdatePriceDto with only NewPrice, and an action PUT api/products/{id}/price that applies it to the entity and returns a ProductListItemDto-like anonymous object.
Create a RegisterRequest with Email ([Required], [EmailAddress]), Password ([Required], [StringLength(64, MinimumLength = 8)]) and Age (int? with [Range(13, 120)]). Write an action that returns 400 with ModelState when invalid.
Register an ITaxCalculator (implemented by GstCalculator, 18%) as SingleInstance, and write a TaxController that receives it and returns the tax for GET api/tax/{amount}.
Write an ICouponService with ApplyCoupon(string code, decimal total) that throws BusinessRuleException for unknown codes and returns the discounted total for "SAVE10" (10% off). Write a controller action that returns 200 with the new total or 400 with the message.
Write an async method UpdateStockAsync(int productId, int newStock) that runs an UPDATE with parameters and returns true if a row was changed.
Write an action GET api/categories/summary that returns each category name with its product count and average price, using one EF6 query.
Write the CREATE TABLE for dbo.Orders: Id identity PK, ProductId FK to Products, Quantity (1–10 via CHECK), Total DECIMAL(18,2), CreatedAt UTC default, plus an index for “orders of a product, newest first”.
Call a procedure dbo.usp_SearchProducts @Term NVARCHAR(50) that returns Id, Name, Price, using EF6 Database.SqlQuery into a ProductRow class, and return the list from GET api/products/proc-search?term=pen.
Add PATCH api/products/{id}/stock that receives { "Change": -3 } and adjusts stock, returning 409 if stock would go below 0.
Implement keyset pagination: GET api/orders?afterId=4000&limit=20 returns up to 20 orders with Id > afterId ordered by Id, plus nextAfterId (the last Id, or null when there are no more).
Add a createdAfter (DateTime?) filter and a "stock" sort option (lowest stock first unless desc=true) to the filter above.
Support multi-word search: q="blue pen" should return products whose Name contains every word. Write the query-building part.
Extend the exception filter so that UnauthorizedAccessException returns 403 with {"message":"You do not have access to this resource."}.
Add a warning log to an action GET api/products/{id}/stock when stock is below 5, including ProductId and Stock as properties.
Document a POST api/products action: summary, a [ResponseType(typeof(ProductDto))], and response comments for 201 and 400.
Add an [AllowAnonymous] action GET api/profile/public-info inside the [Authorize] ProfileController that returns { app = "Shop" } without a token.
Write GET api/orders/{id} that returns 404 if the order does not exist, 403 if it belongs to another user (unless the caller is Admin), and 200 otherwise. Orders are a static Dictionary<int, string> of id → owner user name.
Read allowed origins from Web.config appSettings key "Cors:Origins" (comma-separated) instead of hard-coding them.
Add an OrdersController in a V2 namespace with [ApiVersion("2.0")] whose GET api/v2/orders/{id} returns { Id, Status, Items = new string[0] }.
Write a CorrelationIdHandler that reads an X-Correlation-Id request header (or creates a new GUID), stores it in request.Properties["CorrelationId"], and adds it to the response headers.
Write a helper IsAllowedImage(string fileName, long size) that returns true only for .jpg/.jpeg/.png up to 2 MB, and use it in an upload action for profile photos.
Write curl commands that (1) update product 1’s name with PUT, (2) request page 2 with pageSize 5, and (3) check that pageSize=1000 is capped.
Write a test proving that Get returns NotFoundResult when the service throws nothing but returns null for id 0, and a test that the service is called exactly once.
Add an integration test proving that a request with an Accept: application/json header returns Content-Type application/json.
Cache the result of an expensive GetDashboardStatsAsync() for 30 seconds in MemoryCache, using a helper GetOrAddAsync(key, factory, seconds).
Write an extension method EnsureOwner(this ApiController controller, string ownerUserName) that throws HttpResponseException with 404 when the current user is not the owner (404 hides that the resource exists).
Write a Web.Staging.config transform that sets the ShopDb connection string to Server=sql-staging;Database=Shop;Integrated Security=True; and Environment to Staging.
Write az commands that (1) add an app setting FeatureFlags:NewCheckout=true to the staging slot only and (2) stream the production logs.
Convert the TimingHandler from the Message Handlers lesson into ASP.NET Core middleware that adds an X-Response-Time-ms header.
Common interview questions
- What is ASP.NET Web API 2?
- What is IHttpActionResult?
- What is a Web API, and how is it different from an MVC site?
- What are the REST constraints?
- How does Web API 2 choose an action?
- Which status codes should a Web API 2 action return?
- What is content negotiation?
- What are message handlers?
- How do you handle exceptions globally in Web API 2?
- How are parameters bound in Web API 2?
- How do you validate input in Web API 2?
- How do you add dependency injection to Web API 2?
- How do you use Entity Framework 6 in an API?
- How do you implement sorting and filtering safely?
- How do you document a Web API 2 service?
- How does token authentication work with OWIN in Web API 2?
- How do you enable CORS in Web API 2?
- How would you migrate a Web API 2 service to ASP.NET Core?
- How do you version a Web API 2 service?
- How do you upload files to a Web API 2 endpoint?
- How do you unit-test Web API 2 controllers?
- What improves Web API 2 performance?
- How do you deploy a Web API 2 service to Azure App Service?