Довольно частый вопрос, который возникает у тех кто пробует разрабатывать под UWP это «Как UWP приложению получить данные из базы данных SQL Server?». Напрямую данные получить нельзя. Работа с базами данных у UWP приложений требует настроенного REST-сервиса.
Разработчики клиентских приложений как правило далеки от созданий серверных бэкендов, но им необходимо иметь хотя бы представление о сервисах.
Под катом описание того как создать локальный WCF REST сервис и получить от него данные приложением UWP. Сервис сможет получать данные из базы данных SQL Server, созданной в Azure (но аналогично можно получить данные и из любой локальной базы). Дополнительно, чтобы все не выглядело сильно банально, будет рассмотрена возможность размещения самого сервиса в Azure для работы с ним из все того же клиентского UWP приложения.
Создание REST сервиса
Для того чтобы тестировать наши приложения UWP создадим простой сервис
Удаляем код примера, который будет создан для нас автоматически
[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<Timetable> 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<Timetable> GetScheduleJson()
{
return GetSchedule();
}
private List<Timetable> GetSchedule()
{
List<Timetable> Schedule = new List<Timetable>
{
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:
<endpointBehaviors>
<behavior name="restBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
И ниже в уже существующий код в тэг behavior добавим атрибут name со значением «servicebehavior»:
<serviceBehaviors>
<behavior name="servicebehavior">
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
Теперь в корень тега system.serviceModel можем добавить сервис и endpoint:
<services>
<service name ="ServiceForUWP.Service1" behaviorConfiguration ="servicebehavior" >
<endpoint name ="RESTEndPoint" contract ="ServiceForUWP.IService1" binding ="webHttpBinding"
address ="" behaviorConfiguration ="restBehavior"/>
</service>
</services>
Получаем готовый сервис.
Запустив отладку (при этом необходимо чтобы в 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<Timetable> GetScheduleJson(int id);
Теперь реализовав метод
List<Timetable> 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<T> DeserializeToList<T>(string jsonString)
{
var array = Newtonsoft.Json.Linq.JArray.Parse(jsonString);
List<T> objectsList = new List<T>();
foreach (var item in array)
{
objectsList.Add(item.ToObject<T>());
}
return objectsList;
}
Используя его получить результат из строки json просто:
List<Timetable> appsdata = DeserializeToList<Timetable>(json);
Конечно, необходимо добавить еще и код класса Timetable (точно такой же как и в приложении сервиса). Другие варианты десериализации смотрите в комментах.
Создание базы данных SQL Server в Azure
Создать базу данных в Azure несложно. Нужно зайти на портал и заполнить следующие поля:
Останется только выбрать ценовую категорию. Цены начинаются от 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 в список брандмауэра.
Редактирование возможно из окна Server Explorer Visual Studio
Создадим какую-нибудь таблицу
И внесем любые тестовые данные.
Получение сервисом данных из базы 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<Timetable> 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<Timetable> Schedule = new List<Timetable>();
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.
Выбираем роль и переименовываем на свой вкус
В результате у нас будет создано два проекта: AzureCloudServiceTimetable и TimetableService
В второй (TimetableService) мы можем скопировать код из нашего локального сервиса. А именно – содержимое файлов IService1.cs, Service1.svc.cs, Web.config. После этого проект можно протестировать. В файле Web.config перед публикацией можно сделать изменения. В теге
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
изменить значения на false
Для публикации на Azure необходимо создать пакеты. На проекте AzureCloudServiceTimetable нужно вызвать контекстное меню и выбрать Package.
После окончания процесса будет открыта директория с пакетом и конфигурационным файлом.
Опубликовать на Azure можно с помощью веб интерфейса портала. Заходим на портал. Выбираем пункт Облачные службы (классические), создаем новый и заполняем поля
Необходимо установить 2 флажка: «Развернуть, даже если одна или несколько ролей содержат отдельный экземпляр» и «Запустить развертывание». После развертывания и запуска (запуск может занять некоторое время) можно будет делать запрос по URI:
http://servicetimetable.cloudapp.net/Service1.svc/GetScheduleJson
Этот адрес можно использовать в приложении UWP. Подробнее о развертывании: Создание и развертывание облачной службы
PS: Буду рад уточнениям, дополнениям и правкам.
Автор: asommer