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

Сериализация в Silverlight.

Напомню, что ранее мы создавали код для импорта данных из csv файлов для Silvelight приложения. Файлы обрабатывались на сервере, а клиент мог получить лишь простой ответ вроде «Успех» или «Неудача». Конечно, это недостаточно хороший вариант. Необходимо дать пользователю возможность понять свои ошибки и исправить их. После этого пользователь сможет заново отправить файл для импорта.

Получается, что нам нужно передать сложный объект из HttpHandler в Silverlight приложение. Первое, что приходит в голову – это использование сериализации. Silverlight поддерживает класс XmlSerializer, но почему-то он не работает (или у меня просто не получилось его правильно настроить). С другой стороны, путем поиска по форумам в интернете мне удалось обнаружить, что Silvelight поддерживает DataContract сериализации, которая, по всей, видимости также является XML сериализацией, хотя, конечно, нам не особенно важен формат передачи данных (пока их не слишком много).

Для того, чтобы использовать сериализацию и HttpHandler и Silverlight приложение должны знать тип передаваемого объекта. Для этого классы объявляются в файле с расширением .shared.cs. Необходимо учитывать, что Silvelight не является полноценным .NET приложением, из-за этого некоторые возможности вроде лямбда-выражений в нем отсутствуют, соответственно расшаренный файл не должен их содержать.

Также нужно учитывать некоторые требования к классу, который должен быть сериализован.

Класс должен быть описан как public и иметь атрибут [DataContract].

Класс должен иметь конструктор без параметров.

Все передаваемые поля класса должны быть описаны как public и иметь атрибут [DataMember].

Если класс содержит переменные пользовательских типов, то эти типы должны удовлетворять этим же требованиям.

Если тип некоторых полей задан как перечисление, то перечиследение должно быть описано с атрибутом [DataContract], а каждый из элементов пересчисления должен иметь атрибут [EnumMember].

Возможно, есть и другие требования.

Опишем простой класс, который будем использовать для передачи сообщений.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Runtime.Serialization;
 

namespace AppName.BLL

{
     [DataContract]
     public class ImportResult
     {
         [DataMember]
         public EImportResult Result;
 
         public bool IsSuccess
         {
             get
             {
                 return Result == EImportResult.Success;
             }
         }
 
         [DataMember]
         public List<ImportNotificationItem> Notifications;
 
         public ImportResult()
         {
             Result = EImportResult.Fail;
             Notifications = new List<ImportNotificationItem>();
         }
 
         public ImportResult(EImportResult result)
         {
             Result = result;
             Notifications = new List<ImportNotificationItem>();
         }
 
         public IEnumerable<ImportNotificationItem> GetNotifications()
         {
             return Notifications;
         }
 
         public void AddNotification(ImportNotificationItem item)
         {
             if (item.Type == EImportNotificationType.Error)
             {
                 Result = EImportResult.Fail;
             }
             else
             {
                 if (Result == EImportResult.Success)
                 {
                     Result = EImportResult.SuccessWithWarnings;
                 }
             }
 
             // next lines of code does
             // Notifications.Find(p => p.Message == item.Message && oldNotification.ColumnName == item.ColumnName);
 
             ImportNotificationItem notification = null;
             foreach (var oldNotification in Notifications)
             {
                 if (oldNotification.Message == item.Message && oldNotification.ColumnName == item.ColumnName)
                 {
                     notification = oldNotification;
                     break;
                 }
             }
 
             if (notification != null)
             {
                 if (item.Rows != null)
                 {
                     foreach (var row in item.Rows)
                     {
                         if (!notification.Rows.Contains(row))
                         {
                             notification.Rows.Add(row);
                         }
                     }
                 }
                 else
                 {
                     item.Rows = notification.Rows;
                 }
                
             }
             else
             {
                 Notifications.Add(item);
             }
         }
 
         internal void RemoveWarnings()
         {
             // next code does the same as
             // Notifications.RemoveAll(p => p.Type == EImportNotificationType.Warning);
             // limitation of SilverLight
             int i = 0;
             while (i < Notifications.Count)
             {
                 if (Notifications[i].Type == EImportNotificationType.Warning)
                 {
                     Notifications.RemoveAt(i);
                 }
                 else
                 {
                     i++;
                 }
             }
 
 
             if (Notifications.Count < 1)
             {
                 Result = EImportResult.Success;
             }
         }
     }
 
     [DataContract]
     public class ImportNotificationItem
     {
         [DataMember]
         public EImportNotificationType Type { get; set; }
         [DataMember]
         public string Message { get; set; }
         [DataMember]
         public string Advise { get; set; }
         [DataMember]
         public string ColumnName { get; set; }
         [DataMember]
         public List<int> Rows { get; set; }
 
         public string RowString
         {
             get
             {
                 if (Rows == null)
                 {
                     return string.Empty;
                 }
                 StringBuilder sb = new StringBuilder();
                 foreach (var row in Rows)
                 {
                     if (sb.Length > 0)
                     {
                         sb.Append(", ");
                     }
 
                     sb.Append(row.ToString());
                 }
                 return sb.ToString();
             }
         }
 
         public ImportNotificationItem(EImportNotificationType type, string message,
             string advise, string columnName, List<int> rows)
         {
             Type = type;
             Message = message;
             Advise = advise;
             ColumnName = columnName;
             Rows = rows;
         }
     }
 
     [DataContract]
     public enum EImportNotificationType
     {
         [EnumMember]
         Error,
         [EnumMember]
         Warning,
         [EnumMember]
         Message
     }
 
     [DataContract]
     public enum EImportResult
     {
         [EnumMember]
         Success,
         [EnumMember]
         Fail,
         [EnumMember]
         SuccessWithWarnings,
         [EnumMember]
         PostMessages
     }

}


Теперь предположим на наш метод DoImport самостоятельно каким-то образом заполняет экземпляр типа ImportResult, а нам нужно только передать его на клиентскую сторону.



Делается это в две строки кода. Нужно создать сериализатор и указать тип сериализуемого объекта. После этого сериализуем объект в поток ответа (response stream).



Полный код обработчика имеет следующий вид:



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);
                         }
 
                         var result = LoadFileToDatabase(context.Server.MapPath("~/App_Data/"), serverFileName); context.Response.Write("Success");


 




                        DataContractSerializer ss = new DataContractSerializer(result.GetType());
 
                         ss.WriteObject(context.Response.OutputStream, result);



                     }
                     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 ImportResult 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];
 
                 return 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;
             }
         }
 
     }

}


На клиенте нужно получить и распаковать объект ImportResult. Для этого создается сериализоватор, у которого имеется метод ReadObject, который нужно применить к входному потоку.



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



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



 

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

            DataContractSerializer s = new DataContractSerializer(typeof(ImportResult));
             var result = s.ReadObject(webResponse.GetResponseStream());

 
             this.Dispatcher.BeginInvoke(new UpdateUI(ShowMessage), result);
         }


        public void ShowMessage(ImportResult result)
         {
             HtmlPage.Window.Alert(result.IsSuccess ? "Success" : "Error while import");
 
             dgErrors.ItemsSource = result.GetNotifications();
 
             if (result.Result == EImportResult.SuccessWithWarnings)
             {
                 btnImport.Visibility = System.Windows.Visibility.Collapsed;
                 btnForceImport.Visibility = System.Windows.Visibility.Visible;
             }
         }




    }

}

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

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