WCF REST сервисы и UWP приложения

cdd4f1117c564c919ec1ef4f0a3ef1b5.jpg

Довольно частый вопрос, который возникает у тех кто пробует разрабатывать под UWP это «Как UWP приложению получить данные из базы данных SQL Server?». Напрямую данные получить нельзя. Работа с базами данных у UWP приложений требует настроенного REST сервиса.
Разработчики клиентских приложений как правило далеки от созданий серверных бэкендов, но им необходимо иметь хотя бы представление о сервисах.

Под катом описание того как создать локальный WCF REST сервис и получить от него данные приложением UWP. Сервис сможет получать данные из базы данных SQL Server, созданной в Azure (но аналогично можно получить данные и из любой локальной базы). Дополнительно, чтобы все не выглядело сильно банально, будет рассмотрена возможность размещения самого сервиса в Azure для работы с ним из все того же клиентского UWP приложения.

Создание REST сервиса


Для того чтобы тестировать наши приложения UWP создадим простой сервис

021854bcd31a4521b38541e754924228.PNG

Удаляем код примера, который будет создан для нас автоматически

Код, который удаляется
Из IService1.cs
  [OperationContract]
        string GetData(int value);

        [OperationContract]
        CompositeType GetDataUsingDataContract(CompositeType composite);

        // TODO: Add your service operations here

  // Use a data contract as illustrated in the sample below to add composite types to service operations.
    [DataContract]
    public class CompositeType
    {
        bool boolValue = true;
        string stringValue = "Hello ";

        [DataMember]
        public bool BoolValue
        {
            get { return boolValue; }
            set { boolValue = value; }
        }

        [DataMember]
        public string StringValue
        {
            get { return stringValue; }
            set { stringValue = value; }
        }
    }

Из Service1.svc.cs
       public string GetData(int value)
        {
            return string.Format("You entered: {0}", value);
        }

        public CompositeType GetDataUsingDataContract(CompositeType composite)
        {
            if (composite == null)
            {
                throw new ArgumentNullException("composite");
            }
            if (composite.BoolValue)
            {
                composite.StringValue += "Suffix";
            }
            return composite;
        }


Добавляем в IService1.cs следующую операцию контракта и код класса:
    [ServiceContract]
    public interface IService1
    {
        [WebGet(UriTemplate = "/GetScheduleJson",
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json,
        BodyStyle = WebMessageBodyStyle.Bare)]
        List GetScheduleJson();
    }

    [DataContract]
    public class Timetable
    {
        [DataMember]
        public int id { get; set; }

        [DataMember]
        public DateTime arrivaltime { get; set; }

        [DataMember]
        public Int16 busnumber { get; set; }

        [DataMember]
        public string busstation { get; set; }
    }

Поле arrivaltime можно было бы сделать типа TimeSpan, но с типом DateTime гораздо удобнее впоследствии работать в JSON. В операции контракта можно указать и формат WebMessageFormat.Xml. Сама операция помечена атрибутом WebGet, а значит возвращает результат. При необходимости только выполнить код можно пометить операцию WebInvoke.

А в Service1.svc.cs добавляем следующий код:

        public List GetScheduleJson()
        {
            return GetSchedule();
        }

        private List GetSchedule()
        {
            List Schedule = new List
          {
            new Timetable
            {
                id=1, arrivaltime=DateTime.Parse("12:05:00"), busnumber=5, busstation ="Березка"
            },
            new Timetable
            {
                id=2, arrivaltime =DateTime.Parse("12:10:00"), busnumber=5, busstation ="Детский мир"
            }
          };
            return Schedule;
        }

Упрощенно сконфигурируем Web.config. Добавим endpointBehavior в раздел behaviors:
      
        
          
        
      

И ниже в уже существующий код в тэг behavior добавим атрибут name со значением «servicebehavior»:
     
         
          
          
          
          
        
      

Теперь в корень тега system.serviceModel можем добавить сервис и endpoint:
    
      
      
      
    

