Search code examples
c#asp.net-core-mvcasp.net-core-3.0ignite-ui

Infragistics IgniteUI IGFileUpload doesn't upload files to the server in ASP.NET MVC CORE 3.0


Lets say I have a simple Index.cshtml View:

<link href="~/libs/JQueryUI/jquery-ui.css" rel="stylesheet" />
<link href="~/libs/infragistics/css/themes/infragistics/infragistics.theme.css" rel="stylesheet" />
<link href="~/libs/infragistics/css/structure/infragistics.css" rel="stylesheet" />

@using CommonLib.Source.Common.Converters
@using CommonLib.Source.Common.Extensions
@using Infragistics.Web.Mvc
@using Microsoft.AspNetCore.Http
@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<div id="chart" style="color: blue"></div>

<div id="indicator"></div>

<div id="igFileUpload" style="color: blue"></div>

<script src="~/libs/JQuery/jquery.js"></script>
<script src="~/libs/JQueryUI/jquery-ui.js"></script>
<script src="~/libs/infragistics/js/infragistics.core.js"></script>
<script src="~/libs/infragistics/js/infragistics.lob.js"></script>
<script src="~/libs/Lightweight-Charts/dist/lightweight-charts.standalone.development.js"></script>
<script src="~/MyScripts.js"></script>

@(
    Html.Infragistics().Upload()
        .ID("igFileUpload")
        .Mode(UploadMode.Multiple)
        .AutoStartUpload(true)
        .ProgressUrl(Context.GetAbsoluteUrl("~/IGUploadStatusHandler.ashx"))
        .UploadUrl(Context.GetAbsoluteUrl("~/Data/UploadedFiles")) // just use Url.Content for testing purposes
        .ControlId("serverID1")
        .Width("600px")
        .Render()
)

<div id="error-message" style="color: #FF0000; font-weight: bold;"></div>

<script type="text/javascript">
    $(function () {
        $("#igFileUpload").bind({ iguploadonerror: function (e, args) {
                $("#error-message").html(args.errorMessage).stop(true, true).fadeIn(500).delay(3000).fadeOut(500);
            }
        });
    });
</script>

I also got Startup.cs file:

using System;
using System.IO;
using CommonLib.Source.Common.Utils;
using Infragistics.Web.Mvc;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;

