Excel file upload and read from file

Hi, 

Here I am with yet another question.

I am trying to upload an MS-Excel file, and read from it. Right now I have this:

<SfUploader ID="UploadFiles" AllowedExtensions=".xls, .xlsx" AutoUpload=false DropArea="#DropArea" MaxFileSize="500000">
    <UploaderAsyncSettings RemoveUrl="api/SampleData/Remove">
        <UploaderEvents ValueChange="OnChange"></UploaderEvents>
    </UploaderAsyncSettings>
</SfUploader>

@code {
protected async Task OnChange(UploadChangeEventArgs args)
    {

        foreach (var file in args.Files)
        {
            //Console.WriteLine($"Value is ");
            var path = @"path" + file.FileInfo.Name;

            using (ExcelEngine excelEngine = new ExcelEngine())
            {
                
                //Instantiate the Excel application object
                IApplication application = excelEngine.Excel;

                //Set the default application version
                application.DefaultVersion = ExcelVersion.Xlsx;

                //Load the existing Excel workbook into IWorkbook
                FileStream filestream = new FileStream(path, FileMode.Open, FileAccess.Read);
                IWorkbook workbook = application.Workbooks.Open(filestream);


                //Get the first worksheet in the workbook into IWorksheet
                IWorksheet worksheet = workbook.Worksheets[0];

                filestream.Close();
                file.Stream.Close();
            }

        }

        await Task.CompletedTask;
    }
}

But although it capture the file, I am not able to read the excel, or get any of its properties. It does not give me an error, but let's say I try to get a property like workbook.Worksheets.Count, I get nothing.

There is probably something really obvious and simple that I am missing. Any help would be highly appreciated!


18 Replies 1 reply marked as answer

SP Sureshkumar P Syncfusion Team November 17, 2020 11:39 AM UTC

Hi Eric, 
 
Greetings from Syncfusion support, 
 
Based on your shared backend code, we suspect that you have used our XlsIo namespace to read the Xlsx files. So, we suggest you check the file path of uploaded file to resolve the filestream issue.  
 
Please find the screen shot here: 
 
 
Please confirm whether you have using our Syncfusion Xlsio package to read the file or not. 
 
Regards, 
Sureshkumar P 



ER Eric November 17, 2020 01:09 PM UTC

Hi Sureshkumar,

Yes, I am using Syncfusion.XlsIO. 