Получаем готовый сервис.
Запустив отладку (при этом необходимо чтобы в Solution Explorer был выделен проект) и открыв в браузере (в моем случае порт 64870)
http://localhost:64870/Service1.svc/GetScheduleJson
получим результат в виде JSON:
[{«arrivaltime»:»\/Date (1487408400000+0300)\/», «busnumber»:5, «busstation»: «Березка», «id»:1},{«arrivaltime»:»\/Date (1487408700000+0300)\/», «busnumber»:5, «busstation»: «Детский мир», «id»:2}]

Если мы захотим возвращать данные отфильтрованные по какому-либо параметру, то можем изменить операцию на подобную:

        [WebGet(UriTemplate = "/GetScheduleJson/{id}",
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json,
        BodyStyle = WebMessageBodyStyle.Bare)]
        List GetScheduleJson(int id);

Теперь реализовав метод
List GetScheduleJson(int id)
мы получим результат зайдя по адресу
http://localhost:64870/Service1.svc/GetScheduleJson/1
В данном случае 1 — это параметр, передаваемый методу.

Создание клиентского UWP приложения


Получить данные из приложения UWP проще простого. Есть 2 варианта: использовать Windows.Web.Http.HttpClient или же System.Net.Http.HttpClient
Оба клиента могут быть использованы в UWP приложениях. Web чуть более новый (он вышел в 8.1), и он больше подходит для нативной разработки под UWP. Если же вы планируете использовать код в ASP.NET приложениях или в приложениях Xamarin под другие мобильные платформы, то вам лучше взять Net клиента. Кроме того на данный момент у Web клиента больше настроек и возможностей (например, возможность использования особого SSL сертификата для аутентификации).
Собственно, в .NET Core для приложений UWP, System.Net.Http это обертка над компонентом Windows.Web.Http. Но эта обертка поддерживает те же API, что и пространство System.Net.Http из .NET.
Далее два простых примера получения данных от сервиса:
var uri = new Uri("http://localhost:64870/service1.svc/GetScheduleJson");
            var client = new Windows.Web.Http.HttpClient();
            var json = await client.GetStringAsync(uri);

var uri = new Uri("http://localhost:64870/service1.svc/GetScheduleJson");
            System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
            System.Net.Http.HttpResponseMessage responseGet = await client.GetAsync(uri);
            string json = await responseGet.Content.ReadAsStringAsync();

Для того, чтобы десериализовать данные можно использовать NuGet пакет Newtonsoft.Json
В этом поможет следующий метод, возвращающий generic список:
        public static List DeserializeToList(string jsonString)
        {
            var array = Newtonsoft.Json.Linq.JArray.Parse(jsonString);

            List objectsList = new List();

            foreach (var item in array)
            {
                    objectsList.Add(item.ToObject());
            }
            return objectsList;
        }

Используя его получить результат из строки json просто:
List appsdata = DeserializeToList(json);

Конечно, необходимо добавить еще и код класса Timetable (точно такой же как и в приложении сервиса)

Создание базы данных SQL Server в Azure


Создать базу данных в Azure несложно. Нужно зайти на портал и заполнить следующие поля:

a797579ce7c24ffcb6494a300b370e9f.PNG

Останется только выбрать ценовую категорию. Цены начинаются от 5 USD за месяц. Эту сумму вполне себе покроет бонус, получаемый от бесплатной регистрации в Dev Essentials (25 USD дается каждый месяц в течение года). При регистрации необходимо привязывать карточку. Для подобных регистраций, как правило, создается дополнительная карточка, лимит которой можно регулировать.

Строка подключения ASP.NET (проверка подлинности SQL) к базе данных в таком случае будет:

