2012-12-04 10 views
7

Sto lavorando su un'applicazione ASP.NET MVC 4 che importa ed elabora un file CSV. Sto usando un modulo standard e un controller per il caricamento. Ecco una panoramica di quello che sto facendo attualmente:Caricamento ed elaborazione di file CSV in ASP.NET MVC 4 Considerazioni sull'architettura

Logic Controller

public ActionResult ImportRecords(HttpPostedFileBase importFile){ 

    var fp = Path.Combine(HttpContext.Server.MapPath("~/ImportUploads"), Path.GetFileName(uploadFile.FileName)); 
    uploadFile.SaveAs(fp); 

    var fileIn = new FileInfo(fp); 
    var reader = fileIn.OpenText(); 
    var tfp = new TextFieldParser(reader) {TextFieldType = FieldType.Delimited, Delimiters = new[] {","}}; 
    while(!tfp.EndOfData){ 
     //Parse records into domain object and save to database 
    } 
    ... 
} 

HTML

@using (Html.BeginForm("ImportRecords", "Import", FormMethod.Post, new { @id = "upldFrm", @enctype = "multipart/form-data" })) 
{ 
    <input id="uploadFile" name="uploadFile" type="file" /> 
    <input id="subButton" type="submit" value="UploadFile" title="Upload File" /> 
} 

Il file di importazione può contenere un numero elevato di record (media 40K +) e può richiedere un po 'di tempo per completare. Preferirei non avere un utente seduto alla schermata di importazione per più di 5 minuti per ogni file elaborato. Ho preso in considerazione l'aggiunta di un'applicazione console per guardare la cartella di upload per i nuovi file, e di elaborare quando qualcosa di nuovo è stato aggiunto, ma vorrei vedere quale input ricevo dalla comunità prima di iniziare il mio viaggio lungo questo percorso.

Esiste un modo più efficiente per gestire questa operazione?

Esiste un modo per eseguire questa azione, consentendo all'utente di continuare sulla sua strada, e quindi avvisare l'utente quando l'elaborazione è stata completata?

risposta

10

La soluzione al problema che stavo avendo è un po 'complessa, ma funziona in modo simile alla correzione IFrame. Il risultato è una finestra pop-up che gestisce l'elaborazione, consentendo all'utente di continuare la navigazione all'interno del sito.

Il file viene inviato al server (controller UploadCSV), una pagina di successo viene restituita con un po 'di JavaScript per gestire il primo avvio dell'elaborazione. Quando l'utente fa clic su "Inizia elaborazione", viene aperta una nuova finestra (ImportProcessing/Index) che carica lo stato iniziale (eseguendo il ciclo di un intervallo che recupera gli aggiornamenti di stato) e quindi effettua una chiamata all'azione "Avvia elaborazione", dando il via al processo di elaborazione.

La classe "FileProcessor" che sto utilizzando è contenuta in una variabile di dizionario statica all'interno del controller ImportProcessing; consentendo i risultati dello stato in base alla chiave. Il FileProcessor viene prontamente rimosso dopo che l'operazione è stata completata o si è verificato un errore.

Carica Controller:

[AcceptVerbs(HttpVerbs.Post)] 
     public ActionResult UploadCSV(HttpPostedFileBase uploadFile) 
     { 
      var filePath = string.Empty; 
      if (uploadFile.ContentLength <= 0) 
      { 
       return View(); 
      } 
       filePath = Path.Combine(Server.MapPath(this.UploadPath), "DeptartmentName",Path.GetFileName(uploadFile.FileName)); 
      if (new FileInfo(filePath).Exists) 
      { 
       ViewBag.ErrorMessage = 
        "The file currently exists on the server. Please rename the file you are trying to upload, delete the file from the server," + 
        "or contact IT if you are unsure of what to do."; 
       return View(); 
      } 
      else 
      { 
       uploadFile.SaveAs(filePath); 
       return RedirectToAction("UploadSuccess", new {fileName = uploadFile.FileName, processType = "sonar"}); 
      } 
     } 

[HttpGet] 
     public ActionResult UploadSuccess(string fileName, string processType) 
     { 
      ViewBag.FileName = fileName; 
      ViewBag.PType = processType; 
      return View(); 
     } 

Carica Successo HTML:

@{ 
    ViewBag.Title = "UploadSuccess"; 
} 

<h2>File was uploaded successfully</h2> 
<p>Your file was uploaded to the server and is now ready to be processed. To begin processing this file, click the "Process File" button below. 
</p> 
<button id="beginProcess" >Process File</button> 
<script type="text/javascript"> 
    $(function() { 
     $("#beginProcess").click(BeginProcess); 
     function BeginProcess() { 
      window.open("/SomeController/ImportProcessing/[email protected]&[email protected]", "ProcessStatusWin", "width=400, height=250, status=0, toolbar=0, scrollbars=0, resizable=0"); 
      window.location = "/Department/Import/Index"; 
     } 
    }); 
