{"id":21719358,"url":"https://github.com/wintoncode/winton.domainmodelling.aspnetcore","last_synced_at":"2025-04-12T20:34:49.980Z","repository":{"id":44917354,"uuid":"139850204","full_name":"wintoncode/Winton.DomainModelling.AspNetCore","owner":"wintoncode","description":"Provides conventions for creating an ASP.NET Core based REST API on top of a domain model","archived":false,"fork":false,"pushed_at":"2024-09-03T21:16:25.000Z","size":50,"stargazers_count":2,"open_issues_count":1,"forks_count":3,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-26T14:51:12.239Z","etag":null,"topics":["asp-net-core","domain-driven-design","domain-model","dotnet-core"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wintoncode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-05T13:01:18.000Z","updated_at":"2022-01-18T18:00:25.000Z","dependencies_parsed_at":"2022-09-02T22:34:28.411Z","dependency_job_id":null,"html_url":"https://github.com/wintoncode/Winton.DomainModelling.AspNetCore","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wintoncode%2FWinton.DomainModelling.AspNetCore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wintoncode%2FWinton.DomainModelling.AspNetCore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wintoncode%2FWinton.DomainModelling.AspNetCore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wintoncode%2FWinton.DomainModelling.AspNetCore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wintoncode","download_url":"https://codeload.github.com/wintoncode/Winton.DomainModelling.AspNetCore/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248631142,"owners_count":21136548,"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","domain-driven-design","domain-model","dotnet-core"],"created_at":"2024-11-26T01:36:52.749Z","updated_at":"2025-04-12T20:34:49.952Z","avatar_url":"https://github.com/wintoncode.png","language":"C#","readme":"# Winton.DomainModelling.AspNetCore\n\nConventions useful for creating an ASP.NET Core based REST API on top of a domain model. Specifically, it provides extension methods which convert from domain model types, as defined in [`Winton.DomainModelling.Abstractions`](https://github.com/wintoncode/Winton.DomainModelling.Abstractions) to ASP.NET Core types.\n\n[![NuGet Badge](https://buildstats.info/nuget/Winton.DomainModelling.AspNetCore)](https://www.nuget.org/packages/Winton.DomainModelling.AspNetCore/)\n[![Build history](https://buildstats.info/github/chart/wintoncode/Winton.DomainModelling.AspNetCore?branch=master)](https://github.com/wintoncode/Winton.DomainModelling.AspNetCore/actions)\n\n## `Result` Extensions\n\n`Result\u003cTData\u003e` is a type defined in the `Winton.DomainModelling.Abstractions` package.\nIt is a type that is intended to be returned from domain operations.\nIt allows operations to indicate both successes and failures to the client.\nIn this case the client is an ASP.NET Core Controller.\nIn a Controller, however, we need to return an `IActionResult` rather than a `Result\u003cTData\u003e`. We have two cases to consider:\n\n* If the `Result\u003cTData\u003e` was a success then we want to return a 2xx response from the API containing the data in the body.\n* If the `Result\u003cTData\u003e` was a failure then we want to return a 4xx response from the API containing [problem details](https://tools.ietf.org/html/rfc7807) in the body.\n\nThis library provides a `ToActionResult` extension method for `Result\u003cTData\u003e` which matches on the result and converts it to an appropriate `IActionResult`.\nThere are various overloads to provide flexibility.\n\nIt is expected that this will be used within an [`ApiController`](https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-2.2#annotation-with-apicontroller-attribute) so that ASP.NET Core will apply its REST API conventions to the `IActionResult`.\n\n### Successful Result Mappings\n\nThe following default mappings happen when the `Result` is a `Success`.\n\n| Result           | IActionResult         | HTTP Status   |\n| ---------------- | --------------------- | ------------- |\n| `Success\u003cTData\u003e` | `ActionResult\u003cTData\u003e` | 200 Ok        |\n| `Success\u003cUnit\u003e`  | `NoContentResult`     | 204 NoContent |\n\nThe defaults can be overriden by calling the extension method that takes a success mapping function.\nA common example of when this is used is in a `POST` action when an entity has been created and we would like to return a 201 Created response to the client.\n\n```csharp\n[HttpPost]\npublic async Task\u003cIActionResult\u003e CreateFoo(NewFoo newFoo)\n{\n    return await CreateFoo(newFoo.Bar)\n        .Select(FooResource.Create)\n        .ToActionResult(\n            f =\u003e Created(\n                Url.Action(nameof(Get), new { f.Id }),\n                f));\n}\n```\n\nThe `CreateFoo` method performs the domain logic to create a new `Foo` and returns `Result\u003cFoo\u003e`.\n\n*In a real application it would be defined in the domain model project.\nTo give the domain model an API which is defined in terms of commands and queries and to decouple it from the outer application layers the mediator pattern is often adopted.\n\nJimmy Bogard's [MediatR](https://github.com/jbogard/MediatR) is a useful library for implementing that pattern.*\n\n### Failure Result Mappings\n\nIf the `Result` is a `Failure` then the `Error` it contains is mapped to a [`ProblemDetails`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.problemdetails) and is wrapped in an `IActionResult` with the corresponding status code.\n\nThe following table shows the default mappings.\n\n| Error                | IActionResult         | HTTP Status    |\n| -------------------- | --------------------- | -------------- |\n| `Error`*             | `BadRequestResult`    | 400 BadRequest |\n| `UnauthorizedError`  | `ForbidResult`        | 403 Forbidden  |\n| `NotFoundError`      | `NoContentResult`     | 404 NotFound   |\n\n_*This includes any other types that inherit from `Error` and are not explicitly listed._\n\nThe defaults can be overriden by calling the extension method that takes an error mapping function.\nThis is useful when the domain model has defined additional error types and these need to be converted to the relevant problem details. \nThe status code that is set on the `ProblemDetails` will also be set on the `IActionResult` by the extension method so that the HTTP status code on the response is correct.\n\nFor example consider a domain model that deals with payments.\nIt could be a news service which requires a subscription to access content.\nIt might contain several operations that require payment to be made before they can proceed.\nThis domain may therefore define a new error type as follows:\n\n```csharp\npublic class PaymentRequired : Error\n{\n    public PaymentRequired(string detail)\n        : base(\"Payment Required\", detail)\n    {\n    }\n}\n```\n\nIt would therefore make sense to map this to a `402 Payment Required` HTTP response with relevant `ProblemDetails`.\nThis can be achieved like so:\n\n```csharp\n[HttpGet(\"{id}\")]\npublic async Task\u003cIActionResult\u003e GetNewsItem(string id)\n{\n    return await GetNewsItem(id)\n        .ToActionResult(\n            error =\u003e new ProblemDetails\n            {\n                Detail = error.Detail,\n                Status = StatusCodes.Status402PaymentRequired,\n                Title = error.Title,\n                Type = \"https://example.com/problems/payment-required\"\n            }\n        )\n}\n```\n\nThe type field should return a URI that resolves to human-readable documentation about the type of error that has occurred.\nThis can either be existing documentation, such as [https://httpstatuses.com](https://httpstatuses.com) for common errors, or your own documentation for domain-specific errors.\n\nProblem details is formally documented in [RFC 7807](https://tools.ietf.org/html/rfc7807).\nMore information about how the fields should be used can be found there.\n\nIn order to maintain a loose coupling between the API layer and the domain model each action method should know how to map any kind of domain error.\nTo achieve this we could define a function that does this mapping for us and then use it throughout.\nFor example:\n\n```csharp\ninternal static ProblemDetails MapDomainErrors(Error error)\n{\n    switch (error)\n    {\n        case PaymentRequired _:\n            return new ProblemDetails\n            {\n                Detail = error.Detail,\n                Status = StatusCodes.Status402PaymentRequired,\n                Title = error.Title,\n                Type = \"https://example.com/problems/payment-required\"\n            }\n        // handle other custom types\n        default:\n            return null;\n    }\n}\n```\n\nBy using C# pattern matching we can easily match on the type of error and map it to a `ProblemDetails`.\nReturning `null` in the default case means the existing error mappings for the common error types, as defined above, are used.\n\nIf you have a custom error type and you are happy for your REST API to return `400 Bad Request` when it occurs, then the default error mappings for the base `Error` type should already work for you.\nIt maps the error's details and title to the corresponding fields on the problem details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwintoncode%2Fwinton.domainmodelling.aspnetcore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwintoncode%2Fwinton.domainmodelling.aspnetcore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwintoncode%2Fwinton.domainmodelling.aspnetcore/lists"}