Skip to content

GlebTheProgrammer/TexodeTestProject

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Тестовое задание "TEXODE Technologies"

Разработать клиент серверное приложение для работы с произвольными информационными карточками. Информационная карточка состоит из 2 элементов информации: названия карточки и графического изображения. В качестве вариантов информации можно использовать следующие типы: товары в магазине, книги, мобильные телефоны и т.д.

Основные требования

Серверное приложение должно работать по протоколу HTTP REST и использовать .NET Core. Серверное приложение представляет из себя службу Windows или обычное Windows приложение, предоставляющее HTTP API для клиентского приложения. Клиентская сторона должна представлять из себя Windows приложение с использованием технологии построения графических интерфейсов WPF.

image

Дополнительные требования

Серверная часть

  • Загрузка информационных карточек из файла на стороне сервера. Формат файла на выбор – JSON, XML
  • Сохранение добавленных и изменённых информационных карточек в файл на стороне сервера
  • Обработка запросов пользовательского GUI на CRUD операции с информационными карточками
  • Обработка ошибок при работе с клиентом

Клиентская часть

  • Показ списка информационных карточек в GUI клиентского приложения с отображением графических изображений для каждой информационной карточки
  • Поддержка операций CRUD для информационных карточек
  • Обработка ошибок при работе с сервером
  • Графические изображения могут быть ф формате JPG, PNG. Достаточно поддержки одного из форматов
  • Сортировка информационных карточек по названию
  • Возможность удаления нескольких информационных карточек за одну операцию
  • Проверка ошибок ввода информационных карточке – пустое название и отсутствующее изображение

Использованные в проекте технологии

  1. .Net 6.0
  2. JSON Serialization & Deserialization
  3. AutoMapper (Server)
  4. SOLID, HTTP RESTfull
  5. Swagger UI (Server)
  6. WPF (Client)
  7. xUnit.net

Пояснительная записка к проекту

Разработка проекта велась в течение 1 недели, в рамках которой было разработано клиент-серверное приложение на базе WPF + .Net Core, которое представляет из себя сервер, управляющий доступом к файловому хранилищу по отношению к одному, либо же сразу нескольким клиентам.

Клиентская часть

Графический интерфейс пользователя (GUI)

GUI

Клиентская часть была написана на базе WPF с использованием асинхронных методов и принципов многопоточного программирования (async - await). Пользователь имеет возможность добавлять новые информационные карточки, получать их с сервера, редактировать и удалять уже существующие карточки (CRUD Functionality).

GUI представляет из себя меню с левой стороны экрана, которое отражает в себе весь функционал приложения, а также основной экран, который меняется в зависимости от только что нажатой пользователем кнопки левого меню.

Меню содержит в себе следующие кнопки:

  • Dashboard - Активирует главную панель, где отображаются все доступные информационные карточки
  • Add New Card - Активирует панель, на которой пользователь сможет добавлять новые карточки
  • Update Card - Активирует панель, на которой пользователь сможет изменять выбранную им на главной панели карточку
  • Delete Card - Удаляет выбранную (выбранные) пользователем на главной панели карточку (карточки)
  • Connection - Активирует панель, которая показывает текущее состояние соединения с сервером
  • Logout - При нажатии, происходит выход из приложения

Dashboard

GUI

На главной странице расположена таблица, в которой отображаются все доступные информационные карточки

Операции, доступные с таблицы
  • Зелёная кнопка - Выбрать элемент; Если элемент уже выбран (О чём свидетельствует галочка напротив), отменить выбор
  • Красная кнопка - Выбрать элемент и сразу же удалить его
  • Жёлтая кнопка - Выбрать элемент и перейти на страницу с его редактированием

Также, вверху есть 2 кнопки, отвечающие за сортировку:

Сортировка
  • Unsorted - Привоит все карточки к их первоначальному порядку хранения
  • Sorted By Name - Сортирует все приведенные на главное панели карточки по алфавиту

Наряду с кнопками сортировки, слева приведен Counter, который отображает количество имеющихся карточек

Add New Card

AddNewCard

При добавлении новой карточки, активируется страница, на которой пользователю необходимо будет ввести название, а также установить картинку для информационной карточки. После чего, если данные были введены верно, нажать кнопку Save Card. Таким образом, пользователь сохранит карточку на сервере и будет перенаправлен на главный экран.

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

Update Card

update

При выборе карточки на главной панели, активируется страница, на которой пользователю необходимо будет произвести изменения в текущей карточке (либо же не производить их, если он, вдруг, передумает). Если данные были изменены верно, нажать кнопку Save Card. Таким образом, пользователь обновит карточку на сервере и будет перенаправлен на главный экран.

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

Delete Card

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

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

Connection

connection

Перед тем, как начать пользоваться приложением, пользователю необходимо будет установить соединение с сервером. О том, установлено ли соединение с сервером, будет свидетельствовать соответствующая надпись на странице.

Варианты надписей
  • Чёрная - Ожидается запрос на подключение к серверу
  • Красная - Не удалось установить соединение с сервером
  • Зелёная - Соединение с сервером установлено

Если соединение было установлено успешно, пользователю будет открыт доступ к функционалу приложения.

Если при работе с сервером у клиента произойдёт сбой, он будет перенаправлен на эту страницу с информацией об ошибке, после чего ему повторно необходимо будет устанавливать соединение для дальнейшей работы.

Logout

Завершение работы программы

Заключение по клиентской части

Таким образом, удалось создать полноценное GUI, которое включает в себя все самые основные операции CRUD, позволяющее пользователю не думать о том, как устроено взаимодействие изнутри и спокойно пользоваться приложением.

Серверная часть

На стороне сервера были реализованы такие Endpoints, как:

