Elysia Tools
Navigation
C#
Windows Web Features - C# Samples
Comprehensive C# web features examples for Windows platform including routing, middleware, and static file serving
Samples
Entries inside this sample collection
HTTP Request Routing
Implement flexible HTTP request routing with parameter handling and route constraints
Difficulty
5/10
Estimated time
25 min
Tags
csharp, routing, http, web, windows
Prerequisites
HTTP basics, Regular expressions, Async programming
using System;
using System.Collections.Generic;
using System.Linq;
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.Path}");
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}");
}
}
}Request Processing Middleware
Implement middleware pipeline for request processing, logging, authentication, and response modification
Difficulty
6/10
Estimated time
30 min
Tags
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}");
}
}
}Static File Serving
Implement efficient static file serving with MIME type detection, caching headers, and security features
Difficulty
5/10
Estimated time
25 min
Tags
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}");
}
}
}Tools
Tools frequently paired with this sample
Related