🔷 API Design in C# with TuskLang
API Design in C# with TuskLang
Overview
This guide covers comprehensive API design patterns for C# applications using TuskLang, including RESTful APIs, GraphQL, gRPC, and advanced API patterns.
Table of Contents
1. RESTful API Design 2. GraphQL Integration 3. gRPC Services 4. API Versioning 5. API Documentation 6. Rate Limiting 7. Caching Strategies 8. TuskLang Integration
RESTful API Design
Basic REST Controller
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;
private readonly TuskLang _config; public UsersController(IUserService userService, ILogger<UsersController> logger, TuskLang config)
{
_userService = userService;
_logger = logger;
_config = config;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers([FromQuery] UserQueryParams query)
{
var maxResults = _config.GetValue<int>("api.maxResults", 100);
var users = await _userService.GetUsersAsync(query, maxResults);
return Ok(users);
}
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
return NotFound();
return Ok(user);
}
[HttpPost]
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserRequest request)
{
var user = await _userService.CreateUserAsync(request);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateUser(int id, [FromBody] UpdateUserRequest request)
{
var success = await _userService.UpdateUserAsync(id, request);
if (!success)
return NotFound();
return NoContent();
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteUser(int id)
{
var success = await _userService.DeleteUserAsync(id);
if (!success)
return NotFound();
return NoContent();
}
}
API Response Patterns
public class ApiResponse<T>
{
public bool Success { get; set; }
public string Message { get; set; }
public T Data { get; set; }
public List<string> Errors { get; set; } = new();
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}public class PaginatedResponse<T>
{
public IEnumerable<T> Data { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public int TotalPages { get; set; }
public bool HasNext { get; set; }
public bool HasPrevious { get; set; }
}
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<PaginatedResponse<ProductDto>>> GetProducts(
[FromQuery] ProductQueryParams query)
{
var result = await _productService.GetProductsAsync(query);
return Ok(new PaginatedResponse<ProductDto>
{
Data = result.Items,
Page = query.Page,
PageSize = query.PageSize,
TotalCount = result.TotalCount,
TotalPages = (int)Math.Ceiling((double)result.TotalCount / query.PageSize),
HasNext = query.Page < (int)Math.Ceiling((double)result.TotalCount / query.PageSize),
HasPrevious = query.Page > 1
});
}
}
GraphQL Integration
GraphQL Schema
public class Query
{
private readonly IUserService _userService;
private readonly TuskLang _config; public Query(IUserService userService, TuskLang config)
{
_userService = userService;
_config = config;
}
public async Task<IEnumerable<User>> GetUsers(int? limit = null)
{
var maxLimit = _config.GetValue<int>("graphql.maxResults", 100);
var actualLimit = limit ?? maxLimit;
return await _userService.GetUsersAsync(actualLimit);
}
public async Task<User> GetUser(int id)
{
return await _userService.GetUserByIdAsync(id);
}
}
public class Mutation
{
private readonly IUserService _userService;
public Mutation(IUserService userService)
{
_userService = userService;
}
public async Task<User> CreateUser(CreateUserInput input)
{
return await _userService.CreateUserAsync(input);
}
public async Task<User> UpdateUser(int id, UpdateUserInput input)
{
return await _userService.UpdateUserAsync(id, input);
}
public async Task<bool> DeleteUser(int id)
{
return await _userService.DeleteUserAsync(id);
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public DateTime CreatedAt { get; set; }
public async Task<IEnumerable<Post>> GetPosts([Service] IPostService postService)
{
return await postService.GetPostsByUserIdAsync(Id);
}
}
GraphQL Configuration
public static class GraphQLConfiguration
{
public static IServiceCollection AddGraphQLServices(this IServiceCollection services, TuskLang config)
{
services.AddGraphQLServer()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
.AddType<User>()
.AddType<Post>()
.AddFiltering()
.AddSorting()
.AddProjections()
.AddInMemorySubscriptions()
.AddDataLoader<UserDataLoader>()
.ConfigureSchema(schema =>
{
schema.SetDescription("TuskLang API GraphQL Schema");
}); // Configure GraphQL settings from TuskLang
var maxComplexity = config.GetValue<int>("graphql.maxComplexity", 100);
var maxDepth = config.GetValue<int>("graphql.maxDepth", 10);
var enableIntrospection = config.GetValue<bool>("graphql.enableIntrospection", true);
services.Configure<GraphQLServerOptions>(options =>
{
options.MaximumComplexity = maxComplexity;
options.MaximumDepth = maxDepth;
options.EnableIntrospection = enableIntrospection;
});
return services;
}
}
gRPC Services
gRPC Service Definition
syntax = "proto3";package tusklang.api;
service UserService {
rpc GetUser (GetUserRequest) returns (UserResponse);
rpc GetUsers (GetUsersRequest) returns (GetUsersResponse);
rpc CreateUser (CreateUserRequest) returns (UserResponse);
rpc UpdateUser (UpdateUserRequest) returns (UserResponse);
rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse);
}
message GetUserRequest {
int32 id = 1;
}
message GetUsersRequest {
int32 page = 1;
int32 page_size = 2;
string search = 3;
}
message UserResponse {
int32 id = 1;
string name = 2;
string email = 3;
string created_at = 4;
}
message GetUsersResponse {
repeated UserResponse users = 1;
int32 total_count = 2;
int32 page = 3;
int32 page_size = 4;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message UpdateUserRequest {
int32 id = 1;
string name = 2;
string email = 3;
}
message DeleteUserRequest {
int32 id = 1;
}
message DeleteUserResponse {
bool success = 1;
}
gRPC Service Implementation
public class UserGrpcService : UserService.UserServiceBase
{
private readonly IUserService _userService;
private readonly ILogger<UserGrpcService> _logger;
private readonly TuskLang _config; public UserGrpcService(IUserService userService, ILogger<UserGrpcService> logger, TuskLang config)
{
_userService = userService;
_logger = logger;
_config = config;
}
public override async Task<UserResponse> GetUser(GetUserRequest request, ServerCallContext context)
{
try
{
var user = await _userService.GetUserByIdAsync(request.Id);
if (user == null)
{
throw new RpcException(new Status(StatusCode.NotFound, "User not found"));
}
return new UserResponse
{
Id = user.Id,
Name = user.Name,
Email = user.Email,
CreatedAt = user.CreatedAt.ToString("O")
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting user {UserId}", request.Id);
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<GetUsersResponse> GetUsers(GetUsersRequest request, ServerCallContext context)
{
try
{
var maxPageSize = _config.GetValue<int>("grpc.maxPageSize", 100);
var pageSize = Math.Min(request.PageSize, maxPageSize);
var query = new UserQueryParams
{
Page = request.Page,
PageSize = pageSize,
Search = request.Search
};
var result = await _userService.GetUsersAsync(query);
var response = new GetUsersResponse
{
TotalCount = result.TotalCount,
Page = request.Page,
PageSize = pageSize
};
response.Users.AddRange(result.Items.Select(u => new UserResponse
{
Id = u.Id,
Name = u.Name,
Email = u.Email,
CreatedAt = u.CreatedAt.ToString("O")
}));
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting users");
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
public override async Task<UserResponse> CreateUser(CreateUserRequest request, ServerCallContext context)
{
try
{
var createRequest = new CreateUserRequest
{
Name = request.Name,
Email = request.Email
};
var user = await _userService.CreateUserAsync(createRequest);
return new UserResponse
{
Id = user.Id,
Name = user.Name,
Email = user.Email,
CreatedAt = user.CreatedAt.ToString("O")
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating user");
throw new RpcException(new Status(StatusCode.Internal, "Internal server error"));
}
}
}
API Versioning
Versioned API Controllers
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly TuskLang _config; public UsersController(IUserService userService, TuskLang config)
{
_userService = userService;
_config = config;
}
[HttpGet]
[MapToApiVersion("1.0")]
public async Task<ActionResult<IEnumerable<UserDto>>> GetUsersV1([FromQuery] UserQueryParams query)
{
var users = await _userService.GetUsersAsync(query);
return Ok(users);
}
[HttpGet]
[MapToApiVersion("2.0")]
public async Task<ActionResult<PaginatedResponse<UserDto>>> GetUsersV2([FromQuery] UserQueryParams query)
{
var result = await _userService.GetUsersAsync(query);
return Ok(new PaginatedResponse<UserDto>
{
Data = result.Items,
Page = query.Page,
PageSize = query.PageSize,
TotalCount = result.TotalCount,
TotalPages = (int)Math.Ceiling((double)result.TotalCount / query.PageSize)
});
}
[HttpGet("{id}")]
[MapToApiVersion("1.0")]
public async Task<ActionResult<UserDto>> GetUserV1(int id)
{
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
return NotFound();
return Ok(user);
}
[HttpGet("{id}")]
[MapToApiVersion("2.0")]
public async Task<ActionResult<UserDetailDto>> GetUserV2(int id)
{
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
return NotFound();
var userDetail = new UserDetailDto
{
Id = user.Id,
Name = user.Name,
Email = user.Email,
CreatedAt = user.CreatedAt,
LastLoginAt = user.LastLoginAt,
IsActive = user.IsActive,
Profile = user.Profile
};
return Ok(userDetail);
}
}
API Version Configuration
public static class ApiVersioningConfiguration
{
public static IServiceCollection AddApiVersioningServices(this IServiceCollection services, TuskLang config)
{
services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-API-Version"),
new MediaTypeApiVersionReader("version")
);
}); services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
// Configure versioning settings from TuskLang
var defaultVersion = config.GetValue<string>("api.defaultVersion", "1.0");
var supportedVersions = config.GetValue<string>("api.supportedVersions", "1.0,2.0")
.Split(',', StringSplitOptions.RemoveEmptyEntries);
return services;
}
}
API Documentation
Swagger Configuration
public static class SwaggerConfiguration
{
public static IServiceCollection AddSwaggerServices(this IServiceCollection services, TuskLang config)
{
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = config.GetValue<string>("swagger.title", "TuskLang API"),
Version = "v1",
Description = config.GetValue<string>("swagger.description", "TuskLang API Documentation"),
Contact = new OpenApiContact
{
Name = config.GetValue<string>("swagger.contact.name", "API Support"),
Email = config.GetValue<string>("swagger.contact.email", "support@example.com")
}
}); options.SwaggerDoc("v2", new OpenApiInfo
{
Title = config.GetValue<string>("swagger.title", "TuskLang API"),
Version = "v2",
Description = config.GetValue<string>("swagger.description", "TuskLang API Documentation v2")
});
// Add API key authentication
options.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Header,
Name = "X-API-Key",
Description = "API Key for authentication"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "ApiKey"
}
},
new string[] {}
}
});
// Include XML comments
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
if (File.Exists(xmlPath))
{
options.IncludeXmlComments(xmlPath);
}
});
return services;
}
}
Rate Limiting
Rate Limiting Implementation
public class RateLimitingMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
private readonly TuskLang _config;
private readonly ILogger<RateLimitingMiddleware> _logger; public RateLimitingMiddleware(RequestDelegate next, IMemoryCache cache,
TuskLang config, ILogger<RateLimitingMiddleware> logger)
{
_next = next;
_cache = cache;
_config = config;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var clientId = GetClientId(context);
var endpoint = context.Request.Path;
var maxRequests = _config.GetValue<int>("rateLimit.maxRequests", 100);
var windowMinutes = _config.GetValue<int>("rateLimit.windowMinutes", 1);
var key = $"rate_limit:{clientId}:{endpoint}";
var windowStart = DateTime.UtcNow.AddMinutes(-windowMinutes);
var requests = await _cache.GetOrCreateAsync(key, entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(windowMinutes);
return Task.FromResult(new List<DateTime>());
});
// Remove old requests outside the window
requests.RemoveAll(r => r < windowStart);
if (requests.Count >= maxRequests)
{
_logger.LogWarning("Rate limit exceeded for client {ClientId} on endpoint {Endpoint}",
clientId, endpoint);
context.Response.StatusCode = 429; // Too Many Requests
context.Response.Headers.Add("Retry-After", windowMinutes.ToString());
await context.Response.WriteAsJsonAsync(new
{
error = "Rate limit exceeded",
retryAfter = windowMinutes
});
return;
}
requests.Add(DateTime.UtcNow);
await _cache.SetAsync(key, requests, TimeSpan.FromMinutes(windowMinutes));
await _next(context);
}
private string GetClientId(HttpContext context)
{
// Try to get client ID from various sources
var clientId = context.Request.Headers["X-Client-ID"].FirstOrDefault()
?? context.Request.Headers["X-API-Key"].FirstOrDefault()
?? context.Connection.RemoteIpAddress?.ToString()
?? "unknown";
return clientId;
}
}
Caching Strategies
API Caching
public class ApiCachingService
{
private readonly IMemoryCache _cache;
private readonly TuskLang _config;
private readonly ILogger<ApiCachingService> _logger; public ApiCachingService(IMemoryCache cache, TuskLang config, ILogger<ApiCachingService> logger)
{
_cache = cache;
_config = config;
_logger = logger;
}
public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null)
{
var defaultExpiration = TimeSpan.FromMinutes(_config.GetValue<int>("cache.defaultExpirationMinutes", 5));
var actualExpiration = expiration ?? defaultExpiration;
if (_cache.TryGetValue(key, out T cachedValue))
{
_logger.LogDebug("Cache hit for key {Key}", key);
return cachedValue;
}
_logger.LogDebug("Cache miss for key {Key}, fetching data", key);
var value = await factory();
var cacheOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = actualExpiration,
SlidingExpiration = TimeSpan.FromMinutes(_config.GetValue<int>("cache.slidingExpirationMinutes", 2))
};
_cache.Set(key, value, cacheOptions);
return value;
}
public void Invalidate(string key)
{
_cache.Remove(key);
_logger.LogDebug("Cache invalidated for key {Key}", key);
}
public void InvalidatePattern(string pattern)
{
// Note: This is a simplified implementation
// In production, consider using Redis or a more sophisticated cache
_logger.LogDebug("Cache pattern invalidation requested for {Pattern}", pattern);
}
}
[ApiController]
[Route("api/[controller]")]
public class CachedUsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly ApiCachingService _cachingService;
public CachedUsersController(IUserService userService, ApiCachingService cachingService)
{
_userService = userService;
_cachingService = cachingService;
}
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var cacheKey = $"user:{id}";
var user = await _cachingService.GetOrSetAsync(cacheKey,
() => _userService.GetUserByIdAsync(id));
if (user == null)
return NotFound();
return Ok(user);
}
[HttpGet]
public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers([FromQuery] UserQueryParams query)
{
var cacheKey = $"users:{query.Page}:{query.PageSize}:{query.Search}";
var users = await _cachingService.GetOrSetAsync(cacheKey,
() => _userService.GetUsersAsync(query));
return Ok(users);
}
[HttpPost]
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserRequest request)
{
var user = await _userService.CreateUserAsync(request);
// Invalidate related caches
_cachingService.InvalidatePattern("users:*");
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
}
TuskLang Integration
TuskLang API Configuration
public class TuskLangApiConfig
{
public string BaseUrl { get; set; } = "";
public string ApiKey { get; set; } = "";
public int TimeoutSeconds { get; set; } = 30;
public int RetryCount { get; set; } = 3;
public bool EnableCaching { get; set; } = true;
public int CacheExpirationMinutes { get; set; } = 5;
public bool EnableRateLimiting { get; set; } = true;
public int MaxRequestsPerMinute { get; set; } = 100;
}public class TuskLangApiService
{
private readonly HttpClient _httpClient;
private readonly TuskLang _config;
private readonly ILogger<TuskLangApiService> _logger;
public TuskLangApiService(HttpClient httpClient, TuskLang config, ILogger<TuskLangApiService> logger)
{
_httpClient = httpClient;
_config = config;
_logger = logger;
ConfigureHttpClient();
}
private void ConfigureHttpClient()
{
var baseUrl = _config.GetValue<string>("api.baseUrl", "");
var timeout = TimeSpan.FromSeconds(_config.GetValue<int>("api.timeoutSeconds", 30));
_httpClient.BaseAddress = new Uri(baseUrl);
_httpClient.Timeout = timeout;
var apiKey = _config.GetValue<string>("api.apiKey", "");
if (!string.IsNullOrEmpty(apiKey))
{
_httpClient.DefaultRequestHeaders.Add("X-API-Key", apiKey);
}
}
public async Task<T> GetAsync<T>(string endpoint)
{
try
{
var response = await _httpClient.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(content);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error calling API endpoint {Endpoint}", endpoint);
throw;
}
}
public async Task<T> PostAsync<T>(string endpoint, object data)
{
try
{
var json = JsonSerializer.Serialize(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(endpoint, content);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<T>(responseContent);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error calling API endpoint {Endpoint}", endpoint);
throw;
}
}
}
Summary
This comprehensive API design guide covers:
- RESTful API Design: Standard REST patterns with proper HTTP methods and status codes - GraphQL Integration: Schema definition, resolvers, and configuration - gRPC Services: Protocol buffer definitions and service implementations - API Versioning: Multiple version support with proper routing - API Documentation: Swagger/OpenAPI integration with TuskLang configuration - Rate Limiting: Request throttling with configurable limits - Caching Strategies: Intelligent caching with invalidation patterns - TuskLang Integration: Configuration-driven API settings
The patterns ensure scalable, maintainable, and well-documented APIs that integrate seamlessly with TuskLang's configuration system.