16

I am trying to create an app where user can upload a text file, and gets the altered text back.

I am using React as FE and ASP.NET Core for BE and Azure storage for the database storage.

This is how my HomeController looks like. I created a separate "UploadToBlob" method, to post the data

    public class HomeController : Controller
    {
        private readonly IConfiguration _configuration;

        public HomeController(IConfiguration Configuration)
        {
            _configuration = Configuration;
        }

        public IActionResult Index()
        {
            return View();
        }

        [HttpPost("UploadFiles")]
        //OPTION B: Uncomment to set a specified upload file limit
        [RequestSizeLimit(40000000)]

        public async Task<IActionResult> Post(List<IFormFile> files)
        {
            var uploadSuccess = false;
            string uploadedUri = null;

            foreach (var formFile in files)
            {
                if (formFile.Length <= 0)
                {
                    continue;
                }

                // read directly from stream for blob upload      
                using (var stream = formFile.OpenReadStream())
                {
                    // Open the file and upload its data
                    (uploadSuccess, uploadedUri) = await UploadToBlob(formFile.FileName, null, stream);

                }

            }

            if (uploadSuccess)
            {
                //return the data to the view, which is react display text component.
                return View("DisplayText");
            }
            else
            {
                //create an error component to show there was some error while uploading
                return View("UploadError");
            }
        }

        private async Task<(bool uploadSuccess, string uploadedUri)> UploadToBlob(string fileName, object p, Stream stream)
        {
            if (stream is null)
            {
                try
                {
                    string connectionString = Environment.GetEnvironmentVariable("AZURE_STORAGE_CONNECTION_STRING");

                    // Create a BlobServiceClient object which will be used to create a container client
                    BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);

                    //Create a unique name for the container
                    string containerName = "textdata" + Guid.NewGuid().ToString();

                    // Create the container and return a container client object
                    BlobContainerClient containerClient = await blobServiceClient.CreateBlobContainerAsync(containerName);

                    string localPath = "./data/";
                    string textFileName = "textdata" + Guid.NewGuid().ToString() + ".txt";
                    string localFilePath = Path.Combine(localPath, textFileName);

                    // Get a reference to a blob
                    BlobClient blobClient = containerClient.GetBlobClient(textFileName);

                    Console.WriteLine("Uploading to Blob storage as blob:\n\t {0}\n", blobClient.Uri);

                    FileStream uploadFileStream = File.OpenRead(localFilePath);
                    await blobClient.UploadAsync(uploadFileStream, true);
                    uploadFileStream.Close();
                }
                catch (StorageException)
                {
                    return (false, null);
                }
                finally
                {
                    // Clean up resources, e.g. blob container
                    //if (blobClient != null)
                    //{
                    //    await blobClient.DeleteIfExistsAsync();
                    //}
                }
            }
            else
            {
                return (false, null);
            }

        }

    }

but the console throws errors, saying "'ControllerBase.File(byte[], string)' is a method, which is not valid in the given context (CS0119)"

And because of this error, another error follows "'HomeController.UploadToBlob(string, object, Stream)': not all code paths return a value (CS0161)"

my questions are

  1. Is it a better idea to create a separate method like I did?
  2. how can I resolve the issue regarding the "File" being valid inside of the UploadToBlob method?
  3. If I want to add the file type validation, where should it happen? t.ex. only text file is alid
  4. If I want to read the text string from the uploaded text file, where should I call the
  string contents = blob.DownloadTextAsync().Result;

  return contents;
  1. How can I pass down the "contents" to my react component? something like this?
    useEffect(() => {
        fetch('Home')
            .then(response => response.json())
            .then(data => {
                setForcasts(data)
            })
    }, [])

Thanks for helping this super newbie with ASP.NET Core!

yoonvak
  • 303
  • 1
  • 3
  • 15

2 Answers2

37

1) It is ok to put uploading into separate method, it could also be put into a separate class for handling blob operations

2) File is the name of one of the controllers methods, if you want to reference the File class from System.IO namespace, you need to fully qualify the name

