[CLOSED] FileUploadField as File Dialog

Page 1 of 2 12 LastLast
  1. #1

    [CLOSED] FileUploadField as File Dialog

    Hi

    I have an application where I want to select a file to load into my database using ExcelReaderFactory. in other words I don't want to upload the file to the server I just want to select the file. The only control I can find in EXT.NET is the FileUploadField which I can use to select the file but it then appears that the control retains 'ownership' of the file so it's not possible for ExcelReaderFactory to read it.

    What is the command to force the FileUploadField to release the file or is there another, better way?

    Thanks

    Jeff
    Last edited by Daniil; Dec 23, 2015 at 8:41 AM. Reason: [CLOSED]
  2. #2
    Hi Jeff,

    That is an interesting problem.

    it then appears that the control retains 'ownership' of the file so it's not possible for ExcelReaderFactory to read it.
    How does it look like? I mean what exactly happens?

    Is there a possibility to provide us with a test case?

    Just, personally, I have never dealt with such an issue and cannot suggest anything at the moment.
  3. #3
    Hi Daniil

    Here is a simple example. You will need to change the @"C:\Projects\Test\Data" part of the Path.Combine section to be a valid directory on your PC. If you then click on the Browse button and select a file, then click on Import Data it will crash with "The process cannot access the file 'C:\Projects\Test\Data\Focus Data.xlsx' because it is being used by another process."

    View:

    <!DOCTYPE html>
    <html>
    <head>
        <title></title>
        <script>
            var importData = function () {
                var fileName = App.fufUpload.getValue();
                App.fufUpload.reset();
                Ext.net.DirectMethod.request({
                    url: '@(Url.Action("Import"))',
                        params: { fileName: fileName }
                    });
                };
    
        </script>
    </head>
    <body>
    @Html.X().ResourceManager().ScriptMode(Ext.Net.ScriptMode.Debug).SourceFormatting(true)
    @(Html.X().GridPanel().Width(500)
        .Title("Users").ID("GridPanel").Border(true)
        .Store(Html.X().Store().ID("Store")
            .Model(Html.X().Model().ID("Model").IDProperty("ID")
                .Fields(
                    new ModelField("ID", ModelFieldType.Int),
                    new ModelField("Name"),
                    new ModelField("Period")
                    )
                )
            .Proxy(Html.X().AjaxProxy().Url(Url.Action("Read")).Reader(Html.X().JsonReader().RootProperty("data"))))
        .ColumnModel(
            Html.X().Column().Text("Name").DataIndex("Name").Flex(20),
            Html.X().Column().Text("Period").DataIndex("Period").Flex(20).ID("Period")
        )
            .TopBar(
            Html.X().Toolbar()
                .Items(
                    Html.X().FileUploadField().ID("fufUpload").FieldLabel("File Name").Width(350).LabelWidth(100),
                    Html.X().ToolbarSeparator(),
                    Html.X().Button().Text("Import Data").Icon(Icon.PageExcel).Listeners(lst => lst.Click.Fn = "importData")
                )
        )
    )
    </body>
    </html>
    and Controller:

    using Ext.Net;
    using Ext.Net.MVC;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Web;
    using System.Web.Mvc;
    
    namespace ExtNetBugsMVC.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View();
            }
            public ActionResult Read()
            {
                List<User> lstUsers = new List<User>();
                lstUsers.Add(new User(1000, "Jeff", "00:10"));
                lstUsers.Add(new User(1001, "James", "02:00"));
                lstUsers.Add(new User(1002, "Jenny", "02:00"));
                lstUsers.Add(new User(1003, "Gareth", "02:00"));
                lstUsers.Add(new User(1004, "Graham", "02:00"));
                lstUsers.Add(new User(1005, "Chris", "00:10"));
                return this.Store(lstUsers);
            }
            public virtual ActionResult ChangePeriod(string job)
            {
                return this.Direct();
            }
            public ActionResult GetPeriods()
            {
                List<Job> lstJobs = new List<Job>();
                lstJobs.Add(new Job(1000, "Job1", "00:10"));
                lstJobs.Add(new Job(1001, "Job2", "02:00"));
                return this.Store(lstJobs);
            }
            public ActionResult Import(string fileName)
            {
                FileStream fileStream = System.IO.File.Open(Path.Combine(@"C:\Projects\Test\Data\", fileName), FileMode.Open, FileAccess.Read);
    
                return this.Direct();
            }
        }
        public class User
        {
            public User()
            {
            }
            public User(int id, string name, string period)
            {
                ID = id;
                Name = name;
                Period = period;
            }
            public int ID { get; set; }
            public string Name { get; set; }
            public string Period { get; set; }
        }
        public class Job
        {
            public Job()
            {
            }
            public Job(int id, string name, string period)
            {
                ID = id;
                Name = name;
                Period = period;
            }
            public int ID { get; set; }
            public string Name { get; set; }
            public string Period { get; set; }
        }
    }
  4. #4
    Hello @Argenta!

    Thank you for the very straightforward and objective example. I could reproduce your issue here.

    I could find a solution for your problem by, instead of trying to open the file, copying it to a temp one and opening that temporary file.

    It is important to close the file before returning from the function so I just open the file, copy it to a memory stream, and then close it.

    Path.Combine() was also triggering the busy error, so I just concatenated the paths. If that's a problem, we can think a different approach. But we have at least two problematics here: the browser locks the file (when opening via FileReader()), and the IIS process also does it (when read from code behind).

    I know that after returning from the call without closing the file, IIS keeps the file handle open, so it gets blocked, but I am not completely sure so far as whether is the browser or a code behind event involved in when you select the file with FileUploadField.

    Here is the revisited version of the import Action in your controller:
            public ActionResult Import(string fileName)
            {
                var fileWithPath = @"A:\Temp\" + fileName;
                System.IO.File.Copy(fileWithPath, fileWithPath + "_", true);
                FileStream fileStream = System.IO.File.Open(fileWithPath + "_", FileMode.Open);
                X.AddScript(@"console.log('opened \'" + fileName + @"\'');");
                var memStream = new MemoryStream();
                fileStream.CopyTo(memStream);
                fileStream.Close();
                return this.Direct();
            }
    If you agree with the temp file handling, you should take extra care to make unique names and also deleting them after read.

    I couldn't find a reliable way to release the handle from the file otherwise -- the browser holds it once you select the file, and IIS holds it if you don't close it before the directEvent returns.

    Hope this helps!
    Fabrício Murta
    Developer & Support Expert
  5. #5
    Thanks Fabricio

    I have got the principle behind you're idea to work and it seems perfectly acceptable. I had to make a slight modification because putting the "_" after the file name caused a problem. So I did this:

            
    public ActionResult Import(string fileName)
            {
                var fileWithPath = @"A:\Temp\" + fileName;
    var fileWithPath2 = @"A:\Temp\" +"_" +  fileName;
                System.IO.File.Copy(fileWithPath, fileWithPath2, true);
                FileStream fileStream = System.IO.File.Open(fileWithPath2, FileMode.Open);
                X.AddScript(@"console.log('opened \'" + fileName + @"\'');");
                var memStream = new MemoryStream();
                fileStream.CopyTo(memStream);
                fileStream.Close();
                return this.Direct();
            }
    Thanks for your help

    Jeff
  6. #6
    Hi again

    It would obviously be better if there was a simple FileDialog control. Is there any reason why this can't be provided?

    Thanks

    Jeff
  7. #7
    Hello Jeff!

    Good point you raised. As Ext.NET is but an ExtJS framework, we tend to add support to their controls as they are, and make modifications only when they don't work. Maybe it does not make much sense in just having a file select in ExtJS, they already did it with upload functionality and didn't expect anyone to need it. Me might consider building a simpler button type=File if really necessary.

    As a matter of fact, we did implement our own variation of control that handles uploads, although not as simple as <input type=file ..., it can mimic pretty well the behavior, including by not locking any file handlers by the browser. Below is a couple adaptations on your original sample to use MultiUpload control:

    
    @{
        Layout = null;
    }
    
    <!DOCTYPE html>
    
    <html>
    <head>
        <title></title>
        <script>
            var fileName;
    
            var importData = function () {
                console.log(fileName);
                if (typeof (fileName) == 'undefined') {
                    alert("Select a file... before trying to send.");
                    return;
                }
    
                console.log('will load "'+fileName+'"')
                Ext.net.DirectMethod.request({
                    url: '@(Url.Action("Import"))',
                    params: { fileName: fileName }
                });
            };
    
            var bindFileName = function (item, file, errorCode, message) {
                console.log("Selected file");
                fileName = file.name;
            }
        </script>
    </head>
    <body>
        @Html.X().ResourceManager().ScriptMode(Ext.Net.ScriptMode.Debug).SourceFormatting(true)
        @(Html.X().GridPanel().Width(900)
        .Title("Users").ID("GridPanel").Border(true)
        .Store(Html.X().Store().ID("Store")
            .Model(Html.X().Model().ID("Model").IDProperty("ID")
                .Fields(
                    new ModelField("ID", ModelFieldType.Int),
                    new ModelField("Name"),
                    new ModelField("Period")
                    )
                )
            .Proxy(Html.X().AjaxProxy().Url(Url.Action("Read")).Reader(Html.X().JsonReader().RootProperty("data"))))
        .ColumnModel(
            Html.X().Column().Text("Name").DataIndex("Name").Flex(20),
            Html.X().Column().Text("Period").DataIndex("Period").Flex(20).ID("Period")
        )
            .TopBar(
            Html.X().Toolbar()
                .Items(
                    Html.X().MultiUpload().ID("mupload")
                        .AutoStartUpload(false)
                        .FileTypes("*.*")
                        .FileTypesDescription("All files")
                        .Listeners(listener => listener.FileSelected.Fn="bindFileName"),
                    Html.X().ToolbarSeparator(),
                    Html.X().Button().Text("Import Data").Icon(Icon.PageExcel).Listeners(lst => lst.Click.Fn = "importData")
                )
            )
        )
    </body>
    </html>
    A text field with the filename could be added and value bound in the "bindFileName" function. Left out for simplicity.

    Thus the Import action would be:
            public ActionResult Import(string fileName)
            {
                FileStream fileStream = System.IO.File.Open(Path.Combine(@"A:\Temp\", fileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                X.AddScript(@"console.log('opened \'" + fileName + @"\'');");
                fileStream.Close();
                return this.Direct();
            }
    Notice the MemoryStream approach is still desirable if you want to keep the file opened after running the Import method. If you don't close the file now, IIS (or IISExpress) will be locking the file for the life of the session despite the FileShare flag allowing other instances reading it.
    Fabrício Murta
    Developer & Support Expert
  8. #8
    Thanks Fabricio

    That's very useful. It looks as though you can't change the text on the Browse button? Otherwise I could probably do without the 'Import data' button.

    Jeff
  9. #9
    Hi again Fabricio

    I've been trying to implement this and thought I would restrict the file selection to Excel files. Rather strangely when I change
    .FileTypes("*.*")
    to
    .FileTypes("*.xls*")
    the file selection gets filtered but the FileSelected event is not triggered?

    Regards

    Jeff
  10. #10
    Hello Jeff!

    Glad we are advancing on this!.. As for the button label, you can customize it with a code like this in your MultiUpload control:
    .Button(btn =>
    {
        btn.Add(Html.X().Button().Text("Import data..."));
    })
    The listener not firing for the changed extension needs a little more investigation as for why it is not firing.
    Fabrício Murta
    Developer & Support Expert
Page 1 of 2 12 LastLast

Similar Threads

  1. Replies: 2
    Last Post: Aug 02, 2013, 2:43 AM
  2. Replies: 1
    Last Post: Dec 06, 2012, 5:27 AM
  3. Replies: 1
    Last Post: Nov 02, 2012, 4:07 AM
  4. [CLOSED] File Dialog on Image Click
    By jwf in forum 1.x Legacy Premium Help
    Replies: 3
    Last Post: May 21, 2011, 10:03 AM

Posting Permissions