URI Метод Операция Описание Успешное выполнение Ошибка
api/InformationCards GET READ Получение всех карточек 200 ОК 404 Bad Request / 404 Not Found
api/InformationCards/{id} GET READ Получение карточки по её id 200 ОК 404 Bad Request / 404 Not Found
api/InformationCards POST CREATE Добавление в файл новой карточки 201 Created 404 Bad Request / 404 Not Found
api/InformationCards/{id} PUT UPDATE Обновление всей карточки (Без затрагивания Id) 204 No Content 404 Bad Request / 404 Not Found
api/InformationCards/{id} DELETE DELETE Удаление карточки по её id 200 ОК \ 204 No Content 404 Bad Request / 404 Not Found

Все приведенные выше Endpoints были протестированы при помощи Unit-тестов и библиотеки xUnit.net.

Архитектура

Хранилище данных

Изначально, мной было принято решение – хранить всё на стороне сервера: и название, и саму картинку в виде набора байт. Картинка поступала со стороны клиента, обрабатывалась, переводилась в последовательность байт, после чего эта последовательность конвертировалась в строку формата base64, записывалась в JSON-строку, и после чего, итоговая строка отправлялась на сервер, который в свою очередь сохранял её в файл формата JSON (решение использовать такие сложные манипуляции было принято по причине, что формат данных JSON не поддерживает хранение в нём массива байтов как целостной структуры), после чего отправлял обратно результат операции в виде строки, которая на стороне клиента обрабатывалась, и все необходимые данные по картинке сначала декодировались из формата base64 в массив байт, после чего из этого массива собиралась картинка и отправлялась на представление пользователю. ОДНАКО, при тестировании на 3-х и более картинках, ожидание ответа сервера уже занимало порядка 30 секунд, так как приходилось работать с большим объёмом данных в обе стороны. Поэтому, мной было принято решение хранить картинки локально, а ссылки на них – на стороне сервера в виде строки. Я понимаю, что в реальности, это не имеет никакого смысла, так как каждый пользователь будет получать доступ только к своим картинкам (которые у него будут храниться локально на клиентской части), в то время как сервер будет хранить информацию о карточках абсолютно всех клиентов, зато это позволило в несколько десятков раз увеличить скорость работы приложения, что положительно скажется на итоговом представлении программы пользователями.

Удаление карточек и очистка ненужных ресурсов

Так как мне пришлось работать с многопоточностью, чтоб не блокировать пользовательский интерфейс, при тестировании возникала такая ситуация, что картинку нельзя было удалить на стороне клиента, так как она в это время использовалась в другом потоке (не вызывающем метод на удаление). Мной было принято решение – удалять все неиспользуемые картинки сразу после того, как пользователь выключает приложение. Если задуматься, такой подход никак не повлияет на функционал программы, так как картинки будут отображаться согласно пришедшей с сервера информации. Так как на сервере у модели будет указан уже другой путь, то и эта картинка нигде не будет использоваться (разве что в каком-то из незавершённых потоков), следовательно, рано или поздно, картинка всё же будет удалена. Даже если по какой-то причине картинка не будет удалена и при выключении приложения (такая ситуация тоже может возникнуть), то при следующем запуске приложения, какой-то работы в нём и последующего выключения приложения, эти неиспользуемые картинки будут удалены с сервера со 100-процентной вероятностью, так как содержащих эти картинки потоков просто не будет существовать.

Каким образом вообще функционирует мой сервер???

Как уже было сказано выше, сервер хранит информацию сразу от нескольких клиентов, при этом на запрос GET клиента, сервер отсылает ему сразу все модели, содержащиеся на его стороне. Так как, собственно, проходит отсеивание ненужной информации, информации о чужих данных? На самом деле всё очень просто – мы получаем список всех моделей со стороны сервера, получаем список всех наших картинок, хранящихся на стороне клиента, после чего проверяем картинку каждой из пришедшей к нам моделей на вхождение в список картинок клиента, и, если совпадений не было найдено – то эта модель принадлежит другому клиенту, следовательно, к себе в список мы её не сохраняем и перебираем список моделей с сервера дальше, в поисках таких моделей, которые будут иметь ссылку на картинку, хранящуюся у нас. Таким незамысловатым способом происходит отсеивание ненужной информации.

Нюансы

По поводу сохранения модели. Почему после метода POST я использовал метод GET для всех моделей, а не GET для единичной, только что создавшейся? Это я решил сделать из соображений, как должен работать реальный сервер, на котором хранят свои данные сразу несколько клиентов, и таким образом обмениваются информацией. Да, соглашусь, в моём случае (где картинки хранятся локально) это не имеет большого смысла, и для лучшей скорости работы, лучше было бы использовать метод GET для единичной модели. ОДНАКО, возвращаясь к моему первоначальному видению, где все картинки хранились бы как набор байтов в строке, после того, как мы отправили картинку на сервер, в это время кто-то другой, такой же пользователь, мог бы отправить свою собственную картинку с именем, таким образом, на сервере появились бы сразу 2 новые картинки. Конечно, можно было бы вести лог всех запросов, проверять методы, смотреть что куда приходит и куда что нужно отправлять, однако это бы увеличило нагрузку самой бизнес модели, и вероятность ошибки при таком подходе намного больше, нежели в моём, где одним методом GET мы получаем сразу все состояния моделей на стороне сервера. Таким образом, если бы модели хранились как набор байт, то получение сразу всех моделей позволило бы сразу обоим пользователям увидеть добавленные друг другом картинки на сервер, что, конечно же, максимально приближено к работе реального сервера.

Заключение по серверной части

Таким образом, был создан полностью фонкционирующий сервер, способный работать сразу с несколькими клиентами и не быть привязанным к какому-то одному.