{"id":29406373,"url":"https://github.com/hamedfathi/reprendpoint","last_synced_at":"2025-07-10T23:20:11.109Z","repository":{"id":304014307,"uuid":"1007735785","full_name":"HamedFathi/ReprEndpoint","owner":"HamedFathi","description":"ReprEndpoint is a lightweight ASP.NET Core library that implements the REPR (Request-Endpoint-Response) pattern - a modern alternative to traditional controller-based APIs where each HTTP endpoint gets its own dedicated class.","archived":false,"fork":false,"pushed_at":"2025-06-25T15:06:15.000Z","size":26,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-10T22:53:54.358Z","etag":null,"topics":["asp-net-core","aspnet-core","aspnetcore","controllers","csharp","csharp-library","dotnet","dotnet-core","endpoint","library","minimal-api","repr-design-pattern","repr-pattern","request","request-endpoint-response","respose"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/HamedFathi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-24T12:58:20.000Z","updated_at":"2025-06-25T15:06:18.000Z","dependencies_parsed_at":"2025-07-10T22:53:56.432Z","dependency_job_id":"4c6a93a4-49ec-4537-81ce-d6a32f6c49dc","html_url":"https://github.com/HamedFathi/ReprEndpoint","commit_stats":null,"previous_names":["hamedfathi/reprendpoint"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/HamedFathi/ReprEndpoint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HamedFathi%2FReprEndpoint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HamedFathi%2FReprEndpoint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HamedFathi%2FReprEndpoint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HamedFathi%2FReprEndpoint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HamedFathi","download_url":"https://codeload.github.com/HamedFathi/ReprEndpoint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HamedFathi%2FReprEndpoint/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264686802,"owners_count":23649565,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asp-net-core","aspnet-core","aspnetcore","controllers","csharp","csharp-library","dotnet","dotnet-core","endpoint","library","minimal-api","repr-design-pattern","repr-pattern","request","request-endpoint-response","respose"],"created_at":"2025-07-10T23:20:08.734Z","updated_at":"2025-07-10T23:20:11.099Z","avatar_url":"https://github.com/HamedFathi.png","language":"C#","readme":"# ReprEndpoint Library User Guide\n\n## The REPR Pattern\n\nThe **REPR (Request-Endpoint-Response) pattern** is a modern architectural approach for building ASP.NET Core APIs that promotes clean, maintainable, and testable code. Unlike traditional controller-based architectures, REPR organizes your API around individual endpoint classes, each representing a single operation.\n\n### Why Use the REPR Pattern?\n\n* **Single Responsibility Principle**: Each endpoint class handles exactly one operation, making your code more focused and easier to understand.\n* **Better Testability**: Individual endpoints can be unit tested in isolation without the complexity of controller dependencies.\n* **Improved Organization**: Related logic is contained within a single class, reducing the cognitive load when working with complex APIs.\n* **Enhanced Maintainability**: Changes to one endpoint don't affect others, reducing the risk of introducing bugs.\n* **Cleaner Dependency Injection**: Each endpoint can have its own specific dependencies without bloating a shared controller.\n* **Type Safety**: Strong typing for requests and responses with compile-time validation.\n\n## Getting Started\n\n### Installation\n\nAdd the ReprEndpoint library to your project:\n\n```xml\n\u003cPackageReference Include=\"ReprEndpoint\" Version=\"1.0.1\" /\u003e\n```\n\nOr visit the NuGet package page: [https://www.nuget.org/packages/ReprEndpoint](https://www.nuget.org/packages/ReprEndpoint)\n\nYou can also install it via the Visual Studio Package Manager UI by searching for \"ReprEndpoint\".\n\n### Basic Setup\n\nConfigure your ASP.NET Core application to use ReprEndpoint:\n\n```csharp\nusing TheReprEndpoint;\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Register all endpoints from the current assembly\nbuilder.Services.AddReprEndpoints();\n\n// Add other services\nbuilder.Services.AddEndpointsApiExplorer();\nbuilder.Services.AddSwaggerGen();\n\nvar app = builder.Build();\n\n// Configure middleware\nif (app.Environment.IsDevelopment())\n{\n    app.UseSwagger();\n    app.UseSwaggerUI();\n}\n\n// Map all registered endpoints\napp.MapReprEndpoints();\n\napp.Run();\n```\n\n## Base Classes Overview\n\nThe ReprEndpoint library provides four base classes to suit different endpoint scenarios:\n\n### 1. `ReprEndpoint\u003cTRequest, TResponse\u003e`\n\nUse this when your endpoint needs both a strongly-typed request and response:\n\n```csharp\npublic class CreateUserEndpoint : ReprEndpoint\u003cCreateUserRequest, UserResponse\u003e\n{\n    private readonly IUserService _userService;\n\n    public CreateUserEndpoint(IUserService userService)\n    {\n        _userService = userService;\n    }\n\n    public override async Task\u003cUserResponse\u003e HandleAsync(CreateUserRequest request, CancellationToken ct = default)\n    {\n        var user = await _userService.CreateUserAsync(request.Name, request.Email, ct);\n        return new UserResponse \n        { \n            Id = user.Id, \n            Name = user.Name, \n            Email = user.Email \n        };\n    }\n\n    public override void MapEndpoint(IEndpointRouteBuilder routes)\n    {\n        MapPost(routes, \"/users\")\n            .WithName(\"CreateUser\")\n            .WithOpenApi();\n    }\n}\n\npublic record CreateUserRequest(string Name, string Email);\npublic record UserResponse(int Id, string Name, string Email);\n```\n\n### 2. `ReprRequestEndpoint\u003cTRequest\u003e`\n\nUse this when you need a strongly-typed request but want to return an `IResult` for flexible response handling:\n\n```csharp\npublic class UpdateUserEndpoint : ReprRequestEndpoint\u003cUpdateUserRequest\u003e\n{\n    private readonly IUserService _userService;\n\n    public UpdateUserEndpoint(IUserService userService)\n    {\n        _userService = userService;\n    }\n\n    public override async Task\u003cIResult\u003e HandleAsync(UpdateUserRequest request, CancellationToken ct = default)\n    {\n        var user = await _userService.GetUserAsync(request.Id, ct);\n        if (user == null)\n            return Results.NotFound($\"User with ID {request.Id} not found\");\n\n        await _userService.UpdateUserAsync(request.Id, request.Name, request.Email, ct);\n        return Results.NoContent();\n    }\n\n    public override void MapEndpoint(IEndpointRouteBuilder routes)\n    {\n        MapPut(routes, \"/users/{id}\")\n            .WithName(\"UpdateUser\")\n            .WithOpenApi();\n    }\n}\n\npublic record UpdateUserRequest(int Id, string Name, string Email);\n```\n\n### 3. `ReprResponseEndpoint\u003cTResponse\u003e`\n\nUse this for endpoints that don't require input parameters but return a strongly-typed response:\n\n```csharp\npublic class GetAllUsersEndpoint : ReprResponseEndpoint\u003cList\u003cUserResponse\u003e\u003e\n{\n    private readonly IUserService _userService;\n\n    public GetAllUsersEndpoint(IUserService userService)\n    {\n        _userService = userService;\n    }\n\n    public override async Task\u003cList\u003cUserResponse\u003e\u003e HandleAsync(CancellationToken ct = default)\n    {\n        var users = await _userService.GetAllUsersAsync(ct);\n        return users.Select(u =\u003e new UserResponse(u.Id, u.Name, u.Email)).ToList();\n    }\n\n    public override void MapEndpoint(IEndpointRouteBuilder routes)\n    {\n        MapGet(routes, \"/users\")\n            .WithName(\"GetAllUsers\")\n            .WithOpenApi();\n    }\n}\n```\n\n### 4. `ReprEndpoint`\n\nUse this for simple endpoints that don't need strongly-typed requests or responses:\n\n```csharp\npublic class HealthCheckEndpoint : ReprEndpoint\n{\n    private readonly ILogger\u003cHealthCheckEndpoint\u003e _logger;\n\n    public HealthCheckEndpoint(ILogger\u003cHealthCheckEndpoint\u003e logger)\n    {\n        _logger = logger;\n    }\n\n    public override Task\u003cIResult\u003e HandleAsync(CancellationToken ct = default)\n    {\n        _logger.LogInformation(\"Health check requested\");\n        return Task.FromResult(Results.Ok(new { Status = \"Healthy\", Timestamp = DateTime.UtcNow }));\n    }\n\n    public override void MapEndpoint(IEndpointRouteBuilder routes)\n    {\n        MapGet(routes, \"/health\")\n            .WithName(\"HealthCheck\")\n            .WithOpenApi();\n    }\n}\n```\n\n## Request/Response Binding\n\n### Request Body Binding (Default)\n\nBy default, requests are bound from the request body (typically JSON):\n\n```csharp\npublic class CreateProductEndpoint : ReprEndpoint\u003cCreateProductRequest, ProductResponse\u003e\n{\n    // RequestAsParameters is false by default\n    public override bool RequestAsParameters =\u003e false;\n\n    public override async Task\u003cProductResponse\u003e HandleAsync(CreateProductRequest request, CancellationToken ct)\n    {\n        // request is bound from JSON body\n        // POST /products\n        // Body: { \"name\": \"Laptop\", \"price\": 999.99 }\n    }\n}\n```\n\n### Parameter Binding\n\nOverride `RequestAsParameters` to bind from query string, route values, or form data:\n\n```csharp\npublic class GetUserEndpoint : ReprEndpoint\u003cGetUserRequest, UserResponse\u003e\n{\n    //Apply [AsParameters] on your request.\n    public override bool RequestAsParameters =\u003e true;\n\n    public override async Task\u003cUserResponse\u003e HandleAsync(GetUserRequest request, CancellationToken ct)\n    {\n        // request is bound from route and query parameters\n        // GET /users/123?includeDetails=true\n    }\n\n    public override void MapEndpoint(IEndpointRouteBuilder routes)\n    {\n        MapGet(routes, \"/users/{id}\")\n            .WithName(\"GetUser\")\n            .WithOpenApi();\n    }\n}\n\npublic record GetUserRequest(int Id, bool IncludeDetails = false);\n```\n\n## Endpoint Grouping and Configuration\n\n### Route Groups\n\nGroup related endpoints under a common prefix:\n\n```csharp\npublic class GetUserProfileEndpoint : ReprResponseEndpoint\u003cUserProfile\u003e\n{\n    public override string? GroupPrefix =\u003e \"/api/v1/users\";\n\n    public override Action\u003cRouteGroupBuilder\u003e? ConfigureGroup =\u003e group =\u003e\n    {\n        group.RequireAuthorization()\n             .WithTags(\"Users\")\n             .WithOpenApi();\n    };\n\n    public override void MapEndpoint(IEndpointRouteBuilder routes)\n    {\n        MapGet(routes, \"/{id}/profile\")\n            .WithName(\"GetUserProfile\");\n    }\n}\n```\n\nThis creates the endpoint at `/api/v1/users/{id}/profile` with authorization requirements.\n\n### Advanced Group Configuration\n\n```csharp\npublic class AdminUserEndpoint : ReprEndpoint\u003cAdminRequest, AdminResponse\u003e\n{\n    public override string? GroupPrefix =\u003e \"/api/admin\";\n\n    public override Action\u003cRouteGroupBuilder\u003e? ConfigureGroup =\u003e group =\u003e\n    {\n        group.RequireAuthorization(\"AdminPolicy\")\n             .AddEndpointFilter\u003cAdminLoggingFilter\u003e()\n             .WithTags(\"Administration\")\n             .WithOpenApi();\n    };\n}\n```\n\n## API Versioning Support\n\nThe library integrates seamlessly with ASP.NET Core API versioning:\n\n```csharp\npublic class GetWeatherForecastV1Endpoint : ReprResponseEndpoint\u003cWeatherForecast[]\u003e\n{\n    private static readonly string[] Summaries =\n    [\n        \"Freezing\", \"Bracing\", \"Chilly\", \"Cool\", \"Mild\", \"Warm\", \"Balmy\", \"Hot\", \"Sweltering\", \"Scorching\"\n    ];\n\n    private readonly ILogger\u003cGetWeatherForecastV1Endpoint\u003e _logger;\n\n    public GetWeatherForecastV1Endpoint(ILogger\u003cGetWeatherForecastV1Endpoint\u003e logger)\n    {\n        _logger = logger;\n    }\n\n    public override Task\u003cWeatherForecast[]\u003e HandleAsync(CancellationToken ct = default)\n    {\n        _logger.LogInformation(\"Generating weather forecast for 5 days (V1)\");\n\n        var forecast = Enumerable.Range(1, 5).Select(index =\u003e\n                new WeatherForecast\n                {\n                    Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),\n                    TemperatureC = Random.Shared.Next(-20, 55),\n                    Summary = Summaries[Random.Shared.Next(Summaries.Length)]\n                })\n            .ToArray();\n\n        return Task.FromResult(forecast);\n    }\n\n    public override void MapEndpoint(IEndpointRouteBuilder routes)\n    {\n        var versionSet = routes.NewApiVersionSet()\n            .HasApiVersion(new ApiVersion(1, 0))\n            .Build();\n\n        MapGet(routes, \"/v{version:apiVersion}/weatherforecast\")\n            .WithName(\"GetWeatherForecastV1\")\n            .WithApiVersionSet(versionSet)\n            .WithOpenApi();\n    }\n}\n\npublic record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)\n{\n    public int TemperatureF =\u003e 32 + (int)(TemperatureC / 0.5556);\n}\n```\n\n### Versioning Setup\n\n```csharp\nbuilder.Services.AddApiVersioning(options =\u003e\n{\n    options.DefaultApiVersion = new ApiVersion(1, 0);\n    options.AssumeDefaultVersionWhenUnspecified = true;\n    options.ApiVersionReader = ApiVersionReader.Combine(\n        new QueryStringApiVersionReader(\"version\"),\n        new HeaderApiVersionReader(\"X-Version\"),\n        new UrlSegmentApiVersionReader()\n    );\n})\n.AddApiExplorer(options =\u003e\n{\n    options.GroupNameFormat = \"'v'VVV\";\n    options.SubstituteApiVersionInUrl = true;\n});\n```\n\n## Dependency Injection Integration\n\n### Automatic Registration\n\nRegister all endpoints from assemblies:\n\n```csharp\n// Register from current assembly with default lifetime (Transient)\nbuilder.Services.AddReprEndpoints();\n\n// Register from specific assemblies\nbuilder.Services.AddReprEndpoints(ServiceLifetime.Scoped, typeof(UserEndpoint).Assembly);\n\n// Register specific endpoint types\nbuilder.Services.AddReprEndpoints(ServiceLifetime.Singleton, typeof(HealthCheckEndpoint));\n```\n\n### Service Lifetimes\n\nChoose appropriate service lifetimes based on your needs:\n\n- **Transient** (default): New instance for each request\n- **Scoped**: One instance per HTTP request\n- **Singleton**: Single instance for the application lifetime\n\n### Endpoint Dependencies\n\nInject services into your endpoints:\n\n```csharp\npublic class ProcessOrderEndpoint : ReprEndpoint\u003cProcessOrderRequest, OrderResult\u003e\n{\n    private readonly IOrderService _orderService;\n    private readonly IPaymentService _paymentService;\n    private readonly IInventoryService _inventoryService;\n    private readonly ILogger\u003cProcessOrderEndpoint\u003e _logger;\n\n    public ProcessOrderEndpoint(\n        IOrderService orderService,\n        IPaymentService paymentService,\n        IInventoryService inventoryService,\n        ILogger\u003cProcessOrderEndpoint\u003e logger)\n    {\n        _orderService = orderService;\n        _paymentService = paymentService;\n        _inventoryService = inventoryService;\n        _logger = logger;\n    }\n\n    public override async Task\u003cOrderResult\u003e HandleAsync(ProcessOrderRequest request, CancellationToken ct)\n    {\n        _logger.LogInformation(\"Processing order {OrderId}\", request.OrderId);\n\n        // Check inventory\n        var available = await _inventoryService.CheckAvailabilityAsync(request.Items, ct);\n        if (!available)\n            throw new InvalidOperationException(\"Insufficient inventory\");\n\n        // Process payment\n        var paymentResult = await _paymentService.ProcessPaymentAsync(request.Payment, ct);\n        if (!paymentResult.Success)\n            throw new InvalidOperationException(\"Payment failed\");\n\n        // Create order\n        var order = await _orderService.CreateOrderAsync(request, ct);\n        \n        _logger.LogInformation(\"Order {OrderId} processed successfully\", order.Id);\n        \n        return new OrderResult(order.Id, order.Status, order.Total);\n    }\n\n    public override void MapEndpoint(IEndpointRouteBuilder routes)\n    {\n        MapPost(routes, \"/orders/process\")\n            .WithName(\"ProcessOrder\")\n            .RequireAuthorization()\n            .WithOpenApi();\n    }\n}\n```\n\n## Contributing\n\nWe welcome contributions to make `ReprEndpoint` even better! Here are some ways you can help:\n\n### 🌟 **Star this repository** if you find it useful!\n\nYour star helps others discover this library and motivates continued development.\n\n### 🔧 **Pull Requests Welcome**\n\nWe're open to pull requests!\n\nPlease feel free to fork the repository and submit a pull request. For larger changes, consider opening an issue first to discuss your approach.\n\n### 📝 **Reporting Issues**\n\nFound a bug or have a suggestion? Please open an issue with:\n\n- A clear description of the problem or enhancement\n- Steps to reproduce (for bugs)\n- Sample code demonstrating the issue\n- Expected vs actual behavior\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhamedfathi%2Freprendpoint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhamedfathi%2Freprendpoint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhamedfathi%2Freprendpoint/lists"}