From the initial project setup using the .NET CLI to configuring middleware, controllers, and services, learn every step to build a robust API. Discover best practices for dependency injection, asynchronous actions, and handling exceptions to create scalable, efficient web applications.
1. Setting Up a .NET 8 Web API Project
Concept
Use the .NET CLI to create a new Web API project. This sets up a basic project structure including Program.cs for startup and a WeatherForecast controller as an example.
Code Example
dotnet new webapi -n MyWebApi
2. Program.cs — Minimal API Configuration
Concept
.NET 8 continues the trend towards minimal APIs, allowing you to configure services and endpoints in a simplified and concise manner directly in the Program.cs file.
Code Example
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello, World!");
app.Run();
3. Defining a Controller
Concept
Controllers handle incoming HTTP requests and respond to the client. They are defined by inheriting from ControllerBase and annotating with [ApiController].
Code Example
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
[HttpGet]
public IActionResult Get() => Ok("Hello from MyController");
}
4. Dependency Injection in Controllers
Concept
.NET Core’s built-in dependency injection (DI) makes it easy to manage dependencies. You can inject services into your controllers through their constructors.
Code Example
public class MyService
{
public string GetMessage() => "Injected message";
}
public class MyController : ControllerBase
{
private readonly MyService _myService;
public MyController(MyService myService)
{
_myService = myService;
}
[HttpGet]
public IActionResult Get() => Ok(_myService.GetMessage());
}
5. Configuring Services
Concept
Services (like database contexts, custom services, etc.) are configured in the Program.cs file, making them available for dependency injection throughout your application.
Code Example
builder.Services.AddScoped<MyService>();
6. Environment-Based Configuration
Concept
.NET supports environment-specific configuration files (appsettings.json, appsettings.Development.json, etc.), allowing for different settings based on the application’s environment.
Code Example
// appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Debug"
}
}
}
7. Middleware
Concept
Middleware components form a pipeline that handles requests and responses. Custom middleware can be created for cross-cutting concerns like logging or error handling.
Code Example
app.Use(async (context, next) =>
{
// Custom logic before passing to the next middleware
await next();
// Custom logic after executing the next middleware
});
8. Routing
Concept
Routing in .NET Web API is achieved through attribute routing on controllers and action methods. This allows URLs to be mapped directly to controller actions.
Code Example
[HttpGet("myaction/{id}")]
public IActionResult GetAction(int id) => Ok($"Action with ID = {id}");
9. Model Binding
Concept
Model binding automatically maps data from HTTP requests to action method parameters. It supports complex types, including JSON bodies and query string parameters.
Code Example
public class MyModel
{
public int Id { get; set; }
public string Name { get; set; }
}
[HttpPost]
public IActionResult PostAction([FromBody] MyModel model) => Ok(model);
10. Data Validation
Concept
Data annotations can be used to validate model data. The [ApiController] attribute automatically enforces validation, responding with 400 if the model is invalid.
Code Example
public class MyModel
{
[Required]
public int Id { get; set; }
[StringLength(100)]
public string Name { get; set; }
}
11. Asynchronous Actions
Concept
Asynchronous actions improve scalability by freeing up threads while waiting for I/O operations to complete. Use the async keyword and return Task or Task.
Code Example
[HttpGet("{id}")]
public async Task<IActionResult> GetAsync(int id)
{
var result = await _service.GetByIdAsync(id);
return Ok(result);
}
12. Handling Exceptions Globally
Concept
Global exception handling allows for centralized error processing, logging, and standardized API responses on unhandled exceptions.
Code Example
app.UseExceptionHandler(a => a.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = exceptionHandlerPathFeature.Error;
// Log the exception, generate a custom response, etc.
context.Response.StatusCode = 500;
await context.Response.WriteAsJsonAsync(new { Error = "An unexpected error occurred" });
}));
13. API Versioning
Concept
API versioning helps manage changes to the API over time. The .NET platform supports versioning through query string, URL path, or request header.
Code Example
builder.Services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ReportApiVersions = true;
});
14. Content Negotiation
Concept
Content negotiation allows an API to serve different formats of the response based on the Accept header in the request, enabling support for formats like JSON, XML, etc.
Code Example
builder.Services.AddControllers()
.AddXmlDataContractSerializerFormatters();
15. Custom JSON Serialization Settings
Concept
Customize JSON response formatting, such as camelCase naming or ignoring null values, by configuring JSON serializer settings.
Code Example
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.IgnoreNullValues = true;
});
16. Configuring CORS
Concept
Cross-Origin Resource Sharing (CORS) allows your API to be called from web applications hosted on different domains. Configure the CORS policy as per your requirements.
Code Example
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.WithOrigins("http://example.com"));
});
app.UseCors("AllowSpecificOrigin");
17. Authentication
Concept
Secure your API by enabling authentication, which verifies the identity of users or services making requests.
Code Example
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer(options =>
{
options.Authority = "https://your-auth-server";
options.Audience = "your-api";
});
18. Authorization
Concept
After authentication, authorization determines if an authenticated user has permission to perform an action or access a resource.
Code Example
[Authorize]
public class SecureController : ControllerBase
{
// Action methods here
}
19. Swagger/OpenAPI Integration
Concept
Swagger (OpenAPI) provides interactive documentation for your API, allowing developers to understand and consume it easily.
Code Example
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
app.UseSwagger();
app.UseSwaggerUI();
20. Logging
Concept
.NET Core provides a built-in logging framework that can log messages to various outputs (console, debug window, external services, etc.).
Code Example
logger.LogInformation("This is an informational message");
app.Use(async (context, next) =>
{
logger.LogError("This is an error message before the next middleware");
await next.Invoke();
// Log after calling the next middleware
});
21. Using Entity Framework Core
Concept
Entity Framework Core is an ORM used for data access in .NET applications. It allows you to query and manipulate data using strongly typed objects.
Code Example
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) {}
public DbSet<MyModel> MyModels { get; set; }
}
22. Migrations in Entity Framework Core
Concept
Migrations allow you to apply version control to your database schema by tracking changes in your data models.
Code Example
dotnet ef migrations add InitialCreate
dotnet ef database update
23. Repository Pattern
Concept
The Repository pattern abstracts the data layer, making your application more modular and easier to maintain.
Code Example
public interface IRepository<T>
{
Task<IEnumerable<T>> GetAllAsync();
Task<T> GetByIdAsync(int id);
// Other methods...
}
public class MyRepository<T> : IRepository<T> where T : class
{
private readonly MyDbContext _context;
public MyRepository(MyDbContext context)
{
_context = context;
}
// Implement methods...
}
24. Unit Testing
Concept
Unit testing ensures your Web API functions correctly by testing individual units of code in isolation.
Code Example
public class MyControllerTests
{
[Fact]
public async Task Get_ReturnsExpectedValue()
{
// Arrange
var serviceMock = new Mock<IMyService>();
serviceMock.Setup(service => service.GetAsync()).ReturnsAsync("test");
var controller = new MyController(serviceMock.Object);
// Act
var result = await controller.Get();
// Assert
Assert.Equal("test", result.Value);
}
}
25. Integrating with a Front-end
Concept
.NET Web API can serve as the backend for a front-end application, providing RESTful services.
Code Example
fetch('https://localhost:5001/mycontroller')
.then(response => response.json())
.then(data => console.log(data));
26. Health Checks
Concept
Health checks provide a way to monitor the status of your application and its dependencies, useful for microservices architectures.
Code Example
builder.Services.AddHealthChecks();
app.MapHealthChecks("/health");
27. Using SignalR for Real-time Communication
Concept
SignalR enables real-time web functionality, allowing server-side code to send asynchronous notifications to client-side web applications.
Code Example
public class MyHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
28. Configuring Response Caching
Concept
Response caching reduces the number of requests a server must handle by storing a copy of previously requested resources.
Code Example
[HttpGet("{id}")]
[ResponseCache(Duration = 60)]
public IActionResult GetById(int id)
{
// Retrieve and return your resource
}
29. Static Files
Concept
Serving static files (HTML, CSS, JavaScript, etc.) is essential for backing front-end applications with a .NET Web API.
Code Example
app.UseStaticFiles(); // Enable static file serving
30. Advanced Configuration and Options Pattern
Concept
The options pattern uses classes to represent groups of related settings. Using IOptions, you can access these settings anywhere in your application.
Code Example
public class MySettings
{
public string Setting1 { get; set; }
// Other settings
}
builder.Services.Configure<MySettings>(builder.Configuration.GetSection("MySettings"));
public class MyService
{
private readonly MySettings _settings;
public MyService(IOptions<MySettings> settings)
{
_settings = settings.Value;
}
// Use _settings.Setting1
}
31. Custom Middleware
Concept
Middleware is software that’s assembled into an application pipeline to handle requests and responses. Custom middleware can be created to perform specific tasks.
Code Example
public class MyCustomMiddleware
{
private readonly RequestDelegate _next;
public MyCustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
// Pre-processing logic here
await _next(httpContext); // Call the next middleware in the pipeline
// Post-processing logic here
}
}
// Extension method for easy middleware registration
public static class MyCustomMiddlewareExtensions
{
public static IApplicationBuilder UseMyCustomMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyCustomMiddleware>();
}
}
32. Rate Limiting
Concept
Rate limiting protects your API from overuse by limiting how often a user can make requests within a certain time frame.
Code Example
// Assume using a third-party library like AspNetCoreRateLimit
builder.Services.AddInMemoryRateLimiting();
builder.Services.Configure<IpRateLimitOptions>(options =>
{
options.GeneralRules = new List<RateLimitRule>
{
new RateLimitRule
{
Endpoint = "*",
Limit = 100,
Period = "1h"
}
};
});
33. API Keys Authentication
Concept
API keys are a simple way to authenticate and authorize API calls. They’re passed from client to server either in the query string or header.
Code Example
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private const string APIKEYNAME = "x-api-key";
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("API Key was not provided.");
return;
}
// Validate the extracted API Key here...
await _next(context);
}
}
34. Output Caching
Concept
Output caching allows you to store the response to a request. Subsequent requests can be served from the cache, significantly improving performance.
Code Example
[HttpGet]
[ResponseCache(Duration = 120, Location = ResponseCacheLocation.Client, NoStore = false)]
public IActionResult Get()
{
// Your logic here
}
35. Background Tasks
Concept
Background tasks enable operations to run in the background, independent of user requests, like sending emails or processing long-running jobs.
Code Example
public class MyBackgroundService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Your background task logic here
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
}
}
}
36. WebSockets
Concept
WebSockets provide a full-duplex communication channel over a single, long-lived connection, ideal for real-time applications.
Code Example
app.UseWebSockets();
app.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
// Handle the WebSocket request here
}
else
{
await next();
}
});
37. Request Localization
Concept
Request localization provides a way to localize content for different cultures and languages, based on the request’s information.
Code Example
var supportedCultures = new[] { "en-US", "fr-FR" };
var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
38. Integrating with GraphQL
Concept
GraphQL is a query language for APIs. Integrating a .NET Web API with GraphQL allows for more efficient data retrieval.
Code Example
// Assume using a library like HotChocolate
builder.Services
.AddGraphQLServer()
.AddQueryType<Query>();
app.MapGraphQL();
39. Monitoring and Telemetry
Concept
Monitoring and telemetry involve collecting, analyzing, and acting on data about your application’s performance and usage.
Code Example
// Assume using Application Insights
builder.Services.AddApplicationInsightsTelemetry("YOUR_INSTRUMENTATION_KEY");
40. SignalR Hubs and Real-time Communication
Concept
SignalR is a library that simplifies adding real-time web functionality to apps. Real-time web functionality is the ability to have server code push content to connected clients instantly as it happens, not requiring the server to wait for a client to request new data. SignalR is perfect for developing chat applications, real-time dashboards, and more interactive web applications.
Code Example
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
// Call the broadcastMessage method to update clients.
await Clients.All.SendAsync("broadcastMessage", user, message);
}
}
// Startup or Program.cs
app.MapHub<ChatHub>("/chathub");
Integration in Program.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Other configurations...
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/chathub");
});
}
41. Advanced Entity Framework Core — Relationships
Concept
Entity Framework Core allows for the mapping of complex relationships between entities, such as one-to-one, one-to-many, and many-to-many.
Code Example
public class Author
{
public int AuthorId { get; set; }
public string Name { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
}
42. Custom Validation Attributes
Concept
Custom validation attributes allow you to define your validation logic for data models, extending the built-in validation attributes.
Code Example
public class MyCustomValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// Your custom validation logic here
if (value is int intValue && intValue > 0)
{
return ValidationResult.Success;
}
return new ValidationResult("Value must be positive");
}
}
public class MyModel
{
[MyCustomValidationAttribute]
public int MyProperty { get; set; }
}
43. Advanced Configuration Scenarios
Concept
.NET’s options pattern supports complex configuration scenarios, including nested objects, lists, and validation.
Code Example
public class MyOptions
{
public MyNestedOptions Nested { get; set; }
public List<string> Items { get; set; }
}
public class MyNestedOptions
{
public string Key { get; set; }
}
// In Program.cs or Startup.cs
builder.Services.Configure<MyOptions>(builder.Configuration.GetSection("MyOptions"));
44. Performance Monitoring and Profiling
Concept
Monitoring and profiling an application can identify bottlenecks and inefficiencies, essential for optimizing performance.
Code Example
app.UseMiniProfiler();
45. API Documentation with Swagger and XML Comments
Concept
Enhance your API documentation by integrating XML comments into your Swagger UI, providing a richer experience for developers consuming your API.
Code Example
builder.Services.AddSwaggerGen(c =>
{
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
46. Globalization and Localization
Concept
Globalization and localization allow your application to support multiple languages and cultures, making it accessible to a global audience.
Code Example
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
app.UseRequestLocalization(app.Services.GetRequiredService<IOptions<RequestLocalizationOptions>>().Value);
47. Security Headers
Concept
Improving the security of your web application by adding various HTTP headers can protect against common attacks and vulnerabilities.
Code Example
app.UseHsts();
app.UseXContentTypeOptions();
app.UseReferrerPolicy(opts => opts.NoReferrer());
app.UseXXssProtection(options => options.EnabledWithBlockMode());
app.UseXfo(options => options.Deny());
48. Feature Flags
Concept
Feature flags allow you to toggle features of your application on and off without deploying new code, facilitating easier testing and rollouts.
Code Example
// Using a library like Microsoft.FeatureManagement
builder.Services.AddFeatureManagement();
49. Blazor Integration
Concept
Blazor allows you to build interactive web UIs using C# instead of JavaScript. Integrating Blazor with Web API provides a seamless full-stack development experience.
Code Example
// In a Blazor Server app
@code {
private IEnumerable<WeatherForecast> forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}
}
50. Advanced Middleware for Response Compression
Concept
Response compression can reduce the size of your API responses, improving load times for clients over slow networks.
Code Example
builder.Services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.EnableForHttps = true;
});
app.UseResponseCompression();
C# Programming🚀
Thank you for being a part of the C# community! Before you leave:
If you’ve made it this far, please show your appreciation with a clap and follow the author! 👏️️
Follow us: X | LinkedIn | Dev.to | Hashnode | Newsletter | Tumblr
Visit our other platforms: GitHub | Instagram | Tiktok | Quora | Daily.dev
More content at C# Programming