🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Fonctionnalités Web Windows - Exemples C#
Exemples complets de fonctionnalités web en C# pour plateforme Windows incluant routage, middleware et service fichiers statiques
💻 Routage Requêtes HTTP csharp
🟡 intermediate
⭐⭐⭐
Implémenter routage flexible requêtes HTTP avec gestion paramètres et contraintes route
⏱️ 25 min
🏷️ csharp, routing, http, web, windows
Prerequisites:
HTTP basics, Regular expressions, Async programming
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Net;
using System.IO;
// HTTP Request and Response classes
public class HttpRequest
{
public string Method { get; set; }
public string Path { get; set; }
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
public string Body { get; set; }
public Dictionary<string, string> QueryParameters { get; set; } = new Dictionary<string, string>();
public Dictionary<string, string> PathParameters { get; set; } = new Dictionary<string, string>();
}
public class HttpResponse
{
public int StatusCode { get; set; } = 200;
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
public string Body { get; set; }
public void SetJson(string json)
{
Headers["Content-Type"] = "application/json";
Body = json;
}
public void SetHtml(string html)
{
Headers["Content-Type"] = "text/html";
Body = html;
}
public void SetText(string text)
{
Headers["Content-Type"] = "text/plain";
Body = text;
}
public void SetFile(string filePath, string contentType = null)
{
if (File.Exists(filePath))
{
var fileInfo = new FileInfo(filePath);
Headers["Content-Type"] = contentType ?? GetContentType(fileInfo.Extension);
Headers["Content-Length"] = fileInfo.Length.ToString();
Body = File.ReadAllText(filePath);
}
}
private string GetContentType(string extension)
{
return extension.ToLower() switch
{
".html" or ".htm" => "text/html",
".css" => "text/css",
".js" => "application/javascript",
".json" => "application/json",
".png" => "image/png",
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".svg" => "image/svg+xml",
_ => "application/octet-stream"
};
}
}
// Route delegate type
public delegate Task<HttpResponse> RouteHandler(HttpRequest request);
// Route class
public class Route
{
public string Method { get; set; }
public string Pattern { get; set; }
public RouteHandler Handler { get; set; }
public Regex CompiledPattern { get; set; }
public Route(string method, string pattern, RouteHandler handler)
{
Method = method.ToUpper();
Pattern = pattern;
Handler = handler;
CompiledPattern = CompilePattern(pattern);
}
private Regex CompilePattern(string pattern)
{
// Convert route pattern to regex
string regexPattern = "^" + Regex.Escape(pattern)
.Replace(@"\*", ".*")
.Replace(@":", @"(?<") + @"(?<name>[^/]+)" + @">" + @")" + @"";
return new Regex(regexPattern);
}
public bool Matches(string method, string path, out Dictionary<string, string> parameters)
{
parameters = new Dictionary<string, string>();
if (Method != "*" && Method != method.ToUpper())
return false;
Match match = CompiledPattern.Match(path);
if (!match.Success)
return false;
// Extract parameter values
foreach (Group group in match.Groups)
{
if (group.Name != "0" && !int.TryParse(group.Name, out _))
{
parameters[group.Name] = group.Value;
}
}
return true;
}
}
// Router class
public class Router
{
private readonly List<Route> _routes = new List<Route>();
// 1. Add route methods
public void AddRoute(string method, string pattern, RouteHandler handler)
{
var route = new Route(method, pattern, handler);
_routes.Add(route);
Console.WriteLine($"Added route: {method} {pattern}");
}
public void Get(string pattern, RouteHandler handler)
{
AddRoute("GET", pattern, handler);
}
public void Post(string pattern, RouteHandler handler)
{
AddRoute("POST", pattern, handler);
}
public void Put(string pattern, RouteHandler handler)
{
AddRoute("PUT", pattern, handler);
}
public void Delete(string pattern, RouteHandler handler)
{
AddRoute("DELETE", pattern, handler);
}
public void Any(string pattern, RouteHandler handler)
{
AddRoute("*", pattern, handler);
}
// 2. Route matching
public async Task<HttpResponse> RouteRequest(HttpRequest request)
{
foreach (var route in _routes)
{
if (route.Matches(request.Method, request.Path, out var parameters))
{
request.PathParameters = parameters;
Console.WriteLine($"Route matched: {request.Method} {request.Pattern}");
try
{
return await route.Handler(request);
}
catch (Exception ex)
{
Console.WriteLine($"Error handling route: {ex.Message}");
return CreateErrorResponse(500, "Internal Server Error");
}
}
}
return CreateErrorResponse(404, "Not Found");
}
private HttpResponse CreateErrorResponse(int statusCode, string message)
{
var response = new HttpResponse
{
StatusCode = statusCode,
Body = $"{{"error": "{message}", "status": {statusCode}}}"
};
response.SetJson(response.Body);
return response;
}
}
// Routing examples
public class RoutingExamples
{
private Router _router;
public RoutingExamples()
{
_router = new Router();
SetupRoutes();
}
// 3. Setup example routes
private void SetupRoutes()
{
// Home page
_router.Get("/", async (request) =>
{
var response = new HttpResponse();
response.SetHtml(@"
<html>
<head><title>Web Routing Examples</title></head>
<body>
<h1>Welcome to Web Routing Examples</h1>
<p>Try these routes:</p>
<ul>
<li><a href='/api/hello'>GET /api/hello</a></li>
<li><a href='/api/hello/John'>GET /api/hello/John</a></li>
<li>POST /api/users (with JSON body)</li>
<li><a href='/api/users/123'>GET /api/users/123</a></li>
<li><a href='/api/search?q=csharp'>GET /api/search?q=csharp</a></li>
<li><a href='/files/test.txt'>GET /files/test.txt</a></li>
</ul>
</body>
</html>
");
return response;
});
// Simple API endpoint
_router.Get("/api/hello", async (request) =>
{
var response = new HttpResponse();
var name = request.PathParameters.GetValueOrDefault("name", "World");
var json = $"{{"message": "Hello, {name}!", "timestamp": "{DateTime.Now:yyyy-MM-dd HH:mm:ss}"}}";
response.SetJson(json);
return response;
});
// Parameterized route
_router.Get("/api/hello/:name", async (request) =>
{
var response = new HttpResponse();
var name = request.PathParameters.GetValueOrDefault("name", "Unknown");
var json = $"{{"greeting": "Hello, {name}!", "method": "{request.Method}", "path": "{request.Path}"}}";
response.SetJson(json);
return response;
});
// User endpoints
_router.Get("/api/users/:id", async (request) =>
{
var response = new HttpResponse();
var userId = request.PathParameters.GetValueOrDefault("id", "0");
var user = new
{
id = userId,
name = $"User {userId}",
email = $"user{userId}@example.com",
created = DateTime.Now.AddDays(-30).ToString("yyyy-MM-dd")
};
var json = System.Text.Json.JsonSerializer.Serialize(user);
response.SetJson(json);
return response;
});
_router.Post("/api/users", async (request) =>
{
var response = new HttpResponse();
// In a real app, you'd parse and validate the request body
var newUser = new
{
id = new Random().Next(1000, 9999).ToString(),
status = "created",
message = "User created successfully",
timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
};
var json = System.Text.Json.JsonSerializer.Serialize(newUser);
response.SetJson(json);
return response;
});
// Search endpoint with query parameters
_router.Get("/api/search", async (request) =>
{
var response = new HttpResponse();
var query = request.QueryParameters.GetValueOrDefault("q", "");
var limit = request.QueryParameters.GetValueOrDefault("limit", "10");
var searchResult = new
{
query = query,
results = new[] { $"Result 1 for {query}", $"Result 2 for {query}", $"Result 3 for {query}" },
limit = int.Parse(limit),
total = 3
};
var json = System.Text.Json.JsonSerializer.Serialize(searchResult);
response.SetJson(json);
return response;
});
// File serving route
_router.Get("/files/*", async (request) =>
{
var response = new HttpResponse();
var filename = request.PathParameters.GetValueOrDefault("path", "");
var filePath = Path.Combine("wwwroot", filename);
if (File.Exists(filePath))
{
response.SetFile(filePath);
}
else
{
response.StatusCode = 404;
response.Body = "File not found";
}
return response;
});
// Wildcard route for API
_router.Any("/api/*", async (request) =>
{
var response = new HttpResponse();
response.StatusCode = 405; // Method Not Allowed
var errorJson = $"{{"error": "Method not allowed", "path": "{request.Path}"}}";
response.SetJson(errorJson);
return response;
});
}
// 4. Simulate HTTP requests for testing
public async Task SimulateRequests()
{
Console.WriteLine("=== Simulating HTTP Requests ===\n");
var requests = new[]
{
new HttpRequest { Method = "GET", Path = "/" },
new HttpRequest { Method = "GET", Path = "/api/hello" },
new HttpRequest { Method = "GET", Path = "/api/hello/John" },
new HttpRequest { Method = "POST", Path = "/api/users", Body = "{\"name\": \"John Doe\", \"email\": \"[email protected]\"}" },
new HttpRequest { Method = "GET", Path = "/api/users/123" },
new HttpRequest { Method = "GET", Path = "/api/search", QueryParameters = new Dictionary<string, string> { { "q", "csharp" }, { "limit", "5" } } },
new HttpRequest { Method = "GET", Path = "/api/nonexistent" },
new HttpRequest { Method = "GET", Path = "/files/style.css" },
new HttpRequest { Method = "PUT", Path = "/api/test" }
};
foreach (var request in requests)
{
Console.WriteLine($"Request: {request.Method} {request.Path}");
if (request.QueryParameters.Count > 0)
{
var queryString = string.Join("&", request.QueryParameters.Select(kvp => $"{kvp.Key}={kvp.Value}"));
Console.WriteLine($" Query: {queryString}");
}
if (!string.IsNullOrEmpty(request.Body))
{
Console.WriteLine($" Body: {request.Body}");
}
var response = await _router.RouteRequest(request);
Console.WriteLine($"Response: {response.StatusCode} ({response.Body?.Length ?? 0} bytes)");
if (response.Headers.ContainsKey("Content-Type"))
{
Console.WriteLine($" Content-Type: {response.Headers["Content-Type"]}");
}
if (response.Body != null && response.Body.Length < 200)
{
Console.WriteLine($" Body: {response.Body}");
}
Console.WriteLine();
}
}
// 5. Advanced routing with route constraints
public void AdvancedRoutingExamples()
{
Console.WriteLine("=== Advanced Routing Examples ===");
var router = new Router();
// Route with constraint (numeric ID)
router.Get("/api/products/:id(\d+)", async (request) =>
{
var id = request.PathParameters["id"];
Console.WriteLine($"Numeric product ID: {id}");
var response = new HttpResponse();
response.SetJson($"{{"product": {id}, "type": "numeric"}}");
return response;
});
// Route with constraint (email)
router.Get("/api/users/:email(.+@.+\..+)", async (request) =>
{
var email = request.PathParameters["email"];
Console.WriteLine($"Email parameter: {email}");
var response = new HttpResponse();
response.SetJson($"{{"email": "{email}", "valid": true}}");
return response;
});
// Optional parameter
router.Get("/api/posts/:year/:month?", async (request) =>
{
var year = request.PathParameters["year"];
var month = request.PathParameters.GetValueOrDefault("month", "all");
Console.WriteLine($"Posts for year {year}, month {month}");
var response = new HttpResponse();
response.SetJson($"{{"year": "{year}", "month": "{month}"}}");
return response;
});
Console.WriteLine("Advanced routes configured successfully");
}
}
// Main demonstration
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("=== C# Web Routing Examples ===\n");
try
{
var examples = new RoutingExamples();
// Run basic routing examples
await examples.SimulateRequests();
// Show advanced routing
examples.AdvancedRoutingExamples();
Console.WriteLine("Web routing examples completed successfully!");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
💻 Middleware Traitement Requêtes csharp
🟡 intermediate
⭐⭐⭐
Implémenter pipeline middleware pour traitement requêtes, logging, authentification et modification réponses
⏱️ 30 min
🏷️ csharp, middleware, pipeline, web, windows
Prerequisites:
HTTP basics, Async programming, Pipeline patterns
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Text;
using System.Security.Cryptography;
// HTTP Context
public class HttpContext
{
public HttpRequest Request { get; set; }
public HttpResponse Response { get; set; }
public IDictionary<string, object> Items { get; set; } = new Dictionary<string, object>();
public HttpContext()
{
Request = new HttpRequest();
Response = new HttpResponse();
}
}
// Middleware delegate
public delegate Task<HttpContext> MiddlewareDelegate(HttpContext context, Func<HttpContext, Task<HttpContext>> next);
// Middleware pipeline builder
public class MiddlewarePipeline
{
private readonly List<MiddlewareDelegate> _middlewares = new List<MiddlewareDelegate>();
// 1. Add middleware to pipeline
public MiddlewarePipeline Use(MiddlewareDelegate middleware)
{
_middlewares.Add(middleware);
Console.WriteLine($"Added middleware: {middleware.Method.Name}");
return this;
}
// 2. Build the pipeline
public Func<HttpContext, Task<HttpContext>> Build()
{
Func<HttpContext, Task<HttpContext>> pipeline = context => Task.FromResult(context);
// Build pipeline in reverse order
for (int i = _middlewares.Count - 1; i >= 0; i--)
{
var middleware = _middlewares[i];
var next = pipeline;
pipeline = context => middleware(context, next);
}
return pipeline;
}
// 3. Execute pipeline
public async Task<HttpContext> ExecuteAsync(HttpContext context)
{
var pipeline = Build();
return await pipeline(context);
}
}
// Middleware implementations
public static class Middlewares
{
// 4. Request logging middleware
public static async Task<HttpContext> RequestLogging(HttpContext context, Func<HttpContext, Task<HttpContext>> next)
{
var stopwatch = Stopwatch.StartNew();
var requestId = Guid.NewGuid().ToString("N")[..8];
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] [{requestId}] " +
$"{context.Request.Method} {context.Request.Path} - Started");
try
{
// Add request ID to context
context.Items["RequestId"] = requestId;
context.Items["StartTime"] = DateTime.Now;
// Call next middleware
context = await next(context);
stopwatch.Stop();
var duration = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] [{requestId}] " +
$"{context.Request.Method} {context.Request.Path} - " +
$"{context.Response.StatusCode} ({duration}ms)");
return context;
}
catch (Exception ex)
{
stopwatch.Stop();
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] [{requestId}] " +
$"{context.Request.Method} {context.Request.Path} - " +
$"ERROR: {ex.Message} ({stopwatch.ElapsedMilliseconds}ms)");
throw;
}
}
// 5. Authentication middleware
public static async Task<HttpContext> Authentication(HttpContext context, Func<HttpContext, Task<HttpContext>> next)
{
Console.WriteLine($"[Auth] Checking authentication for {context.Request.Method} {context.Request.Path}");
// Check for Authorization header
if (context.Request.Headers.TryGetValue("Authorization", out var authHeader))
{
Console.WriteLine($"[Auth] Authorization header found: {authHeader}");
// Simple token validation (in production, use proper JWT validation)
if (IsValidToken(authHeader))
{
context.Items["User"] = new { Id = "123", Name = "John Doe", Role = "User" };
context.Items["Authenticated"] = true;
Console.WriteLine("[Auth] Authentication successful");
}
else
{
context.Response.StatusCode = 401;
context.Response.SetJson(@"{""error"": ""Invalid or expired token""}");
Console.WriteLine("[Auth] Authentication failed - Invalid token");
return context;
}
}
else
{
// Allow anonymous access for some routes
if (IsPublicRoute(context.Request.Path))
{
context.Items["Authenticated"] = false;
Console.WriteLine("[Auth] Public route - No authentication required");
}
else
{
context.Response.StatusCode = 401;
context.Response.SetJson(@"{""error"": ""Authentication required""}");
Console.WriteLine("[Auth] Authentication required");
return context;
}
}
return await next(context);
}
// 6. CORS middleware
public static async Task<HttpContext> Cors(HttpContext context, Func<HttpContext, Task<HttpContext>> next)
{
Console.WriteLine($"[CORS] Processing CORS for {context.Request.Method} {context.Request.Path}");
// Add CORS headers
context.Response.Headers["Access-Control-Allow-Origin"] = "*";
context.Response.Headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS";
context.Response.Headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization";
// Handle preflight requests
if (context.Request.Method == "OPTIONS")
{
context.Response.StatusCode = 200;
Console.WriteLine("[CORS] Preflight request handled");
return context;
}
return await next(context);
}
// 7. Rate limiting middleware
public static async Task<HttpContext> RateLimiting(HttpContext context, Func<HttpContext, Task<HttpContext>> next)
{
var clientId = GetClientId(context);
Console.WriteLine($"[RateLimit] Processing request for client: {clientId}");
// Simple rate limiting (in production, use a proper cache like Redis)
if (IsRateLimited(clientId))
{
context.Response.StatusCode = 429;
context.Response.SetJson(@"{""error"": ""Rate limit exceeded""}");
context.Response.Headers["Retry-After"] = "60";
Console.WriteLine($"[RateLimit] Client {clientId} rate limited");
return context;
}
// Record request for rate limiting
RecordRequest(clientId);
return await next(context);
}
// 8. Request body parsing middleware
public static async Task<HttpContext> RequestBodyParsing(HttpContext context, Func<HttpContext, Task<HttpContext>> next)
{
if (!string.IsNullOrEmpty(context.Request.Body))
{
Console.WriteLine($"[BodyParser] Parsing request body ({context.Request.Body.Length} characters)");
// Try to parse as JSON
if (context.Request.Headers.TryGetValue("Content-Type", out var contentType) &&
contentType.Contains("application/json"))
{
try
{
// Simple JSON parsing (in production, use proper JSON library)
var parsedBody = ParseJsonSimple(context.Request.Body);
context.Items["ParsedBody"] = parsedBody;
Console.WriteLine($"[BodyParser] JSON parsed successfully");
}
catch (Exception ex)
{
context.Response.StatusCode = 400;
context.Response.SetJson(@"{""error"": ""Invalid JSON""}");
Console.WriteLine($"[BodyParser] JSON parsing failed: {ex.Message}");
return context;
}
}
}
return await next(context);
}
// 9. Response compression middleware
public static async Task<HttpContext> ResponseCompression(HttpContext context, Func<HttpContext, Task<HttpContext>> next)
{
context = await next(context);
if (context.Response.Body != null && context.Response.Body.Length > 1024)
{
Console.WriteLine($"[Compression] Compressing response ({context.Response.Body.Length} bytes)");
// Simple compression simulation
var originalSize = context.Response.Body.Length;
var compressedBody = CompressString(context.Response.Body);
context.Response.Body = compressedBody;
context.Response.Headers["Content-Encoding"] = "gzip";
var compressionRatio = (1 - (double)compressedBody.Length / originalSize) * 100;
Console.WriteLine($"[Compression] Compressed from {originalSize} to {compressedBody.Length} bytes ({compressionRatio:F1}% reduction)");
}
return context;
}
// 10. Error handling middleware
public static async Task<HttpContext> ErrorHandling(HttpContext context, Func<HttpContext, Task<HttpContext>> next)
{
try
{
return await next(context);
}
catch (Exception ex)
{
Console.WriteLine($"[Error] Unhandled exception: {ex.Message}");
Console.WriteLine($"[Error] Stack trace: {ex.StackTrace}");
context.Response.StatusCode = 500;
context.Response.SetJson($@"{{
""error"": ""Internal server error"",
""message"": ""An unexpected error occurred"",
""requestId"": ""{context.Items.GetValueOrDefault(""RequestId"", ""unknown"")}""
}}");
return context;
}
}
// 11. Final handler middleware
public static async Task<HttpContext> FinalHandler(HttpContext context, Func<HttpContext, Task<HttpContext>> next)
{
Console.WriteLine($"[Handler] Final handler for {context.Request.Method} {context.Request.Path}");
// Generate response based on request
if (context.Response.StatusCode == 0)
{
context.Response.StatusCode = 200;
var response = new
{
message = "Request processed successfully",
method = context.Request.Method,
path = context.Request.Path,
timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
authenticated = context.Items.GetValueOrDefault("Authenticated", false),
requestId = context.Items.GetValueOrDefault("RequestId", "")
};
context.Response.SetJson(System.Text.Json.JsonSerializer.Serialize(response));
}
return await next(context);
}
// Helper methods
private static bool IsValidToken(string token)
{
// Simple validation - check if token starts with "Bearer "
return token.StartsWith("Bearer ") && token.Length > 20;
}
private static bool IsPublicRoute(string path)
{
var publicRoutes = new[] { "/", "/health", "/api/public" };
return Array.Exists(publicRoutes, route => path == route || path.StartsWith(route));
}
private static string GetClientId(HttpContext context)
{
// Use IP address or User-Agent as client identifier
return context.Request.Headers.GetValueOrDefault("X-Forwarded-For", "unknown");
}
private static bool IsRateLimited(string clientId)
{
// Simple rate limiting logic (in production, use proper time windows)
return false; // Always allow for this example
}
private static void RecordRequest(string clientId)
{
// Record request for rate limiting (in production, store in cache/database)
Console.WriteLine($"[RateLimit] Recorded request for client: {clientId}");
}
private static object ParseJsonSimple(string json)
{
// Simple JSON parsing placeholder
return new { parsed = true, length = json.Length };
}
private static string CompressString(string input)
{
// Simulate compression by removing whitespace
return string.Join("", input.Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
}
// Middleware pipeline examples
public class MiddlewareExamples
{
// 12. Create different pipeline configurations
public static async Task RunPipelineExamples()
{
Console.WriteLine("=== Middleware Pipeline Examples ===\n");
// Example 1: Basic pipeline
Console.WriteLine("1. Basic Pipeline:");
await CreateBasicPipeline().ExecuteAsync(CreateSampleRequest("/"));
Console.WriteLine("\n2. Authentication Pipeline:");
await CreateAuthenticatedPipeline().ExecuteAsync(CreateAuthenticatedRequest("/api/protected"));
Console.WriteLine("\n3. Full-featured Pipeline:");
await CreateFullFeaturedPipeline().ExecuteAsync(CreateFullFeaturedRequest());
Console.WriteLine("\n4. Error Handling Pipeline:");
await CreateErrorHandlingPipeline().ExecuteAsync(CreateErrorRequest());
}
private static MiddlewarePipeline CreateBasicPipeline()
{
return new MiddlewarePipeline()
.Use(Middlewares.RequestLogging)
.Use(Middlewares.Cors)
.Use(Middlewares.FinalHandler);
}
private static MiddlewarePipeline CreateAuthenticatedPipeline()
{
return new MiddlewarePipeline()
.Use(Middlewares.RequestLogging)
.Use(Middlewares.Authentication)
.Use(Middlewares.FinalHandler);
}
private static MiddlewarePipeline CreateFullFeaturedPipeline()
{
return new MiddlewarePipeline()
.Use(Middlewares.ErrorHandling)
.Use(Middlewares.RequestLogging)
.Use(Middlewares.Cors)
.Use(Middlewares.RateLimiting)
.Use(Middlewares.Authentication)
.Use(Middlewares.RequestBodyParsing)
.Use(Middlewares.ResponseCompression)
.Use(Middlewares.FinalHandler);
}
private static MiddlewarePipeline CreateErrorHandlingPipeline()
{
return new MiddlewarePipeline()
.Use(Middlewares.ErrorHandling)
.Use(Middlewares.RequestLogging)
.Use((context, next) => throw new InvalidOperationException("This is a test exception"));
}
private static HttpContext CreateSampleRequest(string path)
{
return new HttpContext
{
Request = new HttpRequest
{
Method = "GET",
Path = path,
Headers = new Dictionary<string, string>
{
["User-Agent"] = "Test Client"
}
}
};
}
private static HttpContext CreateAuthenticatedRequest(string path)
{
return new HttpContext
{
Request = new HttpRequest
{
Method = "GET",
Path = path,
Headers = new Dictionary<string, string>
{
["Authorization"] = "Bearer valid-jwt-token-here",
["Content-Type"] = "application/json"
}
}
};
}
private static HttpContext CreateFullFeaturedRequest(string path = "/api/data")
{
return new HttpContext
{
Request = new HttpRequest
{
Method = "POST",
Path = path,
Headers = new Dictionary<string, string>
{
["Authorization"] = "Bearer valid-jwt-token-here",
["Content-Type"] = "application/json",
["User-Agent"] = "Full-Featured Test Client"
},
Body = @"{""name"": ""Test"", ""value"": 123}"
}
};
}
private static HttpContext CreateErrorRequest()
{
return new HttpContext
{
Request = new HttpRequest
{
Method = "GET",
Path = "/api/error",
Headers = new Dictionary<string, string>
{
["User-Agent"] = "Error Test Client"
}
}
};
}
}
// Main program
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("=== C# Middleware Examples ===\n");
try
{
await MiddlewareExamples.RunPipelineExamples();
Console.WriteLine("\nMiddleware examples completed successfully!");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
💻 Service Fichiers Statiques csharp
🟡 intermediate
⭐⭐⭐
Implémenter service efficace fichiers statiques avec détection types MIME, en-têtes cache et caractéristiques sécurité
⏱️ 25 min
🏷️ csharp, static files, web server, windows
Prerequisites:
File system operations, HTTP basics, Security concepts
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Text;
using System.Security.Cryptography;
using System.Linq;
// Static file configuration
public class StaticFileOptions
{
public string RootDirectory { get; set; } = "wwwroot";
public bool EnableDirectoryBrowsing { get; set; } = false;
public bool EnableDefaultFiles { get; set; } = true;
public string[] DefaultFileNames { get; set; } = { "index.html", "default.html", "home.html" };
public bool EnableETag { get; set; } = true;
public bool EnableLastModified { get; set; } = true;
public int CacheMaxAge { get; set; } = 3600; // 1 hour
public bool EnableCompression { get; set; } = true;
public bool EnableRangeRequests { get; set; } = true;
public string[] AllowedExtensions { get; set; } =
{
".html", ".htm", ".css", ".js", ".json", ".xml", ".txt", ".md",
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".ico",
".pdf", ".zip", ".tar", ".gz", ".woff", ".woff2", ".ttf", ".eot"
};
}
// Static file server
public class StaticFileServer
{
private readonly StaticFileOptions _options;
public StaticFileServer(StaticFileOptions options = null)
{
_options = options ?? new StaticFileOptions();
// Ensure root directory exists
if (!Directory.Exists(_options.RootDirectory))
{
Directory.CreateDirectory(_options.RootDirectory);
Console.WriteLine($"Created root directory: {_options.RootDirectory}");
}
CreateSampleFiles();
}
// 1. Serve static file
public async Task<HttpResponse> ServeFileAsync(string relativePath)
{
Console.WriteLine($"[StaticFile] Request for: {relativePath}");
try
{
// Normalize path
relativePath = NormalizePath(relativePath);
// Security: Check for path traversal attempts
if (IsPathTraversal(relativePath))
{
Console.WriteLine("[StaticFile] Path traversal attempt detected");
return CreateErrorResponse(403, "Forbidden");
}
// Resolve full path
string fullPath = Path.Combine(_options.RootDirectory, relativePath);
// Check if path exists
if (!File.Exists(fullPath))
{
// Try to serve default file if it's a directory
if (Directory.Exists(fullPath))
{
if (_options.EnableDefaultFiles)
{
foreach (string defaultFile in _options.DefaultFileNames)
{
string defaultPath = Path.Combine(fullPath, defaultFile);
if (File.Exists(defaultPath))
{
return await ServeFileAsync(Path.Combine(relativePath, defaultFile));
}
}
}
// Directory browsing
if (_options.EnableDirectoryBrowsing)
{
return await ServeDirectoryListingAsync(relativePath, fullPath);
}
}
Console.WriteLine($"[StaticFile] File not found: {fullPath}");
return CreateErrorResponse(404, "File Not Found");
}
// Check file extension
string extension = Path.GetExtension(fullPath).ToLowerInvariant();
if (!_options.AllowedExtensions.Contains(extension))
{
Console.WriteLine($"[StaticFile] File extension not allowed: {extension}");
return CreateErrorResponse(403, "File type not allowed");
}
// Get file info
var fileInfo = new FileInfo(fullPath);
var response = new HttpResponse();
// Set content type
string contentType = GetContentType(extension);
response.Headers["Content-Type"] = contentType;
// Set caching headers
if (_options.EnableETag)
{
string eTag = GenerateETag(fileInfo);
response.Headers["ETag"] = eTag;
response.Headers["Cache-Control"] = $"public, max-age={_options.CacheMaxAge}";
}
if (_options.EnableLastModified)
{
response.Headers["Last-Modified"] = fileInfo.LastWriteTime.ToString("R");
}
// Set file size
response.Headers["Content-Length"] = fileInfo.Length.ToString();
// Read file content
if (IsTextFile(extension))
{
response.Body = await File.ReadAllTextAsync(fullPath);
}
else
{
// For binary files, in a real server you'd stream the content
response.Body = Convert.ToBase64String(await File.ReadAllBytesAsync(fullPath));
response.Headers["Content-Transfer-Encoding"] = "base64";
}
Console.WriteLine($"[StaticFile] Served: {relativePath} ({fileInfo.Length:N0} bytes, {contentType})");
return response;
}
catch (Exception ex)
{
Console.WriteLine($"[StaticFile] Error serving file {relativePath}: {ex.Message}");
return CreateErrorResponse(500, "Internal Server Error");
}
}
// 2. Serve directory listing
private async Task<HttpResponse> ServeDirectoryListingAsync(string relativePath, string fullPath)
{
Console.WriteLine($"[StaticFile] Serving directory listing for: {relativePath}");
var response = new HttpResponse();
response.Headers["Content-Type"] = "text/html";
var directories = Directory.GetDirectories(fullPath)
.Select(Path.GetFileName)
.OrderBy(name => name)
.ToList();
var files = Directory.GetFiles(fullPath)
.Select(Path.GetFileName)
.Where(name => _options.AllowedExtensions.Contains(Path.GetExtension(name).ToLowerInvariant()))
.OrderBy(name => name)
.ToList();
var html = new StringBuilder();
html.AppendLine("<!DOCTYPE html>");
html.AppendLine("<html>");
html.AppendLine("<head>");
html.AppendLine(" <title>Directory Listing</title>");
html.AppendLine(" <style>");
html.AppendLine(" body { font-family: Arial, sans-serif; margin: 20px; }");
html.AppendLine(" h1 { color: #333; }");
html.AppendLine(" ul { list-style-type: none; padding: 0; }");
html.AppendLine(" li { margin: 5px 0; }");
html.AppendLine(" a { text-decoration: none; color: #0066cc; }");
html.AppendLine(" a:hover { text-decoration: underline; }");
html.AppendLine(" .icon { margin-right: 10px; }");
html.AppendLine(" .dir { font-weight: bold; }");
html.AppendLine(" .size { color: #666; font-size: 0.9em; margin-left: 10px; }");
html.AppendLine(" </style>");
html.AppendLine("</head>");
html.AppendLine("<body>");
html.AppendLine($" <h1>Directory listing for {relativePath}</h1>");
html.AppendLine(" <ul>");
// Parent directory link
if (!string.IsNullOrEmpty(relativePath) && relativePath != "/")
{
string parentPath = Path.GetDirectoryName(relativePath).Replace("\\", "/");
if (string.IsNullOrEmpty(parentPath)) parentPath = "/";
html.AppendLine($" <li><span class=\"icon\">📁</span><a href=\"{parentPath}\">../</a></li>");
}
// Directories
foreach (string dir in directories)
{
string dirPath = Path.Combine(relativePath, dir).Replace("\\", "/");
html.AppendLine($" <li class=\"dir\"><span class=\"icon\">📁</span><a href=\"{dirPath}/\">{dir}/</a></li>");
}
// Files
foreach (string file in files)
{
string filePath = Path.Combine(relativePath, file).Replace("\\", "/");
string fullPathFile = Path.Combine(fullPath, file);
var fileInfo = new FileInfo(fullPathFile);
string icon = GetFileIcon(file);
string size = FormatFileSize(fileInfo.Length);
html.AppendLine($" <li><span class=\"icon\">{icon}</span><a href=\"{filePath}\">{file}</a><span class=\"size\">{size}</span></li>");
}
html.AppendLine(" </ul>");
html.AppendLine(" <hr>");
html.AppendLine($" <p>Total: {directories.Count} directories, {files.Count} files</p>");
html.AppendLine("</body>");
html.AppendLine("</html>");
response.Body = html.ToString();
return response;
}
// 3. MIME type detection
private static string GetContentType(string extension)
{
return extension.ToLowerInvariant() switch
{
".html" or ".htm" => "text/html",
".css" => "text/css",
".js" => "application/javascript",
".json" => "application/json",
".xml" => "application/xml",
".txt" => "text/plain",
".md" => "text/markdown",
".csv" => "text/csv",
".png" => "image/png",
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".bmp" => "image/bmp",
".svg" => "image/svg+xml",
".ico" => "image/x-icon",
".pdf" => "application/pdf",
".zip" => "application/zip",
".tar" => "application/x-tar",
".gz" => "application/gzip",
".woff" => "font/woff",
".woff2" => "font/woff2",
".ttf" => "font/ttf",
".eot" => "application/vnd.ms-fontobject",
_ => "application/octet-stream"
};
}
// 4. File icon selection
private static string GetFileIcon(string fileName)
{
string extension = Path.GetExtension(fileName).ToLowerInvariant();
return extension switch
{
".html" or ".htm" => "🌐",
".css" => "🎨",
".js" => "⚡",
".json" => "📋",
".xml" => "📄",
".txt" or ".md" => "📝",
".png" or ".jpg" or ".jpeg" or ".gif" or ".bmp" or ".svg" => "🖼️",
".pdf" => "📕",
".zip" or ".tar" or ".gz" => "📦",
".woff" or ".woff2" or ".ttf" or ".eot" => "🔤",
_ => "📄"
};
}
// 5. ETag generation
private static string GenerateETag(FileInfo fileInfo)
{
string hash = $"{fileInfo.LastWriteTime.Ticks}-{fileInfo.Length}";
using (var sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(hash));
string eTag = Convert.ToBase64String(hashBytes).Replace("+", "-").Replace("/", "_").TrimEnd('=');
return $"W/\"{eTag}\"";
}
}
// 6. Path normalization and security
private static string NormalizePath(string path)
{
if (string.IsNullOrEmpty(path))
return "/";
path = path.Replace('\', '/');
path = path.Trim('/');
if (string.IsNullOrEmpty(path))
return "/";
return "/" + path;
}
private static bool IsPathTraversal(string path)
{
return path.Contains("..") || path.Contains("\\") || path.StartsWith("/");
}
// 7. File type detection
private static bool IsTextFile(string extension)
{
string[] textExtensions =
{
".html", ".htm", ".css", ".js", ".json", ".xml", ".txt", ".md",
".csv", ".log", ".ini", ".config", ".yml", ".yaml"
};
return textExtensions.Contains(extension);
}
// 8. File size formatting
private static string FormatFileSize(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
int order = 0;
double size = bytes;
while (size >= 1024 && order < sizes.Length - 1)
{
order++;
size /= 1024;
}
return $"{size:F1} {sizes[order]}";
}
// 9. Create sample files for demonstration
private void CreateSampleFiles()
{
Console.WriteLine("[StaticFile] Creating sample files...");
try
{
// Create directory structure
string cssDir = Path.Combine(_options.RootDirectory, "css");
string jsDir = Path.Combine(_options.RootDirectory, "js");
string imagesDir = Path.Combine(_options.RootDirectory, "images");
Directory.CreateDirectory(cssDir);
Directory.CreateDirectory(jsDir);
Directory.CreateDirectory(imagesDir);
// Create HTML file
string indexPath = Path.Combine(_options.RootDirectory, "index.html");
File.WriteAllText(indexPath, @"<!DOCTYPE html>
<html>
<head>
<title>Static File Server Demo</title>
<link rel='stylesheet' href='/css/style.css'>
</head>
<body>
<h1>Welcome to Static File Server!</h1>
<p>This is a demonstration of static file serving capabilities.</p>
<img src='/images/demo.png' alt='Demo Image'>
<script src='/js/app.js'></script>
</body>
</html>");
// Create CSS file
string cssPath = Path.Combine(cssDir, "style.css");
File.WriteAllText(cssPath, @"body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
h1 {
color: #333;
text-align: center;
}
img {
max-width: 200px;
display: block;
margin: 20px auto;
}");
// Create JavaScript file
string jsPath = Path.Combine(jsDir, "app.js");
File.WriteAllText(jsPath, @"console.log('Static file server demo loaded!');
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM loaded successfully!');
});");
// Create a simple text file
string textPath = Path.Combine(_options.RootDirectory, "README.txt");
File.WriteAllText(textPath, @"Static File Server Demo
=====================
This directory contains sample files served by the static file server.
Features:
- MIME type detection
- Directory browsing (when enabled)
- Default file serving
- Security checks
- Caching headers
- ETag support");
// Create JSON file
string jsonPath = Path.Combine(_options.RootDirectory, "data.json");
File.WriteAllText(jsonPath, @"{
""name"": ""Static File Server"",
""version"": ""1.0.0"",
""features"": [
""MIME type detection"",
""Directory browsing"",
""Default file serving"",
""Security checks""
],
""created"": """ + DateTime.Now.ToString("yyyy-MM-dd") + @"""
}");
Console.WriteLine("[StaticFile] Sample files created successfully");
}
catch (Exception ex)
{
Console.WriteLine($"[StaticFile] Error creating sample files: {ex.Message}");
}
}
// 10. Error response creation
private static HttpResponse CreateErrorResponse(int statusCode, string message)
{
var response = new HttpResponse
{
StatusCode = statusCode,
Headers = { ["Content-Type"] = "application/json" },
Body = $@"{{
""error"": ""{message}"",
""status"": {statusCode},
""timestamp"": ""{DateTime.Now:yyyy-MM-dd HH:mm:ss}""
}}"
};
return response;
}
// 11. Cleanup method
public void Cleanup()
{
try
{
if (Directory.Exists(_options.RootDirectory))
{
Directory.Delete(_options.RootDirectory, true);
Console.WriteLine($"[StaticFile] Cleaned up root directory: {_options.RootDirectory}");
}
}
catch (Exception ex)
{
Console.WriteLine($"[StaticFile] Error during cleanup: {ex.Message}");
}
}
}
// HTTP Response class (simplified)
public class HttpResponse
{
public int StatusCode { get; set; } = 200;
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
public string Body { get; set; }
}
// Demonstration class
public class StaticFileExamples
{
public static async Task RunExamples()
{
Console.WriteLine("=== C# Static File Serving Examples ===\n");
var options = new StaticFileOptions
{
RootDirectory = "static_files_demo",
EnableDirectoryBrowsing = true,
EnableDefaultFiles = true,
CacheMaxAge = 3600
};
var server = new StaticFileServer(options);
// Test various file requests
var testPaths = new[]
{
"/",
"/index.html",
"/css/style.css",
"/js/app.js",
"/README.txt",
"/data.json",
"/nonexistent.html",
"/../system32/config.sys", // Path traversal test
"/images/nonexistent.jpg",
"/" // Directory listing
};
foreach (string path in testPaths)
{
Console.WriteLine($"\nTesting: {path}");
var response = await server.ServeFileAsync(path);
Console.WriteLine($"Status: {response.StatusCode}");
if (response.Headers.ContainsKey("Content-Type"))
{
Console.WriteLine($"Content-Type: {response.Headers["Content-Type"]}");
}
if (response.Body != null && response.Body.Length < 500)
{
Console.WriteLine($"Body preview: {response.Body.Substring(0, Math.Min(100, response.Body.Length))}...");
}
else if (response.Body != null)
{
Console.WriteLine($"Body length: {response.Body:N0} characters");
}
}
// Cleanup
server.Cleanup();
Console.WriteLine("\nStatic file serving examples completed!");
}
}
// Main program
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("=== C# Static File Server Examples ===\n");
try
{
await StaticFileExamples.RunExamples();
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}