-
Notifications
You must be signed in to change notification settings - Fork 177
Cookbook
This cookbook includes common tasks and solutions for EmbedIOv3.
- Example of a GET Request
- Route parameters
- URL query parameters
-
Form values (
application/x-www-form-urlencoded
) - Reading a request body as JSON (
application/json
) - Custom deserialization of request data (pass anything as a controller method parameter!)
- Multi-part forms and file uploads (
multipart/form-data
) - Writing a custom response body
- Logging (turn off or customize)
- Setting a custom error page
Returning a simple value:
[Route(HttpVerbs.Get, "/ping")]
public string TableTennis()
{
return "pong";
}
Any returned value will be serialized and sent as response body.
The default serialization method uses Swan.Json
. Serialization can be customized at module level by setting the Serializer
property.
If any asynchronous operations are involved, a Task<TResult>
object can be returned:
[Route(HttpVerbs.Get, "/ping")]
public async Task<string> TableTennisAsync()
{
// You will probably want to do something more useful than this.
await Task.Delay(500);
return "pong";
}
Routes can be parametric. Route parameters will be automatically passed as method parameters of the same name. If the type of the parameter is not string
or object
, the relevant part of the URL will be parsed and a 400 Bad Request
response will be sent to the client if parsing is not successful.
// hello/paul
[Route(HttpVerbs.Get, "/hello/{name}")]
public string SayHello(string name)
{
return $"Hello, {name}";
}
A route parameters preceded by a question mark is considered optional.
// orders/123 -> list of orders for customer id 123
// orders/123/456 -> list of orders for customer id 123 containing item id 456
[Route(HttpVerbs.Get, "/orders/{customerId}/{itemId?}")]
public async IEnumerable<Order> RetrieveOrderListByCustomer(int customerId, int? itemId)
{
var orders = itemId.HasValue
? await Database.GetOrdersByCustomerAndItem(customerId, itemId)
: await Database.GetOrdersByCustome(customerId);
if (orders == null)
{
throw HttpException.NotFound("No orders were found in database.");
}
return orders;
}
URL query parameters can be retrieved as method parameters using the QueryField
attribute.
// hello?username=Elvis
[Route(HttpVerbs.Get, "/hello")]
public string Hello([QueryField] string username)
{
return $"Hello {username}";
}
The whole set of query parameters can be retrieved as a method parameter with the QueryData
attribute. Note that the parameter must be of type System.Specialized.NameValueCollection
.
If the request has no query parameters, an empty NameValueCollection
will be passed to the method.
// hello?foo=bar&anything=at_all
[Route(HttpVerbs.Get, "/hello")]
public async Task<string> Hello([QueryData] NameValueCollection parameters)
{
var sb = new StringBuilder();
foreach (var key in parameters.AllKeys)
{
await Task.Yield(); // Easy on the CPU
sb.AppendLine($"Parameter '{key}' is '{parameters[key]}'.");
}
return sb.ToString();
}
Query parameters can also be retrieved directly from the HTTP context, using the GetRequestQueryData
extension method.
// hello?foo=bar&anything=at_all
[Route(HttpVerbs.Get, "/hello")]
public async Task<string> Hello()
{
var parameters = HttpContext.GetRequestQueryData();
var sb = new StringBuilder();
foreach (var key in parameters.AllKeys)
{
await Task.Yield(); // Easy on the CPU
sb.AppendLine($"Parameter '{key}' is '{parameters[key]}'.");
}
return sb.ToString();
}
Form values can be retrieved as method parameters using the FormField
attribute.
[Route(HttpVerbs.Post, "/login")]
public async Task Login([FormField] string user, [FormField("pwd")] string password)
{
// Note that MyProgram.CheckCredentialsAsync is just an example, not part of EmbedIO.
// We'll assume it returns Task<bool>.
var location = await MyProgram.CheckCredentialsAsync(user, password) ? "/home" : "/loginFailed";
throw HttpException.Redirect(location);
}
The whole set of form values can be retrieved as a method parameter with the FormData
attribute. Note that the parameter must be of type System.Specialized.NameValueCollection
.
If the request body contains no form data, an empty NameValueCollection
will be passed to the method.
[Route(HttpVerbs.Post, "/login")]
public async Task Login([FormData] NameValueCollection data)
{
// Note that MyProgram.CheckCredentialsAsync is just an example, not part of EmbedIO.
// We'll assume it returns Task<bool>.
var location = await MyProgram.CheckCredentialsAsync(data["user"], data["pwd"])
? "/home"
: "/loginFailed";
throw HttpException.Redirect(location);
}
Form data can also be retrieved directly from the HTTP context, using the GetRequestFormDataAsync
extension method.
[Route(HttpVerbs.Post, "/login")]
public async Task Login()
{
// Note that MyProgram.CheckCredentialsAsync is just an example, not part of EmbedIO.
// We'll assume it returns Task<bool>.
var data = await HttpContext.GetRequestFormDataAsync();
var location = await MyProgram.CheckCredentialsAsync(data["user"], data["pwd"])
? "/home"
: "/loginFailed";
throw HttpException.Redirect(location);
}
A request body, deserialized as JSON, can be retrieved as a method parameter using the JsonData
attribute.
If the request body cannot be deserialized to the type of the parameter, a 400 Bad Request
response will be sent to the client.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// Request body: { "Name": "John"; "Age": 42 }
[Route(HttpVerbs.Post, "/describe")]
public string DescribePerson([JsonData] Person person)
{
return $"{person.Name} is {person.Age} years old.";
}
A deserialized request body can also be retrieved directly from the HTTP context, using the GetRequestDataAsync<TData>
extension method.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// Request body: { "Name": "John"; "Age": 42 }
[Route(HttpVerbs.Post, "/describe")]
public async Task<string> DescribePerson()
{
var person = await HttpContext.GetRequestDataAsync<Person>();
return $"{person.Name} is {person.Age} years old.";
}
The attributes used in the above examples are not based on some internal-use-only black magic. If an attribute that implements one or more of the following interfaces, if placed on a controller method parameter, will be automatically used to retrieve data from a request and inject it into the method call:
-
IRequestDataAttribute<TController>
when the code to retrieve data is independent of the type of the parameter; -
IRequestDataAttribute<TController, TData>
when a specific parameter type is required to inject retrieved data; -
INonNullRequestDataAttribute<TController, TData>
same as the previous, to use when the retrieved data can never benull
, to keep static analyzers happy.
All three interfaces are quite simple, each consisting of just one method. Refer to the linked documentation for more details. You can also have a look at the source code for the following classes to see how simple it is to create your own data-retrieving attributes:
-
JsonDataAttribute
(implementingIRequestDataAttribute<TController>
) -
QueryDataAttribute
andFormDataAttribute
(implementingIRequestDataAttribute<TController, TData>
) -
QueryFieldAttribute
andFormFieldAttribute
(implementing bothIRequestDataAttribute<TController>
andIRequestDataAttribute<TController, TData>
- twice!)
There is no built-in functionality in EmbedIO to read a multi-part form from a request body. However, you can use the HttpMultipartParser library to do that, as shown below.
[Route(HttpVerbs.Post, "/upload")]
public async Task UploadFile()
{
var parser = await MultipartFormDataParser.ParseAsync(Request.InputStream);
// Now you can access parser.Files
// ...
}
You can open the response body as a Stream
with the OpenResponseStream
extension method.
[Route(HttpVerbs.Get, "/binary")]
public async Task GetBinary()
{
// Call a fictional external source
using (var stream = HttpContext.OpenResponseStream())
{
await stream.WriteAsync(dataBuffer, 0, 0);
}
}
You can open the response body as a TextWriter
with the OpenResponseText
extension method.
[Route(HttpVerbs.Get, "/hello")]
public async Task GetBinary()
{
using (var writer = HttpContext.OpenResponseText())
{
await writer.WriteAsync("Hello!");
}
}
If all you want is to turn logging off, do this before initializing your web server:
Logger.UnregisterLogger<ConsoleLogger>();
Refer to the documentation for Swan.Logger
for more detailed information, including how to log on files.
HTTP exceptions can be handled both at module level and at web server level.
Here's how toset a custom handler for a web server:
var server = new WebServer(8877);
server.HandleHttpException(async (context, exception) =>
{
context.Response.StatusCode = exception.StatusCode;
switch (exception.StatusCode)
{
case 404:
await context.SendStringAsync("Your content", "text/html", Encoding.UTF8);
break;
default:
await HttpExceptionHandler.Default(context, exception);
break;
}
});