среда, 25 августа 2010 г.

Импорт csv файлов в Silverlight. Часть 2.

Мы остановились на том, что создали код загрузки данных на сервер. Теперь нужно подготовить серверный обработчик, который будет сохранять файл, обрабатывать его и возращать какой-то ответ Silverlight приложению.

Создаем новый HttpHandler, называем его fileupload.ashx.

В нем напишем код для сохранения файла на сервер.

    public class fileupload : IHttpHandler
     {
 
         public void ProcessRequest(HttpContext context)
         {
             try
             {


                     string filename = context.Request.QueryString["filename"].ToString();
 
                         string serverFolderName = context.Server.MapPath("~/App_Data/");
                         string serverFileName = Guid.NewGuid().ToString().Replace('-', 'x') + ".csv";// +filename;
 
                         using (FileStream fs = File.Create(serverFolderName + serverFileName))
                         {
                             SaveFile(context.Request.InputStream, fs);
                         }
 
                         LoadFileToDatabase(context.Server.MapPath("~/App_Data/"), serverFileName);


                    context.Response.Write("Success");
             }
             catch (Exception ex)
             {
                 context.Response.Write("Error while import");
             }
         }
 
         private void LoadFileToDatabase(string folderName, string fileName)
         {


         }
 
         private void SaveFile(Stream stream, FileStream fs)
         {
             byte[] buffer = new byte[4096];
             int bytesRead;
             while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
             {
                 fs.Write(buffer, 0, bytesRead);
             }
         }
 
         public bool IsReusable
         {
             get
             {
                 return false;
             }
         }
 
     }


Этот код просто сохраняет файл со случайным именем в папку App_Data (кстати, нужно потом не забыть его стереть).



Нам еще нужно не забыть проверить права пользователя, может ли он осуществлять импорт? Это достаточно стандартный сценарий. Самое просто – это передать информацию о пользователе через строку запроса.



Теперь вспомним, что Silverlight – это клиентская технология, так что пользователь может легко обмануть сервер, и подсунуть ему неправильную информацию о себе (можно это сделать и более простым способом, хотя сейчас это не слишком важно).



Так что воспользуемся средствами Silverlight и Ria Services. Как мы помним, у нас есть специальный AuthenticationService для этих целей.



Модифицируем наш код.



Сначала нужно проверить, что пользователь аутентифицирован, а затем предположим, что у нас есть некоторая роль Администратора, и только Администратор может делать импорт файлов.



Предположим, что



using System;

using System.Web;

using System.IO;

using System.Data.OleDb;

using System.Data;

using System.Xml.Serialization;

using System.Runtime.Serialization;
 
 

namespace Sample.Web

{
     public class fileupload : IHttpHandler
     {
 
         public void ProcessRequest(HttpContext context)
         {
             try
             {
                 var serv = new Sample.Web.AuthenticationService();
                 var user = serv.GetUser();
 
                 if (user != null && user.IsAuthenticated)
                 {
                     string filename = context.Request.QueryString["filename"].ToString();
 
                     // try check rights
 
                     if (user.IsAdmin)
                     {
                         string serverFolderName = context.Server.MapPath("~/App_Data/");
                         string serverFileName = Guid.NewGuid().ToString().Replace('-', 'x') + ".csv";// +filename;
 
                         using (FileStream fs = File.Create(serverFolderName + serverFileName))
                         {
                             SaveFile(context.Request.InputStream, fs);
                         }
 
                         LoadFileToDatabase(context.Server.MapPath("~/App_Data/"), serverFileName);


                    context.Response.Write("Success");


                     }
                     else
                     {
                         throw new Exception("Access denied. No permissions");
                     }
                 }
                 else
                 {
                     throw new Exception("Access denied. User not authenticated");
                 }
             }
             catch (Exception ex)
             {
                 context.Response.Write("Error while import");
             }
         }
 
         private void LoadFileToDatabase(string folderName, string fileName)
         {


         }
 
         private void SaveFile(Stream stream, FileStream fs)
         {
             byte[] buffer = new byte[4096];
             int bytesRead;
             while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
             {
                 fs.Write(buffer, 0, bytesRead);
             }
         }
 
