| Предыдущая лекция | Следующая лекция | |
|---|---|---|
| Аутентификация и авторизация | Содержание |
Возвращаемся к проекту на C#.
Мы остановились на списке материалов.
Реализуем получение списка материалов выбранного продукта с помощью HTTP-запроса.
Напоминаю, в лекции про АПИ было задание реализовать конечную точку
GET /material/{productId:int}:
На C# нам надо решить две задачи:
Для HTTP-запросов будем использовать встроенный класс HttpClient
var client = new HttpClient();
В АПИ мы использовали "базовую" авторизацию. В C# я не нашёл встроенных библиотек для облегчения формирования заголовка для "базовой" авторизации, но реализация не сложная - напишем сами.
Формируем base64-кодированную строку:
var basic = Convert.ToBase64String(
ASCIIEncoding.ASCII.GetBytes("admin:password"));
И добавляем заголовок нашему http-клиенту:
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", basic);
Теперь можно запрашивать данные.
У класса HttpClient все методы асинхронные, нужно понимать как работают async/await.
Я для облегчения вашей работы нарисовал реализацию:
// в параметрах URL
private Task<string> GetBody(string url){
var basic = Convert.ToBase64String(
ASCIIEncoding.ASCII.GetBytes("esmirnov:111103"));
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", basic);
return client.GetStringAsync(url);
}
Метод GetStringAsync возвращает объект Task<string>, т.е. задачу, которая выполняется в отдельном потоке и по завершении вернёт строку (тело ответа)
Для работы с JSON нужно установить NuGet пакет Newtonsoft.Json и использовать метод десериализации объекта:
// в месте, где нам нужно распарсить ответ сервера
materialList = JsonConvert.DeserializeObject<Material[]>("тут ответ вашего АПИ");
В общем случае может быть десериализован любой валидный JSON, но мы явно в угловых скобках указываем, что ожидаем массив материалов.
Весь метод получения списка материалов помещается в 3 строки:
private async void GetMaterials()
{
var resp = await GetBody(
$"http://localhost:8080/material/{currentProduct.Id}");
materialList = JsonConvert
.DeserializeObject<Material[]>(resp);
Invalidate("materialList");
}
Так как метод GetBody возвращает задачу (Task), то мы ставим перед ним ключевое слово await, то есть ждём завершения задачи, а сам метод помечаем как асинхронный (async), чтобы система знала, что этот метод асинхронный.
Для вызова http-метода DELETE используется метод DeleteAsync:
// тут, как в GetBody, создайте клиента и задайте заголовок
client.DeleteAsync(
$"http://localhost:8080/material/{productId}/{materialId}");
При работе с таблицей ProductMaterial не забываем, что у нас составной первичный ключ и нужны идентификаторы и продукта и материала.
В проекте нарисуйте форму добавления материала в текущий продукт (в окне продукции)
Реализуйте отправку POST-запроса, который добавит новый материал в таблицу ProductMaterial
// при добавлении материала
// у вас должен быть выбран материал
// и указано его количество
// сначала запихиваем объект в JSON-строку.
var jsonString = JsonConvert.SerializeObject(
new {
MaterialId = Id выбранного материала,
Count = количество
});
// создаём контент для http-запроса
var json = new StringContent(
jsonString,
Encoding.UTF8,
"application/json");
// и вызываем POST-запрос
var client = new HttpClient();
// не забываем добавить авторизацию
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", basic);
var result = client.PostAsync(
$"http://localhost:8080/material/{currentProduct.Id}",
json);
Часто в встречается задача сделать авторизацию пользователя и скрыть часть информации для неавторизованного пользователя.
Скрытие сделать просто - сделайте привязку (binding) атрибута IsVisible к какому-нибудь свойству (мы это уже делали для кнопки массовой смены цены продукции).
Показать форму с полями ввода для логина/пароля вы тоже уже можете.
Осталось разобраться как отправить запрос авторизации на сервер и обработать его на сервере...
Если отправлять обычным POST-запросом с телом запроса в JSON-формате, то вы это тоже уже умеете. Но для авторизации часто используется старый формат запроса: application/x-www-form-urlencoded.
Запрос с таким форматом выглядит примерно так:
POST /login
Content-Type: application/x-www-form-urlencoded
login=qwerty
&password=asdf
т.е. параметры запроса формируются как querystring, но передаются в теле запроса.
app.MapPost(
"/login",
async (context) =>
{
/*
параметры в таком формате автоматически не парсятся
приходится их вручную доставать из тела запроса
*/
var formData = await context.Request.ReadFormAsync();
var login = formData["login"].ToString();
var password = formData["password"].ToString();
/*
"Успешность" авторизации определяем кодом ответа
у меня, как обычно, логин и пароль прибиты гвоздями
но в реальном приложении надо читать из базы
*/
if (login == "admin" && password == "password")
{
/*
Тут можно передать не только код,
но и информацию о пользователе
*/
context.Response.StatusCode = 200;
}
else
context.Response.StatusCode = 401;
}).AllowAnonymous();
Кнопку "Авторизоваться" и окно авторизации сделайте сами
Для хранения информации об авторизации заведем класс Globals со статическими свойствами:
public class Globals
{
public static string? token = null;
public static bool isAuth() => token != null;
public static void createToken(string login, string password)
{
token = Convert.ToBase64String(
ASCIIEncoding.ASCII.GetBytes($"{login}:{password}"));
}
}
У нас остаётся базовая авторизация и я сразу формирую токен в нужном формате. Если используется авторизация по токену (Bearer), то метод createToken не нужен, просто сохранить токен: Globals.token = "токен, полученный при авторизации".
И в окне авторизации реализуем отправку запроса:
private async void AuthButton_OnClick(object? sender, RoutedEventArgs e)
{
/*
создаем словарь
и записываем в него данные в виде пары ключ/значение
для кадого передаваемого параметра
*/
var param = new Dictionary<string, string>
{
{ "login", LoginTextBox.Text },
{ "password", PasswordTextBox.Text }
};
var client = new HttpClient();
/*
В инициализатор запроса добавляется содержимое
*/
var req = new HttpRequestMessage(
HttpMethod.Post,
"http://localhost:8080/login")
{
Content = new FormUrlEncodedContent(param)
};
var response = await client.SendAsync(req);
/*
При успешной авторизации сохраняем токен
*/
if (response.StatusCode == HttpStatusCode.OK)
{
Globals.createToken(
LoginTextBox.Text,
PasswordTextBox.Text);
/*
тут закрываем окно авторизации,
а в главном окне обновляем состояние
визуальных элементов доступных только
авторизованному пользователю
*/
}
/*
в else можно написать логику для неуспешной авторизации,
например, показать окно
*/
}
В коде используются свойства LoginTextBox и PasswordTextBox - это имена соответствующих визуальных элементов в окне авторизации.
Ну и во всех запросах, требующих авторизации, вставляем токен авторизации:
private Task<string> GetBody(string url)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(
"Basic",
Globals.token // берём готовый
);
return client.GetStringAsync(url);
}