{"id":44537633,"url":"https://github.com/jasonmitchell/hallo","last_synced_at":"2026-02-13T18:52:01.286Z","repository":{"id":53039967,"uuid":"150962582","full_name":"jasonmitchell/hallo","owner":"jasonmitchell","description":null,"archived":false,"fork":false,"pushed_at":"2021-10-27T07:57:11.000Z","size":135,"stargazers_count":15,"open_issues_count":8,"forks_count":4,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-23T17:58:34.341Z","etag":null,"topics":["aspnetcore","hal","hypermedia"],"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/jasonmitchell.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}},"created_at":"2018-09-30T11:46:58.000Z","updated_at":"2025-07-05T20:50:22.000Z","dependencies_parsed_at":"2022-08-27T18:30:55.544Z","dependency_job_id":null,"html_url":"https://github.com/jasonmitchell/hallo","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/jasonmitchell/hallo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonmitchell%2Fhallo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonmitchell%2Fhallo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonmitchell%2Fhallo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonmitchell%2Fhallo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jasonmitchell","download_url":"https://codeload.github.com/jasonmitchell/hallo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonmitchell%2Fhallo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29414285,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-13T06:24:03.484Z","status":"ssl_error","status_checked_at":"2026-02-13T06:23:12.830Z","response_time":78,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["aspnetcore","hal","hypermedia"],"created_at":"2026-02-13T18:52:00.529Z","updated_at":"2026-02-13T18:52:01.267Z","avatar_url":"https://github.com/jasonmitchell.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hallo\n![](https://github.com/jasonmitchell/hallo/workflows/Build/badge.svg?branch=master)\n[![NuGet](https://img.shields.io/nuget/v/Hallo.svg?style=flat)](https://www.nuget.org/packages/Hallo/)\n[![NuGet](https://img.shields.io/nuget/v/Hallo.AspNetCore.svg?style=flat)](https://www.nuget.org/packages/Hallo.AspNetCore/)\n[![NuGet](https://img.shields.io/nuget/v/Hallo.AspNetCore.Mvc.svg?style=flat)](https://www.nuget.org/packages/Hallo.AspNetCore.Mvc/)\n\nHallo is an implementation of the [Hypertext Application Language (HAL)](http://stateless.co/hal_specification.html)\nformat for ASP.NET Core.\n\n## Why Hallo?\nThe primary design goal of Hallo is to enable generation of HAL documents through content negotiation\nwithout requiring HAL-specific code in models or controllers.\n\n\n## Getting started with Hallo\n\n## Installing Hallo\nHallo is available on Nuget as three packages:\n- [Hallo](https://www.nuget.org/packages/Hallo/)\n- [Hallo.AspNetCore](https://www.nuget.org/packages/Hallo.AspNetCore/)\n- [Hallo.AspNetCore.Mvc](https://www.nuget.org/packages/Hallo.AspNetCore.Mvc)\n\n```\ndotnet add package Hallo\ndotnet add package Hallo.AspNetCore\ndotnet add package Hallo.AspNetCore.Mvc\n```\n\nThe Hallo package is the core library which provides types for writing HAL representation generators\nand serializing objects to HAL+JSON strings. The Hallo.AspNetCore provides basic support for serializing \nHAL representations to the `HttpResponse` body stream and the Hallo.AspNetCore.Mvc package provides an\noutput formatter to leverage ASP.NET MVC content negotiation functionality.\n\nThe rest of this readme will assume you are using the Hallo.AspNetCore.MVC package.\n\n\n## Using Hallo\nHallo does not require any changes to existing models or controllers so it can easily be added to an \nexisting project.  \n\nTo get started using Hallo you need to first register the output formatter in ASP.NET Core to enable\ncontent negotiation for HAL responses:\n\n```csharp\nservices.AddControllers(options =\u003e\n{\n    options.RespectBrowserAcceptHeader = true;\n    options.OutputFormatters.Add(new HalJsonOutputFormatter());\n})\n```\n\nFor every resource you want to generate a HAL document for you need to derive a new class from `Hal\u003cT\u003e` \nand implement one or more of `IHalState\u003cT\u003e`, `IHalEmbedded\u003cT\u003e` or `IHalLinks\u003cT\u003e`:\n\n```csharp\npublic class PersonRepresentation : Hal\u003cPerson\u003e, \n                                    IHalState\u003cPerson\u003e,\n                                    IHalLinks\u003cPerson\u003e, \n                                    IHalEmbedded\u003cPerson\u003e\n{\n    public object StateFor(Person resource)\n    {\n        return new\n        {\n            resource.FirstName,\n            resource.LastName\n        };\n    }\n    \n    public IEnumerable\u003cLink\u003e LinksFor(Person resource)\n    {\n        yield return new Link(Link.Self, $\"/people/{resource.Id}\");\n        yield return new Link(\"contacts\", $\"/people/{resource.Id}/contacts\");\n    }\n\n    public object EmbeddedFor(Person resource)\n    {\n        return new\n        {\n            Contacts = new List\u003cPerson\u003e()\n        };\n    }\n}\n```\n\nEach resource representation needs to be registered in the ASP.NET Core `IServiceCollection`:\n\n```csharp\nservices.AddTransient\u003cHal\u003cPerson\u003e, PersonRepresentation\u003e();\n```\n\nGiven the example above, a HTTP request such as:\n```http\nGET http://localhost:5000/people/1\nAccept: application/hal+json\n```\n\nwill produce the result:\n```json\n{\n  \"firstName\": \"Geoffrey\",\n  \"lastName\": \"Merrill\",\n  \"_embedded\": {\n    \"contacts\": []\n  },\n  \"_links\": {\n    \"self\": {\n      \"href\": \"/people/1\"\n    },\n    \"contacts\": {\n      \"href\": \"/people/1/contacts\"\n    }\n  }\n}\n```\n\n\n### Dependency Injection\nAs resource representations are registered with and resolved through ASP.NET Core services, the standard\napproach to injecting dependencies can be applied.\n\n#### Example\n```csharp\npublic class PersonRepresentation : Hal\u003cPerson\u003e, \n                                    IHalEmbeddedAsync\u003cPerson\u003e\n{\n    private readonly ContactsLookup _contacts;\n\n    public PersonRepresentation(ContactsLookup contacts)\n    {\n        _contacts = contacts;\n    }\n\n    public async Task\u003cobject\u003e EmbeddedForAsync(Person resource)\n    {\n        var contacts = await _contacts.GetFor(resource.Id);\n        \n        return new\n        {\n            Contacts = contacts\n        };\n    }\n}\n```\n\n\n### Async Support\nHallo provides the interfaces `IHalStateAsync\u003cT\u003e`, `IHalEmbeddedAsync\u003cT\u003e` and `IHalLinksAsync\u003cT\u003e`.\nThese interfaces define asynchronous version of the `StateFor`, `EmbeddedFor` and `LinksFor` methods\nto enable the execution of asynchronous code as part of the HAL document generation process.\n\n#### Example\n```csharp\npublic class PersonRepresentation : Hal\u003cPerson\u003e, \n                                    IHalLinks\u003cPerson\u003e, \n                                    IHalEmbeddedAsync\u003cPerson\u003e\n{\n    public IEnumerable\u003cLink\u003e LinksFor(Person resource)\n    {\n        yield return new Link(Link.Self, $\"/people/{resource.Id}\");\n        yield return new Link(\"contacts\", $\"/people/{resource.Id}/contacts\");\n    }\n\n    public async Task\u003cobject\u003e EmbeddedForAsync(Person resource)\n    {\n        var contacts = await FetchContactsAsync(resource.Id);\n        \n        return new\n        {\n            Contacts = contacts\n        };\n    }\n}\n```\n\n### Nested Representations\nSometimes it is necessary to produce \"nested\" HAL documents.  For example it is common to generate `_links`\nfor resources under the `_embedded` property in the root HAL document.\n\nHallo supports recursive generation of HAL documents by wrapping embedded resources in a `HalRepresentation`.\n\n#### Example\n```csharp\npublic class PersonRepresentation : Hal\u003cPerson\u003e, \n                                    IHalLinks\u003cPerson\u003e, \n                                    IHalEmbedded\u003cPerson\u003e\n{\n    public IEnumerable\u003cLink\u003e LinksFor(Person resource)\n    {\n        yield return new Link(Link.Self, $\"/people/{resource.Id}\");\n        yield return new Link(\"contacts\", $\"/people/{resource.Id}/contacts\");\n    }\n\n    public object EmbeddedFor(Person resource)\n    {\n        var spouse = new Person\n        {\n            Id = 321,\n            FirstName = \"A\",\n            LastName = \"Spouse\"\n        };\n\n        var links = LinksFor(spouse);\n        \n        return new\n        {\n            Spouse = new HalRepresentation(spouse, links)\n        };\n    }\n}\n```\n\nThe above example will produce a response of:\n\n```json\n{\n  \"id\": 1,\n  \"firstName\": \"Geoffrey\",\n  \"lastName\": \"Merrill\",\n  \"_embedded\": {\n    \"spouse\": {\n      \"id\": 321,\n      \"firstName\": \"A\",\n      \"lastName\": \"Spouse\",\n      \"_links\": {\n        \"self\": {\n          \"href\": \"/people/321\"\n        },\n        \"contacts\": {\n          \"href\": \"/people/321/contacts\"\n        }\n      }\n    }\n  },\n  \"_links\": {\n    \"self\": {\n      \"href\": \"/people/1\"\n    },\n    \"contacts\": {\n      \"href\": \"/people/1/contacts\"\n    }\n  }\n}\n```\n\n### Prefixing Links With a Virtual Path\nIf a deployed API is available via a virtual path such as an IIS sub-application/virtual directory, API \ngateway or reverse proxy it may be necessary to prefix links with the virtual path.  For example, an API \nmay be developed locally with the URL `http://localhost:5000/people/{id}` however the API is deployed \nto production behind an API gateway with the URL `http://my-app/address-book/people/{id}`.\nIn this scenario it may be preferable to generate links prefixed with `/address-book`.\n\nThis can be easily achieved by ensuring the \n[PathBase](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httprequest.pathbase?view=aspnetcore-3.1)\nproperty for the request is set and using the ASP.NET Core \n[IUrlHelper](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.iurlhelper?view=aspnetcore-3.1)\nto create links rather than the string building approach used in this README.\n\n#### Example\nThe following ASP.NET Core services need to be registered on startup:\n```csharp\nservices.AddSingleton\u003cIActionContextAccessor, ActionContextAccessor\u003e();\nservices.AddScoped(x =\u003e {\n    var actionContext = x.GetRequiredService\u003cIActionContextAccessor\u003e().ActionContext;\n    var factory = x.GetRequiredService\u003cIUrlHelperFactory\u003e();\n    return factory.GetUrlHelper(actionContext);\n});\n```\n\nThe `IUrlHelper` can then be injected into representations and used to create links:\n```csharp\npublic class PersonRepresentation : Hal\u003cPerson\u003e, \n                                    IHalLinks\u003cPerson\u003e\n{\n    private readonly IUrlHelper _urlHelper;\n\n    public PersonRepresentation(IUrlHelper urlHelper)\n    {\n        _urlHelper = urlHelper;\n    }\n\n    public IEnumerable\u003cLink\u003e LinksFor(Person resource)\n    {\n        var self = _urlHelper.Action(\"Get\", \"People\", new {id = resource.Id});\n        var contacts = _urlHelper.Action(\"List\", \"Contacts\", new {personId = resource.Id});\n\n        yield return new Link(Link.Self, self);\n        yield return new Link(\"contacts\", contacts);\n    }\n}\n```\n\nAssuming a `PathBase` value of `/address-book`, the above example will produce a response of:\n```json\n{\n  \"id\": 1,\n  \"firstName\": \"Geoffrey\",\n  \"lastName\": \"Merrill\",\n  \"_links\": {\n    \"self\": {\n      \"href\": \"/address-book/people/1\"\n    },\n    \"contacts\": {\n      \"href\": \"/address-book/people/1/contacts\"\n    }\n  }\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjasonmitchell%2Fhallo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjasonmitchell%2Fhallo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjasonmitchell%2Fhallo/lists"}