         public bool IsReusable
         {
             get
             {
                 return false;
             }
         }
 
     }

}


Теперь нам нужно разобрать csv файл с данными. Для этого я использую Microsoft.Jet драйвер. Стоит учитывать некоторый особенности работы с Microsoft.Jet.



Microsoft.Jet умеет читать текстовые и csv файлы.



CSV файлы могут включать в себя заколовки колонок, также нужно указать в строке подключения HDR=Yes, если в первой строке содержатся заголовки.



Для csv файлов можно задать символ для разделителя. Можно задать этот символ в строке подключения, например так FTM=Delimited(,). Правда это не работает, во всяком случае, у меня.



Для того, чтобы разделительно был правильно установлен я меняю следующий ключ реестра:



HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Text\Format



Пожалуй, наиболее интересным фактом является то, что Microsoft.Jet не должен работать на 64-битных операционных системах (забавно).



Далее, оказывается, что Microsoft.Jet все же работает на 64-битных операционных системах в режиме Wow6432. Для этого нужно сделать какие-то хитрые операции по импорту dll в реестр, затем найти в реесте следующий ключ и заменить его на нужный.



HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Jet\4.0\Engines\Text\Format



Код импорта будем выглядеть так:



        private void LoadFileToDatabase(string folderName, string fileName)
         {
             // change registry key at: (set to ,)
             // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Text\Format
             using (OleDbConnection connection = new OleDbConnection(string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=\"text;HDR=Yes;FMT=Delimited(,)\"", folderName)))
             {
                 connection.Open();
                 DataSet ds = new DataSet();
                 using (OleDbDataAdapter adapter = new OleDbDataAdapter(string.Format("select * from {0}", fileName), connection))
                 {
                     adapter.Fill(ds);
                 }
 
                 DataTable table = ds.Tables[0];
 
                 DoImport(table);
             }
         }


Весь, получившийся код обработчика:



using System;

using System.Web;

using System.IO;

using System.Data.OleDb;

using System.Data;

using System.Xml.Serialization;

using System.Runtime.Serialization;
 
 

namespace Sample.Web

{
     public class fileupload : IHttpHandler
     {
 
         public void ProcessRequest(HttpContext context)
         {
             try
             {
                 var serv = new Sample.Web.AuthenticationService();
                 var user = serv.GetUser();
 
                 if (user != null && user.IsAuthenticated)
                 {
                     string filename = context.Request.QueryString["filename"].ToString();
 
                     // try check rights
 
                     if (user.IsAdmin)
                     {
                         string serverFolderName = context.Server.MapPath("~/App_Data/");
                         string serverFileName = Guid.NewGuid().ToString().Replace('-', 'x') + ".csv";// +filename;
 
                         using (FileStream fs = File.Create(serverFolderName + serverFileName))
                         {
                             SaveFile(context.Request.InputStream, fs);
                         }
 
                         LoadFileToDatabase(context.Server.MapPath("~/App_Data/"), serverFileName); context.Response.Write("Success");
                     }
                     else
                     {
                         throw new Exception("Access denied. No permissions");
                     }
                 }
                 else
                 {
                     throw new Exception("Access denied. User not authenticated");
                 }
             }
             catch (Exception ex)
             {
                 context.Response.Write("Error while import");
             }
         }
 
         private void LoadFileToDatabase(string folderName, string fileName)
         {
             // change registry key at: (set to ,)
             // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Text\Format
             using (OleDbConnection connection = new OleDbConnection(string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=\"text;HDR=Yes;FMT=Delimited(,)\"", folderName)))
             {
                 connection.Open();
                 DataSet ds = new DataSet();
                 using (OleDbDataAdapter adapter = new OleDbDataAdapter(string.Format("select * from {0}", fileName), connection))
                 {
                     adapter.Fill(ds);
                 }
 
                 DataTable table = ds.Tables[0];
 
                 DoImport(table);
             }
         }
 
