Strypper / mauisland

MAUIsland 🏝️ is the number 1 controls gallery for .NET MAUI
MIT License
194 stars 14 forks source link

REFIT: Upload file or image in one object #107

Closed Strypper closed 1 year ago

Strypper commented 1 year ago

Description

Please see the refit documents for this issue: https://github.com/reactiveui/refit

Situation

This is refit example showing us how to upload file through HTTP

public interface ISomeApi
{
    [Multipart]
    [Post("/users/{id}/photo")]
    Task UploadPhoto(int id, [AliasAs("myPhoto")] StreamPart stream);
}

The problem

Our API in Intranet Authentication Controller

    [AllowAnonymous]
    [HttpPost]
    public async Task<IActionResult> Register([FromForm] UserSignUpDTO dto, CancellationToken cancellationToken = default)
    {
        var user = new User()
        {
            UserName = dto.username,
            Email = dto.email
        };
        var result = await _userRepository.CreateAccount(user, dto.password);
        if (result.Succeeded)
        {
            if (dto.avatarfile is null) return Ok();
            if (_mediaService.IsImage(dto.avatarfile))
            {
                using (Stream stream = dto.avatarfile.OpenReadStream())
                {
                    Tuple<bool, string> uploadresults = await _mediaService.UploadAvatarToStorage(stream, dto.avatarfile.FileName);
                    var isUploaded = uploadresults.Item1;
                    var stringUrl = uploadresults.Item2;
                    if (isUploaded && !string.IsNullOrEmpty(stringUrl))
                    {
                        user.ProfilePic = stringUrl;
                        await _userRepository.UpdateUser(user, cancellationToken);
                        return Ok();
                    }
                    else return BadRequest("Look like the image couldnt upload to the storage, but your account have created successfully");
                }
            }
        }
        return BadRequest();
    }

UserSignUpDTO:

public record UserSignUpDTO(string username,
                            string password,
                            string firstname,
                            string lastname,
                            string email,
                            string phonenumber,
                            string[]? roles,
                            IFormFile? avatarfile)
{ }

As you can see the avatarfile is an IFormFile which we included inside this object, so far we haven't found a way to use refit to send this kind of object to our backend to recognize.

If we don't have a solution, we must call 2 requests to complete our registration process. we had the code for uploading files in using the System.Net.Http will be this massive like this

This is an example of my previous object uploading a file to ASP.NET

        public async Task<PetaverseMedia> CreatePetAvatarAsync(Animal petInfo, StorageFile avatar)
        {
            if (avatar != null)
            {
                var multipartFormContent = new MultipartFormDataContent();

                var handle = avatar.CreateSafeFileHandle(options: FileOptions.RandomAccess);
                var stream = new FileStream(handle, FileAccess.ReadWrite) { Position = 0 };
                var media = new StreamContent(stream);
                media.Headers.Add("Content-Type", "image/jpeg");
                multipartFormContent.Add(media, name: "avatar", fileName: $"{petInfo.Name}");

                try
                {
                    var result = await _httpClient.PostAsync($"api/Animal/UploadPetAvatar/{petInfo.Id}", multipartFormContent);
                    string stringReadResult = await result.Content.ReadAsStringAsync();
                    return JsonConvert.DeserializeObject<PetaverseMedia>(stringReadResult);
                }
                catch (Exception ex)
                {
                    await new HttpRequestErrorContentDialog()
                    {
                        Title = "Can't upload avatar"
                    }.ShowAsync();
                    return null;
                }
            }
            else return null;
        }

Public API Changes

        try
        {
            var response = await this.intranetAuthenticationRefit.Register(new RegisterDTO(userName, firstName, lastName, phoneNumber, email, password, profilePicStream));
            if (!response.IsSuccessStatusCode)
            {
                var errorContentJson = JsonConvert.DeserializeObject<RefitErrorMessageModel>(response.Error.Content);
                throw new Exception(errorContentJson.title);
            }
        }
        catch (ApiException ex)
        {
            throw new Exception(ex.Message);
        }

Intended Use-Case

Create user information with an avatar with only one call

Strypper commented 1 year ago
  1. Upload only the image in the body
  2. Upload with one parameter and 1 image in body
  3. Upload a complex object that has an IFormFile property inside it
Strypper commented 1 year ago

@duydh1995 have done the investigation about this issue and found out some of the ByteArrayPart and the StreamPart type On the server, asp.net seem to accept the DTO But on the front end in order to make create such an accepted request that it has to be in a format like below

    [Multipart]
    [Put("/User/TestUpload3")]
    Task TestUpload3([AliasAs("Id")] int id, [AliasAs("Name")] string name, [AliasAs("File")] StreamPart file);

I try do this for a cleaner code but the server quickly responded back 400 bad requests

    [Multipart]
    [Put("/User/TestUpload3")]
    Task TestUpload3(TestUploadByteArrayPartDTO1 dto);
public class TestUploadByteArrayPartDTO1
{
    [AliasAs("Id")]
    public int Id { get; set; }

    [AliasAs("Name")]
    public string Name { get; set; }

    [AliasAs("File")]
    public StreamPart File { get; set; }
};