Статьи

Практическое руководство по приложениям для обмена фотографиями с использованием Windows Phone и Azure

Предыдущий пост из этой серии назывался « Теория облачного приложения для обмена фотографиями с использованием Windows Phone и Azure» . Мы прошли через архитектуру простого приложения для обмена изображениями. Шаги с 1 по 5 в этом процессе включают в себя фотосъемку с помощью камеры и загрузку ее в хранилище BLOB-объектов. Чтобы следовать этой статье, вам необходимо установить Windows Phone SDK и Windows Azure SDK для Visual Studio .

Шаг 1. Создание приложения для телефона

Примечание. Рекомендуется запускать Visual Studio «от имени администратора» на протяжении всего курса. Хотя это не требуется для разработки приложения для телефона, оно требуется для работы со средой разработки Windows Azure. Также обратите внимание, что если Visual Studio работает как администратор, он также запустит эмулятор Windows Phone в этом режиме — если у вас уже запущен эмулятор, не забудьте закрыть его, прежде чем пытаться запустить приложение телефона из Visual Studio, работающей в этом режиме. ,

Начнем с телефонной части. Мы начнем с нового проекта Windows Phone и сделаем снимок с камеры. В Windows Phone есть концепция под названием «Задачи», разработанная для предоставления общих функций платформы с использованием стандартного, согласованного внешнего вида. Они разбиты на две категории: средства запуска, которые просто запускают определенные функции, такие как карты или поисковые приложения, и средства выбора, которые предназначены для возврата данных в приложение. В этом случае мы собираемся использовать CameraCaptureTask чтобы предложить пользователю сделать фотографию с помощью камеры, которая затем возвращается в приложение. Обратите внимание, что в Windows Phone 7.5 (Mango) у вас есть прямой доступ к каналу камеры, если вы хотите предоставить собственный пользовательский интерфейс для фотографирования или захвата видео.

Мы сохраним макет MainPage страницы приложения относительно простым, состоящим из элемента управления Image , в котором будет отображаться захваченный снимок, и двух Buttons : первая для запуска захвата и вторая для загрузки захваченного изображения в облако.

 <StackPanel> <Image x:Name="CaptureImage" Height="200" Width="200" /> <Button Content="Capture" Click="CaptureClick" /> <Button Content="Upload" Click="UploadClick" /> </StackPanel> 

В коде для MainPage нам нужно создать экземпляр CameraCaptureTask , назначить обработчик события для события Completed , а затем вызвать метод Show для задачи, когда пользователь нажимает кнопку Capture .

 private CameraCaptureTask camera = new CameraCaptureTask(); private string filename; public MainPage() { InitializeComponent(); camera.Completed += CameraCompleted; } private void CameraCompleted(object sender, PhotoResult e) { var bmp = PictureDecoder.DecodeJpeg(e.ChosenPhoto); filename = Guid.NewGuid() + ".jpg"; using (var file = IsolatedStorageFile.GetUserStoreForApplication().OpenFile(filename, FileMode.Create, FileAccess.Write)) { bmp.SaveJpeg(file, bmp.PixelWidth, bmp.PixelHeight, 0, 100); } CaptureImage.Source = bmp; } private void CaptureClick(object sender, RoutedEventArgs e) { camera.Show(); } private void UploadClick(object sender, RoutedEventArgs e) { } 

Когда CameraCaptureTask возвращается, мы берем возвращенный поток (e.ChosenPhoto) и декодируем его в изображение, которое одновременно сохраняется в IsolatedStorage (локальное хранилище для приложения) и отображается в элементе управления изображением CaptureImage . Если вы запустите этот код в эмуляторе, вы сможете использовать симуляцию камеры, чтобы вернуть изображение по умолчанию, которое будет отображаться в вашем приложении, как показано на правом изображении на рисунке 1.

Blob Storage Figure 1

фигура 1

Шаг 2. Создание облачного приложения

Следующим шагом является создание нашего облачного проекта, который можно добавить в то же решение, которое содержит ваше телефонное приложение. Щелкните правой кнопкой мыши узел Solution в Solution Explorer и выберите Add> New Project, а затем в диалоговом окне Add New Project выберите шаблон Windows Azure Project из узла Cloud. Дайте новому проекту новое имя, например «ImageSharingCloud», и нажмите «ОК» для продолжения. Затем вам будет предложено добавить один или несколько проектов Windows Azure на основе набора существующих шаблонов. В этом случае мы собираемся создать веб-роль службы WCF с именем ImageServices и ImageServices роль с именем ImageWorker . Добавьте их, выбрав роль и нажав стрелку вправо (см. Рисунок 2); затем вы можете переименовать их, чтобы их было легче идентифицировать в вашем решении.