About the path, "Duh", yeah, I missed that it was obviously a placeholder. But now, how do I get the path (if it's that even possible given browser security)? I am more concerned on being able to read from the file uploaded.

Thanks for your help.


SP Sureshkumar P Syncfusion Team November 18, 2020 08:13 AM UTC

Hi Eric, 
 
Thanks for your update. 
 
You can get the file path as like below constant path. Because modern browser will not tell you the path on the client, for security reasons.  
 
Please refer the sample code example here:  
public async Task OnChange(UploadChangeEventArgs args) 
    { 
        foreach (var file in args.Files) 
        { 
            var path = @"D:\" + file.FileInfo.Name; 
 
            using (ExcelEngine excelEngine = new ExcelEngine()) 
            { 
 
                //Instantiate the Excel application object 
                IApplication application = excelEngine.Excel; 
 
                //Set the default application version 
                application.DefaultVersion = ExcelVersion.Xlsx; 
 
                //Load the existing Excel workbook into IWorkbook 
                FileStream filestream = new FileStream(path, FileMode.Open, FileAccess.Read); 
                IWorkbook workbook = application.Workbooks.Open(filestream); 
                Console.WriteLine("WorkSheetCount" + workbook.Worksheets.Count); 
 
                //Get the first worksheet in the workbook into IWorksheet 
                IWorksheet worksheet = workbook.Worksheets[0]; 
 
                filestream.Close(); 
                file.Stream.Close(); 
            } 
 
        } 
 
        await Task.CompletedTask; 
    } 
 
 
To know more about the path in client side, please refer the below stack overflow link: https://stackoverflow.com/questions/1130560/get-full-path-of-a-file-with-fileupload-control  
 
We have prepared the sample based on your requirement. please find the sample here: https://www.syncfusion.com/downloads/support/forum/159780/ze/UploaderXlsIo470268572  
 
Regards, 
Sureshkumar P 



ER Eric November 18, 2020 01:56 PM UTC

Hi Sureshkumar,

Thank you for this.

Maybe I started this the wrong way. Instead of focusing on the path and how to obtain in on the client side, I should had focused on just getting the data from the file, I do not care if it's on the client or server side.

It's this feasible?

Again, thanks a lot for you help and sorry for the trouble.


ER Eric November 19, 2020 11:28 PM UTC

Looking forward to any tips on this.


SP Sureshkumar P Syncfusion Team November 20, 2020 02:14 AM UTC

Hi Eric, 
 
Thanks for your update, 
 
As per our previous update, we cannot able to get the selected file root path in the server and client side. But we can get the saved path through WebRootPath as like below code. So, we suggest you use server-side service to save the selected file to get the saved file path to achieve your requirement.  
 
Please find the code example here: 
 [HttpPost("[action]")] 
        public async void Save(IList<IFormFile> chunkFile, IList<IFormFile> UploadFiles) 
        { 
            long size = 0; 
            try 
            { 
                // for chunk-upload 
                foreach (var file in chunkFile) 
                { 
                    var filename = ContentDispositionHeaderValue 
                                        .Parse(file.ContentDisposition) 
                                        .FileName 
                                        .Trim('"'); 
                    filename = hostingEnv.WebRootPath + $@"\{filename}"; 
                    size += file.Length; 
                     
                    if (!System.IO.File.Exists(filename)) 
                    { 
                        using (FileStream fs = System.IO.File.Create(filename)) 
                        { 
                            file.CopyTo(fs); 
                            fs.Flush(); 
                        } 
                    } 
                    else 
                    { 
                        using (FileStream fs = System.IO.File.Open(filename, FileMode.Append)) 
                        { 
                            file.CopyTo(fs); 
                            fs.Flush(); 
                        } 
                    } 
                    using (ExcelEngine excelEngine = new ExcelEngine()) 
                    { 
 
                        //Instantiate the Excel application object 
                        IApplication application = excelEngine.Excel; 
 
                        //Set the default application version 
                        application.DefaultVersion = ExcelVersion.Xlsx; 
 
                        //Load the existing Excel workbook into IWorkbook 
                        FileStream filestream = new FileStream(filename, FileMode.Open, FileAccess.Read); 
                        IWorkbook workbook = application.Workbooks.Open(filestream); 
 
                        //Get the first worksheet in the workbook into IWorksheet 
                        IWorksheet worksheet = workbook.Worksheets[0]; 
 
                        filestream.Close(); 
                    } 
                } 
            } 
            catch (Exception e) 
            { 
                Response.Clear(); 
                Response.StatusCode = 204; 
                Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = "File failed to upload"; 
                Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = e.Message; 
            } 
 
        } 
 
 
Please find the screen shot here: 
 
 
We have created the sample based on your requirement. please find the sample here: https://www.syncfusion.com/downloads/support/forum/159780/ze/BlazorApp1816845306  
 
Regards, 
Sureshkumar P 


Marked as answer

ER Eric November 20, 2020 07:23 PM UTC

Hi SureshKumar,

Thank you for this.

For several hours I was not being able to do anything with the data, always getting a System.ObjectDisposedException: 'IFeatureCollection has been disposed if I tried to do anything with the data (like save with a ApplicationDbContext context or sending it to another class somewhere else). At the end, changing the Save function, from void to Task did the trick and now it works.

Thanks again!


SP Sureshkumar P Syncfusion Team November 23, 2020 11:22 AM UTC

Hi Eric, 
 
Thanks for your update. 
 
Regards, 
Sureshkumar P 



ER Eric November 23, 2020 03:42 PM UTC

Hi Sureshkumar,

One quick thing to follow up from my issues, and hoping it can help you in any way in the future. I opened an issue request on GitHub for something totally unrelated to what we have discussed, however, while browsing other questions recently posted there, funny coincidently enough I found someone who had asked a question almost identical to the issue I was having, with always getting a System.ObjectDisposedException: 'IFeatureCollection when trying to use context with the database or other functions.

And their reply was:  "Never use async void for web APIs. Use async Task if you don't have any return parameters. Otherwise the server doesn't know when you're done and starts cleaning up immediately." 

The user had already mentioned that changing void to task had done the trick, but still could not understand why. GitHub Post

Hope this helps you somehow.

Best regards,
Eric


SP Sureshkumar P Syncfusion Team November 24, 2020 10:21 AM UTC

Hi Eric, 
 
Thanks for your valuable suggestion.  
 
Yes, we should avoid async void. We only use async void for event handlers (or things that are logically event handlers). A controller action is not an event handler. Thus, we should use async Task. 
 
Regards, 
Sureshkumar P 



ER Eric November 30, 2020 05:42 PM UTC

Hi Sureshkumar,

Right now everything is working great, however, if I try to change to controller to [Authorize], I always get a 401 response. Do you know how I can fix this?

Best regards,
Eric


SP Sureshkumar P Syncfusion Team December 1, 2020 12:16 PM UTC

Hi Eric, 
 
Based on your shared information, we suspect that you have missed to add the UseAuthentication before the line of UseAuthorization when authorize the controller. We suggest you call the UseAuthentication to resolve the issue. 
 
To know more about the issue, then please refer the below stack overflow and Microsoft documentation link. 
 
Documentation links: 
  1. https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/authentication-filters
  2. https://stackoverflow.com/questions/56185834/asp-net-core-api-always-returns-401-unauthorized-whenever-i-send-a-request-with
Regards, 
Sureshkumar P 



ER Eric December 1, 2020 03:00 PM UTC

Hi Sureshkumar,

About the authentication and authorization, note that this component is far from being the only function on the website (actually right now it's just a tiny part of it). Everything else works fine regarding Authorization, or even Authorization for a specific role. 

In order to double check, I used the function in another controller that I had for other purposes (with several working tasks), and if I add a [AllowAnonymous] to the specific save task, it works. Otherwise, it fails. 


BC Berly Christopher Syncfusion Team December 2, 2020 04:00 PM UTC

Hi Eric, 

We would like to inform you that the uploader component allows you to add additional header on file upload, and can be received in the server-side. You can achieve this behaviour using FileSelected / BeforeUpload event and its CurrentRequest argument by configure header as key-value pair.    
   
[index.razor 
 
@using Syncfusion.Blazor.Inputs  
<SfUploader ID="UploadFiles">  
    <UploaderEvents FileSelected="onFileSelect"></UploaderEvents>  
    <UploaderAsyncSettings SaveUrl="api/SampleData/Save">  
    </UploaderAsyncSettings>  
</SfUploader>  
  
@code {  
  
    private void onFileSelect(SelectedEventArgs args)  
    {  
        var accessToken = "Basic test123";  
        args.CurrentRequest = new List<object> { new { Authorization = accessToken } };  
    }  
  
}  
[SampleDataController.cs]  
[HttpPost("[action]")]  
        public async void Save(IList<IFormFile> UploadFiles)  
        {  
            //to get authorization Header to handle save file on server side    
            var authorizationHeader = Request.Headers["Authorization"];  
            try  
            {  
                foreach (var file in UploadFiles)  
                {  
                    if (UploadFiles != null)  
                    {  
                        var filename = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');  
                        filename = hostingEnv.WebRootPath + $@"\{filename}";  
                        if (!System.IO.File.Exists(filename))  
                        {  
                            using (FileStream fs = System.IO.File.Create(filename))  
                            {  
                                file.CopyTo(fs);  
                                fs.Flush();  
                            }  
                        }  
                        else  
                        {  
                            Response.Clear();  
                            Response.StatusCode = 204;  
                            Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = "File already exists.";  
                        }  
                    }  
                }  
                Response.Headers.Add("ID""Failure"); // Assign the custom data in the response header.  
            }  
            catch (Exception e)  
            {  
                Response.Clear();  
                Response.ContentType = "application/json; charset=utf-8";  
                Response.StatusCode = 204;  
                Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = "File failed to upload";  
                Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = e.Message;  
            }  
              
        }  
 
   
Please find the sample from the below link.  
   
Hope this will help for your requirement, please share the details to us if you need any further assistance on this. 

Regards,  
Berly B.C 


RF Robert Förster February 9, 2021 03:49 PM UTC

Hi,

you dont need to know where the file is on your machine if you use the memorystream.


protected async Task OnChange(UploadChangeEventArgs args)
    {

        foreach (var file in args.Files)
        {
            Console.WriteLine($"Uploaded Excel.");
            
            using (ExcelEngine excelEngine = new ExcelEngine())
            {
                //Instantiate the Excel application object
                IApplication application = excelEngine.Excel;

                //Set the default application version
                application.DefaultVersion = ExcelVersion.Xlsx;

                var filestream = file.Stream;

                // Set the position to the beginning of the stream.
                filestream.Seek(0SeekOrigin.Begin);

                //Load the existing Excel workbook into IWorkbook
                IWorkbook workbook = application.Workbooks.Open(filestream);
                Console.WriteLine("WorkSheetsCount: " + workbook.Worksheets.Count);
                //Get the first worksheet in the workbook into IWorksheet
                IWorksheet worksheet = workbook.Worksheets[0];
                Console.WriteLine("WorksheetsCount: " + worksheet.Rows.Count());

                var excelRows = worksheet.ExportData<ExcelApplicationImportDto>(2,1,10000,19);

                // Set
                exceldata = excelRows;

                file.Stream.Close();
            }

        }

        await Task.CompletedTask;
    }


VS Vignesh Srinivasan Syncfusion Team February 10, 2021 05:44 AM UTC

Hi Eric, 
 
Thanks for sharing your solution. 
 
Please let us know if you need any further assistance. 
 
Regards, 
 
Vignesh Srinivasan. 



CH Christoph December 27, 2022 07:32 PM UTC

Dear all,

the problem Eric ran into is still not solved (as of 20.4). JWT Authentication is not automatically passed during "Save" or "Delete" by the <SfUploader> component.

To get it working, you need to read the .NET auth-token:

@inject Microsoft.AspNetCore.Components.WebAssembly.Authentication.IAccessTokenProvider TokenProvider

<SfUploader ID="FileUpload" AllowedExtensions=".xls, .xlsx, .csv" AllowMultiple="false" AutoUpload="false">
<UploaderEvents FileSelected="onFileSelect"></UploaderEvents>
  <UploaderAsyncSettings SaveUrl="api/UCTUpload/save" RemoveUrl="api/UCTUpload/delete"></UploaderAsyncSettings>
</SfUploader>

@code {
private async Task onFileSelect(SelectedEventArgs args)
{
var accessTokenResult = await TokenProvider.RequestAccessToken();
if (accessTokenResult.TryGetToken(out var token))
{
args.CurrentRequest = new List<object> { new { Authorization = "Bearer " + token.Value } };
}
}
}

Thus, you don't need to make the controller [AllowAnonymous] and handle the Authorization-Header manually as in Christopher's example above.

TBH, this should be handled by the <SfUploader> component.

Regards,
Christoph



UD UdhayaKumar Duraisamy Syncfusion Team December 28, 2022 11:53 AM UTC


Loader.
Up arrow icon