Еще одной интересной задачей, с которой мне пришлось сталкнуться – это импорт данных из 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!");
}
}
}
Комментариев нет:
Отправить комментарий