Blob Storage Figure 2

фигура 2

Шаг 3. Создание подписи общего доступа

Чтобы загрузить фотографию из нашего приложения Windows Phone непосредственно в хранилище BLOB-объектов Windows Azure, нам необходимо знать один из двух ключей доступа, выданных через консоль управления Windows Azure для учетной записи хранения, в которую вы хотите загрузить. Эти ключи предназначены для использования на веб-сайтах или в службах, расположенных на сервере, а не для включения в клиентские приложения. Silverlight, независимо от того, используется ли он для создания настольных приложений или приложений для Windows Phone, является клиентской технологией, и вы не должны включать ключи доступа в эти приложения.

Windows Azure предоставляет альтернативную стратегию для загрузки содержимого в хранилище BLOB-объектов с использованием подписей общего доступа (SAS). Когда приложение хочет загрузить контент в хранилище BLOB-объектов, оно запрашивает SAS из службы; служба использует один из ключей доступа для генерации SAS, который возвращается вызывающему приложению; приложение использует SAS вместо ключа доступа при загрузке контента.

Мы собираемся работать с недавно созданным проектом ImageServices чтобы предоставить сервис, который может быть вызван нашим приложением Windows Phone для возврата SAS. Во-первых, вам нужно сообщить проекту ImageServices какую учетную запись хранения вы хотите использовать. Дважды щелкните узел «Свойства» для проекта ImageServices в обозревателе решений, чтобы открыть диалоговое окно «Свойства». На вкладке «Настройки» необходимо создать новую запись DataConnectionString с типом «Строка подключения». Нажатие на кнопку эллипсов откроет диалоговое окно Строка подключения учетной записи хранения, показанное на рисунке 3. Здесь мы собираемся использовать эмулятор хранения, который поставляется с Windows Azure SDK. Тем не менее, когда вы в конечном итоге перейдете к публикации своего приложения, вам нужно будет изменить его, чтобы использовать имя учетной записи и ключ учетной записи хранения.

Blob Storage Figure 3

Рисунок 3

Следующим шагом является создание службы, которая будет возвращать SAS приложению. Щелкните правой кнопкой ImageServices проект ImageServices в обозревателе решений и выберите «Добавить»> «Новый элемент», а затем выберите шаблон службы WCF в диалоговом окне «Добавить новый элемент». Дайте новому сервису имя UploadService.svc и нажмите «Добавить», чтобы создать новый сервис. Измените интерфейс службы, чтобы включить метод с именем UploadUriWithSharedAccessSignature следующим образом:

 [ServiceContract] public interface IUploadService { [OperationContract] Uri UploadUriWithSharedAccessSignature(string userId); } 

Этот метод принимает один параметр, который будет использоваться для разделения загрузок от разных пользователей в хранилище BLOB-объектов. Метод возвращает Uri, в комплекте с подписью общего доступа, контейнера хранилища BLOB-объектов, в который приложение может загружать файлы. Следующий код обеспечивает реализацию этого метода вместе с закрытым методом InitializeStorage , который используется для загрузки информации о конфигурации облака и создания экземпляра класса CloudBlobClient который является классом-оболочкой для работы с хранилищем BLOB-объектов.

 public class UploadService : IUploadService { private const SharedAccessPermissions ContainerSharedAccessPermissions = SharedAccessPermissions.Write | SharedAccessPermissions.Delete | SharedAccessPermissions.List; private CloudBlobClient cloudBlobClient; private void InitializeStorage() { CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) => { configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)); }); var storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString"); cloudBlobClient = storageAccount.CreateCloudBlobClient(); } public Uri UploadUriWithSharedAccessSignature(string userId) { try { InitializeStorage(); var container = this.cloudBlobClient.GetContainerReference("imageservice"); container.CreateIfNotExist(); var sas = container.GetSharedAccessSignature(new SharedAccessPolicy() { Permissions = ContainerSharedAccessPermissions, SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromMinutes(5) }); var uriBuilder = new UriBuilder(container.Uri + "/" + userId) { Query = sas.TrimStart('?') }; return uriBuilder.Uri; } catch (Exception exception) { throw new WebFaultException<string>(exception.Message, HttpStatusCode.InternalServerError); } } } 

В этом случае SharedAccessSignature создается с 5-минутным SharedAccessSignature действия. Это означает, что наше приложение для Windows Phone сможет загружать файлы только в этом окне. Это значительно снижает риск раскрытия ключа доступа к приложению Windows Phone.

