Skip to content
Rok Kos edited this page Mar 27, 2024 · 24 revisions

Cookbook

This cookbook includes common tasks and solutions for EmbedIOv3.

Example of a GET Request

Returning a simple value:

    [Route(HttpVerbs.Get, "/ping")]
    public string TableTennis()
    {
        return "pong";
    }

Automatic serialization of results

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.

Asynchronous controller methods

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";
    }

Route parameters

Using parametric routes

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}";
    }

Optional route parameters

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

Retrieving single parameters

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}";
    }

Retrieving all parameters as a collection

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 (application/x-www-form-urlencoded)

Retrieving single values

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);
    }

Retrieving all values as a collection

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);
    }

Reading a request body as JSON (application/json)

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.";
    }

Custom deserialization of request data (pass anything as a controller method parameter!)

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:

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:

Multi-part forms and file uploads (multipart/form-data)

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
        // ...
    }

Writing a custom response body

Writing to the response body as a stream

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);
        }
    }

Writing to the response body as text

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!");
        }
    }

Logging (turn off or customize)

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.

Setting a custom error page

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;
        }
    });