         private void SaveFile(Stream stream, FileStream fs)
         {
             byte[] buffer = new byte[4096];
             int bytesRead;
             while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
             {
                 fs.Write(buffer, 0, bytesRead);
             }
         }
 
         public bool IsReusable
         {
             get
             {
                 return false;
             }
         }
 
     }

}


Теперь можно вернуться к коду Silverlight страницы и сделать вот что: в зависимости в результата импорта выводить разные сообщения. По сути нужно просто выводить на экран то, что получено в респонсе.



Делается это примерно так:



namespace Sample

{
     using System.Windows.Controls;
     using System.Windows.Navigation;
     using System;
     using System.Collections.Generic;
     using System.Windows.Browser;
     using System.Net;
     using System.IO;
     using System.Xml.Serialization;
     using System.Runtime.Serialization;
     /// <summary>
     /// <see cref="Page"/> class to present information about the current application.
     /// </summary>
     public partial class Import : Page
     {
         private FileInfo _file;
         public delegate void UpdateUI(string msg);
 

         /// <summary>
         /// Creates a new instance of the <see cref="About"/> class.
         /// </summary>
         public Import()
         {
             InitializeComponent();
         }
 
         /// <summary>
         /// Executes when the user navigates to this page.
         /// </summary>
         protected override void OnNavigatedTo(NavigationEventArgs e)
         {
         }
 
         private void btnFileUpload_Click(object sender, System.Windows.RoutedEventArgs e)
         {
             OpenFileDialog openFile = new OpenFileDialog();
             openFile.Multiselect = false;
             openFile.Filter = "CSV files|*.csv";
             var result = openFile.ShowDialog();
             if (result.HasValue && result.Value == true)
             {
                 var file = openFile.File;
                 txtFileName.Text = file.Name;
                 _file = file;
             }
         }
 
 
         private void btnImport_Click(object sender, System.Windows.RoutedEventArgs e)
         {
             if (_file != null)
             {
                 if (HtmlPage.Window.Confirm("Are you sure you want to import data?"))
                 {
                     DoImport(false);
                 }
             }
         }
 
 
         private void DoImport(bool ignoreWarnings)
         {
             if (_file != null)
             {
                 UploadFile(_file.Name, _file.OpenRead(), ignoreWarnings);
             }
         }
 
         private void UploadFile(string fileName, Stream data, bool ignoreWarnings)
         {
             var uri = HtmlPage.Document.DocumentUri;
 
             var pathElements = uri.AbsolutePath.Split('/');
             var path = uri.AbsolutePath.Replace(pathElements[pathElements.Length - 1], string.Empty);
             UriBuilder ub = new UriBuilder(uri.Scheme, uri.Host, uri.Port, path + "fileupload.ashx");
             ub.Query = string.Format("filename={0}", fileName);
 
             var request = HttpWebRequest.Create(ub.Uri) as HttpWebRequest;
             request.Method = "POST";
             request.BeginGetRequestStream(WriteToStreamCallback, request);
 
         }
 
         private void WriteToStreamCallback(IAsyncResult asynchronousResult)
         {
             var request = asynchronousResult.AsyncState as HttpWebRequest;
             Stream requestStream = request.EndGetRequestStream(asynchronousResult);
 
             if (_file != null)
             {
                 PushData(_file.OpenRead(), requestStream);
             }
 
             request.BeginGetResponse(ReadFromStreamCallback, request);
         }
 
         private void PushData(Stream input, Stream output)
         {
             byte[] buffer = new byte[4096];
             int bytesRead;
 
             while ((bytesRead = input.Read(buffer, 0, buffer.Length)) != 0)
             {
                 output.Write(buffer, 0, bytesRead);
             }
 
             input.Close();
             output.Close();
         }
 
         private void ReadFromStreamCallback(IAsyncResult asynchronousResult)
         {
             HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
             HttpWebResponse webResponse = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);
 

            this.Dispatcher.BeginInvoke(new UpdateUI(ShowMessage), webResponse.GetResponseStream().ToString());

        }
 

        public void ShowMessage(string msg)
         {
             HtmlPage.Window.Alert(msg);
         }

    }

}

Комментариев нет:

Отправить комментарий