FileStream uploadFileStream = System.IO.File.OpenRead(localFilePath);

To the other compile error, you need to return something from the UploadToBlob method, now it does not return anything from the try block

3) File type validation can be put into the controller action method

4) it depends on what you plan to do with the text and how are you going to use it. Would it be a new action of the controller (a new API endpoint)?

5) you could create a new API endpoint for downloading files

UPDATE:

For word replacement you could use a similar method:

private Stream FindMostFrequentWordAndReplaceIt(Stream inputStream)
{
    using (var sr = new StreamReader(inputStream, Encoding.UTF8)) // what is the encoding of the text? 
    {
        var allText = sr.ReadToEnd(); // read all text into memory
        // TODO: Find most frequent word in allText
        // replace the word allText.Replace(oldValue, newValue, stringComparison)
        var resultText = allText.Replace(...);

        var result = new MemoryStream();
        using (var sw = new StreamWriter(result))
        {
            sw.Write(resultText);
        }
        result.Position = 0;
        return result;
    }
}

it would be used in your Post method this way:

using (var stream = formFile.OpenReadStream())
{
    var streamWithReplacement = FindMostFrequentWordAndReplaceIt(stream);

    // Upload the replaced text:
    (uploadSuccess, uploadedUri) = await UploadToBlob(formFile.FileName, null, streamWithReplacement);

}
Martin Staufcik
  • 8,295
  • 4
  • 44
  • 63
  • 1
    Thanks! Regarding the answers, Q3, can I add the validation within the try? Q4, I would like to get the text from the uploaded file, and find the most used word and alter that word. Maybe then it is better to create another API controller where I can pass the text from the file and call that controller to pass down the data with ```` ``` string contents = blob.DownloadTextAsync().Result; return contents; ``` like this? – yoonvak Mar 30 '20 at 10:31
  • 1
    You could also validate the file name directly in the `Post` method. `IFromFile` has property `FileName`. – Martin Staufcik Mar 30 '20 at 10:33
  • 1
    Q4 - If the operation for altering the most used word is done before upload of the blob to blob storage, then it could be called in the `Post` method (and be performed on the stream of file data coming from `IFromFile`). If the operation is done afterward, after upload, independently, then it would be probably a new action in your controller and then the file would need to be first downloaded from storage, then altered, and then uploaded back to blob storage. – Martin Staufcik Mar 30 '20 at 10:38
  • 1
    I want the user updated text file to be - find most used word and change that word in text and show the changed text to the user. if it is possible, I would like to 1. get the file’s text content before uploading when Post method is being called and save that content so I add the “DownloadTextAsync()” method inside of the POST method, but it seems like I am calling this method to the wrong subject? I understood that ‘blobClient’ is the reference to the blob, where I can get the file’s data but this must be wrong? – yoonvak Mar 30 '20 at 14:40
  • 1
    Also it seems like I cannot use “CloudBlobContainer” nor the “CloudBlockBlob blob”. Is it because inside of the POST method, the blob has been just initialized and does not exist when these twos are executed? – yoonvak Mar 30 '20 at 14:41
  • 1
    Also when I test the POST method, the console throws “Refused to load the font '' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'font-src' was not explicitly set, so 'default-src' is used as a fallback.” which I googled but have no idea what it means? I have tried different ways but keep getting CANNOT POST/“ But could not really find the solid anwers. Could this be related to my POST method? – yoonvak Mar 30 '20 at 14:41
  • 1
    For the issue with loading fonts, please have a look at this post: https://stackoverflow.com/questions/30280370/how-does-content-security-policy-work. It describes solution to issues in developer console.Please note, `default-src` is the default policy for loading javascript, images, CSS, fonts, AJAX requests. – Martin Staufcik Mar 31 '20 at 06:59
  • 1
    modified the answer, now it has example of replacing the word in the uploaded file – Martin Staufcik Mar 31 '20 at 07:30
9

You probably have this method inside MVC controller in which File method exists. Add in your code System.IO.File instead of File

Shoaib Khalil
  • 1,874
  • 17
  • 10