Шаг 4: Получение SharedAccessSignature

Теперь, когда у нас есть служба, которая генерирует подпись общего доступа, нам нужно вызывать ее из приложения Windows Phone. Для этого нам нужно добавить ссылку на UploadService в проект Windows Phone. Убедитесь, что вы создали решение и можете перейти к UploadService (щелкните правой кнопкой мыши на UploadService.svc и выберите «Просмотр в браузере»). Щелкните правой кнопкой мыши проект Windows Phone и выберите «Добавить ссылку на службу». В диалоговом окне Add Service Reference, показанном на рисунке 4, нажмите кнопку Discover. Как только службы обнаружены, выберите UploadService , укажите пространство имен, в этом случае мы будем использовать UploadService и нажмите OK.

Blob Storage Figure 4

Рисунок 4

Это не только создаст необходимые прокси-классы для UploadService вызовов к UploadService , но также создаст и добавит в проект Windows Phone файл с именем ServiceReferences.ClientConfig . В этом файле вы увидите информацию о конфигурации конечной точки UploadService . Вам нужно будет изменить это, чтобы изменить указатель на сервер разработки Visual Studio (по умолчанию) на эмулятор вычислений Windows Azure. Обычно это включает в себя изменение адреса конечной точки на http://127.0.0.1:81/UploadService.svc; хотя фактический номер порта может отличаться, проверьте, на каком номере порта работает ваша служба, когда вы запускаете проект ImageSharingCloud .

Шаг 5: Завершение загрузки

