0

Now before you mark this as a duplicate, I have read other threads (Multipart body length limit exceeded exception). But no answers have worked so far.

So in my .NET 6 API I am expecting a form. The endpoint looks as follows:

[HttpPost]
public async Task<IActionResult> Post([FromForm] PostUploadDto post)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);
    
    var author = userService.GetUser(post.Author);

    var newPost = new Post
    {
        AuthorId = author.Id,
        Description = post.Description,
        Posted = post.Posted,
        RatingNegative = 0,
        RatingPositive = 0,
        Title = post.Title,
    };

    await postService.CreatePost(newPost);

    try
    {
        foreach (var file in post.Files)
        {
            var fileName = $"{newPost.Id}_{file.FileName}";
            var filePath = Path.Combine("path", fileName);

            using (var stream = new FileStream(filePath, FileMode.Create))
            {
                await file.CopyToAsync(stream);
            }

            var uploadDate = DateTime.Now;

            await postService.AddPostFile(new PostFile
            {
                PostId = newPost.Id,
                Path = filePath,
                Name = fileName,
                Uploaded = uploadDate
            });
        }

        return Ok(newPost.Id);
    }
    catch (Exception ex)
    {
        return BadRequest(new { message = ex.Message, innerMessage = ex.InnerException?.Message });
    }
}

Here I expect a form which will be constructed in a React TS application. This application is pretty simple: on a submit formdata will be created and sent to the endpoint above. The code which is used to compose form data can be seen below:

const [title, setTitle] = useState('')
const [description, setDescription] = useState('')
const [selectedFiles, setSelectedFiles] = useState<File[]>([])

const getFormData = () => {
    const formData = new FormData()

    formData.append('title', title)
    formData.append('description', description)

    if (user) {
        formData.append('author', user.id.toString())
    }

    formData.append('posted', new Date().toDateString())
    selectedFiles.forEach((file, index) => {
        formData.append('files', file);
    });

    return formData
}

const handleSubmit = (e: any) => {
    e.preventDefault()

    if (user && authorisationToken) {
        const data = getFormData()

        apiService.post<Post>(`https://host/api/posts`, data, authorisationToken, true).then((response) => {
            setPosts([...posts, response.data])
            setTitle('')
            setDescription('')
            setSelectedFiles([])
            setIsModalOpen(false)
        })
            .catch((error) => {
                console.error('API error:', error)
            })
    }
}

This data will be received by an endpoint. The apiService uses standard fetch from React. The apiService looks like follows (relevant code):

async function request<T>(url: string, method: string, data?: any, token?: string, isForm = false): Promise<ApiResponse<T>> {
    let contentType = 'application/json'
    let bodyData = data ? JSON.stringify(data) : undefined

    if (isForm) {
        contentType = `multipart/form-data; boundary=---------------------------${Date.now().toString(16)}`
        bodyData = data
    }

    const headers: HeadersInit = {
        'Content-Type': contentType,
    };

    if (token) {
        headers['Authorization'] = `Bearer ${token}`;
    }

    const response = await fetch(url, {
        method,
        headers,
        body: bodyData,
    });

    const responseData: T = await response.json();

    return {
        data: responseData,
        status: response.status,
    };
}

const apiService = {
    ...

    post<T>(url: string, data: any, token ?: string, isForm = false): Promise<ApiResponse< T >> {
        return request<T>(url, 'POST', data, token, isForm);
    },

    ...
}

The request is being sent properly to the right endpoint, nothing goes wrong here. It only goes wrong with the formdata validation of my API which is hosted on IIS (Windows 11 server). I never had issues with this before until now. I know that this might seem as a duplicate but I have changed my configuration based on existing threads and I am still struggling with this issue. As proof here is a code snippet from my program.cs:

builder.Services.Configure<FormOptions>(x =>
{
    x.ValueLengthLimit = int.MaxValue;
    x.MultipartBodyLengthLimit = int.MaxValue;
});

Are there any steps I might have overlooked, does anybody have a suggestion, do I need to provide more info?

POSTMAN request (working)

In postman I have a request which I got off my swagger. This request works fine and uploads files just fine too. The headers for the request can be seen below

Postman posts request

vatbub
  • 2,713
  • 18
  • 41
Berend Hulshof
  • 1,039
  • 1
  • 16
  • 36
  • Your client code has some issues. 1) You should not set a boundary, or even content type fetch itself will arrange that. 2) If you want to post `FormData` you should not convert it to JSON. – Eldar Jun 19 '23 at 07:00
  • @Eldar when I am not setting the boundary I get an exception saying that the boundary is missing. FormData is not being serialized in this case (see if statement); unless that JSON.stringify modifies the original data form the parameter. If you can come up with a solution for my problem with point 1 I'm all ears! – Berend Hulshof Jun 19 '23 at 11:07

5 Answers5

0

this parameters will solve your error

make sure to set them in your .config file

<configuration>
  <system.web>
    <httpRuntime maxRequestLength="1048576" />
  </system.web>
  <system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="1073741824" /> <!-- 1 GB -->
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>
codeTCS
  • 41
  • 5
0

It could be that you either have to decorate your class where the endpoint is being called with the following code:

[RequestFormLimits(ValueCountLimit = 2500)]  //Add your limit

And/Or add the following option to your FormOptions in the Program.cs class

builder.Services.Configure<FormOptions>(x => {
    x.ValueCountLimit = int.MaxValue;
});
IceCode
  • 1,466
  • 13
  • 22
0

Try to add this to web.config

<requestFiltering>
  <requestLimits maxAllowedContentLength="209715200" />
</requestFiltering>

Add the [RequestFormLimits] attribute on top of Upload action.

[HttpPost]
[RequestFormLimits(MultipartBodyLengthLimit = 209715200)]
TengFeiXie
  • 176
  • 5
0

For IIS:

web.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <!-- ~ 2GB -->
    <httpRuntime maxRequestLength="2147483647" /> // kbytes
  </system.web>
  <system.webServer>
    <security>
      <requestFiltering>
        <!-- ~ 4GB -->
        <requestLimits maxAllowedContentLength="4294967295" /> // bytes
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

Program.cs:

builder.Services.Configure<IISServerOptions>(options =>
{
    options.MaxRequestBodySize = null;
});
builder.Services.Configure<FormOptions>(options =>
{
    options.ValueLengthLimit = int.MaxValue;
    options.MultipartBodyLengthLimit = long.MaxValue;
});

For Kestrel:

Program.cs:

builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxRequestBodySize = null;
});
builder.Services.Configure<FormOptions>(options =>
{
    options.ValueLengthLimit = int.MaxValue;
    options.MultipartBodyLengthLimit = long.MaxValue;
});

See also MS learn: Upload files in ASP.NET Core

Danut Radoaica
  • 1,860
  • 13
  • 17
0

The only solution for me was to not set the headers for the request. Doing this solved the issue for me. I have tried these answers but they did not help sadly.

Berend Hulshof
  • 1,039
  • 1
  • 16
  • 36