</script> 

Una volta che questa nuova finestra si apre, l'elaborazione dei file inizia. Gli aggiornamenti vengono recuperati da una classe FileProcessing personalizzata.

ImportProcessing Controller:

public ActionResult Index(string fileName, string type) 
     { 
      ViewBag.File = fileName; 
      ViewBag.PType = type; 
      switch (type) 
      { 
       case "somematch": 
        if (!_fileProcessors.ContainsKey(fileName)) _fileProcessors.Add(fileName, new SonarCsvProcessor(Path.Combine(Server.MapPath(this.UploadPath), "DepartmentName", fileName), true)); 
        break; 
       default: 
        break; 
      } 
      return PartialView(); 
     } 

ImportProcessing Indice:

@{ 
    ViewBag.Title = "File Processing Status"; 
} 
@Scripts.Render("~/Scripts/jquery-1.8.2.js") 

<div id="StatusWrapper"> 
    <div id="statusWrap"></div> 
</div> 
<script type="text/javascript"> 
    $(function() { 
     $.ajax({ 
      url: "GetStatusPage", 
      data: { fileName: "@ViewBag.File" }, 
      type: "GET", 
      success: StartStatusProcess, 
      error: function() { 
       $("#statusWrap").html("<h3>Unable to load status checker</h3>"); 
      } 
     }); 
     function StartStatusProcess(result) { 
      $("#statusWrap").html(result); 
      $.ajax({ 
       url: "StartProcessing", 
       data: { fileName: "@ViewBag.File" }, 
       type: "GET", 
       success: function (data) { 
        var messag = 'Processing complete!\n Added ' + data.CurrentRecord + ' of ' + data.TotalRecords + " records in " + data.ElapsedTime + " seconds"; 
        $("#statusWrap #message").html(messag); 
        $("#statusWrap #progressBar").attr({ value: 100, max: 100 }); 
        setTimeout(function() { 
         window.close(); 
        }, 5000); 
       }, 
       error: function (xhr, status) { 
        alert("Error processing file"); 
       } 
      }); 
     } 
    }); 
</script> 

Infine Status Checker html:

@{ 
    ViewBag.Title = "GetStatusPage"; 
} 
<h2>Current Processing Status</h2> 
    <h5>Processing: @ViewBag.File</h5> 
    <h5>Updated: <span id="processUpdated"></span></h5> 
    <span id="message"></span> 
    <br /> 
    <progress id="progressBar"></progress> 
<script type="text/javascript"> 
    $(function() { 
     var checker = undefined; 
     GetStatus(); 
     function GetStatus() { 
      if (checker == undefined) { 
       checker = setInterval(GetStatus, 3000); 
      } 
      $.ajax({ 
       url: "[email protected]", 
       type: "GET", 
       success: function (result) { 
        result = result || { 
         Available: false, 
         Status: { 
          TotalRecords: -1, 
          CurrentRecord: -1, 
          ElapsedTime: -1, 
          Message: "No status data returned" 
         } 
        }; 
        if (result.Available == true) { 
         $("#progressBar").attr({ max: result.Status.TotalRecords, value: result.Status.CurrentRecord }); 
         $("#processUpdated").text(result.Status.Updated); 
         $("#message").text(result.Status.Message); 
        } else { 
         clearInterval(checker); 
        } 

       }, 
       error: function() { 
        $("#statusWrap").html("<h3>Unable to load status checker</h3>"); 
        clearInterval(checker); 
       } 
      }); 
     } 
    }); 
</script> 
0

Solo un pensiero, ma è possibile collegare l'elaborazione dei file CSV e al termine di tale attività chiamare un altro metodo che fondamentalmente fornisce una finestra di dialogo modale o una sorta di avviso javascript sul lato client che consente all'utente di sapere che l'elaborazione ha completato.

Task.Factory.StartNew(() => ProcessCsvFile(fp)).ContinueWith((x) => NotifyUser()); 

o qualcosa del genere. Penso che alla fine vorrai guardare una sorta di threading perché non ha alcun senso che un utente rimanga bloccato guardando uno schermo mentre avviene una sorta di elaborazione lato server.

+0

grazie per l'input, ho intenzione di giocare con questo per un po 'e vedere cosa ne esce. Mi assicurerò di accettare questa risposta se funziona per me. – TNCodeMonkey

+0

Ho cercato in giro cercando di trovare il modo migliore per avvicinarmi usando questo metodo, ma ho capito che non era quello di cui avevo bisogno. Non rimedierebbe al fatto che il post rimarrà aperto fino a quando l'azione non sarà tornata, indipendentemente dal threading. Ho aggiunto una risposta in fondo con la mia soluzione. – TNCodeMonkey