Показаны сообщения с ярлыком csv. Показать все сообщения
Показаны сообщения с ярлыком csv. Показать все сообщения

среда, 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);
         }

    }

}

Импорт csv файлов в Silverlight

Еще одной интересной задачей, с которой мне пришлось сталкнуться – это импорт данных из csv файлов с использованием Silverlight.

Сразу стоит отметить, что, поскольку Silverlight – это урезанная клиентская технология, то в нем нет никаких полезных классов для импорта csv.

Так что импорт будет происходить по следующей схеме:

1. Пользователь выбирает csv файл на своем жестком диске

2. Файл загружается на сервер

3. На сервере происходит обработка данных

4. Данные загружаются в базу

5. Информация об ошибках и другие сообщения возвращаются клиенту.

Попробуем разобраться с каждым из этапов.

Выбор файла:

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

Вот так будет выглядеть код страницы:

<navigation:Page
x:Class="Sample.Import"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
Style="{StaticResource PageStyle}" >
<Grid x:Name="LayoutRoot">
<ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}">
<StackPanel x:Name="ContentStackPanel" Style="{StaticResource ContentStackPanelStyle}">
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<TextBlock Text="File:"/>
<TextBox Name="txtFileName" Width="150" Margin="30,0,0,0" IsEnabled="False" Background="Gray"/>
<Button Name="btnFileUpload" Content="Browse" Margin="0,0,0,0" Height="23" Click="btnFileUpload_Click" />
</StackPanel>
<Button HorizontalAlignment="Left" Content="Import" Height="23" Name="btnImport" Width="75"
Margin="0,10,0,0" Click="btnImport_Click" />
</StackPanel>
</ScrollViewer>
</Grid>
</navigation:Page>

При нажатии на кнопку Browse нужно открыть диалоговое окно выбора файла и сохранить выбор.

        private FileInfo _file;


 


        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;
             }
         }


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



Почти все операции, вроде поключения к серверу, создания стрима для запроса и т.д., являются асинхронными, из-за этого почти все наши методы будут вызываться асинхронно через callback.



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



Код подключения к серверу будет выглядеть так:



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();


                }


            }


        }


private void DoImport()


        {


if (_file != null)


            {


                UploadFile(_file.Name, _file.OpenRead());


            }


        }


private void UploadFile(string fileName, Stream data)


        {


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);


            }


        }


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 btnImport_Click(object sender, System.Windows.RoutedEventArgs e)
         {
             if (_file != null)
             {
                 if (HtmlPage.Window.Confirm("Are you sure you want to import data?"))
                 {
                     DoImport();
                 }
             }
         }
 
 
         private void DoImport()
         {
             if (_file != null)
             {
                 UploadFile(_file.Name, _file.OpenRead());
             }
         }
 
         private void UploadFile(string fileName, Stream data)
         {
             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);
             }
 
         }
 
         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();
         }


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



        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 ReadFromStreamCallback(IAsyncResult asynchronousResult)
         {
             HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
             HttpWebResponse webResponse = (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);
 
             this.Dispatcher.BeginInvoke(new UpdateUI(ShowMessage));
         }
 
         public void ShowMessage()
         {
             HtmlPage.Window.Alert("Complete!");
         }


Весь код сейчас будет выглядеть вот так:



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();
 
         /// <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();
                 }
             }
         }
 
 
         private void DoImport()
         {
             if (_file != null)
             {
                 UploadFile(_file.Name, _file.OpenRead());
             }
         }
 
         private void UploadFile(string fileName, Stream data)
         {
             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));
         }
 
         public void ShowMessage()
         {
             HtmlPage.Window.Alert("Complete!");
         }
     }

}