Which architecture approach use in handler with a lot operations

Andrii Shylin 221 Reputation points
2024-01-27T14:39:32.1366667+00:00

Hello! I am writing to address a problem I am facing with a controller action that has to perform a lot of operations. I am looking for ways to architect it in a better way. Do you have any suggestions? This might be an example:

[HttpPost]
public async Task<IActionResult> UploadDatabase()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        ModelState.AddModelError("File", 
            $"The request couldn't be processed (Error 1).");
        // Log error

        return BadRequest(ModelState);
    }

    // Accumulate the form data key-value pairs in the request (formAccumulator).
    var formAccumulator = new KeyValueAccumulator();
    var trustedFileNameForDisplay = string.Empty;
    var untrustedFileNameForStorage = string.Empty;
    var streamedFileContent = Array.Empty<byte>();

    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, HttpContext.Request.Body);

    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(
            section.ContentDisposition, 
            out var contentDisposition);

        if (hasContentDispositionHeader)
        {
            if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
            {
                untrustedFileNameForStorage = contentDisposition.FileName.Value;
                // Don't trust the file name sent by the client. To display
                // the file name, HTML-encode the value.
                trustedFileNameForDisplay = WebUtility.HtmlEncode(contentDisposition.FileName.Value);

                streamedFileContent = await FileHelpers.ProcessStreamedFile(
                  section, 
                  contentDisposition, 
                  ModelState, 
                  _permittedExtensions, 
                  _fileSizeLimit);

                if (!ModelState.IsValid)
                {
                    return BadRequest(ModelState);
                }
            }
            else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
            {
                // Don't limit the key name length because the 
                // multipart headers length limit is already in effect.
                var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name).Value;
                var encoding = GetEncoding(section);

                if (encoding == null)
                {
                    ModelState.AddModelError(
                     "File", 
                     $"The request couldn't be processed (Error 2).");
                    // Log error

                    return BadRequest(ModelState);
                }

                using (var streamReader = new StreamReader(
                    section.Body,
                    encoding,
                    detectEncodingFromByteOrderMarks: true,
                    bufferSize: 1024,
                    leaveOpen: true))
                {
                    // The value length limit is enforced by 
                    // MultipartBodyLengthLimit
                    var value = await streamReader.ReadToEndAsync();

                    if (string.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
                    {
                        value = string.Empty;
                    }

                    formAccumulator.Append(key, value);

                    if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
                    {
                        // Form key count limit of 
                        // _defaultFormOptions.ValueCountLimit 
                        // is exceeded.
                        ModelState.AddModelError(
                           "File", 
                           $"The request couldn't be processed (Error 3).");
                        // Log error

                        return BadRequest(ModelState);
                    }
                }
            }
        }

        // Drain any remaining section body that hasn't been consumed and
        // read the headers for the next section.
        section = await reader.ReadNextSectionAsync();
    }

    // Bind form data to the model
    var formData = new FormData();
    var formValueProvider = new FormValueProvider(
        BindingSource.Form,
        new FormCollection(formAccumulator.GetResults()),
        CultureInfo.CurrentCulture);
    var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
        valueProvider: formValueProvider);

    if (!bindingSuccessful)
    {
        ModelState.AddModelError(
         "File", 
         "The request couldn't be processed (Error 5).");
        // Log error

        return BadRequest(ModelState);
    }

    var file = new AppFile()
    {
        Content = streamedFileContent,
        UntrustedName = untrustedFileNameForStorage,
        Note = formData.Note,
        Size = streamedFileContent.Length, 
        UploadDT = DateTime.UtcNow
    };

    _context.File.Add(file);
    await _context.SaveChangesAsync();

    return Created(nameof(StreamingController), null);
}

which I taken from the Upload File official documentation

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,400 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,648 questions
0 comments No comments
{count} votes

Accepted answer
  1. Ruikai Feng - MSFT 2,556 Reputation points Microsoft Vendor
    2024-01-29T03:44:39.47+00:00

    Hi,@Andrej If you want to reduce the codes in controller,you could create a new method in your controller, refactor the controller,For example: Modify

     public IActionResult UploadDatabase()
     {
         
         var formAccumulator = new KeyValueAccumulator();
         
         // Bind form data to the model
         var formData = new FormData();
         var formValueProvider = new FormValueProvider(
             BindingSource.Form,
             new FormCollection(formAccumulator.GetResults()),
             CultureInfo.CurrentCulture);
         var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
             valueProvider: formValueProvider);
    
         if (!bindingSuccessful)
         {
             ModelState.AddModelError(
              "File",
              "The request couldn't be processed (Error 5).");
             // Log error
    
             return BadRequest(ModelState);
         }
    
          return Created(nameof(StreamingController), null);return View();
     }
    

    To

     public async Task<IActionResult> UploadDatabase()
     {
         var formAccumulator = new KeyValueAccumulator();
         ...........
    
         var bindingSuccessful = await BindFormData(formAccumulator);
    
         if (!bindingSuccessful)
         {
             ModelState.AddModelError("File",
                 "The request couldn't be processed (Error 5).");
             // Log error
    
             return BadRequest(ModelState);
         }
    
         return Created(nameof(StreamingController), null);
     }
    
     private async Task<bool> BindFormData(KeyValueAccumulator formAccumulator)
     {
         //Bind form data to the model
         var formData = new MultipartFormDataContent();
         var formValueProvider = new FormValueProvider(
             BindingSource.Form,
             new FormCollection(formAccumulator.GetResults()),
             CultureInfo.CurrentCulture);
         return await TryUpdateModelAsync(formData, prefix: "",
             valueProvider: formValueProvider);
     }
    
    
    

    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment". Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards, Ruikai Feng

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 61,731 Reputation points
    2024-01-27T17:38:22.2533333+00:00

    Refactor code blocks to functions so the overall logic is easier to see.

    1 person found this answer helpful.
    0 comments No comments