Skip to content

Sending Form Data ​

Standard Form Data ​

Posting HTTP form data in the request can be done with the extension method shown below:

cs
public async Task write_form_data(IAlbaHost system)
{
    var form1 = new Dictionary<string, string>
    {
        ["a"] = "what?",
        ["b"] = "now?",
        ["c"] = "really?"
    };

    await system.Scenario(_ =>
    {
        // This writes the dictionary values to the HTTP
        // request as form data, and sets the content-length
        // header as well as setting the content-type
        // header to application/x-www-form-urlencoded
        _.WriteFormData(form1);
    });
}

snippet source | anchor

There's a second overload that attempts to use an object and its properties to populate the form data:

cs
[Fact]
public async Task can_bind_to_form_data()
{
    await using var system = await AlbaHost.For<Startup>();

    var input = new InputModel {
        One = "one",
        Two = "two",
        Three = "three"
    };

    await system.Scenario(_ =>
    {
        _.Post.FormData(input)
            .ToUrl("/gateway/insert");
    });

    GatewayController.LastInput.ShouldNotBeNull();

    GatewayController.LastInput.One.ShouldBe("one");
    GatewayController.LastInput.Two.ShouldBe("two");
    GatewayController.LastInput.Three.ShouldBe("three");
}

snippet source | anchor

Do note that this only adds first level properties, so if you need to deeper accessors like add "Prop1.Prop2.Prop3," you'll have to resort to the dictionary approach.

Multipart Form Data & File Uploads ​

MultipartFormData(...) builds a multipart body of form fields and file uploads:

cs
var result = await host.Scenario(x =>
{
    x.Post.MultipartFormData(form => form
            // Files with the same name bind to a collection; the content type
            // comes from the file name extension unless one is passed
            .File("files", Encoding.UTF8.GetBytes("Hello there!"), "greeting.txt")
            .File("files", image, "TestImage.jpg")
            .Field("additionalContent", "My additional string of fun times"))
        .ToUrl("/api/files/upload");

    x.StatusCodeShouldBeOk();
});

snippet source | anchor

  • File(name, bytes, fileName) and File(name, stream, fileName) add a file. The application binds it by name, to an IFormFile parameter or property, or to a collection when several files share the name.
  • The file's content type comes from the extension of fileName, such as text/csv for report.csv, and falls back to application/octet-stream. Pass a content type as the last argument to override it.
  • A stream is read when the request is sent, so keep it open until the scenario completes.
  • Field(name, value) adds a form value.

Minimal API endpoints that bind IFormFile validate an antiforgery token when the application uses antiforgery. Call .DisableAntiforgery() on such an endpoint in the test host, or send a token, because Alba does not supply one.

For full control over the body, pass a MultipartFormDataContent instead:

cs
public async Task write_multipart_form_data(IAlbaHost system)
{
    // Read our file into a stream
    await using var imageFile = File.OpenRead("TestImage.jpg");
    // Extract the name from the path
    var imageFileName = Path.GetFileName(imageFile.Name);
    // Create the stream content
    using var content = new StreamContent(imageFile);
    // Remember to manually set the media type as it won't be done automatically
    content.Headers.ContentType = new MediaTypeHeaderValue(MediaTypeNames.Image.Jpeg);

    // Create the MultiPartForm content & add the file
    using var formData = new MultipartFormDataContent();
    formData.Add(content, "files", imageFileName);

    // If you have other content in the form object, you can add it as well!
    formData.Add(new StringContent("My additional metadata"), "metadata");

    var result = await system.Scenario(_ =>
    {
        // This extension will write the content to the request
        // body and set the required headers
        _.Post.MultipartFormData(formData).ToUrl("/api/files/upload");
        _.StatusCodeShouldBeOk();
    });
}

snippet source | anchor