{"id":13655310,"url":"https://github.com/ardalis/Result","last_synced_at":"2025-04-23T12:33:15.251Z","repository":{"id":37958995,"uuid":"223262363","full_name":"ardalis/Result","owner":"ardalis","description":"A result abstraction that can be mapped to HTTP response codes if needed.","archived":false,"fork":false,"pushed_at":"2025-04-11T18:06:24.000Z","size":416,"stargazers_count":935,"open_issues_count":24,"forks_count":118,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-04-14T05:54:13.003Z","etag":null,"topics":["csharp","design-patterns","domain-driven-design","dotnet","dotnet-core","functional","hacktoberfest","maybe","result"],"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/ardalis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2019-11-21T20:45:19.000Z","updated_at":"2025-04-13T08:50:35.000Z","dependencies_parsed_at":"2024-02-22T13:25:50.399Z","dependency_job_id":"5ba8aea9-b518-4295-97a5-8b2cd81282e9","html_url":"https://github.com/ardalis/Result","commit_stats":{"total_commits":129,"total_committers":21,"mean_commits":6.142857142857143,"dds":0.3798449612403101,"last_synced_commit":"e4ab1a6d6c3ee9964f128cd3fd8c80e24d216ea1"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ardalis%2FResult","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ardalis%2FResult/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ardalis%2FResult/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ardalis%2FResult/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ardalis","download_url":"https://codeload.github.com/ardalis/Result/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250435286,"owners_count":21430254,"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":["csharp","design-patterns","domain-driven-design","dotnet","dotnet-core","functional","hacktoberfest","maybe","result"],"created_at":"2024-08-02T03:01:02.559Z","updated_at":"2025-04-23T12:33:10.193Z","avatar_url":"https://github.com/ardalis.png","language":"C#","readme":"[![Ardalis.Result - NuGet](https://img.shields.io/nuget/v/Ardalis.Result.svg?label=Ardalis.Result%20-%20nuget)](https://www.nuget.org/packages/Ardalis.Result) [![NuGet](https://img.shields.io/nuget/dt/Ardalis.Result.svg)](https://www.nuget.org/packages/Ardalis.Result) [![Build Status](https://github.com/ardalis/Result/workflows/.NET%20Core/badge.svg)](https://github.com/ardalis/Result/actions?query=workflow%3A%22.NET+Core%22)\n\n[![Ardails.Result.AspNetCore - NuGet](https://img.shields.io/nuget/v/Ardalis.Result.AspNetCore.svg?label=Ardalis.Result.AspNetCore%20-%20nuget)](https://www.nuget.org/packages/Ardalis.Result.AspNetCore) [![NuGet](https://img.shields.io/nuget/dt/Ardalis.Result.AspNetCore.svg)](https://www.nuget.org/packages/Ardalis.Result.AspNetCore) \u0026nbsp; [![Ardails.Result.FluentValidation - NuGet](https://img.shields.io/nuget/v/Ardalis.Result.FluentValidation.svg?label=Ardalis.Result.FluentValidation%20-%20nuget)](https://www.nuget.org/packages/Ardalis.Result.FluentValidation) [![NuGet](https://img.shields.io/nuget/dt/Ardalis.Result.FluentValidation.svg)](https://www.nuget.org/packages/Ardalis.Result.FluentValidation)\n\n\u003ca href=\"https://twitter.com/intent/follow?screen_name=ardalis\"\u003e\n    \u003cimg src=\"https://img.shields.io/twitter/follow/ardalis.svg?label=Follow%20@ardalis\" alt=\"Follow @ardalis\" /\u003e\n\u003c/a\u003e \u0026nbsp; \u003ca href=\"https://twitter.com/intent/follow?screen_name=nimblepros\"\u003e\n    \u003cimg src=\"https://img.shields.io/twitter/follow/nimblepros.svg?label=Follow%20@nimblepros\" alt=\"Follow @nimblepros\" /\u003e\n\u003c/a\u003e\n\n# Result\n\nA result abstraction that can be mapped to HTTP response codes if needed.\n\n## Docs\n\nDocs are located in the /docs folder and available online at [result.ardalis.com](https://result.ardalis.com) Please add issues for new docs requests and [pull requests for docs issues](https://github.com/ardalis/Result/issues?q=is%3Aopen+is%3Aissue+label%3Adocumentation) are welcome!\n\n## Learn More\n\n* [Getting Started With Ardalis.Result](https://blog.nimblepros.com/blogs/getting-started-with-ardalis-result/)\n* [Transforming Results With the Map Method](https://blog.nimblepros.com/blogs/transforming-results-with-the-map-method/)\n* [Avoid Using Exceptions to Determine API Status Codes and Responses](https://ardalis.com/avoid-using-exceptions-determine-api-status/)\n* [Fluent Validation in MediatR with Results](https://youtu.be/9KuLsPV8BYU)\n\n## What Problem Does This Address?\n\nMany methods on service need to return some kind of value. For instance, they may be looking up some data and returning a set of results or a single object. They might be creating something, persisting it, and then returning it. Typically, such methods are implemented like this:\n\n```csharp\npublic Customer GetCustomer(int customerId)\n{\n  // more logic\n  return customer;\n}\n\npublic Customer CreateCustomer(string firstName, string lastName)\n{\n  // more logic\n  return customer;\n}\n```\n\nThis works great as long as we're only concerned with the happy path. But what happens if there are multiple failure modes, not all of which make sense to be handled by exceptions?\n\n- What happens if customerId is not found?\n- What happens if required `lastName` is not provided?\n- What happens if the current user doesn't have permission to create new customers?\n\nThe standard way to address these concerns is with exceptions. Maybe you throw a different exception for each different failure mode, and the calling code is then required to have multiple catch blocks designed for each type of failure. This makes life painful for the consumer, and results in a lot of exceptions for things that aren't necessarily *exceptional*. Like this:\n\n```csharp\n[HttpGet]\npublic async Task\u003cActionResult\u003cCustomerDTO\u003e\u003e GetCustomer(int customerId)\n{\n  try\n  {\n    var customer = _repository.GetById(customerId);\n    \n    var customerDTO = CustomerDTO.MapFrom(customer);\n    \n    return Ok(customerDTO);\n  }\n  catch (NullReferenceException ex)\n  {\n    return NotFound();\n  }\n  catch (Exception ex)\n  {\n    return new StatusCodeResult(StatusCodes.Status500InternalServerError);\n  }\n}\n```\n\nAnother approach is to return a `Tuple` of the expected result along with other things, like a status code and additional failure mode metadata. While tuples can be great for individual, flexible responses, they're not as good for having a single, standard, reusable approach to a problem.\n\nThe Result pattern provides a standard, reusable way to return both success as well as multiple kinds of non-success responses from .NET services in a way that can easily be mapped to API response types. Although the [Ardalis.Result](https://www.nuget.org/packages/Ardalis.Result/) package has no dependencies on ASP.NET Core and can be used from any .NET Core application, the [Ardalis.Result.AspNetCore](https://www.nuget.org/packages/Ardalis.Result.AspNetCore/) companion package includes resources to enhance the use of this pattern within ASP.NET Core web API applications.\n\n## Sample Usage\n\n### Creating a Result\n\nThe [sample folder](https://github.com/ardalis/Result/tree/main/sample/Ardalis.Result.SampleWeb) includes some examples of how to use the project. Here are a couple of simple uses.\n\nImagine the snippet below is defined in a domain service that retrieves WeatherForecasts. When compared to the approach described above, this approach uses a result to handle common failure scenarios like missing data denoted as NotFound and or input validation errors denoted as Invalid. If execution is successful, the result will contain the random data generated by the final return statement.\n\n```csharp\npublic Result\u003cIEnumerable\u003cWeatherForecast\u003e\u003e GetForecast(ForecastRequestDto model)\n{\n    if (model.PostalCode == \"NotFound\") return Result\u003cIEnumerable\u003cWeatherForecast\u003e\u003e.NotFound();\n\n    // validate model\n    if (model.PostalCode.Length \u003e 10)\n    {\n        return Result\u003cIEnumerable\u003cWeatherForecast\u003e\u003e.Invalid(new List\u003cValidationError\u003e {\n            new ValidationError\n            {\n                Identifier = nameof(model.PostalCode),\n                ErrorMessage = \"PostalCode cannot exceed 10 characters.\" \n            }\n        });\n    }\n\n    var rng = new Random();\n    return new Result\u003cIEnumerable\u003cWeatherForecast\u003e\u003e(Enumerable.Range(1, 5)\n        .Select(index =\u003e new WeatherForecast\n        {\n            Date = DateTime.Now.AddDays(index),\n            TemperatureC = rng.Next(-20, 55),\n            Summary = Summaries[rng.Next(Summaries.Length)]\n        })\n    .ToArray());\n}\n```\n\n### Translating Results to ActionResults\n\nContinuing with the domain service example from the previous section, it's important to show that the domain service doesn't know about `ActionResult` or other MVC/etc types. But since it is using a `Result\u003cT\u003e` abstraction, it can return results that are easily mapped to HTTP status codes. Note that the method above returns a `Result\u003cIEnumerable\u003cWeatherForecast\u003e` but in some cases, it might need to return an `Invalid` result, or a `NotFound` result. Otherwise, it returns a `Success` result with the actual returned value (just like an API would return an HTTP 200 and the actual result of the API call).\n\nYou can apply the `[TranslateResultToActionResult]` attribute to an [API Endpoint](https://github.com/ardalis/ApiEndpoints) (or controller action if you still use those things) and it will automatically translate the `Result\u003cT\u003e` return type of the method to an `ActionResult\u003cT\u003e` appropriately based on the Result type.\n\n```csharp\n[TranslateResultToActionResult]\n[HttpPost(\"Create\")]\npublic Result\u003cIEnumerable\u003cWeatherForecast\u003e\u003e CreateForecast([FromBody]ForecastRequestDto model)\n{\n    return _weatherService.GetForecast(model);\n}\n```\n\nAlternatively, you can use the `ToActionResult` helper method within an endpoint to achieve the same thing:\n\n```csharp\n[HttpPost(\"/Forecast/New\")]\npublic override ActionResult\u003cIEnumerable\u003cWeatherForecast\u003e\u003e Handle(ForecastRequestDto request)\n{\n    return this.ToActionResult(_weatherService.GetForecast(request));\n\n    // alternately\n    // return _weatherService.GetForecast(request).ToActionResult(this);\n}\n```\n\n### Translating Results to Minimal API Results\n\nSimilar to how the `ToActionResult` extension method translates `Ardalis.Results` to `ActionResults`, the `ToMinimalApiResult` translates results to the new `Microsoft.AspNetCore.Http.Results` `IResult` types in .NET 6+. The following code snippet demonstrates how one might use the domain service that returns a `Result\u003cIEnumerable\u003cWeatherForecast\u003e\u003e` and convert to a `Microsoft.AspNetCore.Http.Results` instance.\n\n```csharp\napp.MapPost(\"/Forecast/New\", (ForecastRequestDto request, WeatherService weatherService) =\u003e\n{\n    return weatherService.GetForecast(request).ToMinimalApiResult();\n})\n.WithName(\"NewWeatherForecast\");\n```\n\nThe full Minimal API sample can be found in the [sample folder](./sample/Ardalis.Result.SampleMinimalApi/Program.cs).\n\n### Mapping Results From One Type to Another\n\nA common use case is to map between domain entities to API response types usually represented as DTOs. You can map a result containing a domain entity to a Result containing a DTO by using the `Map` method. The following example calls the method `_weatherService.GetSingleForecast` which returns a `Result\u003cWeatherForecast\u003e` which is then converted to a `Result\u003cWeatherForecastSummaryDto\u003e` by the call to `Map`. Then, the result is converted to an `ActionResult\u003cWeatherForecastSummaryDto\u003e` using the `ToActionResult` helper method.\n\n```csharp\n[HttpPost(\"Summary\")]\npublic ActionResult\u003cWeatherForecastSummaryDto\u003e CreateSummaryForecast([FromBody] ForecastRequestDto model)\n{\n    return _weatherService.GetSingleForecast(model)\n        .Map(wf =\u003e new WeatherForecastSummaryDto(wf.Date, wf.Summary))\n        .ToActionResult(this);\n}\n```\n\n## ASP.NET API Metadata\n\nBy default, Asp Net Core and API Explorer know nothing about `[TranslateResultToActionResult]` and what it is doing. To reflect `[TranslateResultToActionResult]` behavior in metadata generated by API Explorer (which is then used by tools like Swashbuckle, NSwag etc.), you can use `ResultConvention`:\n\n```csharp\nservices.AddControllers(mvcOptions =\u003e mvcOptions.AddDefaultResultConvention());\n```\n\nThis will add `[ProducesResponseType]` for every known `ResultStatus` to every endpoint marked with `[TranslateResultToActionResult]`.\nTo customize ResultConvention behavior, one may use the `AddResultConvention` method:\n\n```csharp\nservices.AddControllers(mvcOptions =\u003e mvcOptions\n    .AddResultConvention(resultStatusMap =\u003e resultStatusMap\n        .AddDefaultMap()\n     ));\n```\n\nThis code is functionally equivalent to the previous example.\n\nFrom here you can modify the ResultStatus to HttpStatusCode mapping\n\n```csharp\nservices.AddControllers(mvcOptions =\u003e mvcOptions\n    .AddResultConvention(resultStatusMap =\u003e resultStatusMap\n        .AddDefaultMap()\n        .For(ResultStatus.Ok, HttpStatusCode.OK, resultStatusOptions =\u003e resultStatusOptions\n            .For(\"POST\", HttpStatusCode.Created)\n            .For(\"DELETE\", HttpStatusCode.NoContent))\n        .For(ResultStatus.Error, HttpStatusCode.InternalServerError)\n    ));\n```\n\n`ResultConvention` will add `[ProducesResponseType]` for every result status configured in `ResultStatusMap`. `AddDefaultMap()` maps every known ResultType, so if you want to exclude certain ResultType from being listed (e.g. your app doesn't have authentication and authorization, and you don't want 401 and 403 to be listed as `SupportedResponseType`) you can do this:\n\n```csharp\nservices.AddControllers(mvcOptions =\u003e mvcOptions\n    .AddResultConvention(resultStatusMap =\u003e resultStatusMap\n        .AddDefaultMap()\n        .For(ResultStatus.Ok, HttpStatusCode.OK, resultStatusOptions =\u003e resultStatusOptions\n            .For(\"POST\", HttpStatusCode.Created)\n            .For(\"DELETE\", HttpStatusCode.NoContent))\n        .Remove(ResultStatus.Forbidden)\n        .Remove(ResultStatus.Unauthorized)\n    ));\n```\n\nAlternatively, you can specify which (failure) result statuses are expected from a certain endpoint:\n\n```csharp\n[TranslateResultToActionResult()]\n[ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)]\n[HttpDelete(\"Remove/{id}\")]\npublic Result RemovePerson(int id)\n{\n    // Method logic\n}\n```\n\n`!!!` If you list a certain Result status in `ExpectedFailures`, it must be configured in `ResultConvention` on startup.\n\nAnother configurable feature is what part of the Result object is returned in case of specific failure:\n\n```csharp\nservices.AddControllers(mvcOptions =\u003e mvcOptions\n    .AddResultConvention(resultStatusMap =\u003e resultStatusMap\n        .AddDefaultMap()\n        .For(ResultStatus.Error, HttpStatusCode.BadRequest, resultStatusOptions =\u003e resultStatusOptions\n            .With((ctrlr, result) =\u003e string.Join(\"\\r\\n\", result.ValidationErrors)))\n    ));\n```\n\n### Using Results with FluentValidation\n\nWe can use Ardalis.Result.FluentValidation on a service with FluentValidation like that:\n\n```csharp\npublic async Task\u003cResult\u003cBlogCategory\u003e\u003e UpdateAsync(BlogCategory blogCategory)\n{\n    if (Guid.Empty == blogCategory.BlogCategoryId) return Result\u003cBlogCategory\u003e.NotFound();\n\n    var validator = new BlogCategoryValidator();\n    var validation = await validator.ValidateAsync(blogCategory);\n    if (!validation.IsValid)\n    {\n        return Result\u003cBlogCategory\u003e.Invalid(validation.AsErrors());\n    }\n\n    var itemToUpdate = (await GetByIdAsync(blogCategory.BlogCategoryId)).Value;\n    if (itemToUpdate == null)\n    {\n        return Result\u003cBlogCategory\u003e.NotFound();\n    }\n\n    itemToUpdate.Update(blogCategory.Name, blogCategory.ParentId);\n\n    return Result\u003cBlogCategory\u003e.Success(await _blogCategoryRepository.UpdateAsync(itemToUpdate));\n}\n```\n\n## Getting Started\n\nIf you're building an ASP.NET Core Web API you can simply install the [Ardalis.Result.AspNetCore](https://www.nuget.org/packages/Ardalis.Result.AspNetCore/) package to get started. Then, apply the `[TranslateResultToActionResult]` attribute to any actions or controllers that you want to automatically translate from Result types to ActionResult types.\n","funding_links":[],"categories":["Results Objects Pattern (Discriminate Unions + Functional Programming)","csharp","hacktoberfest"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fardalis%2FResult","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fardalis%2FResult","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fardalis%2FResult/lists"}