Server=tcp:timetableserverok.database.windows.net,1433;Initial Catalog=timetabledb;Persist Security Info=False;User ID={your_username};Password={your_password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;

Ее можно посмотреть, зайдя в свойства базы данных на портале Azure.
Для того чтобы получить возможность доступа к базе данных с текущей машины необходимо добавить ее IP в список брандмауэра

18a5b6c0f84b45e2ba3dbc3aea720658.PNG

Редактирование возможно из окна Server Explorer Visual Studio

df0390fac4dc4bdc882abb70f2c60287.PNG

9a19182832b04703834d4e9983cba1ab.PNG

Создадим какую-нибудь таблицу

79e1a36879dd43faaf612612015c067e.PNG

И внесем любые тестовые данные.

Получение сервисом данных из базы SQL Server-а


Для того чтобы «вытянуть» данные из базы нам необходимо внести небольшие изменения в проект нашего сервиса.
Добавить два пространства имен:
using System.Data;
using System.Data.SqlClient;

Переменную содержащую текст строки подключения к базе SQL Server:
public string ConnectionString = "Server=tcp:timetableserverok.database.windows.net,1433;Initial Catalog=timetabledb;Persist Security Info=False;User ID=alexej;Password=ЗДЕСЬ_ПАРОЛЬ;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;";

Я использую только что созданную базу в Azure, но, как уже упоминал, можно подключаться к любым базам данных в том числе и локальным (строку подключения в таком случае, конечно, необходимо будет заменить).

Теперь, чтобы «вытянуть» данные из базы, необходимо изменить код метода GetSchedule на следующий:

         private List GetSchedule()
        {
            using (DataSet ds = new DataSet())
            {
                using (SqlConnection sqlCon = new SqlConnection(ConnectionString))
                {
                    try
                    {
                        sqlCon.Open();
                        string sqlStr = "select * from Timetable";
                        using (SqlDataAdapter sqlDa = new SqlDataAdapter(sqlStr, sqlCon))
                        {
                            sqlDa.Fill(ds);
                        }
                    }
                    catch
                    {
                        return null;
                    }
                    finally
                    {
                        sqlCon.Close();
                    }
                }

                List Schedule = new List();

                using (DataTable dt = ds.Tables[0])
                {
                    foreach (DataRow dr in dt.Rows)
                    {
                        Schedule.Add(new Timetable()
                        {
                            id = Convert.ToInt16((dr["ID"])),
                            arrivaltime = DateTime.Parse(dr["arrivaltime"].ToString()),
                            busnumber = Convert.ToInt16((dr["busnumber"] ?? 0)),
                            busstation = dr["busstation"].ToString()
                        });
                    }
                }
                return Schedule;
            }
        }

Создание облачного сервиса


Для того чтобы разместить сервис в Azure необходимо скачать и установить Azure SDK for .NET (приблизительно 450 Мб) и создать новый проект особого типа Cloud Service.

f3b6b31d42dd470ab6feb6e6a1609ae5.PNG

Выбираем роль и переименовываем на свой вкус

0497de136c3c456c8da10991603d5b51.PNG

В результате у нас будет создано два проекта: AzureCloudServiceTimetable и TimetableService
В второй (TimetableService) мы можем скопировать код из нашего локального сервиса. А именно — содержимое файлов IService1.cs, Service1.svc.cs, Web.config
После этого проект можно протестировать.
В файле Web.config перед публикацией можно сделать изменения. В теге


изменить значения на false

Для публикации на Azure необходимо создать пакеты. На проекте AzureCloudServiceTimetable нужно вызвать контекстное меню и выбрать Package.

44d3aaeeca4a414fb2fc19193f3a90fb.PNG

После окончания процесса будет открыта директория с пакетом и конфигурационным файлом.
Опубликовать на Azure можно с помощью веб интерфейса портала.
Заходим на портал. Выбираем пункт Облачные службы (классические), создаем новый и заполняем поля

8b84260505004753bf6c96929368d697.PNG

Необходимо установить 2 флажка: «Развернуть, даже если одна или несколько ролей содержат отдельный экземпляр» и «Запустить развертывание».
После развертывания и запуска (запуск может занять некоторое время) можно будет делать запрос по URI:
http://servicetimetable.cloudapp.net/Service1.svc/GetScheduleJson
Этот адрес можно использовать в приложении UWP
Подробнее о развертывании:
Создание и развертывание облачной службы

PS: Буду рад уточнениям, дополнениям и правкам.

Комментарии (2)

  • 1 марта 2017 в 10:53

    +1

    1. Какие преимущества у wcf-сервиса перед приложением Web API?
    2. Как-то длинно десериализуете json. Можно же же примерно так:
    List appsdata = JsonConvert.DeserializeObject(jsonString);
    • 1 марта 2017 в 11:13

      0

      1. Сравнение доступно здесь: WCF и ASP.NET Web API
      2. А если есть что-то невалидное?

© Habrahabr.ru