{"id":21437633,"url":"https://github.com/bpawluk/easyapi","last_synced_at":"2025-04-05T12:01:57.217Z","repository":{"id":229715892,"uuid":"770075785","full_name":"bpawluk/EasyApi","owner":"bpawluk","description":"Web APIs for Blazor made easy","archived":false,"fork":false,"pushed_at":"2025-03-19T19:17:19.000Z","size":269,"stargazers_count":68,"open_issues_count":2,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-29T11:03:23.010Z","etag":null,"topics":["api","blazor","blazor-server","blazor-webassembly","contract"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/BlazorUtils.EasyApi","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/bpawluk.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}},"created_at":"2024-03-10T21:01:21.000Z","updated_at":"2025-03-24T10:25:33.000Z","dependencies_parsed_at":"2024-04-14T11:29:54.863Z","dependency_job_id":"442fa938-639a-4909-9989-97cdebaeb4a6","html_url":"https://github.com/bpawluk/EasyApi","commit_stats":null,"previous_names":["bpawluk/easyapi"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bpawluk%2FEasyApi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bpawluk%2FEasyApi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bpawluk%2FEasyApi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bpawluk%2FEasyApi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bpawluk","download_url":"https://codeload.github.com/bpawluk/EasyApi/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247332556,"owners_count":20921853,"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":["api","blazor","blazor-server","blazor-webassembly","contract"],"created_at":"2024-11-23T00:28:01.301Z","updated_at":"2025-04-05T12:01:57.192Z","avatar_url":"https://github.com/bpawluk.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EasyApi\nIntroducing Easy Api – a new way to define and consume Web APIs for Blazor apps. \n\n## Why would you want to use it?\n\n**✅ Contract-First API development**\n- Bring order to your development process and gain a clear and consistent API structure.\n\n**✅ Shared data model**\n- Harness the true power of Blazor with a single data model for your server and client code.  \n- Forget about countless DTOs, mapping, versioning, and runtime errors.  \n- Welcome EasyApi's type-safe contract shared between both ends of your application.\n\n**✅ Unified logic across all Render Modes**\n- Stop worrying about where your client code is running.  \n- Just call your requests and let EasyApi do the rest.  \n- It does not matter if it is Wasm, SSR or prerendering – the job gets done.\n\n**✅ No boilerplate**\n- Save time and focus on what's important.  \n- EasyApi handles both client and server configuration for you.  \n- All you need to do is define the contract and handle your requests.\n\n## Sounds good? \nJump right into practice with [EasyApi Website](https://github.com/bpawluk/EasyApiWebsite) – an application that demonstrates how to set up and use EasyApi.\n\n## Want to know more? \nThe path to understanding EasyApi is straightforward and consists of getting to know the three key elements of the library - the contract, the server, and the client apps.\n\n### Contract\nEasyApi Contract defines the API endpoints you want to expose and consume. Each endpoint is represented as a C# ```class``` that primarily defines the structure of incoming requests and the type of returned responses along with the HTTP medium details.\n\n#### Setup\n\n**1️⃣ Add a new Class Library Project to your Solution.**\n\n**2️⃣ Reference the [BlazorUtils.EasyApi](https://www.nuget.org/packages/BlazorUtils.EasyApi) NuGet package.**\n\n```xml\n\u003cPackageReference Include=\"BlazorUtils.EasyApi\" Version=\"[use the latest version here]\" /\u003e\n```\n\n#### Defining the Contract\n\n**1️⃣ Create a ```class``` for your requests.**\n\n```csharp\npublic class AddComment {}\n```\n\n**2️⃣ Declare the HTTP Method you want to use by implementing one of the predefined ```interfaces```.**\n\n```csharp\npublic class AddComment : IPost {}\n```\n\nIf the API endpoint is to respond with specific data, use a generic version of the ```interface``` and specify the expected response type.  \n\n```csharp\npublic class AddComment : IPost\u003cGuid\u003e {}\n```\n\nThe full list of available ```interfaces``` includes: \n  - ```IHead```, \n  - ```IGet``` and ```IGet\u003cResponseType\u003e```,\n  - ```IPost``` and ```IPost\u003cResponseType\u003e```, \n  - ```IPut``` and ```IPut\u003cResponseType\u003e```, \n  - ```IPatch``` and ```IPatch\u003cResponseType\u003e```, \n  - ```IDelete``` and ```IDelete\u003cResponseType\u003e```.\n\n**3️⃣ Declare the API endpoint's route by using the ```RouteAttribute```.**\n\n```csharp\n[Route(\"api/articles/{ArticleID}/comments\")]\npublic class AddComment : IPost\u003cGuid\u003e {}\n```\n\nUse ```ProtectedRouteAttribute``` if the endpoint requires authorization.\n\n```csharp\n[ProtectedRoute(\"api/articles/{ArticleID}/comments\")]\npublic class AddComment : IPost\u003cGuid\u003e {}\n```\n\n\u003e [!CAUTION]\n\u003e Authorization is used only to protect the API endpoint. EasyApi requests are not authorized during pre-rendering and server-side rendering scenarios. Use [Blazor authorization measures](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/#authorization) to protect your Client app. \n\n**4️⃣ Define the parameters that make up the request structure.**\n\n```csharp\n[ProtectedRoute(\"api/articles/{ArticleID}/comments\")]\npublic class AddComment : IPost\u003cGuid\u003e\n{\n    [RouteParam]\n    public Guid ArticleID { get; init; }\n\n    [BodyParam]\n    public string Author { get; init; } = default!;\n\n    [BodyParam]\n    public string Content { get; init; } = default!;\n}\n```\n\nYou can specify a different way of sending values for each of the parameters by using one of the predefined ```attributes```: \n- ```BodyParamAttribute``` - value sent in the body of a HTTP request (if the HTTP Method supports it), \n- ```HeaderParamAttribute``` - value sent as a HTTP Header, \n- ```QueryStringParamAttribute``` - value sent as an argument appended to the requested URL, \n- ```RouteParamAttribute``` - value sent within the requested URL itself.\n\n\u003e [!NOTE]  \n\u003e When using route parameters, the route must contain matching ```{PropertyName}``` placeholders.\n\n### Server \nThe responsibility of the EasyApi Server app is to handle all requests defined in the Contract.\n\n#### Setup\n\n**1️⃣ You should start with a ```Microsoft.NET.Sdk.Web``` SDK Project that is the Server for your application.**\n\n**2️⃣ Reference the [BlazorUtils.EasyApi.Server](https://www.nuget.org/packages/BlazorUtils.EasyApi.Server) NuGet package.**\n\n```xml\n\u003cPackageReference Include=\"BlazorUtils.EasyApi.Server\" Version=\"[use the latest version here]\" /\u003e\n```\n\n**3️⃣ Register EasyApi services.**\n\n```csharp\nusing BlazorUtils.EasyApi;\nusing BlazorUtils.EasyApi.Server;\n\nvar contractAssembly = typeof(AddComment).Assembly;\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services\n    .AddEasyApi()\n    .WithContract(contractAssembly)\n    .WithServer();\n```\n\n**4️⃣ Map EasyApi endpoints.**\n```csharp\nvar app = builder.Build();\n// [...]\napp.MapRequests();\n```\n\n#### Handling requests\n\n**1️⃣ Create the request handler ```class```.**\n\n```csharp\ninternal class AddCommentHandler : IHandle\u003cAddComment, Guid\u003e { }\n```\n\n**2️⃣ Implement the ```IHandle``` ```interface```.**\n\n```csharp\ninternal class AddCommentHandler(ICommentsRepository CommentsRepository) : IHandle\u003cAddComment, Guid\u003e\n{\n    public async Task\u003cHttpResult\u003cGuid\u003e\u003e Handle(AddComment request, CancellationToken cancellationToken)\n    {\n        var newComment = new Comment(request);\n        var newCommentID = await CommentsRepository.Add(newComment, cancellationToken);\n        return HttpResult\u003cGuid\u003e.Created(newCommentID);\n    }\n}\n```\n\n### Client\nThe EasyApi Client app creates requests defined in the Contract and sends them to the Server for processing.\n\n#### Setup\n\n**1️⃣ You should start with a ```Microsoft.NET.Sdk.BlazorWebAssembly``` SDK Project that is your Client application.**\n\n**2️⃣ Reference the [BlazorUtils.EasyApi.Client](https://www.nuget.org/packages/BlazorUtils.EasyApi.Client) NuGet package.**\n\n```xml\n\u003cPackageReference Include=\"BlazorUtils.EasyApi.Client\" Version=\"[use the latest version here]\" /\u003e\n```\n\n**3️⃣ Register EasyApi services.**\n\n```csharp\nusing BlazorUtils.EasyApi;\nusing BlazorUtils.EasyApi.Client;\n\nvar contractAssembly = typeof(AddComment).Assembly;\nvar builder = WebAssemblyHostBuilder.CreateDefault(args);\n\nbuilder.Services\n    .AddEasyApi()\n    .WithContract(contractAssembly)\n    .WithClient();\n```\n\n**4️⃣ Setup the ```HttpClient```.**\n\nSimply register it as a service,\n\n```csharp\nbuilder.Services.AddScoped(provider =\u003e new HttpClient\n{\n    BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)\n});\n```\n\nor [configure a provider](#http-client-provider) for more control over the ```HttpClient```.\n\n#### Sending requests\n\n**1️⃣ Inject the caller of the endpoint you want to use.**\n\n```csharp\n@inject ICall\u003cAddComment, Guid\u003e AddComment\n```\n\n**2️⃣ Create the request.**\n\n```csharp\nvar request = new AddComment()\n{\n    ArticleID = Article.ID,\n    Author = \"EasyApi Enjoyer\",\n    Content = \"This is very easy!\"\n};\n```\n\n**3️⃣ Call the API endpoint.**\n\nUse the ```Call``` method to simply get the response,\n\n```csharp\nGuid newCommentID = await AddComment.Call(request);\n```\n\nor turn to the ```CallHttp``` method if you need to access the ```HttpResult```.\n\n```csharp\nHttpResult\u003cGuid\u003e result = await AddComment.CallHttp(request);\nif (result.StatusCode == HttpStatusCode.Created)\n{\n    Guid newCommentID = result.Response!;\n}\n```\n\n## Additional configuration\n\n### Response Persistence\nResponse Persistence allows you to seamlessly store and reuse EasyApi request responses for improved performance and user experience.\n\n**1️⃣ In order to use it, you must first configure one of the available persistence options.**\n- [Prerendered Response Persistence](#prerendered-response-persistence)\n- [In-memory Response Persistence](#in-memory-response-persistence)\n\n**2️⃣ Then inject IPersistentCall as the caller of the endpoint you want to use.**\n\n```csharp\n@inject IPersistentCall\u003cGetComments, IEnumerable\u003cComment\u003e\u003e GetComments\n```\n\n**3️⃣ And provide a unique storage key for the request when calling the API endpoint.**\n\n```csharp\nvar comments = await GetComments.Call(\"a-unique-request-identifier\", request);\n```\n\nOnce the initial request is completed and persisted, it will be used for subsequent calls according to the rules of the configured persistence options.\n\n#### Forcing fresh calls\nTo ensure that fresh data is loaded from the server (e.g. after a user action), you can set the `forceFreshCall` flag to `true` when using `IPersistentCall`. It will bypass all persistence options and send the request directly to the server.\n\n```csharp\nvar comments = await GetComments.Call(\"a-unique-request-identifier\", request, true);\n```\n\n#### Prerendered Response Persistence\nPrerendered Response Persistence is a useful option when running your Blazor applications with prerendering. \n\nIt can be used to avoid repeated requests between prerendered and interactive sessions, improving performance and eliminating UI flicker.\n\nhttps://github.com/user-attachments/assets/cf770605-21cc-4587-be76-e96a3cc3d902\n\nPrerendered Response Persistence needs to be configured both in the Server...\n\n```csharp\nbuilder.Services\n    .AddEasyApi()\n    .WithContract(contractAssembly)\n    .WithServer()\n    .Using\u003cPrerenderedResponsePersistence\u003e();\n```\n\n... and Client setup.\n\n```csharp\nbuilder.Services\n    .AddEasyApi()\n    .WithContract(contractAssembly)\n    .WithClient()\n    .Using\u003cPrerenderedResponsePersistence\u003e();\n```\n\nAfter that, it can be used as described in [Response Persistence](#response-persistence).\n\n\u003e [!NOTE]  \n\u003e Prerendered Response Persistence is a one-time persistence option. It persists the request responses received during prerendering and discards them once they have been used again when the interactive session starts.\n\n#### In-memory Response Persistence\nIn-memory Response Persistence can be used to increase the responsiveness of your application by reducing the number of backend calls within an interactive user session. It involves storing request responses in the application's memory and reusing them for repeated requests (e.g. when navigating to the same page multiple times). \n\nYou can configure it in your Client and/or Server setup depending on your needs.\n\n```csharp\nbuilder.Services\n    .AddEasyApi()\n    .WithContract(contractAssembly)\n    .With[Client/Server]()\n    .Using\u003cInMemoryResponsePersistence\u003e();\n```\n\nAfter that, it can be used as described in [Response Persistence](#response-persistence).\n\n#### Custom configuration\n\nThe default In-memory Response Persistence configuration does not include data expiration. Responses are persisted and reused for the duration of a single user session. Consider using custom configuration to set up expiration, or turn off persistence altogether for requests that contain highly dynamic data.\n\nTo do this, you can implement your custom persistence configuration.\n\n```csharp\ninternal class CustomInMemoryResponsePersistence : IInMemoryResponsePersistence\n{\n    public InMemoryResponsePersistenceOptions Configure(IRequest request)\n    {\n        if (responses should not be persisted for the request)\n        {\n            return new() { IsEnabled = false };\n        }\n\n        if (responses should expire for the request)\n        {\n            return new()\n            {\n                IsEnabled = true,\n                AbsoluteExpiration = TimeSpan.FromMinutes(5),\n                SlidingExpiration = TimeSpan.FromMinutes(1)\n            };\n        }\n\n        return new() { IsEnabled = true };\n    }\n}\n```\n\nAnd configure it in your Client and/or Server setup.\n\n```csharp\nbuilder.Services\n    .AddEasyApi()\n    .WithContract(contractAssembly)\n    .With[Client/Server]()\n    .Using\u003cCustomInMemoryResponsePersistence\u003e();\n```\n\n### Client extensions\n\n#### HTTP client provider\nBy default, EasyApi uses the ```HttpClient``` registered in the service collection of your Client app. Whenever you need more control over the ```HttpClient``` creation or need to manage different ```HttpClients``` for different API requests, you should set up your own provider.\n\n**1️⃣ Implement the ```IHttpClientProvider``` ```interface```.**\n\n```csharp\ninternal class HttpClientProvider : IHttpClientProvider\n{\n    public HttpClient GetClient(IRequest request)\n    {\n        // your logic goes here\n    }\n}\n```\n\n**2️⃣ Register the HTTP client provider for your Client app.**\n\n```csharp\nbuilder.Services\n    .AddEasyApi()\n    .WithContract(contractAssembly)\n    .WithClient()\n    .Using\u003cHttpClientProvider\u003e();\n```\n\n### Server extensions\n\n#### API endpoints customization\nEasyApi handles the basic API endpoint configuration for you. For advanced scenarios that require more configuration, you should set up your own API endpoint customization.\n\n**1️⃣ Implement the ```IEndpointsCustomization``` ```interface```.**\n\n```csharp\ninternal class EndpointsCustomization : IEndpointsCustomization\n{\n    public void Customize\u003cRequest\u003e(RouteHandlerBuilder builder)\n    {\n        // your logic goes here\n    }\n}\n```\n\n**2️⃣ Register the endpoints customization for your Server app.**\n\n```csharp\nbuilder.Services\n    .AddEasyApi()\n    .WithContract(contractAssembly)\n    .WithServer()\n    .Using\u003cEndpointsCustomization\u003e();\n```\n\n## Change Log\n\n### v2.2.0\n- Added possibility to access ClaimsPrincipal of the current User from EasyApi Handlers, see issue https://github.com/bpawluk/EasyApi/issues/6\n\n### v2.1.0\n- Added possibility to configure JSON Serialization, see issue https://github.com/bpawluk/EasyApi/issues/7\n\n### v2.0.0\n- .NET 9 upgrade\n\n### v1.1.0\n- Introduced new overloads for IPersistentCall methods to allow forcing fresh request calls ignoring all persisted data\n- Introduced absolute expiration of responses in in-memory persistence\n- Introduced sliding expiration of responses in in-memory persistence\n- Fixed intermittent loss of responses persisted during prerendering\n- Fixed incorrect setup of custom response persistence \n\n### v1.0.0\n- .NET 8 upgrade\n- Introduced IPersistentCall\n- Introduced PrerenderedResponsePersistence\n- Introduced InMemoryResponsePersistence\n\n### v0.5.1\n- Fixed incorrect HTTP method mapping for IPut requests.\n- Fixed null string parameters being deserialized as empty strings.\n- Query String and Header params are now skipped when sending null values.\n\n### v0.5.0\n- Baseline version of the library.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbpawluk%2Feasyapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbpawluk%2Feasyapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbpawluk%2Feasyapi/lists"}