Последний шаг — вызов метода UploadUriWithSharedAccessSignature для извлечения SAS, который затем используется для загрузки фотографии в хранилище BLOB-объектов. Следующий код начинается с вызова этого метода, затем, когда ответ получен, фотография загружается в хранилище CloudBlobUploader класса CloudBlobUploader . Полный код этого класса доступен в конце этого поста.

 private void UploadClick(object sender, RoutedEventArgs e) { var client = new UploadServiceClient(); client.UploadUriWithSharedAccessSignatureCompleted += UploadUriWithSharedAccessSignatureCompleted; client.UploadUriWithSharedAccessSignatureAsync(Guid.NewGuid().ToString()); } private void UploadUriWithSharedAccessSignatureCompleted(object sender, UploadUriWithSharedAccessSignatureCompletedEventArgs e) { if (e.Error == null) { // Determine upload path - Add filename to container path var builder = new UriBuilder(e.Result); builder.Path = builder.Path + "/" + filename; var blobUrl = builder.Uri; // Open the image file from isolates storage to read from IsolatedStorageFileStream file = IsolatedStorageFile.GetUserStoreForApplication().OpenFile(filename, FileMode.Open, FileAccess.Read); // Create the uploader and kick off the uploader var uploader = new CloudBlobUploader(file, blobUrl.AbsoluteUri); uploader.UploadFinished += (s, args) => { this.Dispatcher.BeginInvoke(() => { MessageBox.Show("Upload complete!"); }); }; uploader.StartUpload(); } } 

Чтобы запустить это приложение, вам нужно запустить как проект ImageSharingCloud проект ImageSharing Windows Phone. Сделайте снимок, нажав кнопку «Захват», затем нажмите кнопку «Загрузить». Если это успешно, вы должны увидеть сообщение о том, что загрузка завершена. На этом этапе вы можете просмотреть эмулятор хранилища Windows Azure и проверить загруженный образ. Следующий код — это класс CloudBlobUploader, который используется на последнем шаге этого руководства. Это можно использовать в любом приложении для загрузки содержимого в хранилище BLOB-объектов.

 public class CloudBlobUploader { private const long ChunkSize = 4194304; private readonly IList<string> blockIds = new List<string>(); private readonly long dataLength; private readonly Stream fileStream; private readonly string uploadUrl; private readonly bool useBlocks; private string currentBlockId; private long dataSent; public CloudBlobUploader(Stream fileStream, string uploadUrl) //, IStorageCredentials credentials) { this.fileStream = fileStream; this.uploadUrl = uploadUrl; // this.StorageCredentials = credentials; dataLength = this.fileStream.Length; dataSent = 0; // Upload the blob in smaller blocks if it's a "big" file. useBlocks = (dataLength - dataSent) > ChunkSize; } public event EventHandler<ParameterEventArgs<bool>> UploadFinished; public event EventHandler<ParameterEventArgs<double>> UploadProgress; //public IStorageCredentials StorageCredentials { get; set; } public void StartUpload() { if (uploadUrl == null) { RaiseUploadFinished(false); } var uriBuilder = new UriBuilder(uploadUrl); // Set a timeout query string parameter. uriBuilder.Query = string.Format( CultureInfo.InvariantCulture, "{0}{1}", uriBuilder.Query.TrimStart('?'), string.IsNullOrEmpty(uriBuilder.Query) ? "timeout=10000" : "&timeout=10000"); if (useBlocks) { // Encode the block name and add it to the query string. currentBlockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())); uriBuilder.Query = string.Format( CultureInfo.InvariantCulture, "{0}&comp=block&blockid={1}", uriBuilder.Query.TrimStart('?'), currentBlockId); } // With or without using blocks, we'll make a PUT request with the data. var request = (HttpWebRequest) WebRequestCreator.ClientHttp.Create(uriBuilder.Uri); request.Method = "PUT"; request.BeginGetRequestStream(WriteToStreamCallback, request); } private void WriteToStreamCallback(IAsyncResult asynchronousResult) { try { var request = (HttpWebRequest) asynchronousResult.AsyncState; Stream requestStream = request.EndGetRequestStream(asynchronousResult); var buffer = new byte[4096]; int bytesRead = 0; int tempTotal = 0; fileStream.Position = dataSent; while (((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) && (tempTotal + bytesRead < ChunkSize)) { requestStream.Write(buffer, 0, bytesRead); requestStream.Flush(); dataSent += bytesRead; tempTotal += bytesRead; } requestStream.Close(); request.BeginGetResponse(ReadHttpResponseCallback, request); } catch (Exception exception) { RaiseUploadFinished(false); } } private void ReadHttpResponseCallback(IAsyncResult asynchronousResult) { try { var request = (HttpWebRequest) asynchronousResult.AsyncState; var response = (HttpWebResponse) request.EndGetResponse(asynchronousResult); if (useBlocks) { blockIds.Add(currentBlockId); } RaiseUploadProgress(100.0*dataSent/dataLength); // If there is more data, send another request. if (dataSent < dataLength) { StartUpload(); } else { fileStream.Close(); fileStream.Dispose(); if (useBlocks) { // Commit the blocks into the blob. PutBlockList(); } else { RaiseUploadFinished(true); } } } catch (Exception exception) { RaiseUploadFinished(false); } } private void PutBlockList() { if (uploadUrl == null) { RaiseUploadFinished(false); } var uriBuilder = new UriBuilder(uploadUrl); uriBuilder.Query = string.Format( CultureInfo.InvariantCulture, "{0}{1}", uriBuilder.Query.TrimStart('?'), string.IsNullOrEmpty(uriBuilder.Query) ? "comp=blocklist" : "&comp=blocklist"); var request = (HttpWebRequest) WebRequestCreator.ClientHttp.Create(uriBuilder.Uri); request.Method = "PUT"; // x-ms-version is required for put block list request.Headers["x-ms-version"] = "2009-09-19"; request.BeginGetRequestStream(BlockListWriteToStreamCallback, request); } private void BlockListWriteToStreamCallback(IAsyncResult asynchronousResult) { try { var request = (HttpWebRequest) asynchronousResult.AsyncState; Stream requestStream = request.EndGetRequestStream(asynchronousResult); var document = new XDocument(new XElement("BlockList", from blockId in blockIds select new XElement("Uncommitted", blockId))); XmlWriter writer = XmlWriter.Create(requestStream, new XmlWriterSettings {Encoding = Encoding.UTF8}); long length = 0L; document.Save(writer); writer.Flush(); length = requestStream.Length; requestStream.Close(); request.BeginGetResponse(BlockListReadHttpResponseCallback, request); } catch (Exception exception) { RaiseUploadFinished(false); } } private void BlockListReadHttpResponseCallback(IAsyncResult asynchronousResult) { try { var request = (HttpWebRequest) asynchronousResult.AsyncState; var response = (HttpWebResponse) request.EndGetResponse(asynchronousResult); RaiseUploadFinished(true); } catch (Exception exception) { RaiseUploadFinished(false); } } private void RaiseUploadProgress(double progress) { if (UploadProgress != null) { UploadProgress(this, progress); } } private void RaiseUploadFinished(bool response) { if (UploadFinished != null) { UploadFinished(this, response); } } } public class ParameterEventArgs<T> : EventArgs { public ParameterEventArgs(T parameter) { Parameter1 = parameter; } public T Parameter1 { get; set; } /// <summary> /// Converts the parameter into a ParameterEventArgs /// </summary> public static implicit operator ParameterEventArgs<T>(T parameter) { return new ParameterEventArgs<T>(parameter); } }