{"id":21706692,"url":"https://github.com/ogulcanturan/ogu.response","last_synced_at":"2025-05-13T00:50:17.569Z","repository":{"id":183203201,"uuid":"669773058","full_name":"ogulcanturan/Ogu.Response","owner":"ogulcanturan","description":"This library provides a generic response type compatible with IActionResult in Microsoft.AspNetCore.Mvc","archived":false,"fork":false,"pushed_at":"2025-05-02T22:48:34.000Z","size":216,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-13T00:50:03.493Z","etag":null,"topics":["asp-net-core","csharp","generic","json","response"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ogulcanturan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2023-07-23T11:36:37.000Z","updated_at":"2025-05-02T22:48:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"a797e6b7-d31e-4920-9853-28784673f639","html_url":"https://github.com/ogulcanturan/Ogu.Response","commit_stats":null,"previous_names":["ogulcanturan/ogu.aspnetcore.response","ogulcanturan/ogu.response"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogulcanturan%2FOgu.Response","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogulcanturan%2FOgu.Response/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogulcanturan%2FOgu.Response/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogulcanturan%2FOgu.Response/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ogulcanturan","download_url":"https://codeload.github.com/ogulcanturan/Ogu.Response/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253850882,"owners_count":21973672,"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","csharp","generic","json","response"],"created_at":"2024-11-25T22:14:07.678Z","updated_at":"2025-05-13T00:50:17.557Z","avatar_url":"https://github.com/ogulcanturan.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# \u003cimg src=\"logo/ogu-logo.png\" alt=\"Header\" width=\"24\"/\u003e Ogu.Response\n\n[![.NET Core Desktop](https://github.com/ogulcanturan/Ogu.Response/actions/workflows/dotnet.yml/badge.svg?branch=master)](https://github.com/ogulcanturan/Ogu.Response/actions/workflows/dotnet.yml)\n[![NuGet](https://img.shields.io/nuget/v/Ogu.AspNetCore.Response.Json.svg?color=1ecf18)](https://nuget.org/packages/Ogu.AspNetCore.Response.Json)\n[![Nuget](https://img.shields.io/nuget/dt/Ogu.AspNetCore.Response.Json.svg?logo=nuget)](https://nuget.org/packages/Ogu.AspNetCore.Response.Json)\n\n## Introduction\n\nProvides a generic response type (`IResponse`) compatible with `IActionResult` in `Microsoft.AspNetCore.Mvc`. This library is designed to simplify API responses by offering a structured model that can contain data, errors, validation errors, and additional extensions.\n\n## Features\n\n- **Generic Response:** A flexible and generic response model that can hold various types of data, including single objects, collections, or custom data structures.\n- **Error Handling:** Easily handle errors and exceptions in API responses, providing consistent error formats.\n- **Validation Errors:** Supports validation errors, making it easy to report validation issues in the response.\n- **Extensions:** Includes support for adding custom extensions to the response for specific use cases.\n\n## Installation\n\nYou can install the library via NuGet Package Manager:\n\n```bash\ndotnet add package Ogu.AspNetCore.Response.Json\n```\n## Usage\n\n**example 1:** Returning an Success Response\n```csharp\npublic IActionResult GetExample1()\n{\n    return HttpStatusCode.OK.ToSuccessResponse(new string[]{ \"Freezing\", \"Bracing\", \"Chilly\" }).ToAction();\n}\n```\n\noutput\n\n```bash\n{\n  \"success\": true,\n  \"status\": 200,\n  \"data\": [\n    \"Freezing\",\n    \"Bracing\",\n    \"Chilly\"\n  ]\n}\n```\n\n**example 2:** Returning an Error Response Using an Enum\n```csharp\npublic enum ErrorKind\n{\n    [Error(\"Example Error\", \"Don't worry, everything's gonna be alright\", \"\", \"https://google.com\")]\n    ExampleErrorOccurred = 1\n}\n```\n```csharp\npublic IActionResult GetExample2()\n{\n    return HttpStatusCode.OK.ToFailureJsonResponse(ErrorKind.ExampleErrorOccurred).ToAction();\n}\n```\n\noutput\n\n```bash\n{\n  \"success\": false,\n  \"status\": 200,\n  \"errors\": [\n    {\n      \"title\": \"Example Error\",\n      \"description\": \"Don't worry, everything's gonna be alright\",\n      \"traces\": \"\",\n      \"code\": \"1\",\n      \"helpLink\": \"https://google.com\",\n      \"type\": 0 // 0: Custom, 1: Validation, 2: Exception\n    }\n  ]\n}\n```\n\n**example 3:** Returning an Error Response via ModelState\n```csharp\npublic IActionResult GetExample10([FromBody] SampleModel sample)\n{\n    return ModelState.IsValid\n        ? HttpStatusCode.OK.ToSuccessJsonResponse().ToAction()\n        : ModelState.ToJsonAction(); \n}\n```\n\noutput\n\n```bash\n{\n  \"success\": false,\n  \"status\": 400,\n  \"errors\": [\n    {\n      \"title\": \"Validation Error\",\n      \"description\": \"One or more validation errors occurred.\",\n      \"type\": 1,\n      \"validationFailures\": [\n        {\n          \"propertyName\": \"Name\",\n          \"message\": \"The field Name must be a string with a minimum length of 3 and a maximum length of 50.\",\n          \"severity\": 0\n        }\n      ]\n    }\n  ]\n}\n```\n\n\u003e [!TIP]\n\u003e If controller attached with `[ApiController]` attribute, ASP.NET Core automatically returns a 400 Bad Request when ModelState is invalid — before your controller code is even reached. To take full control you need to suppress this behavior:  \n\u003e `services.Configure\u003cApiBehaviorOptions\u003e(options =\u003e options.SuppressModelStateInvalidFilter = true);` \n\n**example 4:** Including Additional Data in the Response\n```csharp\npublic IActionResult GetExample5()\n{\n    var samples = HttpStatusCode.OK.ToSuccessJsonResponse(new string[]{ \"Freezing\", \"Bracing\", \"Chilly\" });\n    \n    samples.Extras[\"IsExample\"] = true;\n\n    return samples.ToAction();\n}\n```\n\noutput\n\n```bash\n{\n  \"success\": true,\n  \"status\": 200,\n  \"data\": [\n    \"Freezing\",\n    \"Bracing\",\n    \"Chilly\"\n  ],\n  \"extras\": {\n    \"isExample\": true\n  }\n}\n```\n\n**example 5:** Returning an Error Response via an Occurred Exception\n```csharp\npublic IActionResult GetExample8()\n{\n    try\n    {\n        int x = 0;\n        int y = 5 / x; // Will throw an exception\n\n        return HttpStatusCode.OK.ToSuccessJsonResponse().ToAction();\n    }\n    catch (Exception ex)\n    {\n        return ex.ToJsonResponse().ToAction();\n    }\n}\n```\n\noutput\n\n```bash\n{\n  \"success\": false,\n  \"status\": 500,\n  \"errors\": [\n    {\n      \"title\": \"Exception\",\n      \"description\": \"Attempted to divide by zero.\",\n      \"traces\": \"DivideByZeroException: Attempted to divide by zero.\",\n      \"code\": \"-2147352558\",\n      \"type\": 2\n    }\n  ]\n}\n```\n\n**example 6:** Returning an Error Response via an Occurred Exception\n```csharp\npublic IActionResult GetExamples14([FromQuery][Required] ExceptionTraceLevel traceLevel)\n{\n    try\n    {\n        var innerInnerEx = new InvalidOperationException(\"Operation isn't valid\");\n\n        var innerEx = new ApplicationException(\"Application caught an expected exception\", innerInnerEx);\n\n        throw new Exception(\"There are some exceptions\", innerEx);\n    }\n    catch (Exception ex)\n    {\n        return ex.ToJsonResponse(traceLevel).ToAction();\n    }\n}\n```\n\noutput: when the traces level 1 ( default )\n\n```bash\n{\n  \"success\": false,\n  \"status\": 500,\n  \"errors\": [\n    {\n      \"title\": \"Exception\",\n      \"description\": \"There are some exceptions\",\n      \"traces\": \"Exception: There are some exceptions -\u003e ApplicationException: Application caught an expected exception -\u003e InvalidOperationException: Operation isn't valid\",\n      \"code\": \"-2146233088\",\n      \"type\": 2\n    }\n  ]\n}\n```\n\n**example 7:** Returning an Error Response via a Custom Validation Rule\n```csharp\npublic IActionResult GetExamples13([FromBody] string id)\n{\n    var idRule = JsonValidationRules.GreaterThanRule(nameof(id), id, 0);\n\n    if (idRule.IsFailed())\n    {\n        return idRule.Failure.ToJsonResponse().ToAction();\n    }\n\n    var storedIdValue = idRule.GetStoredValue\u003cint\u003e();\n\n    return HttpStatusCode.OK.ToSuccessJsonResponse(storedIdValue).ToAction();\n}\n```\n\noutput: When the requested id value is 0\n\n```bash\n{\n  \"success\": false,\n  \"status\": 400,\n  \"errors\": [\n    {\n      \"title\": \"Validation Error\",\n      \"description\": \"One or more validation errors occurred.\",\n      \"type\": 1,\n      \"validationFailures\": [\n        {\n          \"propertyName\": \"id\",\n          \"message\": \"id must be valid number and value must be greater than 0.\",\n          \"attemptedValue\": \"0\",\n          \"severity\": 0\n        }\n      ]\n    }\n  ]\n}\n```\n\noutput: When the requested id value is greater than 1\n\n```bash\n{\n  \"success\": true,\n  \"status\": 200,\n  \"data\": 1\n}\n```\n\n### Handling Exceptions\n\nIn ASP.NET Core, you can register the exception-handling middleware early in the pipeline to ensure consistent error responses using `JsonResponse`.\n\n```csharp\napp.UseExceptionHandler(cfg =\u003e\n{\n    cfg.Run(async (context) =\u003e\n    {\n        var contextFeature = context.Features.Get\u003cIExceptionHandlerFeature\u003e();\n\n        var jsonResponse = contextFeature.Error.ToJsonResponse(ExceptionTraceLevel.Basic); // Default uses ExceptionTraceLevel.Basic\n        \n        // (optional) Add extras into the response model. e.g., CorrelationId etc.\n        jsonResponse.Extras.Add(nameof(HeaderNames.RequestId), Activity.Current?.Id ?? context.TraceIdentifier);\n\n        await jsonResponse.ToJsonAction().ExecuteResultAsync(context);\n    });\n});\n```\n\n```csharp\n[HttpGet(\"examples/15\")]\npublic IActionResult GetExamples15()\n{\n    var innerInnerEx = new InvalidOperationException(\"Operation isn't valid\");\n    var innerEx = new ApplicationException(\"Application caught an expected exception\", innerInnerEx);\n    throw new Exception(\"There are some exceptions\", innerEx);\n}\n```\n\noutput:\n\n```bash\n{\n  \"success\": false,\n  \"status\": 500,\n  \"errors\": [\n    {\n      \"title\": \"Exception\",\n      \"description\": \"There are some exceptions\",\n      \"traces\": \"Exception: There are some exceptions -\u003e ApplicationException: Application caught an expected exception -\u003e InvalidOperationException: Operation isn't valid\",\n      \"code\": \"-2146233088\",\n      \"type\": 2\n    }\n  ],\n  \"extras\": {\n    \"requestId\": \"00-127004cd9c8f88d3559496646d2624c8-4db22e889d3a2b52-00\"\n  }\n}\n```\n\n### Handling Model Validation Errors\n\nIn ASP.NET Core, you can customize how model validation errors are returned by configuring ApiBehaviorOptions. This allows you to return a consistent `JsonResponse` instead of the default `BadRequestObjectResult`.\n\n```csharp\nservices.Configure\u003cApiBehaviorOptions\u003e(options =\u003e\n{\n    // Override the default behavior to return JsonResponse for model validation errors\n    options.InvalidModelStateResponseFactory = context =\u003e context.ModelState.ToJsonAction();\n});\n```\n\n## Built-in Validation Rules\n\nThere are 8 built-in validation rules:\n\n- **JsonValidationRules.GreaterThanRule**: To check if a property value is greater than a specified threshold.   \n  Parsed value can be retrieved through the rule's `GetStoredValue\u003cT\u003e()` method.\n\n- **JsonValidationRules.SmallerThanRule**: To check if a property value is smaller than a specified threshold.\n  Parsed value can be retrieved through the rule's `GetStoredValue\u003cT\u003e()` method.\n\n- **JsonValidationRules.EqualToRule**: To check if a property value is equal to a specified value.\n  Parsed value can be retrieved through the rule's `GetStoredValue\u003cT\u003e()` method.\n\n- **JsonValidationRules.NotEmptyRule**: To check if a property value is empty.\n\n- **JsonValidationRules.ValidBooleanRule**: To check if a property value is a valid boolean string (\"true\" or \"false\").  \n  Parsed value can be retrieved through the rule's `GetStoredValue\u003cbool\u003e()` method.\n\n- **JsonValidationRules.ValidEnumRule**: To check if a property value is a valid enum value of the specified enum type.  \n  Parsed value can be retrieved through the rule's `GetStoredValue\u003cT\u003e()` method.\n\n- **JsonValidationRules.ValidNumberRule**: To check if a property value is a valid number (integer or floating-point).  \n  Parsed value can be retrieved through the rule's `GetStoredValue\u003cT\u003e()` method.\n\n- **JsonValidationRules.ValidJsonRule**: To check if a property value is a valid json string.  \n  Parsed value can be retrieved through the rule's `GetStoredValue\u003cJsonDocument\u003e()` method.\n\nYou can extend the rules above, just like the one below.\n```csharp\npublic static IValidationFailure InvalidBooleanFormat(string propertyName, object attemptedValue)\n{\n    return new JsonValidationFailure(\n        propertyName,\n        $\"The value '{attemptedValue}' for '{propertyName}' is not a valid boolean.\",\n        attemptedValue\n    );\n}\n```\n```csharp\npublic static ValidationRule ValidBooleanRule(string propertyName, string propertyValue)\n{\n    return new ValidationRule(JsonValidationFailures.InvalidBooleanFormat(propertyName, propertyValue),\n        (v) =\u003e\n        {\n            if (!bool.TryParse(propertyValue, out var parsedValue))\n            {\n                return true; // Return true to indicate validation failure if parsing fails\n            }\n\n            v.Store(parsedValue); // Store the parsed boolean value \n\n            return false; // Return false indicating validation success\n        });\n}\n```\n\n## Deserialization Process\n\nWhen deserializing a JsonResponse, you cannot use JsonSerializer.Deserialize directly, as it does not support deserializing interfaces. If the response indicates a failure, the deserialization process will throw an exception.\n\nTo address this, you should use DeserializableJsonResponse instead.\n\n```csharp\n\nvar deserializableJsonResponse = JsonSerializer.Deserialize\u003cDeserializableJsonResponse\u003e(mySerializedJsonResponse);\n\nIJsonResponse jsonResponse = deserializableJsonResponse.ToJsonResponse();\n\nIJsonResponse\u003cT\u003e jsonResponseT = deserializableJsonResponse.ToJsonResponse\u003cT\u003e(); // For generic type\n\n```\n\nIf you're using HttpClient to retrieve data of type JsonResponse, you can create extensions as shown below:\n\n```csharp\npublic static class HttpContentJsonResponseExtensions\n{\n    public static async Task\u003cIJsonResponse\u003e ToJsonResponseAsync(this HttpContent content, JsonSerializerOptions serializerOptions = null, CancellationToken cancellationToken = default)\n    {\n        var deserializableJsonResponse = await content.ReadFromJsonAsync\u003cDeserializableJsonResponse\u003e(serializerOptions, cancellationToken);\n\n        return deserializableJsonResponse.ToJsonResponse();\n    }\n\n    public static async Task\u003cIJsonResponse\u003cT\u003e\u003e ToJsonResponseAsync\u003cT\u003e(this HttpContent content, JsonSerializerOptions serializerOptions = null, CancellationToken cancellationToken = default)\n    {\n        var deserializableJsonResponse = await content.ReadFromJsonAsync\u003cDeserializableJsonResponse\u003e(serializerOptions, cancellationToken);\n\n        return deserializableJsonResponse.ToJsonResponse\u003cT\u003e(serializerOptions);\n    }\n}\n```\n\nUsage\n\n```csharp\nusing (var response = await _httpClient.GetAsync(relativeUri, cancellationToken\n{\n    return await response.Content.ToJsonResponseAsync(cancellationToken: cancellationToken);\n}\n\nusing (var response = await _httpClient.GetAsync(relativeUri, cancellationToken\n{\n    return await response.Content.ToJsonResponseAsync\u003cT\u003e(cancellationToken: cancellationToken); // For generic type\n}\n```\n\n\n## Sample Application\nA sample application demonstrating the usage of Ogu.Response can be found [here](https://github.com/ogulcanturan/Ogu.Response/tree/master/samples/Sample.Api).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fogulcanturan%2Fogu.response","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fogulcanturan%2Fogu.response","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fogulcanturan%2Fogu.response/lists"}