namespace CryptoBotCoreMVC
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddMvc(option => option.EnableEndpointRouting = false);
            //services.Configure<UploadAppSettings>(options =>
            //{
            //    options.FileUploadPath = $@"{Directory.GetCurrentDirectory()}\Data"; //WebUtils.GetWebAppAbsolutePath("
            //}); // TODO: fu doesn't work | FileUploadPath is set in the View because it is too early to do it here
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider svp)
        {
            WebUtils.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>(), env); 
            if (env.IsDevelopment())
                app.UseDeveloperExceptionPage();

            app.UseStaticFiles();
            app.UseRouting();
            app.UseUploadModuleMiddleware();
            app.UseUploadHandlerMiddleware();
            app.UseFileServer(new FileServerOptions
            {
                FileProvider = new PhysicalFileProvider($@"{Directory.GetCurrentDirectory()}\Data"),
                RequestPath = "/Data",
                EnableDirectoryBrowsing = true
            });

            app.UseMvc(routes =>
            {
                routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

Reference here: https://www.igniteui.com/help/using-igniteui-controls-in-asp.net-core-1.0-project

To be safe I set maximum file size in web.config:

<security>
  <requestFiltering>
    <requestLimits maxAllowedContentLength="1073741824" /> <!-- 1 GB -->
  </requestFiltering>
</security>

and in IIS:

enter image description here

This is what Fiddler shows:

enter image description here

1st:

enter image description here

2nd and 3rd:

enter image description here

According to this:
https://www.igniteui.com/help/igupload-using-http-handler-and-modules
The error means:

Status: 3 - File not found - this status is used when it is not found such key in the dictionary
Error: 5 - File does not exist with the specified key in the request

Here is the result itself:

enter image description here

Here is the reference for the control documentation (some of it is not applicable for ASP.NET CORE):

https://www.igniteui.com/help/igupload-igupload
https://www.igniteui.com/file-upload/overview

The problem is that as you can see, while the control works, the files are not actually uploaded to the server. I don't believe this is a bug, I think that I am not familiar enough with CORE 3.0 and I am missing something rather obvious in configuration of IGUploadStatusHandler.ashx.
I would rather want to avoid digging for IF default Web Handler code if possible.

/EDIT

As per @MasLoo suggestion I implemented the middleware to replace the apparently required IgnoreRoute but I fail to understand how throwing 404 directly from it would make the Handler work:

(...)
app.UseMiddleware<IgnoreRouteMiddleware>();
(...)

:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace CryptoBotCoreMVC.Middlewares
{
    public class IgnoreRouteMiddleware
    {
        private readonly RequestDelegate _next;

        public IgnoreRouteMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            if (context.Request.Path.HasValue && context.Request.Path.Value.Contains("IGUploadStatusHandler.ashx"))
            {
                context.Response.StatusCode = 404;
                return;
            }

            await _next.Invoke(context);
        }
    }
}

After doing a test it indeed throws 404 right away, actually twice:

enter image description here

On commenting the:

//context.Response.StatusCode = 404;

The fiddler catches this:

enter image description here

with the following message:

enter image description here

I didn't post the messages because they are exactly the same as the first one with Content-Length: 0.

Further ideas would be warmly welcome.

// EDIT 2

I think I am almost there, but not quite. I noticed that for some reason if I keep urls default, the IF Code instead of looking for a folder for files in UploadUrl it looks for ig_fua34sf345sdf13sdf3454erdsf2345asd3425df5235d54df345.aspx path so I changed my HtmlHelper into:

Html.Infragistics().Upload()
    .ID("igFileUpload")
    .Mode(UploadMode.Multiple)
    .AutoStartUpload(true)
    .ProgressUrl(Context.GetAbsoluteUrl("~/IGUploadStatusHandler.ashx"))
    .UploadUrl(Context.GetAbsoluteUrl("~/ig_fua34sf345sdf13sdf3454erdsf2345asd3425df5235d54df345.aspx"))
    .ControlId("serverID1")
    .Width("600px")
    .Render()

and now I am getting 500s:

enter image description here

with the following Stack Trace:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Thu, 21 Nov 2019 00:17:16 GMT
Content-Length: 1398

System.InvalidOperationException: Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
   at Microsoft.AspNetCore.Server.IIS.Core.HttpRequestStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at Microsoft.AspNetCore.Server.IIS.Core.WrappingStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at Infragistics.Web.Mvc.MiddleWare.UploadModuleMiddleware.ReadFully(Stream input)
   at Infragistics.Web.Mvc.MiddleWare.UploadModuleMiddleware.Invoke(HttpContext context)
   at CryptoBotCoreMVC.Middlewares.IgnoreRouteMiddleware.Invoke(HttpContext context) in <Path>\CSharp\Projects\CryptoBotCoreMVC\CryptoBotCoreMVC\Middlewares\IgnoreRouteMiddleware.cs:line 23
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: pl-PL,pl;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 244
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary3RFkBWmZhhsbjzXr
Host: localhost
Pragma: no-cache
Referer: http://localhost/CryptoBotCoreMVC
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: cors
Origin: http://localhost
Sec-Fetch-Site: same-origin

Now we are getting somewhere, it seems that changing Kestrel or IIS configuration should be sufficient in this case:

services.Configure<KestrelServerOptions>(options =>
{
    options.AllowSynchronousIO = true;
});
services.Configure<IISServerOptions>(options =>
{
    options.AllowSynchronousIO = true;
});

and indeed it is, I am gonna post detailed answer soon.


Solution

  • In order to make IgUpload Control from IgniteUI work with ASP.NET CORE MVC 3.0 you need to perform the following steps (Be aware however that it still isn't streaming large files in chunks, for this refer to this article (which hasn't been updated for ASP.NET CORE)):

    1. Add Infragistics.Web.AspNetCore and IgniteUI NuGet packages OR add the scripts, .css files and .dll file manually from your Infragistics installation folder as dependencies:

      enter image description here

    2. Set maxAllowedContentLength in web.config file:

      enter image description here

    3. Set Maximum allowed content length (Bytes) in IIS: CTRL + R > inetmgr > <Server or Website> > IIS Category > RequestFiltering > Actions Pane (to the right side) > Edit Feature Settings...

    4. Include options.AllowSynchronousIO = true in your Startup.cs file for Kestrel and/or IIS to prevent Middleware from throwing Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead exception.

    5. Set maxFileSizeLimit to a positive value, because despite the documentation statement it will not work for larger files if you set it to null, -1 or if you won't set this option at all.

    6. Set FileUploadPath to a Physical Directory on the application Drive (not the Virtual one).

      enter image description here

    7. Render the Control including ProgressUrl, UploadUrl and ControlID options and set them to the exact values visible below to prevent Middleware from throwing various inxeplicit 404 errors (code samples will be on the bottom of this answer).

      enter image description here

    8. Get the desired result:

      enter image description here

    IMPORTANT: IgnoreRoute is NOT required!

    Code samples:

    Startup.cs:

    using System.IO;
    using Infragistics.Web.Mvc;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Server.Kestrel.Core;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    
    namespace IgUpload
    {
        public class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddMvc(options => options.EnableEndpointRouting = false);
                services.Configure<KestrelServerOptions>(options => options.AllowSynchronousIO = true);
                services.Configure<IISServerOptions>(options => options.AllowSynchronousIO = true);
                services.Configure<UploadAppSettings>(options =>
                {
                    options.maxFileSizeLimit = "1073741824"; // 1 GB
                    options.FileUploadPath = $@"{Directory.GetCurrentDirectory()}\Data\UploadedFiles";
                });
            }
    
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
                app.UseStaticFiles();
                app.UseRouting();
                app.UseUploadModuleMiddleware();
                app.UseUploadHandlerMiddleware();
                app.UseMvc(routes => routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"));
            }
        }
    }
    

    Index.cshtml:

    @using Infragistics.Web.Mvc
    @using Microsoft.AspNetCore.Http
    @{
        ViewData["Title"] = "Index";
    }
    
    <div id="igFileUpload"></div>
    
    <script src="~/libs/JQuery/jquery.js"></script>
    <script src="~/libs/JQueryUI/jquery-ui.js"></script>
    <script src="~/libs/infragistics/js/infragistics.core.js"></script>
    <script src="~/libs/infragistics/js/infragistics.lob.js"></script>
    
    @(
        Html.Infragistics().Upload()
            .ID("igFileUpload")
            .Mode(UploadMode.Multiple)
            .AutoStartUpload(true)
            .ProgressUrl(Url.Content("~/IGUploadStatusHandler.ashx"))
            .UploadUrl(Url.Content("~/ig_fua34sf345sdf13sdf3454erdsf2345asd3425df5235d54df345.aspx"))
            .ControlId("serverID1")
            .Width("600px")
            .Render()
    )
    
    <div id="error-message" style="color: #FF0000; font-weight: bold;"></div>
    
    <script type="text/javascript">
        $(function () {
            $("#igFileUpload").bind({ iguploadonerror: function (e, args) {
                    $("#error-message").html(args.errorMessage).stop(true, true).fadeIn(500).delay(3000).fadeOut(500);
                }
            });
        });
    </script>