Skip to content

Request Contracts

HTTP request contracts define the shape of your API calls using interfaces and attributes. Every HTTP contract implements IHttpRequest<TResult> and is decorated with attributes that describe the HTTP verb, route, parameters, and body.

All HTTP contracts must implement IHttpRequest<TResult> where TResult is the deserialized response type.

using Shiny.Mediator.Http;
[Http(HttpVerb.Get, "/api/users/{Id}")]
public class GetUserRequest : IHttpRequest<UserResponse>
{
[HttpParameter(HttpParameterType.Path)]
public string Id { get; set; }
}

The [Http] attribute defines the HTTP verb and route template for the request.

[Http(HttpVerb.Get, "/api/resource")]
[Http(HttpVerb.Post, "/api/resource")]
[Http(HttpVerb.Put, "/api/resource/{Id}")]
[Http(HttpVerb.Patch, "/api/resource/{Id}")]
[Http(HttpVerb.Delete, "/api/resource/{Id}")]

GET - Retrieve data

[Http(HttpVerb.Get, "/api/products")]
public class GetProductsRequest : IHttpRequest<List<Product>>
{
[HttpParameter(HttpParameterType.Query)]
public string Category { get; set; }
[HttpParameter(HttpParameterType.Query)]
public int Page { get; set; } = 1;
}

POST - Create a resource

[Http(HttpVerb.Post, "/api/products")]
public class CreateProductRequest : IHttpRequest<Product>
{
[HttpBody]
public CreateProductDto Body { get; set; }
}

PUT - Replace a resource

[Http(HttpVerb.Put, "/api/products/{Id}")]
public class UpdateProductRequest : IHttpRequest<Product>
{
[HttpParameter(HttpParameterType.Path)]
public string Id { get; set; }
[HttpBody]
public UpdateProductDto Body { get; set; }
}

PATCH - Partially update a resource

[Http(HttpVerb.Patch, "/api/products/{Id}")]
public class PatchProductRequest : IHttpRequest<Product>
{
[HttpParameter(HttpParameterType.Path)]
public string Id { get; set; }
[HttpBody]
public PatchProductDto Body { get; set; }
}

DELETE - Remove a resource

[Http(HttpVerb.Delete, "/api/products/{Id}")]
public class DeleteProductRequest : IHttpRequest<Product>
{
[HttpParameter(HttpParameterType.Path)]
public string Id { get; set; }
}

The [HttpParameter] attribute maps a property to a specific part of the HTTP request. There are three parameter types:

Path parameters are substituted into the route template. The property name must match the {placeholder} in the route.

[Http(HttpVerb.Get, "/api/users/{UserId}/orders/{OrderId}")]
public class GetOrderRequest : IHttpRequest<OrderResponse>
{
[HttpParameter(HttpParameterType.Path)]
public string UserId { get; set; }
[HttpParameter(HttpParameterType.Path)]
public string OrderId { get; set; }
}

Query parameters are appended to the URL as query string values.

[Http(HttpVerb.Get, "/api/search")]
public class SearchRequest : IHttpRequest<SearchResults>
{
[HttpParameter(HttpParameterType.Query)]
public string Term { get; set; }
[HttpParameter(HttpParameterType.Query)]
public int PageSize { get; set; } = 25;
[HttpParameter(HttpParameterType.Query)]
public string SortBy { get; set; }
}
// Resulting URL: /api/search?Term=hello&PageSize=25&SortBy=name

Header parameters are added as HTTP request headers.

[Http(HttpVerb.Get, "/api/data")]
public class GetDataRequest : IHttpRequest<DataResponse>
{
[HttpParameter(HttpParameterType.Header)]
public string ApiVersion { get; set; } = "2.0";
[HttpParameter(HttpParameterType.Header)]
public string CorrelationId { get; set; }
}

The [HttpBody] attribute marks a property to be serialized as the JSON request body.

[Http(HttpVerb.Post, "/api/orders")]
public class CreateOrderRequest : IHttpRequest<OrderResponse>
{
[HttpBody]
public OrderDto Order { get; set; }
}

There can only be one [HttpBody] property per contract. The body is serialized to JSON.

Here’s a full set of contracts for a typical CRUD API:

using Shiny.Mediator;
using Shiny.Mediator.Http;
// List
[Http(HttpVerb.Get, "/api/products")]
public class ListProductsRequest : IHttpRequest<List<Product>>
{
[HttpParameter(HttpParameterType.Query)]
public string Category { get; set; }
[HttpParameter(HttpParameterType.Query)]
public int Page { get; set; } = 1;
[HttpParameter(HttpParameterType.Query)]
public int PageSize { get; set; } = 25;
}
// Get by ID
[Http(HttpVerb.Get, "/api/products/{Id}")]
public class GetProductRequest : IHttpRequest<Product>
{
[HttpParameter(HttpParameterType.Path)]
public string Id { get; set; }
}
// Create
[Http(HttpVerb.Post, "/api/products")]
public class CreateProductRequest : IHttpRequest<Product>
{
[HttpBody]
public CreateProductDto Body { get; set; }
}
// Update
[Http(HttpVerb.Put, "/api/products/{Id}")]
public class UpdateProductRequest : IHttpRequest<Product>
{
[HttpParameter(HttpParameterType.Path)]
public string Id { get; set; }
[HttpBody]
public UpdateProductDto Body { get; set; }
}
// Delete
[Http(HttpVerb.Delete, "/api/products/{Id}")]
public class DeleteProductRequest : IHttpRequest<Product>
{
[HttpParameter(HttpParameterType.Path)]
public string Id { get; set; }
}

Usage:

IMediator mediator; // injected
// List products
var products = await mediator.Request(new ListProductsRequest
{
Category = "electronics",
Page = 1
});
// Get single product
var product = await mediator.Request(new GetProductRequest { Id = "123" });
// Create product
var created = await mediator.Request(new CreateProductRequest
{
Body = new CreateProductDto { Name = "Widget", Price = 9.99m }
});
// Update product
var updated = await mediator.Request(new UpdateProductRequest
{
Id = "123",
Body = new UpdateProductDto { Name = "Updated Widget", Price = 12.99m }
});
// Delete product
var deleted = await mediator.Request(new DeleteProductRequest { Id = "123" });

A single contract can use path, query, header, and body parameters together:

[Http(HttpVerb.Post, "/api/v1/tenants/{TenantId}/items")]
public class CreateItemRequest : IHttpRequest<ItemResponse>
{
[HttpParameter(HttpParameterType.Path)]
public string TenantId { get; set; }
[HttpParameter(HttpParameterType.Query)]
public bool Validate { get; set; } = true;
[HttpParameter(HttpParameterType.Header)]
public string CorrelationId { get; set; }
[HttpBody]
public CreateItemDto Body { get; set; }
}