Программное средство по обмену услугами для владельцев собак

  • Вид работы:
    Дипломная (ВКР)
  • Предмет:
    Информационное обеспечение, программирование
  • Язык:
    Русский
    ,
    Формат файла:
    MS Word
    1,06 Мб
  • Опубликовано:
    2016-10-14
Вы можете узнать стоимость помощи в написании студенческой работы.
Помощь в написании работы, которую точно примут!

Программное средство по обмену услугами для владельцев собак


К ДИПЛОМНОМУ ПРОЕКТУ

НА ТЕМУ:

«Программное средство по обмену услугами для владельцев собак»

Содержание

Введение

. Аналитический обзор и постановка задачи

. Методы и модели, положенные в основу проекта

.1 Задание критериев отбора объявлений по фильтрам

.2 Редактирование личных данных (авторизированный пользователь)

.3 Редактирование объявления (авторизированный пользователь)

.4 Редактирование питомца (авторизированный пользователь)

.5 Создание нового объявления (авторизированный пользователь)

.6 Добавление нового питомца (авторизированный пользователь)

.7 Отправка личного сообщения пользователю

.8 Добавление комментария объявлению (авторизированный пользователь)

. Разработка проекта программного обеспечения

.1 Анализ предметной области и разработка требований

.2 Разработка схемы ресурсов системы

.3 Разработка схемы работы системы

.4 Разработка проекта базы данных

.5 Разработка структуры классов

. Разработка алгоритмов и их программная реализация

.1 Алгоритм редактирования и удаления объявления

.2 Алгоритм редактирования и удаления питомца

.3 Алгоритм сохранения фотографий

.4 Алгоритм отображения списков диалогов

.5 Алгоритм подбора объявлений

. Тестирование программного средства

Заключение

Список использованных источников

Приложения

Введение

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

Все мы знаем такую поговорку: Собака друг человека. И это действительно так. Собака всегда верой и правдой искренне служит человеку, не требуя ничего взамен. Люди же, в свою очередь, заботятся о хвостатых, обеспечивая им достойную жизнь.

Многообразие пород собак говорит о неугасающем интересе человека к этим домашним питомцам. Породы отличают собак по характеру, нравам, эстетическим и эмоциональным характеристикам и позволяют человеку выбрать любимца по конкретным параметрам поведения, внешнего вида и пр. Культура собаководчества в нашей стране развивается с каждым годом и уже вплотную приблизилась к культуре в развитых странах.

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

Существует много интернет площадок для размещения и поиска любого рода частных и коммерческих объявлений. Владельцы собак вынуждены заниматься поиском интересующих услуг и товаров для своих питомцев на разных площадках. Хотя существует довольно стандартный набор необходимых для ухода услуг и товаров, все они разбросаны в различных, порой неструктурированных рубриках различных ресурсов.

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

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

1. Аналитический обзор и постановка задачи

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

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

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

Поэтому для сравнения блока объявлений разрабатываемой системы с частичными аналогами выделим ряд критериев оценки:

а)      визуализация объявлений карте;

б)      возможность подбора объявлений по специализированным для животных критериям (порода, вид и т.д.);

в)      возможность подбора объявлений по типу (спрос/предложение) и подробной категории (выгул, дрессура, косметические услуги и т.д.)

г)       возможность подбора объявлений по стоимости;

д)      возможность просмотра подробной информации об объявлении;

е)       возможность связи с владельцем объявления;

ж)      наличие отзывов и оценок;

з)       возможность сортировки объявлений по различным критериям (по рейтингу, стоимости услуг и т.п.).

Для сравнения блока с личным кабинетом, реализующим работу с персональными данными, питомцами, общение между пользователями разрабатываемой системы с частичными аналогами выделим следующие критерии оценки:

а)      возможность создавать анкету пользователя с личной информацией;

б)      возможность создавать анкеты питомцев;

в)      возможность просмотра подробной информации о пользователе-владельце питомца;

г)       возможность связи с пользователем через отправку личных сообщений;

д)      организация поиска среди всех пользователей сайта;

е)       организация поиска среди всех питомцев сайта;

ж)      добавление других пользователей в друзья.

Для сравнения и анализа блока объявлений примем портал Cenotavr (#"896826.files/image001.jpg">





Рисунок 1 - Главная страница портала cenotavr.by

Ресурс содержит довольно подробные, характерные для данной области фильтры. Можно выбрать вид животного (кот, собака, птица, грызун и т.п.), определиться с типом услуги (выгул, дрессировка и т.п.). При переходе к подробному описанию объявления даже для неавторизированного пользователя раскрывается форма для связи с владельцем объявления, что очень удобно, и отображается блок с картой, где установлена метка. Ресурс не позволяет отсортировать объявления, что может послужить причиной выбора неактуального устаревшего объявления. Также ресурс не предоставляет пользователям возможности оставления отзыва и оценки и не визуализирует все подходящие объявления на карте.









Рисунок 2 - Страница поиска объявления портала cenotavr.by

Petonik.com - это социальная сеть для владельцев животных, с небольшим блоком объявлений.

Данный сервис дает возможность редактировать личный профиль, создавать анкеты питомцам, добавлять им фотографии. На сайте в разделе личной информации пользователя можно задать только имя и город. Для анкеты питомцев можно указать гораздо больше информации о питомце, включая пол, породу, дату рождения, описание. Предоставлена возможность вести поиск других пользователей и питомцев, вести переписку с другими пользователями. Работа с личным кабинетом организована достаточно удобно.

Объявления не являются центральным аспектом сайта, они реализованы в более общем виде без четкой разбивки по категориям. Для них предусмотрено лишь три рубрики: «Помогаем», «Нужен дом» и «Потеряшки». Соответственно, разместить объявление можно лишь в какой-то из этих рубрик. Фильтров и типа сортировки, кроме выбора региона (города), не предусмотрено. Карты для отображения объявлений также не предусмотрено. Поиск объявления не очень удобен.

Рисунок 3 - Страница личного профиля на сайте petonik.com

питомец программный тестирование данные

Рисунок 4 - Страница питомца на сайте petonik.com

Рисунок 5 - Страница поиска объявления на сайте petonik.com

В целом портал достаточно логичен. Функционала сайта достаточно, для создания анкеты и добавления питомцев. Для хорошей площадки объявлений не хватает четкой структуризации информации объявления и хорошей системы поиска и сортировки.

Портал Paws (#"896826.files/image009.gif">

Рисунок 6 - Страница личного профиля на сайте paws.pro

Рисунок 7 - Страница питомца на сайте paws.pro

Таблица 1 - Сравнение показателей ресурсов cenotavr.by, petonik.com

Показатель для сравнения

cenotavr.by

petonik.com

1

визуализация объявлений карте

Отчасти (отображается выбранное объявление после перехода к подробной информации)

Нет

2

возможность подбора объявлений по специализированным для животных критериям (порода, вид и т.д.)

Да

Нет

3

возможность подбора объявлений по типу (спрос/предложение) и подробной категории (выгул, дрессура, косметические услуги и т.д.)

Да

Нет

4

возможность подбора объявлений по стоимости

Нет

Нет

5

возможность просмотра подробной информации об объявлении

Да

Да

6

возможность отбора объявлений по дополнительным параметрам

Нет

Нет

7

возможность связи с владельцем объявления

Да (для любого пользователя)

Да (только авторизированный пользователь)

8

наличие отзывов и оценок

Нет

Только комментарий

9

возможность сортировки объявлений по различным критериям (по цене, по дате, стоимости услуг и т.п.)

Нет

Нет


Таблица 2 - Сравнение показателей ресурсов paws.pro, petonik.com

Показатель для сравнения

paws.pro

petonik.com

1

возможность создавать анкету пользователя с личной информацией

Да

Только с указанием имени и города

2

возможность создавать анкеты питомцев

Да

Да

3

возможность просмотра подробной информации о пользователе-владельце питомца

Да

Да

4

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

Да

Да

5

организация поиска среди всех пользователей сайта

Да

Да

Показатель для сравнения

paws.pro

petonik.com

6

организация поиска среди всех питомцев сайта

Да (для любого пользователя)

Да (только авторизированный пользователь)

7

Добавление других пользователей в друзья

Да

Нет (только подписаться на новости)


Таким образом наиболее близким прототипом для блока объявлений является портал cenotavr.by, а для блока работы с личными данными и питомцами - портал paws.pro.

Исходя из вышеизложенного определим задачи разрабатываемого веб-приложения:

˗        визуализация объявлений на карте;

˗        возможность поиска объявления по категориям и разнообразным параметрам, задаваемым фильтрами (спрос/предложения, город, порода собаки, возможность выезда на дом, временные параметры (за день, неделю, месяц) и др. набор некоторых фильтров специфичен для категорий объявлений);

˗        возможность публикации отзыва к объявлению;

˗        возможность просмотра средней стоимости услуги по заданным параметрам (особенно актуально для при назначении стоимости услуги);

˗        возможность создания и публикации объявления, а также его редактирование и удаление;

˗        ведение рейтингов объявлений по отзывам и оценкам пользователей;

˗        возможность просмотра подробной информации объявления, профиля пользователя, страницы питомца;

˗        возможность обмена личными сообщениями.

2. Методы и модели, положенные в основу проекта

 

.1 Задание критериев отбора объявлений по фильтрам (любой пользователь, в том числе неавторизированный)


Для поиска подходящих объявлений пользователь должен перейти в раздел объявления нажав соответствующую ссылку. После этого отображается страница с набором объявлений. Задавая значения фильтрам поиска, пользователь определяет важные для него критерии отбора. Система в ответ подбирает по этим критериям подходящие объявления и отображает их пользователю. Кроме того пользователь может задать режим сортировки результатов подбора. Система проверяет, есть ли среди отобранных объявлений те, у которых задан параметр адрес. Если такие объявления имеются, то пользователь может увидеть соответствующие маркеры на карте, которые помогут ему сориентироваться по месту расположения. Все подходящие критериям отбора объявления, независимо от параметра ‘адрес’ пользователь может увидеть в представленном системой списке объявлений.

Диаграмма деятельности представлена на рисунке 8.

Рисунок 8 - Поиск объявлений

 

.2 Редактирование личных данных (авторизированный пользователь)


Для редактирования личных данных пользователь должен осуществить вход в систему, затем перейти в раздел ‘Моя страница’ и нажать ссылку ‘Редактировать’ для перехода в режим редактирования личных данных. При входе система проверит введенные для авторизации данные и в случае обнаружения ошибок сообщит об этом пользователю.

Далее, находясь в режиме редактирования личных данных, пользователь вносит необходимые изменения, а система проверяет их на наличие ошибок и в случае их обнаружения сообщает об этом пользователю. Внеся изменения, пользователь нажимает кнопку сохранить, для подтверждения сохранения изменений. Система в ответ производит необходимые изменения, и пользователь видит исправленную страницу личных данных. Диаграмма деятельности представлена на рисунке 9.

Рисунок 9 - Редактирование личных данных

 

.3 Редактирование объявления (авторизированный пользователь)


Для редактирования объявления пользователь должен осуществить вход в систему, затем перейти в раздел ‘Мои объявления’ и нажать ссылку ‘Редактировать’ для перехода в режим редактирования объявления. Далее, находясь в режиме редактирования объявления, пользователь вносит необходимые изменения а система проверяет их на наличие ошибок и в случае их обнаружения сообщает об этом пользователю. Внеся изменения, пользователь нажимает кнопку сохранить, для подтверждения сохранения изменений. Система в ответ производит необходимые изменения, и пользователь видит исправленную страницу объявления.

Диаграмма деятельности представлена на рисунке 10.

Рисунок 10 - Редактирование объявления

 

.4 Редактирование питомца (авторизированный пользователь)


Для редактирования питомца пользователь должен осуществить вход в систему, затем перейти в раздел ‘Мои питомцы’ и нажать ссылку ‘Редактировать’ для перехода в режим редактирования питомца. Далее, находясь в режиме редактирования питомца, пользователь вносит необходимые изменения, а система проверяет их на наличие ошибок и в случае их обнаружения сообщает об этом пользователю. Внеся изменения, пользователь нажимает кнопку сохранить, для подтверждения сохранения изменений. Система в ответ производит необходимые изменения, и пользователь видит исправленную страницу питомца.

Диаграмма деятельности представлена на рисунке 11.

Рисунок 11 - Редактирование питомца

 

.5 Создание нового объявления (авторизированный пользователь)


Для добавления объявления пользователь должен осуществить вход в систему, затем перейти в раздел ‘Мои объявления’ и нажать ссылку ‘Добавить’. Далее, находясь в режиме создания нового объявления, пользователь заполняет необходимые данные на форме, а система проверяет их на наличие ошибок и в случае их обнаружения сообщает об этом пользователю. Далее пользователь нажимает кнопку сохранить, для подтверждения создания нового объявления. Объявление сохраняется в системе, и пользователь видит новое объявление в списке объявлений.

Диаграмма деятельности представлена на рисунке 12.

Рисунок 12 - Создание нового объявления

2.6 Добавление нового питомца (авторизированный пользователь)

Для добавления питомца пользователь должен осуществить вход в систему, затем перейти в раздел ‘Мои питомцы’ и нажать ссылку ‘Добавить’. Далее, находясь в режиме создания нового питомца, пользователь заполняет необходимые данные на форме, а система проверяет их на наличие ошибок и в случае их обнаружения сообщает об этом пользователю. Далее пользователь нажимает кнопку сохранить, для подтверждения создания нового питомца. Питомец сохраняется в системе, и пользователь видит информацию о новом питомце.

Диаграмма деятельности представлена на рисунке 13.

Рисунок 13 - Создание нового питомца

2.7 Отправка личного сообщения пользователю (авторизированный пользователь)

Для отправки сообщений другим пользователям необходимо осуществить вход в систему, затем перейти в раздел ‘Мои друзья’ выбрать пользователя из списка друзей и нажать ссылку ‘Отправить сообщение’. Далее пользователь набирает в поле текст сообщения и нажимает кнопку ‘Отправить’. Пользователь-отправитель видит новое сообщение в истории переписки, а пользователь-получатель получает уведомление о наличии непрочитанного сообщения.

Диаграмма деятельности представлена на рисунке 14.

Рисунок 14 - Отправка личного сообщения

2.8 Добавление комментария объявлению (авторизированный пользователь)

Для того, что бы оставить комментарий объявлению, необходимо осуществить вход в систему, затем перейти в раздел ‘Объявления’ и выбрать объявление из списка для просмотра. Далее пользователь переходит к комментариям объявления, нажав на ссылку ‘Посмотреть комментарии’ под описанием объявления. В конце отображенного списка существующих комментариев в специальном поле пользователь вводит текст комментария и нажимает кнопку отправить. Введенный комментарий появляется в списке комментариев и доступен для просмотра другими пользователями.

Диаграмма деятельности представлена на рисунке 15.

Рисунок 15 - Добавление комментария объявлению

 

3. Разработка проекта программного обеспечения

 

.1Анализ предметной области и разработка требований


Важным аспектом приложения являются объявления. При выборе любого рода услуг человек руководствуются некоторыми критериями: чаще всего это такие критерии как стоимость, репутация, уровень качества. Есть также специфичные критерии, которые существуют только в рамках некоторой категории объявлений.

Поэтому необходимо предоставить для пользователей удобный и многофункциональный фильтр поиска, чтобы обеспечить наиболее простой способ подбора необходимой услуги и избавить пользователей от выискивания важной информации из текста объявлений. Объявления пользователей необходимо разбить на категории и выделить специфичные фильтры для каждой категории (например: при поиске объявлений о продаже собаки логично иметь фильтр для породы, для объявлений о выгуле логично иметь фильтр по местоположению в городе чтобы “сводить” владельцев по соседству, для косметических услуг фильтр для отбора объявлений по возможности выезда на дом, такой фильтр разумеется нелогичен для категории “продажа собак”). Также при реализации приложения следует для каждой категории объявлений выделить отдельно из общего текста объявления некоторые важные для этой категории объявлений атрибуты в отдельные поля чтобы помочь пользователю не упустить наиболее важную информацию при создании объявления и создавать ее в структурированном виде. Сам текст объявления в таком случае будет содержать по сути лишь дополнительное описание. Такой подход поможет организовать на сайте удобный и хорошо функциональный поиск объявлений.

Для обеспечения связи между пользователями должна быть организована возможность личной переписки, а также возможность оставлять комментарии под объявлением и выставление оценок. На основе оценок и отзывов должен быть организован рейтинг, который поможет пользователям в выборе.

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

В системе предполагается наличие нескольких типов пользователей.

Рисунок 16- Диаграмма основных пользователей системы

Для роли Admin должны быть предусмотрены некоторые дополнительные возможности работы с системой. Кроме того предусмотрен ограниченный набор функционала для неавторизированного пользователя.

Более полное описание вариантов использования системы представлено далее. Отражение вариантов использования на функциях системы представлены в таблице приложения Б.

Рисунок 17 - Диаграмма вариантов использования неавторизированного пользователя

Рисунок 18 - Диаграмма вариантов использования авторизированного пользователя (User)

Рисунок 19 - Диаграмма вариантов использования авторизированного пользователя (Admin)

Для достижения всех описанных выше целей, был сформирован следующий список требований к системе:

˗        система должна позволять регистрироваться на сайте и создавать личный кабинет;

˗        система должна предоставлять возможность редактирования, личных данных в личном кабинете пользователя;

˗        должна быть реализована возможность поиска объявления по категориям и разнообразным параметрам, задаваемым фильтрами (спрос/предложения, город, порода собаки, возможность выезда на дом, временные параметры (за день, неделю, месяц) и др. набор некоторых фильтров специфичен для категорий объявлений);

˗        должна быть реализована возможность просмотра подробной информации объявления, профиля пользователя, страницы питомца;

˗        должна быть реализована возможность публикации отзыва к объявлению;

˗        система должна позволять авторизированным пользователям обмениваться личными сообщениями;

˗        должна быть реализована возможность просмотра средней стоимости услуги по заданным параметрам (особенно актуально при размещении объявления для ориентации пользователя по ценовому вопросу);

˗        система должна позволять создавать и публиковать объявления, а также их редактирование и удаление;

˗        должен быть реализован расчет рейтингов объявлений по отзывам и оценкам пользователей.

Для пользователя Admin необходимо ко всему прочему предоставить следующий функционал:

˗        возможность правки и удаления комментариев;

˗        возможность удаления пользователей, питомцев и объявлений;

˗        возможность допуска объявления к публикации или ее блокирование.

3.2 Разработка схемы ресурсов системы


Исходя из определенных требований к программному комплексу, можно перейти к разработке схемы ресурсов. Схемы ресурсов отображают конфигурацию блоков данных и обрабатывающих блоков. На чертеже РТДП 40324.034.001 представлена схема ресурсов разрабатываемой системы.

Схема ресурсов была построена на основании следующих правил обработки данных:

˗        система представляет собой веб-приложение asp.net, находящееся на веб-сервере, к которому клиент обращается посредством HTTP-запросов через интернет соединение;

˗        все пользовательские данные хранятся в таблицах базы данных;

˗        браузер формирует запрос и с помощью библиотеки ОС winsock.dll передает данные посредством протокола HTTP на веб-сервер;

˗        веб-сервер через интерфес internet Server API (ISAPI), находящийся в файле asp.net_isapi.dll передает запрос рабочему процессу;

˗        работа приложений.NET обеспечивается за счет наличия такого механизма как общеязыковая среда исполнения (Common Language Runtime, CLR) aspnet_wp.exe.

Для разработки системы был выбран шаблон MVC а именно ASP.NET MVC5. Язык программирования С#. Шаблон MVC разделяет работу веб-приложения на три отдельные функциональные роли: модель данных (model), пользовательский интерфейс (view) и управляющую логику (controller). Таким образом, изменения, вносимые в один из компонентов, оказывают минимально возможное воздействие на другие компоненты. Взаимодействия между контроллером, моделью и представлением показаны на рисунке 20:



Рисунок 20 - Взаимодействия в приложении MVC

Обработка основной части запросов выглядит следующим образом:

а)      на сервер приходит запрос от пользователя;

б)      Сервер IIS запускает ASP.NET модуль, который будет запускать приложение на выполнение для обработки запроса;

в)      cистема маршрутизации ASP.NET определяет каким методом и в каком контроллере будет обработан запрос;

г)       выполняется нужный метод действия и упраление передается в представление View;

д)      движок представлений Razor рендерит View и отдаёт пользователю в виде Html и исполняемых скриптов.

В качестве хранилища данных для приложения используется созданная в MS-SQL Server база данных. База данных является реляционной, что означает использование связанных отношениями таблиц. Такая структура позволяет получить выигрыш в производительности за счет возможности объединения таблиц при запросах. Для доступа к данным в БД использована технология ADO.NET Entity Framework(EF). EF в сочетании с LINQ (Language-Integrated Query) представляет собой реализацию ORM для платформы.NET Framework от компании Microsoft. Entity Framework содержит механизмы создания и работы с сущностями базы данных через объектно-ориентированный код на языке, совместимым с CLR. LINQ представляет собой библиотеку, расширяющую возможности C#, и облегчающую создание запросов (благодаря LINQ можно создавать SQL-подобные запросы в коде C#).

Entity Framework является продолжением другого API-интерфейса для работы с базами данных в.NET - ADO.NET, в котором для работы с базами данных приходилось писать запросы на SQL и вставлять их в код.

В некоторых запросах к серверу используется технология ajax (обращение к серверу без перезагрузки страницы в браузере). Таким образом, разгружается канал передачи данных, а пользователь получает более дружественное поведение приложения, напоминающее desktop-приложения.

Для обеспечения требования к расширяемости и изменяемости приложения была выбрана многоуровневая архитектура. Разрабатываемое приложение разделено на три уровня.

Уровень представления. Это интерфейсный компонент, который представляет первый уровень, собственно приложение для конечного пользователя. Первый уровень не должен иметь прямых связей с базой данных (по требованиям безопасности), быть нагруженным основной бизнес-логикой (по требованиям масштабируемости) и хранить состояние приложения (по требованиям надежности).

Уровень бизнес-логики. Содержит набор компонентов, которые отвечают за обработку полученных от уровня представлений данных, реализует всю необходимую логику приложения, все вычисления, взаимодействует с уровнем данных и передает уровню представления результат обработки. Это по сути ядро всего приложения, в котором реализована модель поведения программной системы и ее логика.

Уровень данных. Хранит модели, описывающие используемые сущности, также здесь размещаются специфичные классы для работы с разными технологиями доступа к данным. В заданном приложении при использовании технологии EntityFramework это класс-наследник от DbContext, следящий за состоянием сущностей в памяти приложения и осуществляющий непосредственные запросы к БД MS SQL Server. Здесь также хранятся классы репозиториев через которые уровень бизнес-логики взаимодействует с базой данных.

Взаимодействие с приложением MVC осуществляется в соответствии с естественным циклом действий пользователя и обновлений представления, при котором предполагается, что представление не содержит информации о состоянии. Пользователь через браузер отправляет запрос на веб-сервер. Получив запрос, веб-сервер запускает обработку кода интерпретатором, при необходимости происходит выборка данных из БД, производятся необходимые вычисления. Далее страница-ответ наполняется данными, после чего передается клиенту.

 

.3 Разработка схемы работы системы


Схема работы системы представлена в графической части на чертеже РТДП-40324.034.002. Неавторизированный пользователь может просматривать объявления и пользоваться фильтром для поиска. При авторизации система проверяет, зарегистрирован ли пользователь с указанной парой логин-пароль в системе. Если в ходе авторизации возникла ошибка, система предлагает пользователю повторно ввести логин и пароль. При успешной авторизации пользователь попадает в личный кабинет, где может работать со своими личными данными, своими объявлениями, питомцами, также он может отправлять личные сообщения другим пользователям и оставлять отзывы чужим объявлениям.

Приложение состоит из нескольких блоков.

Блок входа в личный кабинет и авторизация. Этот блок служит для авторизации пользователя вы системе.

Блок регистрации в системе. Этот блок включает заполнение формы для регистрации в системе, включая логин, пароль, имя и фамилия, пол.

Блок поиска объявлений. Этот блок включает поиск объявлений по фильтрам, просмотр списка объявлений, просмотр их маркеров на карте, просмотр полной информации об объявлении, просмотр отзывов, добавление отзыва объявлению (только для авторизированных пользователей).

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

Блок работы с питомцами пользователя. В этом блоке пользователь может создавать питомцев, удалять их и редактировать, также он может добавлять и удалять фотографии питомцам.

Блок работы с объявлениями пользователя. В этом блоке пользователь может создавать объявления, удалять и редактировать их, также он модет добавлять и удалять фотографии для объявления.

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

 

.4 Разработка проекта базы данных

 

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


Таблица 3 - Информационные сущности системы

Сущность

Описание сущности

1

Пользователь

Описывает пользователя системы и его права. Атрибутами являются: логин, пароль, его роль в системе

2

Профиль пользователя

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

3

Объявление

Описывает конкретное объявление, атрибуты: категория объявления, его название, текст, город и местоположение при необходимости, тип объявления (спрос/предложение), пол и порода питомца при необходимости, стоимость, средняя оценка по результатам отзывов, возможен ли выезд на дом (для некоторых категорий)

Сущность

Описание сущности

4

Категория объявления

Описывает категорию объявления, атрибуты: название категории

5

Питомец

Описывает конкретного питомца: кличка питомца, его порода, пол, дата рождения, возможность вязки, чип-номер при наличии, награды питомца, что любит, что не любит, описание питомца

6

Порода питомца

Описывает породу питомца, атрибуты: название породы, описание породы

7

Награда питомца

Описывает конкретную награду у питомца, атрибуты: название награды, питомец владелец награды

8

Сообщение

Описывает конкретное личное сообщение, атрибуты: дата и время отправки, тема, текст, пользователь-отправитель, пользователь-получатель, статус (просмотрено/не просмотрено)

9

Отзыв

Описывает конкретный отзыв к определенному объявлению, атрибуты: текст отзыва, оценка, дата и время написания отзыва, указание к какому объявлению относится отзыв


Кроме того выявлены нижеуказанные отношения между сущностями

Таблица 4 - Отношения между сущностями

Отношение

Описание отношения

1

Пользователь - профиль пользователя

Каждому пользователю соответствует 1 профиль, каждому профилю соответствует 1 пользователь. Это означает, что после регистрации в системе у пользователя автоматически создается одна страница профиля, где будет содержаться его личная информация

Отношение

Описание отношения

2

Профиль пользователя- питомец

Каждый пользователь может иметь от 0 до n питомцев, каждый питомец всегда имеет одного пользователя-владельца

3

Профиль пользователя- объявление

Каждый пользователь может иметь от 0 до n объявлений, каждое объявление всегда имеет одного пользователя-владельца

4

Профиль пользователя- сообщение

Каждый пользователь имеет от 0 до n сообщений, Каждое сообщение принадлежит только одному пользователю в системе. Сообщение принадлежит пользователю, если он является его автором

5

Объявление - отзыв

Каждое объявление может иметь от 0 до n отзывов, каждый отзыв принадлежит одному объявлению

6

Питомец - награда

Каждый питомец может иметь от 0 до n наград, каждая награда принадлежит одному питомцу

7

Профиль пользователя - отзыв

Каждый пользователь может быть автором от 0 до n отзывов, каждое отзыв написан одним конкретным пользователем

8

Объявление - категория объявления

Каждое объявление принадлежит к определенной категории, каждая категория объявления может иметь от 0 до n объявлений в этой категории

9

Питомец - порода

Каждый питомец может относиться к 0 или 1 породе, каждая порода может относиться к 0..n питомцам

10

Объявление - порода

Каждое объявление может относиться к одной породе, каждая порода может относиться к 0..n объявлениям. Для некоторых объявлений может быть задана порода (порода актуальна, например для объявлениях о покупке/продаже питомцев)

На основании вышеизложенного была спроектирована БД. После этапа даталогического проектирования и нормализации до 4ой нормальной формы средствами ErWin 4.0 мы получаем следующую модель БД:

Рисунок 21 - Модель базы данных программной системы

В результате описание полученных таблиц имеет вид:

а)      таблица Users (Пользователи), описывающая сущность пользователя, со следующими полями:

)        UserId int IDENTITY(1,1) NOT NULL, - первичный ключ;

2)      RoleId int NOT NULL, - вторичный ключ;

3)      Email nvarchar(50) NOT NULL, -- логин для входя в систему;

Таблица связана один-к-одному с таблицей Profiles;

б)      таблица Roles (Роли):

)        RoleId int IDENTITY(1,1) NOT NULL, - первичный ключ;

)        Namenvarchar(50) NOT NULL, - название роли.

Таблица связана один-ко-многим с таблицей Users;

в)      таблица Profiles (Профили):

)        UserIdint IDENTITY(1,1) NOT NULL, - первичный ключ;

)        FirstNamenvarchar(50) NOT NULL - имя пользователя;

)        LastName nvarchar(50) NOT NULL - фамилия пользователя;

4)      Sex bit NOT NULL - пол пользователя;

)        BirthDate datetime NULL - дата рождения;

)        City nvarchar(50) NULL - город;

7)      Adress nvarchar(50) NULL - адрес;

8)      PhoneNumber nvarchar(50) NULL - номер телефона;

)        AvatarPath nvarchar(50) NULL - путь к фото аватарки;

)        HaveImages  bit NOT NULL - имеет ли профиль фотографии.

Таблица связана отношением один-ко-многим с таблицей Pets, Advertizements, Users, Messages, Reviews.

г)       таблица Advertizements (Объявления):

)        AdvertizementIdint IDENTITY(1,1) NOT NULL, - первичный ключ;

)        Topic nvarchar(50) NOT NULL, - название объявления;

3)      Text nvarchar(max) NOT NULL, - текст объявления;

)        Pricefloat NULL - стоимость;

5)      IsDemandbit NOT NULL - тип объявления(спрос/предложение);

6)      IsWithDeparturebit NULL- выезд на дом (есть/нет);

7)      Citynvarchar(50) NULL, - город;

8)      Adress  nvarchar(50) NOT NULL, - адрес;

)        DateAndTimedatetime NOT NULL, - дата и время публикации;

)        UserIdint NOT NULL - вторичный ключ;

)        DogBreedIdint NULL - вторичный ключ;

)        CategoryIdint NOT NULL - вторичный ключ;

)        Sexbit NULL - пол питомца (при необходимости);

14)    Markfloat NULL - средняя оценка по отзывам;

15)    HaveImagesbit NOT NULL - есть ли фотографии.

Таблица связана отношением один-ко-многим с таблицей Reviews (Отзывы);

д)      таблица Pets (Питомцы):

)        PetId int IDENTITY(1,1), - первичный ключ;

)        Name nvarchar(50) NOT NULL, - кличка питомца;

)        Sex bit NOT NULL - пол;

)        BirthDatedatetime NULL - дата рождения;

)        Copulationbit NULL - возможность вязки (да/нет);

6)      Descriptionnvarchar(max) NULL - описание питомца;

)        ChipNumbernvarchar(50) NULL - чип-номер;

)        UserIdint NOT NULL - вторичный ключ;

)        DogBreedId int NULL - вторичный ключ;

)        WhatLikenvarchar(max) NULL - что любит;

)        WhatDontLikenvarchar(50) NULL - что не любит;

)        HaveImagesbit NOT NULL - есть ли фотографии.

Таблица связана отношением один-ко-многим с таблицей Rewards (Награды);

е)       таблица Messages (Сообщения):

1)      MessageId int IDENTITY(1,1), - первичный ключ;

)        Topic nvarchar(50) NULL - тема сообщения;

3)      Text nvarchar(max) NOT NULL - текст сообщения;

)        DateAndTimedatetime NOT NULL - дата и время отправки;

)        WhoDeletedint NULL - Id пользователя, удалившего сообщение;

6)      ReceiverIdint NOT NULL - Id пользователя-получателя сообщений;

7)      AuthorIdint NOT NULL - вторичный ключ (Id пользователя-отправителя);

8)      HasBeenViewedbit NULL - было ли сообщение просмотрено получателем;

ж)      таблица Rewiews (Отзывы):

1)      ReviewId int IDENTITY (1,1) NOT NULL - первичный ключ;

)        Textnvarchar(max) NOT NULL - текст отзыва;

)        Markint NULL - оценка отзыва;

)        DateAndTimedatetime NOT NULL - дата и время отправки;

)        AdvertizementIdint NOT NULL - вторичный ключ (Id объявления);

)        AuthorIdint NOT NULL - вторичный ключ (Id автора отзыва);

з)       таблица Categories (Категории объявлений):

1)      CategoryIdint IDENTITY(1,1) NOT NULL - первичный ключ;

)        Namenvarchar(50) NOT NULL - название категории объявления;

и)      таблица Rewards (Награды):

1)      RewardId int IDENTITY(1,1) NOT NULL, - первичный ключ;

)        RewardText nvarchar(max) NOT NULL, - описание награды;

)        PetIdint NOT NULL - вторичный ключ (питомец-обладатель награды);

к)      таблица DogBreads (Породы):

)        DogBreedId int IDENTITY(1,1) NOT NULL, - первичный ключ;

)        Name nvarchar(50) NOT NULL, - название;

3)      Description nvarchar(max) NULL - описание.

Таблица связана отношением один-ко-многим с таблицами Advertizements и Pets.

 

.5 Разработка структуры классов


На рисунке 22 отображена итоговая структура классов предметной области программной системы:

Рисунок 22 - Диаграмма классов программной системы

4. Разработка алгоритмов и их программная реализация

 

.1 Алгоритм редактирования и удаления данных объявления


Алгоритм редактирования и удаления данных объявления представлен на рисунке 23. Если выбрана операция удаления, программа извлекает из данных формы идентификационный номер удаляемого объявления вместе с другой полезной информацией, такой как идентификационный номер автора объявления. Этот идентификационный номер используется приложением для ограничения прав на удаление так, чтобы только авторизированный пользователь с таким номером мог произвести удаление. Поскольку удаление данных, это непосредственная операция по изменению состояния приложения, то запросы Get являются нежелательными для подобных действий. Это связано с тем, что в этом случае удаление можно произвести просто перейдя по ссылке, что небезопасно. Потому в данном конкретном случае используется запрос типа Post. При удалении объявления проверяется, есть ли в таблице “Rewievs” записи, связанные с этим объявлением. Если имеются, то программа удаляет записи из этой таблицы и только затем удаляет данные объявления.

При выборе операции редактирования объявления (нажали ссылку «Редактировать»), сервер в ответ формирует страницу с формой редактирования данных, заполненную данными выбранного объявления. После того как пользователь внес необходимые изменения, он нажимает кнопку «Сохранить» для подтверждения сохранения изменений. Тогда система проверяет, корректны ли введённые данные и в случае обнаружения ошибки выдаёт сообщения, поясняющие суть ошибок. Если данные корректны, система производит сохранение изменений в БД. Кроме того, в случае, если к самому объявлению на форме были добавлены фотографии, то система проверяет их на корректность формата и на непустое значение и далее присваивает этим файлам уникальные имена и сохраняет их в файловой системе сервера. Детализация алгоритма сохранения фотографии приведена далее.

При выборе операции добавления нового объявления (нажали ссылку «Добавить»), сервер в ответ формирует страницу с формой добавления нового объявления. После того как пользователь заполнил форму, он нажимает кнопку «Сохранить» для подтверждения сохранения объявления. Тогда система проверяет, корректны ли введённые данные и в случае обнаружения ошибки выдаёт сообщения, поясняющие суть ошибок. Если данные корректны, система производит сохранение нового объявления в БД. Кроме того, в случае, если к самому объявлению на форме были добавлены фотографии, то система проверяет их на корректность формата и на непустое значение и далее присваивает этим файлам уникальные имена и сохраняет их в файловой системе сервера. Детализация алгоритма сохранения фотографии приведена далее.

При нажатии на кнопку “Добавить”, программа извлекает из строки запроса идентификационный номер объявления и формирует страницу с формой для нового объявления. После того как пользователь заполнил формы и нажал кнопку «Сохранить», система проверяет, корректны ли введённые данные. Если ошибок не обнаружено, новое объявление сохраняется в БД, в случае наличия ошибок во введенных данных отображаются соответствующие сообщения. Если к самому объявлению на форме были добавлены фотографии, то система проверяет их на корректность формата и на непустое значение и далее присваивает этим файлам уникальные имена и сохраняет их в файловой системе сервера.

Рисунок 23 - Алгоритм редактирования и удаления объявления

 

.2 Алгоритм редактирования и удаления данных питомца


Алгоритм редактирования и удаления данных питомца представлен на рисунке 24. Он аналогичен предыдущему алгоритму, с тем лишь отличием, что на форме имеется возможность добавлять питомцу награды. В соответствии с этим при редактировании и при добавлении нового питомца происходит проверка наличия на форме наград, если они обнаружены, то происходит их сохранение в БД. При удалении питомца также проверяется наличие у него записей о наградах (записей в таблице Rewards), если они обнаружены, то вместе с удалением питомца происходит и удаление этих записей.

Аналогично предыдущему алгоритму при заполнении формы система проверяет ее на наличие ошибок. Также производится проверка на наличие фоторгафий и их сохранение в файловой системе, как это описано в предыдущем алгоритме.

Рисунок 24 - Алгоритм редактирования и удаления данных питомца

 

.3 Алгоритм сохранения фотографий


Алгоритм сохранения фотографий представлен на рисунке 25.

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

Фотографии могут принадлежать одной из трех сущностей (пользователь, питомец, объявление). По этой причине фотографии для определенного пользователя сохраняются по пути ‘~/ProfilePhotos/[ID-номер пользователя]’, фотографии для определенного питомца по пути ‘~/PetPhotos/[ID-номер питомца]’, фотографии для определенного объявления по пути ‘~/AdvPhotos/[ID-номер объявления]’. В разных местах сайта необходимо отображать одну и ту же фотографию но различного размера. Например, для отображения списка объявлений рядом с каждым объявлением будет маленькая фотография-иконка, для отображения подробной информации об объявлении более крупная, а в случае просмотра фотографий объявления еще более крупная.

С целью оптимизации передачи данных по сети интернет от сервера клиенту было принято решение создавать три версии фотографии разного расширения и размера с приставками соответственно ‘big’ ‘small’ ‘tiny’. Для обеспечения уникального имени файла в пределах директории обрабатываемой сущности используется следующая схема именования: приставка+порядковый_номер. Например, файл будет существовать в трех версиях в виде ‘big1.jpg’, ‘small1.jpg’, ‘tiny1.jpg’. После определения директории для сохранения, система присваивает переменной nextNum максимальный номер в названии уже сохраненных на сервере файлов для обрабатываемой сущности. Далее в цикле осуществляется проход по коллекции отправленных клиентом файлов. Если файл корректный, то формируются правила его сохранения в трех версиях, включая шаблон именования приставка+ nextNum. Потом в цикле сохраняются эти три версии по определенной ранее директории. Если такой директории нет, то она создается. В конце итерации счетчик nextNum увеличивается на единицу.

Рисунок 25 - Алгоритм сохранения фотографий

4.4 Алгоритм отображения списков диалогов


Алгоритм отображения списков диалогов пользователя представлен на рисунке 27. В приложении реализована возможность пользователей обмениваться личными сообщениями. У каждого пользователя есть список его диалогов (диалог здесь понимается как перечень сообщений, в котором данный пользователь выступает в качестве одного участника, а другим участником выступает другой конкретный пользователь). Все сообщения пользователей хранятся в одной таблице в БД.

Приведенный алгоритм отображает логику формирования списка диалогов пользователя, каждый элемент которого представляет собой последнее сообщение в истории переписки и пометки пользователя, с которым ведется переписка. Для формирования такого списка сначала из БД извлекается коллекция messages из последних сообщений в каждом диалоге. Далее полученная коллекция отсортировывается по времени. После в цикле осуществляется проход по этой коллекции. В каждой итерации цикла проверяется, является ли авторизированный пользователь автором текущего сообщения цикла mes. Если да, то переменной author присваивается значение ‘Вы’. В противном случае значение для этой переменной формируется из объекта mes как author=mes.Author.FirstName+mes.Author.LastName. Если авторизированный пользователь не является автором сообщения и mes имеет пометку ‘непрочитанное’, то переменной darkBackGround присваивается ‘true’, чтобы сделать фон для данного диалога затемненным при отображении на странице. В противном случае darkBackGround=false.

Кроме того при отображении диалога имеет смысл показывать информацию о пользователе, с которым авторизированный пользователь ведет переписку. Для хранения этой информации служит переменная usrToShow. Она получает значение mes.Receiver, если автор сообщения это авторизированный пользователь и значение mes.Author в противоположном случае. Далее в итерации переменной unViewed присваивается количество сообщений из БД, помеченных как непрочитанные и у которых получатель это зарегистрированный пользователь, а отправитель это пользователь usrToShow. Вся эта информация необходима для отображения информации о диалоге пользователя включая сам текст сообщения, информация о собеседник и количество непрочитанных авторизированным пользователем сообщений в этом диалоге. Пояснение по внешнему виду списка диалогов приведено на рисунке 26.

Рисунок 26 - Внешний вид списка диалогов пользователя

Рисунок 27 - Алгоритм отображения списков диалогов

 

.5 Алгоритм подбора объявлений


Алгоритм подбора объявлений по заданным фильтрам представлен на рисунке 28. В приложении реализована возможность поиска объявлений по определенным критериям, а также возможность задавать порядок отображения подходящих объявлений. В случае, если пользователь изменил состояние одного из фильтров поиска объявлений, на сервер приходит текущее состояние всех фильтров и заданное пользователем состояние порядка отображения объявлений. Далее из БД извлекаются объявления, соответствующие этим фильтрам.

В случае если пользователем была определена сортировка по цене, полученные объявления сортируются по цене начиная от наименьшей, если была определена сортировка по дате, то объявления сортируются по дате начиная с самых новых, если была определена сортировка по рейтингу, то объявления сортируются по рейтингу, сформированному на основе отзывов и оценок других пользователей для этого объявления. Поскольку на сайте для разгрузки пропускных каналов реализована пагенация, то из результата отбирается первые pageSize. Далее проверяется информация о запросу. Если запрос типа ajax, то возвращается только часть разметки, касающаяся подобранных объявлений. Если тип запроса не ajax, то формируется и возвращается страница с результатами целиком.

Рисунок 28 - Алгоритм подбора объявлений

5. Тестирование программного средства

 

Тестирование программного обеспечения - проверка соответствия между реальным и ожидаемым поведением программы, осуществляемая на конечном наборе тестов, выбранном определенным образом. В более широком смысле, тестирование - это одна из техник контроля качества, включающая в себя активности по планированию работ (Test Management), проектированию тестов (Test Design), выполнению тестирования (Test Execution) и анализу полученных результатов (Test Analysis).Все методы тестирования можно условно разделить на две группы: тестирование по методу белого ящика и тестирование по методу черного ящика.

Разность этих подходов заключается в наличии доступа к исходному коду тестируемого ПО. Как можно догадаться из названий методов - при использовании «черного ящика» тестировщик использует только внешние рычаги взаимодействия с программой: с помощью пользовательского интерфейса или подключившись к тестируемой системе. При работе с «белым ящиком» тестировщик имеет доступ к коду, тем самым тестируя внутреннюю структуру программы.

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

Для тестирования приложения был выбран метод черного ящика, тестирование производилось вручную. Был разработан набор smoke-тестов, призванных поверхностно проверить работоспособность системы, и набор criticalpath-тестов (функциональных тестов), целью которых является проверить работоспособность основного функционала приложения и его соответствие требованиям системы. Наборы тестов приведены ниже.

Разработаны 3 smoke-теста, которые предназначены для проверки общей работоспособности системы. Разработанные тесты описаны в таблице 5.

Таблица 5 -Smoke тесты

ID

Модуль

Подмодуль

Описание теста

Ожидаемый результат

SM_T_1

Веб-риложение

HomePage

Запуск приложения: 1.Ввести адрес в адресную строку браузера

1.Отображается домашняя страница с картой и боковой панелью поиска и первой страницей списка объявлений по умолчанию, на карте отображаются все объявления

SM_T_2

Веб-риложение

Authorization

Вход в систему с корректными данными: 1.Ввести логин ‘jenik90@tut.by’ и пароль ‘111111’ 2.Нажать кнопку ‘Войти’

1.Отобразится главная страница профиля в личном кабинете пользователя

SM_T_3

Веб-риложение

Authorization

Вход с некорректными данными: 1.Ввести логин ‘notexist@tut.by’ и пароль ‘111111’ 2.Нажать кнопку ‘Войти’

1.Поля формы заполнены введёнными данными. 2.Отображается сообщение о том, что логин или пароль введён неверно

 

Также разработан набор функциональных тестов. Разработанные тесты приведены в таблице 6.


Таблица 6 - Функциональные тесты

Иденти-фикатор

Модуль

Подмодуль/экран

Описание теста

Ожидаемый результат

CP-Т_1

веб-приложение

Search

Поиск объявления по фильтрам, задать значения основным фильтрам  1.Перейти на домашнюю страницу  2.Выбрать категорию объявления 3.Выбрать город  4.Выбрать период для отображения  5.Задать на слайдере диапозон стоимости

1.Отображается домашняя страница с картой и боковой панелью поиска и первой страницей списка объявлений по умолчанию, на карте отображаются все объявления  2.Отображается первая страница списка объявлений выбранной категории, на карте отображаются все объявления для данной категории, где задан атрибут адреса  3. Отображается первая страница списка объявлений для выбранной категории и данного города, на карте отображаются все подходящие элементы, где задан атрибут адреса  4.Отображается первая страница списка объявлений для выбранной категории, выбранного города соответствующих указанному периоду, на карте отображаются все подходящие элементы, где задан атрибут адреса   5. Отображается первая страница списка объявлений для выбранной категории, выбранного города соответствующих указанному периоду и диапозону цен, на карте отображаются все подходящие элементы, где задан атрибут адреса фильтрам возле заданного адреса

CP-Т_2

веб-приложение

Search

Поиск объявления по фильтрам, задать значения основным и дополнительным фильтрам  1.Перейти на домашнюю страницу  2.Выбрать категорию объявления ‘Продажа собак’ 3.Выбрать город  4.Выбрать период для отображения  5.Задать на слайдере диапозон стоимости  6. Нажать кнопку ‘Дополнительно’ внизу блока фильтров 7. Выбрать породу из списка пород в появившемся блоке дополнительных параметров поиска  8.Выбрать пол в появившемся блоке дополнительных параметров

1.Отображается домашняя страница с картой и боковой панелью поиска и первой страницей списка объявлений по умолчанию, на карте отображаются все объявления  2.Отображается первая страница списка объявлений выбранной категории, на карте отображаются все объявления для данной категории, где задан атрибут адреса  3. Отображается первая страница списка объявлений для выбранной категории и данного города, на карте отображаются все подходящие элементы, где задан атрибут адреса  4.Отображается первая страница списка объявлений для выбранной категории, выбранного города соответствующих указанному периоду, на карте отображаются все подходящие элементы, где задан атрибут адреса   5. Отображается первая страница списка объявлений для выбранной категории, выбранного города соответствующих указанному периоду и диапозону цен, на карте отображаются все подходящие элементы, где задан атрибут адреса фильтрам возле заданного адреса 6.Отображается блок дополнительных параметров поиска (по породе и полу)  7.Отображается первая страница объявлений, соответствующих всем предыдущим фильтрам а также выбранной породе, на карте отображаются все подходящие элементы, где задан атрибут адреса  8.Отображается первая страница объявлений, соответствующих всем предыдущим фильтрам а также выбранной породе и заданному полу, на карте отображаются все подходящие элементы, где задан атрибут адреса

CP-Т_3

веб-приложение

Search

Проверка меток на карте 1.Перейти на домашнюю страницу  2.Задать произвольные значения фильтрам поиска 3.Кликнуть по любой метке на карте  4.Кликнуть кнопке ‘Подробнее’ у всплывшего возле метки краткого описания объявления

1.Отображается домашняя страница с картой и боковой панелью поиска и первой страницей списка объявлений по умолчанию, на карте отображаются все объявления  2.Отображается первая страница списка объявлений, подходящих заданным фильтрам, на карте отображаются все подходящие объявления, где задан атрибут адреса  3. Возле метки появляется блок с краткой информацией по объявлению  4.Отображается страница с подробной информацией по выбранному объявлению

CP-Т_4

веб-приложение

Sorting

Проверка сортировки результатов 1.Перейти на домашнюю страницу  2.Задать произвольные значения фильтрам поиска  3.Под блоком карты рядом с полем ‘сортировать по’ выбрать из списка значение ‘дате начать со старых’  4.Под блоком карты рядом с полем ‘сортировать по’ выбрать из списка значение ‘цене начать с дешевых’ 5.Под блоком карты рядом с полем ‘сортировать по’ выбрать из списка значение ‘цене начать с дорогих’ 6.Под блоком карты рядом с полем ‘сортировать по’ выбрать из списка значение ‘рейтингу’

1.Отображается домашняя страница с картой и боковой панелью поиска и первой страницей списка объявлений по умолчанию, на карте отображаются все объявления  2.Отображается первая страница списка объявлений, подходящих заданным фильтрам, на карте отображаются все подходящие объявления, где задан атрибут адреса  3. Отображается первая страница списка объявлений, подходящих заданным фильтрам и отсортированных по возрастанию по дате, на карте изменений не происходит  4. Отображается первая страница списка объявлений, подходящих заданным фильтрам и отсортированных по возрастанию по цене, на карте изменений не происходит  5. Отображается первая страница списка объявлений, подходящих заданным фильтрам и отсортированных по убыванию по цене, на карте изменений не происходит  6. Отображается первая страница списка объявлений, подходящих заданным фильтрам и отсортированных по убыванию по рейтингу, на карте изменений не происходит

CP-Т_5

веб-приложение

Authorization

Вход в личный кабинет 1.Перейти на домашнюю страницу 2.В блоке входа в личный кабинет нажать кнопку ‘Войти’  3.В блоке входа в поле ‘email’ввести существующий логин  4.В блоке входа в поле ‘пароль’ ввести неверный пароль 5.Нажать кнопку ‘Войти’ 6.В блоке входа в поля ‘email’ и ‘пароль’ ввести корректные значения для логина и пароля и нажать кнопку ‘Войти’

1. Отображается домашняя страница с картой и боковой панелью поиска и первой страницей списка объявлений по умолчанию, на карте отображаются все объявления  2.Возле поля ‘email’ появляется сообщение о необходимости указания emaila, возле поля ‘пароль’ появляется сообщение о необходимости указания пароля  3.Возле поля ‘email’ сообщение об ошибке исчезает   4.Возле поля ‘пароль’ сообщение об ошибке исчезает   5.В блоке входа появляется сообщение о неверном пароле   6.Отображается главная страница профиля личного кабинета

CP-Т_6

веб-приложение

Аccount

Регистрация пользователя 1.Перейти на домашнюю страницу 2.Нажать на меню Регистрация  3.Ввести в поле’ e-mail’ лат. буквами значение по шаблону *@*.* 4.Ввести поле ‘Пароль’ лат. значение не менее 6 знаков  5.Ввести в поле ‘подтвердите пароль’ тот же пароль  6.Заполнить поля ‘Имя’ и ‘Фамилия’ буквенными значениями не менее 2-х символов  7.Выбрать пол, поставить чекбокс о согласии с условиями сайта 8.Нажать кнопку ‘Зарегистрироваться’

1. Отображается домашняя страница с картой и боковой панелью поиска и первой страницей списка объявлений по умолчанию, на карте отображаются все объявления  2.Отображается страница с формой для регистрации пользователя  3.Отображается введенный email в поле email  4. В поле пароль отобразятся точки вместо вводимых символов 5. В поле ‘Подтвердите пароль’ отобразятся точки вместо вводимых символов  6. Поля ‘Имя’ и ‘Фамилия’ заполнятся ответствующими значениями 7.Радиокнопка отобразится возле соответствующего значения пола, чекбокс пометится как отмеченный 8.Произойдет регистрация нового пользователя в системе и отобразится главная страница профиля зарегистрированного пользователя

CP-Т_7

веб-приложение

Аccount

Редактирование личных данных пользователя  1.Войти в существующий личный кабинет на сайте  2.Нажать кнопку ‘Редактировать’ под фотографией-аватаркой на главной странице профиля 3.Заполнить поля в отобразившейся форме новыми корректными значениями  4.Нажать кнопку ‘Выбрать файл’ для загрузки фотографии  5.В появившемся диалоговом окне выбрать файл фотографии и нажать кнопку ‘Открыть’  6.Нажать кнопку ‘Сохранить’

1.Отобразится главная страница профиля в личном кабинете пользователя  2.Отобразится форма для редактирования личных данных со старыми значениями в полях для заполнения   3.В полях для заполнения появляются новые значения   4.Отобразится диалоговое окно для загрузки фотографии   5.На странице рядом с кнопкой ‘Выберите файл’ появляется изображение выбранной в диалоговом окне фотографии, также рядом с кнопкой отображается имя загружаемого файла  6.Отображается главная страница профиля в личном кабинете пользователя с новыми значениями данных, в слайдере фотографий появляется новая загруженная фотография

CP-Т_8

веб-приложение

Аccount

Добавление питомца в личном кабинете  1.Войти в личный кабинет на сайте 2.Нажать ссылку ‘Мои питомцы’ в левой боковой навигационной панели  3.Нажать ссылку ‘Создать питомца’ в конце списка питомцев 4.Заполнить появившуюся форму корректными данными о питомце 5.Нажать ссылку ‘Добавить’ возле метки ‘Награды’ на форме  6.Заполнить появившееся поле информацией о награде  7.Повторить пункты 4-5 для создания у питомица списка наград 8.Нажать кнопку ‘Выбрать файл’ для загрузки фотографии  9.В появившемся диалоговом окне выбрать файл фотографии и нажать кнопку ‘Открыть’ 10.Нажать кнопку ‘Добавить фото’ 11.Повторить пункты 8-10 несколько раз для подготовки нескольких фотографий для загрузки на сервер 12.Нажать кнопку ‘Сохранить’

1.Отобразится главная страница профиля в личном кабинете пользователя  2.Отобразится списокпитомцев пользователя с краткой информацией по каждому из них   3.Отобразится форма для добавления нового питомца с пустыми полями   4.Поля заполняются введенными данными 5.Появиться поле для информации о награде  6.В появившемся поле отображается введенная информация   7.В блоке ‘Награды’ появляется группа заполненных полей о наградах питомца   8.Отобразится диалоговое окно для загрузки фотографии   9.На странице рядом с кнопкой ‘Выберите файл’ появляется изображение выбранной в диалоговом окне фотографии, также рядом с кнопкой отображается имя загружаемого файла  10.Появляется дополнительный блок для загрузки фотографии   11. В блоке ‘Фотографии’ появляется группа выбранных для загрузки на сервер фотографий питомца   12.Отображается страница с подробной информацией о созданном питомце, на слайдере появляются загруженные фотографии

CP-Т_9

веб-приложение

Account

Добавление объявления в личном кабинете  1.Войти в личный кабинет на сайте 2.Нажать ссылку ‘Мои объявления’ в левой боковой навигационной панели  3.Нажать ссылку ‘Добавить объявление’ в конце списка объявлений 4.Заполнить появившуюся форму корректными данными о новом объявлении  5.Нажать кнопку ‘Выбрать файл’ для загрузки фотографии  6.В появившемся диалоговом окне выбрать файл фотографии и нажать кнопку ‘Открыть’  7.Нажать кнопку ‘Добавить фото’ 8.Повторить пункты 8-10 несколько раз для подготовки нескольких фотографий для загрузки на сервер 9.Нажать кнопку ‘Сохранить’

1.Отобразится главная страница профиля в личном кабинете пользователя  2.Отобразится список объявлений пользователя с краткой информацией по каждому из них   3.Отобразится форма для добавления нового объявления с незаполненными полями 4.Поля формы заполняются введенными данными  5.Отобразится диалоговое окно для загрузки фотографии      6.На странице рядом с кнопкой ‘Выберите файл’ появляется изображение выбранной в диалоговом окне фотографии, также рядом с кнопкой отображается имя загружаемого файла  7.Появляется дополнительный блок для загрузки фотографии   8.В блоке ‘Фотографии’ появляется группа выбранных для загрузки на сервер фотографий для объявления   9.Отображается страница с подробной информацией о созданном объявлении, на слайдере появляются загруженные фотографии

CP-Т_10

веб-приложение

Messages

Отправка личного сообщения пользователю  1.Войти в личный кабинет на сайте 2.Нажать ссылку ‘Мои друзья’ в левой боковой навигационной панели  3.В отобразившемся списке пользователей нажать ссылку подробнее возле интересующего пользователя  4.Нажать кнопку ‘Отправить сообщение’ под фотографией пользователя  5.Заполнить поле для текста сообщения и нажать кнопку ‘Отправить’  6.Нажать ссылку ‘Выйти’ в блоке заголовка сайта  7.Ввести логин и пароль пользователя, которому было отправлено сообщение и нажать кнопку ‘Войти’  8.Нажать ссылку ‘Мои сообщения’ в левой боковой навигационной панели  9.В отобразившемся списке диалогов нажать на диалог, где отправитель это предыдущий авторизированный пользователь

1.Отобразится главная страница профиля в личном кабинете пользователя  2.Отобразится список друзей пользователя с краткой информацией по каждому из них   3.Отобразится страница с подробной информацией о выбранном пользователе  4.Отобразится страница с историей переписки с данным пользователем и форма для отправки нового сообщения, состоящая из одного поля   5.Поле заполнится введенными данными и после нажатия ‘Отправить’ появится как последнее сообщение в истории переписки, при этом поле для ввода сообщения станет пустым  6.Отобразится домашняя страница формой для входя в личный кабинет и ссылкой на регистрацию в левой боковой панели   7. Отобразится главная страница профиля в личном кабинете пользователя, возле ссылки ‘Мои сообщения отображена пометка с количеством непрочитанных сообщений’  8.Отображается список диалогов пользователя, отсортированный по убывания по времени, в каждом диалоге отображено последнее сообщение (или его начало, если сообщение длинное) в истории переписки, диалог с предыдущим авторизированным пользователем подсвечен затемненным фонов и имеет пометку с количеством непрочитанных в этом диалоге сообщений   9. Отображается история переписки с последним с предыдущим авторизированным пользователем, последнее сообщение в истории это сообщение отправленное на предыдущем этапе теста. Рядом со ссылкой ‘Мои сообщения’ пометка количества непрочитанных сообщение уменьшается на число сообщение, которые были только что просмотрены в данном диалоге

CP-Т_11

веб-приложение

Messages

Проверка уведомления о пришедшем сообщении вне режима работы с диалогом  Для реализации теста необходима работа на двух компьютерах одновременно под двумя разными аккаунтами на сайте  В ПЕРВОМ АККАУНТЕ:. 1.Войти в личный кабинет на сайте 2.Нажать ссылку ‘Мои друзья’ в левой боковой навигационной панели  3.В отобразившемся списке пользователей нажать ссылку подробнее возле пользователя обладателя второго аккаунта в тесте 4.Нажать кнопку ‘Отправить сообщение’ под фотографией пользователя  ВО ВТОРОМ АККАУНТЕ:. 5.Войти в личный кабинет на сайте В ПЕРВОМ АККАУНТЕ: 6.Заполнить поле для текста сообщения и нажать кнопку ‘Отправить’

В ПЕРВОМ АККАУНТЕ:. 1.Отобразится главная страница профиля в личном кабинете пользователя  2.Отобразится список друзей пользователя с краткой информацией по каждому из них   3.Отобразится страница с подробной информацией о выбранном пользователе  4.Отобразится страница с историей переписки с данным пользователем и форма для отправки нового сообщения, состоящая из одного поля ВО ВТОРОМ АККАУНТЕ:  5. Отобразится главная страница профиля в личном кабинете пользователя  6. В ПЕРВОМ АККАУНТЕ:.  Поле заполнится введенными данными и после нажатия ‘Отправить’ появится как последнее сообщение в истории переписки, при этом поле для ввода сообщения станет пустым ВО ВТОРОМ АККАУНТЕ:.  Возле ссылки ‘Мои сообщения пометка с количеством непрочитанных сообщений’увеличивается на 1, а в случае отсутствия ранее непрочитанных сообщений становится равной ‘(+1)’, кроме того срабатывает звуковой сигнал, оповещающий о пришедшем сообщении

CP-Т_12

веб-приложение

Messages

Проверка уведомления о пришедшем сообщении в режиме работы с диалогом  Для реализации теста необходима работа на двух компьютерах одновременно под двумя разными аккаунтами на сайте  В ПЕРВОМ АККАУНТЕ:. 1.Войти в личный кабинет на сайте 2.Нажать ссылку ‘Мои друзья’ в левой боковой навигационной панели  3.В отобразившемся списке пользователей нажать ссылку подробнее возле пользователя обладателя второго аккаунта в тесте 4.Нажать кнопку ‘Отправить сообщение’ под фотографией пользователя  ВО ВТОРОМ АККАУНТЕ:. 5.Войти в личный кабинет на сайте 6. Нажать ссылку ‘Мои диалоги’ в левой боковой навигационной панели  7. В списке диалогов выбрать диалог-переписку с пользователем обладателем первого аккаунта   В ПЕРВОМ АККАУНТЕ: 8.Заполнить поле для текста сообщения и нажать кнопку ‘Отправить’

В ПЕРВОМ АККАУНТЕ:. 1.Отобразится главная страница профиля в личном кабинете пользователя  2.Отобразится список друзей пользователя с краткой информацией по каждому из них   3.Отобразится страница с подробной информацией о выбранном пользователе  4.Отобразится страница с историей переписки с данным пользователем и форма для отправки нового сообщения, состоящая из одного поля ВО ВТОРОМ АККАУНТЕ:  5. Отобразится главная страница профиля в личном кабинете пользователя  6. Отобразится список диалогов пользователя  7. Отображается история переписки второго и первого полоьзователя  8. В ПЕРВОМ АККАУНТЕ:.  Поле заполнится введенными данными и после нажатия ‘Отправить’ появится как последнее сообщение в истории переписки, при этом поле для ввода сообщения станет пустым ВО ВТОРОМ АККАУНТЕ:.  срабатывает звуковой сигнал, оповещающий о пришедшем сообщении, в истории переписки появляется отправленное первым пользователем сообщение

CP-Т_13

веб-приложение

Profile

Проверка удаления объявления пользователем  1.Войти в личный кабинет на сайте 2.Нажать ссылку ‘Мои объявления’ в левой боковой навигационной панели  3.Нажать кнопку ‘Подробнее’ рядом с интересующим из списка объявлением  4.В открывшемся окне нажать кнопку ‘Удалить’  5.В появившемся диалоговом окне нажать кнопку ‘Отмена’  6.Повторить пункт 4  7.В появившемся диалоговом окне нажать кнопку ‘ОК’

1.Отобразится главная страница профиля в личном кабинете пользователя  2.Отобразится список объявлений пользователя с краткой информацией по каждому из них   3.Отобразится страница с подробной информацией о выбранном объявлении  4.Отобразится диалоговое окно о подтверждении удаления   5. Отобразится страница с подробной информацией об объявлении, удаление произведено не будет  6. Отобразится диалоговое окно о подтверждении удаления   7.Отобразится список объявлений пользователя без удаленного объявления

CP-Т_14

веб-приложение

Profile

Проверка удаления питомца пользователем  1.Войти в личный кабинет на сайте 2.Нажать ссылку ‘Мои питомцы’ в левой боковой навигационной панели  3.Нажать кнопку ‘Подробнее’ рядом с интересующим из списка питомцем  4.В открывшемся окне нажать кнопку ‘Удалить’  5.В появившемся диалоговом окне нажать кнопку ‘Отмена’  6.Повторить пункт 4  7.В появившемся диалоговом окне нажать кнопку ‘ОК’

1.Отобразится главная страница профиля в личном кабинете пользователя  2.Отобразится список питомцев пользователя с краткой информацией по каждому из них   3.Отобразится страница с подробной информацией о выбранном питомце  4.Отобразится диалоговое окно о подтверждении удаления   5. Отобразится страница с подробной информацией о питомце, удаление произведено не будет   6. Отобразится диалоговое окно о подтверждении удаления   7.Отобразится список питомцев пользователя без удаленного объявления

CP-Т_15

веб-приложение

Sorting

Проверка страничной пагинации 1.Перейти на домашнюю страницу  2.Задать произвольные значения фильтрам поиска  3.Нажать внизу страницы ссылку на 2-ую страницу  4.Нажать внизу страницы ссылку на 4-ую страницу  5.Нажать внизу страницы ссылку на последнюю страницу  6.Нажать внизу страницы ссылку на 1-ую страницу

1.Отображается домашняя страница с картой и боковой панелью поиска и первой страницей списка объявлений по умолчанию, на карте отображаются все объявления, внизу страницы ссылка на первую страницу будет подсвечена синим фоном и будет недоступна для нажатия, ссылки на остальные страницы будут иметь фон по умолчанию (белый)  2.Отображается первая страница списка объявлений, подходящих заданным фильтрам, на карте отображаются все подходящие объявления, где задан атрибут адреса, внизу страницы ссылка на первую страницу будет подсвечена синим фоном и будет недоступна для нажатия, ссылки на остальные страницы будут иметь фон по умолчанию (белый)    3. Отображается вторая страница списка объявлений, подходящих заданным фильтрам, на карте изменений не происходит, внизу страницы ссылка на вторую страницу будет подсвечена синим фоном и будет недоступна для нажатия, ссылки на остальные страницы будут иметь фон по умолчанию (белый)     4. Отображается четвертая страница списка объявлений, подходящих заданным фильтрам, на карте изменений не происходит, внизу страницы ссылка на 4-ую страницу будет подсвечена синим фоном и будет недоступна для нажатия, ссылки на остальные страницы будут иметь фон по умолчанию (белый)     5. Отображается последняя страница списка объявлений, подходящих заданным фильтрам, на карте изменений не происходит, внизу страницы ссылка на последнюю страницу будет подсвечена синим фоном и будет недоступна для нажатия, ссылки на остальные страницы будут иметь фон по умолчанию (белый)     6. Вновь отображается первая страница списка объявлений, подходящих заданным фильтрам, на карте изменений не происходит, внизу страницы ссылка на первую страницу будет подсвечена синим фоном и будет недоступна для нажатия, ссылки на остальные страницы будут иметь фон по умолчанию (белый)



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

Заключение

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

Был произведен глубокий анализ предметной области, разработана спецификация требований к системе, выявлены бизнес-акторы и их функции. Были определены основные варианты использования системы в рамках действий бизнес-акторов и выявлен основной функционал приложения для его реализации. Полученное в результате приложение позволяет пользователям создавать на сайте личный кабинет, в котором предоставляется возможность создавать страницы своим питомцам а также публиковать объявления в различных рубриках. Реализована возможность оставлять отзывы объявлениям, а также возможность обмена личными сообщениями.

Полученное в итоге приложение имеет многоуровневую архитектуру (модель представления, модель бизнес-логики, модель данных). Каждый уровень является изолированным и мало связан логически с другими уровнями. Связь уровней осуществляется через реализацию интерфейсов, что позволяет легко модифицировать приложение, вносить изменение и заменять некоторые модули программы на другие без серьезных проблем с перестроением приложения и рефакторингом. Так например можно легко заменить модель данных, используя в качестве хранилища не БД а XML-файл или CSV-файл. Для этого лишь надо реализовать несколько публичных интерфейсов, избегая при этом масштабного рефакторинга кода.

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

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

В ходе работы над проектом были приобретены практические навыки использования таких технологий как ASP.NET MVC5, JQuery, JavaScript. Также были изучены основы работы с Entity Framework 6, Ajax, DI (Dependency Injection), Html и CSS. Кроме того в проекте использовалось сторонний API (Yandex maps) и были получены навыки работы с ним. Также использовались некоторые специфичные библиотеки JavaScript (слайдер фотографий, слайдер для диапозона цен, диалоговые окна).

Список использованных источников

1.      Фримен, А. ASP.NET MVC 4 с примерами на C# 5.0 для профессионалов. 4-е изд. - Россия: Вильямс, 2013. - 688 с.

.        Фримен, А. JQuery для профессионалов- Москва-Питер-Киев: Вильямс, 2013. - 953 с.

.        Нейгел, К. C#5.0 и платформа.NET 4.5 для профессионалов-СПб: Москва-Питер-Киев: Вильямс, 2014. - 1435 с.

.        Рихтер, Д. CLR via C#. Программирование на платформе Microsoft.NET Framework 4.5 на языке C#. - СпБ.: Питер, 2013. - 896 с

.        Дейт Дж. Кристофер Введение в системы баз данных. - М.: дом "Вильяме", 2005. - 8-е издание 1328 с.

.        Герберт Шилдт. C# 4.0: полное руководство - М.: «Вильямс», 2011. - 1056 с.

7.      Yandex Maps APIs [Электронный ресурс]. - Режим доступа: https://tech.yandex.ru/maps/

.        Bootstrap 3 [Электронный ресурс]. - Режим доступа: http://getbootstrap.com/

.        Руководство по ASP.NET MVC 5 [Электронный ресурс]. - Режим доступа: http://metanit.com/sharp/mvc5/

.        Симан, М. Внедрение зависимостей в.NET. - СпБ.: Питер, 2013. -464 с.

.        Голдштейн, С. Оптимизация приложений на платформе.Net. /Голдштейн С., Зурбалев Д., Флатов И. - Россия: ДМК Пресс, 2014. - 524с.

.        Microsoft developer Network [Электронный ресурс]. - Режим доступа: https://msdn.microsoft.com/ru-ru/.

.        Тамре, Л. Введение в тестирование программного обеспечения / Л. Тамре - М.: Вильямс, 2003 - 368с.

Приложения

Приложение А

Код программы

using System;

using System.Collections.Generic;System.Linq;MyPet.Domain.Abstract;MyPet.Domain.Entities;System.Data.Entity;MyPet.Domain.Concrete

{class AdvRepository: IRepository<Advertizement>

{EFDbContext context = new EFDbContext();IEnumerable<Advertizement> GetAll()

{context.Advertizements.Include("Category").Include("DogBreed").Include("AdvertizementOwner");

}Advertizement Get(int id)

{context.Advertizements.Include("Category").Include("DogBreed").Include("AdvertizementOwner").FirstOrDefault(x=>x.AdvertizementId==id);

}void Create(Advertizement adv)

{.Advertizements.Add(adv);

}void Update(Advertizement adv)

{.Entry(adv).State = EntityState.Modified;

}IEnumerable<Advertizement> Find(Func<Advertizement, Boolean> predicate)

{context.Advertizements.Include("Category").Include("DogBreed").Include("AdvertizementOwner").Where(predicate).ToList();

}void Delete(int id)

{adv = context.Advertizements.Find(id);(adv != null).Advertizements.Remove(adv);

}void Save()

{.SaveChanges();

}

}

}MyPet.Domain.Entities;System.Data.Entity;System.Data.Entity.ModelConfiguration.Conventions;MyPet.Domain.Concrete

{class EFDbContext: DbContext

{DbSet<Reward> Rewards { get; set; }DbSet<Role> Roles { get; set; }DbSet<User> Users { get; set; }DbSet<DogBreed> DogBreeds { get; set; }DbSet<Profile> Profiles { get; set; }DbSet<Pet> Pets { get; set; }DbSet<Message> Messages { get; set; }DbSet<Friend> Friends { get; set; }DbSet<Advertizement> Advertizements { get; set; }DbSet<AdvertizementCategory> AdvertizementCategories { get; set; }DbSet<Review> Reviews { get; set; }

}

}System.Linq;System.Collections.Generic;System.Web;System.Web.Mvc;System.Security.Claims;System.Data.Entity;Microsoft.Owin.Security;MyPet.WebUI.Models.ViewModels; // пространство имен LoginViewModel

using MyPet.Domain.Entities; // пространство имен моделей

using MyPet.Domain.Concrete;MyPet.Domain.Abstract;System.Threading.Tasks;MyPet.WebUI.Models;System.IO;System;ImageResizer;System.Text.RegularExpressions;MyPet.WebUI.Controllers

{class AccountController: Controller

{IMyPetRepository repository;IRepository<User> userRepository;IRepository<Profile> profRepository;AccountController(IRepository<User> userRepo, IMyPetRepository repoAll, IRepository<Profile>profRepo)

{.userRepository = userRepo;.repository = repoAll;.profRepository = profRepo;

}IAuthenticationManager AuthenticationManager

{

{HttpContext.GetOwinContext().Authentication;

}

}

[HttpPost]ActionResult Login(LoginModel model)

{(ModelState.IsValid)

{user = userRepository.GetAll().FirstOrDefault(u => u.Email == model.Email && u.Password == model.Password);(user == null)

{.AddModelError("", "Неверный логин или пароль.");

}

{.Authorise(user);RedirectToAction("Details", new { id=user.UserId});

}

}View(model);

}ActionResult Logout()

{.SignOut();RedirectToAction("List", "Advertizements");

}

[NonAction]ActionResult UserProfile(int id=0) // bottlenick!! The aportunity to authorise not through login-password

{(!Request.IsAuthenticated)

{curUser=userRepository.Get(id);.Authorise(curUser);

}= User.Identity.GetUserId<int>();RedirectToAction("Details", new { id});

}ActionResult List(int id=0)

{(id == 0)View(userRepository.GetAll().Select(x=>x.Profile));

{user = userRepository.Find(x => x.UserId == id);(user != null)

{View(user.Select(x => x.Profile));

}RedirectToAction("UserProfile", "Account", new { id = User.Identity.GetUserId<int>() });

}

}

[Authorize]ActionResult Details(int id)

{user = userRepository.Get(id);.FillViewBag(id);path = Server.MapPath(String.Concat("~/ProfilePhotos/", user.UserId.ToString().Trim(), "/").Trim());avatarPath = String.Concat("small", this.GetMinNumberInFileNames(path).ToString()).Trim();["AvatarPath"] = avatarPath;(user != null)View(user.Profile);return("Details", "Account", new { id = User.Identity.GetUserId<int>() });

}

[Authorize]ActionResult Edit()

{id = User.Identity.GetUserId<int>();prof = userRepository.Get(id).Profile;(prof != null)

{.Cities = new string[] { "Минск" }.Concat(repository.Advertizements.Select(x => x.City).OrderBy(x => x)).Distinct();View(prof);

}return("Details", "Account", new { id = User.Identity.GetUserId<int>() });

}

[HttpPost]

[Authorize]ActionResult Edit(Profile prof, HttpPostedFileBase[] uploads)

{id = User.Identity.GetUserId<int>();(ModelState.IsValid && prof.UserId == id)

{(uploads.Where(x => x!=null).FirstOrDefault() != null && prof.HaveImages == false).HaveImages = true;.Update(prof);.Save();= profRepository.Get(prof.UserId);

{(uploads != null && uploads.Where(x => x!=null).FirstOrDefault() != null)

{path = Server.MapPath(String.Concat("~/ProfilePhotos/", prof.UserId.ToString().Trim(), "/").Trim());i = this.GetMaxNumberInFileNames(path) + 1;(var upload in uploads)

{(upload != null)

{(upload.ContentLength > 0)

{versions = new Dictionary<string, string>();

//Define the versions to generate.Add("big", "maxwidth=768&maxheight=1024&format=jpg");.Add("small", "maxwidth=240&maxheight=320&format=jpg");.Add("tiny", "&maxheight=108&format=jpg");//maxwidth=81

//Generate each version(var suffix in versions.Keys)

{.InputStream.Seek(0, SeekOrigin.Begin);

//Let the image builder add the correct extension based on the output file type.Current.Build(ImageJob(upload.InputStream,.Concat(path, suffix, i.ToString()),Instructions(versions[suffix]),, true));

}

}++;

}

}

}

}(NullReferenceException) { }RedirectToAction("Details", new { id = prof.UserId });

}

{.Cities = new string[] { "Минск" }.Concat(repository.Advertizements.Select(x => x.City).OrderBy(x => x)).Distinct();View(prof);

}

}

[Authorize]ActionResult EditProfileAjax()

{id = User.Identity.GetUserId<int>();prof = userRepository.Get(id).Profile;.Cities = new string[] { "Минск" }.Concat(repository.Advertizements.Select(x => x.City).OrderBy(x => x)).Distinct();(prof != null)PartialView(prof);return("UserProfile", "Account", new { id });

}

[HttpPost]

[Authorize]ActionResult EditProfileAjax(Profile prof)

{id=User.Identity.GetUserId<int>();(ModelState.IsValid && prof.UserId==id)

{.Get(id).Profile = prof;.Save();.FillViewBag(id);PartialView("DetailsPartial", userRepository.Get(prof.UserId).Profile);

}.Cities = new string[] { "Минск" }.Concat(repository.Advertizements.Select(x => x.City).OrderBy(x => x)).Distinct();PartialView(prof);

}

[ValidateAntiForgeryToken]void Authorise(User user)

{claim = new ClaimsIdentity("ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString(), ClaimValueTypes.String));.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email, ClaimValueTypes.String));.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",

"OWIN Provider", ClaimValueTypes.String));(user.Role != null).AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, user.Role.Name, ClaimValueTypes.String));.SignOut();.SignIn(new AuthenticationProperties{IsPersistent = true}, claim);

}void FillViewBag(int id)

{pets = repository.Pets.Where(x => x.UserId == id);.Pets = pets;advs = repository.Advertizements.Where(x => x.AdvertizementOwnerId == id);.Advs = advs;

}PartialViewResult Vidget()

{userId;profile=null;name;(Request.IsAuthenticated && int.TryParse(User.Identity.GetUserId<int>().ToString(), out userId))

{= userRepository.Get(userId).Profile;

}(profile != null)= profile.FirstName + " " + profile.LastName;name = null;PartialView((object)name);

}ActionResult GetImg(string id)

{file=new FileInfo(Server.MapPath("~/Avatars/default.jpg"));(id != null)

{[] strs = id.Split(new char[] { '-' });(strs.Length == 2 && (new FileInfo(Server.MapPath(String.Concat("~/ProfilePhotos/", strs[0].Trim(), "/", strs[1], ".jpg").Trim()))).Exists)

{= new FileInfo(Server.MapPath(String.Concat("~/ProfilePhotos/", strs[0].Trim(), "/", strs[1], ".jpg").Trim()));

}

{prof=profRepository.Find(x=>x.UserId==int.Parse(strs[0])).FirstOrDefault();(prof.Sex)= new FileInfo(Server.MapPath("~/Avatars/default.jpg"));= new FileInfo(Server.MapPath("~/Avatars/default2.jpg"));

}(file.Exists)File(file.FullName, "text/plain", file.Name);return Content("");

}

[Authorize]

[HttpPost]ActionResult DeletePhoto(DeletePhotoModel delPhModel)

{authUserId = User.Identity.GetUserId<int>();(delPhModel.WhoCanDeleteId == authUserId && delPhModel.OwnerId != 0 && delPhModel.PhotoNumber != 0)

{di = new DirectoryInfo(Server.MapPath(String.Concat("~/ProfilePhotos/", delPhModel.OwnerId.ToString().Trim()).Trim()));(di.Exists)

{(var file in di.GetFiles())

{fotoNumberLenght = delPhModel.PhotoNumber.ToString().Length;fileNameLength = Path.GetFileNameWithoutExtension(file.Name).Length;(file.Name.Contains(delPhModel.PhotoNumber.ToString()) &&

(Path.GetFileNameWithoutExtension(file.Name).LastIndexOf(delPhModel.PhotoNumber.ToString()) == fileNameLength - fotoNumberLenght))

{.Delete();

}

}(di.GetFiles().Length == 0)

{.Delete();prof = profRepository.Find(x => x.UserId == delPhModel.OwnerId).FirstOrDefault();(prof != null).HaveImages = false;.Save();

}

}

}null;

}int GetMaxNumberInFileNames(string path)

{

//var path = Server.MapPath(String.Concat("~/ProfilePhotos/", prof.UserId.ToString().Trim(), "/").Trim());.CreateDirectory(path);di = new DirectoryInfo(path);r = new Regex(@"\d+");ddd = di.GetFiles().Select(x => x.Name).FirstOrDefault();teg;maxNumberInFileNames = 0;(string fileName in di.GetFiles().Select(x => x.Name))

{= r.Match(fileName);(teg.Success && int.Parse(teg.ToString()) > maxNumberInFileNames)= int.Parse(teg.ToString());

}maxNumberInFileNames;

}int GetMinNumberInFileNames(string path)

{.CreateDirectory(path);di = new DirectoryInfo(path);r = new Regex(@"\d+");ddd = di.GetFiles().Select(x => x.Name).FirstOrDefault();teg;minNumberInFileNames = int.MaxValue;(string fileName in di.GetFiles().Select(x => x.Name))

{= r.Match(fileName);(teg.Success && int.Parse(teg.ToString()) < minNumberInFileNames)= int.Parse(teg.ToString());

}minNumberInFileNames==int.MaxValue?0:minNumberInFileNames;

}

}

}

using System;System.Collections.Generic;System.Linq;System.Web;System.Web.Mvc;MyPet.Domain.Abstract;MyPet.Domain.Entities;MyPet.WebUI.Models;MyPet.WebUI.Models.ViewModels;System.IO;ImageResizer;MyPet.WebUI.Controllers

{class AdvertizementsController: Controller

{

// GET: AdvertizementsIMyPetRepository repository;IRepository<Advertizement> advRepository;int PageSize = 3;AdvertizementsController(IMyPetRepository repo, IRepository<Advertizement> advrepo)

{.repository = repo;.advRepository = advrepo;

}ActionResult MyAdvs(int? id)

{(id == null)= User.Identity.GetUserId<int>();advs = advRepository.Find(x => x.AdvertizementOwnerId == id);(advs != null)View(advs);RedirectToAction("List");

}ActionResult Details(int id)

{adv = advRepository.Get(id);<string[]> advPhotos=null;(adv != null)

{dir = new DirectoryInfo(Server.MapPath(String.Concat("~/AdvPhotos/", id).Trim()));(dir!=null && dir.Exists)

{= new List<string[]>();<FileInfo> files = dir.GetFiles();[] curFiles;filesGroupCount=files.Where(x=>x.Name.Contains("tiny")).Count();(int i=1; i<filesGroupCount+1; i++)

{= new string[3];[0] = Path.GetFileNameWithoutExtension(files.FirstOrDefault(x => x.Name.Contains("big") && x.Name.Contains(i.ToString())).Name);[1] = Path.GetFileNameWithoutExtension(files.FirstOrDefault(x => x.Name.Contains("small") && x.Name.Contains(i.ToString())).Name);[2]=Path.GetFileNameWithoutExtension(files.FirstOrDefault(x=>x.Name.Contains("tiny") && x.Name.Contains(i.ToString())).Name);.Add(curFiles);

}

}["AdvPhotos"] = advPhotos;.ReviewsCount = (repository.Reviews.Where(x => x.AdvertizementId == adv.AdvertizementId).Count());.AdvsCount = advRepository.Find(x => (x.AdvertizementOwnerId == adv.AdvertizementOwnerId) && (x.AdvertizementId != id)).Count();View(adv);

}return("Details", "Account", new { id = User.Identity.GetUserId<int>() });

}ActionResult Create()

{adv = new Advertizement();.Categories = repository.AdvertizementCategories;.DogBreeds = repository.DogBreeds;View(adv);

}

[HttpPost]ActionResult Create(Advertizement adv, HttpPostedFileBase[] uploads)

{(ModelState.IsValid)

{.DateAndTime = DateTime.Now;.AdvertizementOwnerId = User.Identity.GetUserId<int>();.City = repository.Profiles.FirstOrDefault(x => x.UserId == User.Identity.GetUserId<int>()).City;(uploads.Where(x => x.ContentLength > 0).FirstOrDefault() != null).HaveImages = true;.Create(adv);.Save();= advRepository.Get(adv.AdvertizementId);(uploads!=null && uploads.Where(x=>x.ContentLength>0).FirstOrDefault()!=null)

{path = Server.MapPath(String.Concat("~/AdvPhotos/", adv.AdvertizementId.ToString().Trim(), "/").Trim());.CreateDirectory(path);i=1;(var upload in uploads)

{(upload.ContentLength>0)

{versions = new Dictionary<string, string>();

//Define the versions to generate.Add("big", "maxwidth=768&maxheight=1024&format=jpg");.Add("small", "maxwidth=240&maxheight=320&format=jpg");.Add("tiny", "&maxheight=108&format=jpg");//maxwidth=81

//Generate each version(var suffix in versions.Keys)

{.InputStream.Seek(0, SeekOrigin.Begin);

//Let the image builder add the correct extension based on the output file type.Current.Build(ImageJob(upload.InputStream,.Concat(path, suffix, i.ToString()),Instructions(versions[suffix]),,true));

}

}++;

}

}.Advs = advRepository.Find(x => x.AdvertizementOwnerId == adv.AdvertizementOwnerId);RedirectToAction("Details", new { id=adv.AdvertizementId});

}

{.Categories = repository.AdvertizementCategories;.DogBreeds = repository.DogBreeds;View(adv);

}

}ActionResult Edit(int id)

{adv = advRepository.Get(id);(adv != null)

{.Categories = repository.AdvertizementCategories;.DogBreeds = repository.DogBreeds;View(adv);

}return("Details", "Account", new { id = User.Identity.GetUserId<int>() });

}

[HttpPost]ActionResult Edit(Advertizement adv, HttpPostedFileBase[]uploads)

{(ModelState.IsValid)

{.DateAndTime = DateTime.Now;.City = repository.Profiles.FirstOrDefault(x => x.UserId == User.Identity.GetUserId<int>()).City;(uploads.Where(x => x != null).FirstOrDefault() != null && adv.HaveImages == false).HaveImages = true;.Update(adv);.Save();= advRepository.Get(adv.AdvertizementId);

{(uploads != null && uploads.Where(x=>x.ContentLength>0).FirstOrDefault()!=null)

{path = Server.MapPath(String.Concat("~/AdvPhotos/", adv.AdvertizementId.ToString().Trim(), "/").Trim());.CreateDirectory(path);di = new DirectoryInfo(path);existingPhotoCount = di.GetFiles().Length / 3;

// здесь вместо i надо указать номер файла следующий за последним в директории

int i = existingPhotoCount + 1;(var upload in uploads)

{(upload != null)

{(upload.ContentLength > 0)

{versions = new Dictionary<string, string>();

//Define the versions to generate.Add("big", "maxwidth=768&maxheight=1024&format=jpg");.Add("small", "maxwidth=240&maxheight=320&format=jpg");.Add("tiny", "&maxheight=108&format=jpg");//maxwidth=81

//Generate each version(var suffix in versions.Keys)

{.InputStream.Seek(0, SeekOrigin.Begin);

//Let the image builder add the correct extension based on the output file type.Current.Build(ImageJob(upload.InputStream,.Concat(path, suffix, i.ToString()),Instructions(versions[suffix]),, true));

}

}++;

}

}

}

}(NullReferenceException) { }.Advs = advRepository.Find(x => x.AdvertizementOwnerId == adv.AdvertizementOwnerId);RedirectToAction("Details", new { id = adv.AdvertizementId });

}

{.Categories = repository.AdvertizementCategories;.DogBreeds = repository.DogBreeds;View(adv);

}

}

//public ActionResult EditAjax(int id)

//{

// var adv = advRepository.Get(id);

// if (adv != null)

// {

// ViewBag.Categories = repository.AdvertizementCategories;

// ViewBag.DogBreeds = repository.DogBreeds;

// return PartialView(adv);

// }

// else return

// RedirectToAction("Details", "Account", new { id = User.Identity.GetUserId<int>() });

//}

//[HttpPost]

//public ActionResult EditAjax(Advertizement adv)

//{

// int id = User.Identity.GetUserId<int>();

// if (ModelState.IsValid && adv.AdvertizementOwnerId == id)

// {

// advRepository.Update(adv);

// advRepository.Save();

// adv = advRepository.Get(adv.AdvertizementId);

// return PartialView("DetailsPartial", adv);

// }

// return PartialView(adv);

//}

[Authorize]ActionResult Delete(int id)

{adv=advRepository.Get(id);userid = User.Identity.GetUserId<int>();(adv != null && adv.AdvertizementOwnerId == userid)

{.Delete(id);.Save();path = Server.MapPath(String.Concat("~/AdvPhotos/", id.ToString().Trim(), "/"));di = new DirectoryInfo(path);(di!=null && di.Exists)

{(var file in di.GetFiles())

{.Delete();

}.Delete();

}RedirectToAction("MyAdvs");

}return RedirectToAction("DetailsPartial", "Account", new { id = userid });

}

[Authorize]

[HttpPost]ActionResult DeletePhoto(DeletePhotoModel delPhModel)

{authUserId = User.Identity.GetUserId<int>();(delPhModel.WhoCanDeleteId == authUserId && delPhModel.OwnerId!=0 && delPhModel.PhotoNumber!=0)

{di = new DirectoryInfo(Server.MapPath(String.Concat("~/AdvPhotos/",delPhModel.OwnerId.ToString().Trim()).Trim()));(di.Exists)

{(var file in di.GetFiles())

{fotoNumberLenght=delPhModel.PhotoNumber.ToString().Length;fileNameLength=Path.GetFileNameWithoutExtension(file.Name).Length;(file.Name.Contains(delPhModel.PhotoNumber.ToString()) &&

(Path.GetFileNameWithoutExtension(file.Name).LastIndexOf(delPhModel.PhotoNumber.ToString()) == fileNameLength-fotoNumberLenght))

{.Delete();

}

}(di.GetFiles().Length==0)

{.Delete();adv=advRepository.Find(x => x.AdvertizementId == delPhModel.OwnerId).FirstOrDefault();(adv != null).HaveImages = false;

}

}

}null;

}ViewResult List()

{View();

}PartialViewResult GetAdvData(int id = 1)

{pi = new PagingInfo { CurrentPage = id, ItemsPerPage = PageSize, TotalItems = advRepository.GetAll().Count() };si = new SearchInfo

{= SortWay.MaxDatesFirst,= pi

};model = new AdvsListViewModel { Advs = si.FilterAdvs(advRepository.GetAll()), PagingInfo = pi };PartialView(model);

}PartialViewResult Filter()

{PartialView(FillSearchInfoByLists(SearchInfo {PagingInfo=new PagingInfo

{= 1,= PageSize,= advRepository.GetAll().Count()

}, WayToSort=SortWay.MaxDatesFirst }));

}

[HttpPost]PartialViewResult Filter(SearchInfo si)

{

//System.Threading.Thread.Sleep(3000);<Advertizement> advs =si.FilterAdvs(advRepository.GetAll());pi = si.PagingInfo;.ItemsPerPage = this.PageSize;PartialView("GetAdvData", new AdvsListViewModel { Advs=advs, PagingInfo=pi});

}ActionResult GetImg(string id)

{

//string path = (filename == null || filename == "") ? @"D:\ИИТ БГУИР\Курсач\MyRepetitor\MyRepetitor.WebUI\AdvPhotos\73\" + filename + ".jpg": (@"D:\ИИТ БГУИР\Курсач\MyRepetitor\MyRepetitor.WebUI\AdvPics\default.jpg");(id != null)

{[] strs = id.Split(new char[] { '-' });(strs.Length<2)Content("");file;= new FileInfo(Server.MapPath(String.Concat("~/AdvPhotos/",strs[0].Trim(),"/", strs[1], ".jpg").Trim()));(file.Exists)File(file.FullName, "text/plain", file.Name);return Content("");

}return Content("");

}

// JsonResults methods for ajax requestsJsonResult GetAdvsJson(SearchInfo si)

{data = si.FilterAdvsWithoutPaging(advRepository.GetAll()).Select(a => new {= a.Topic,= Url.Action("Details", "Advertizements", new { id=a.AdvertizementId}),= String.Concat(a.City??"", ", ", a.Adress??""),=a.Price,=String.Concat(a.AdvertizementOwner.FirstName, " ", a.AdvertizementOwner.LastName),= Url.Action("Details", "Account", new { id = a.AdvertizementOwnerId })

});Json(data, JsonRequestBehavior.AllowGet);

}SearchInfo FillSearchInfoByLists(SearchInfo searchInfo)

{.Categories = repository.AdvertizementCategories;.DogBreeds = repository.DogBreeds;.Cities = new string[] { "Минск" }.Concat(repository.Advertizements.Where(x=>x.City!=null).Select(x => x.City).OrderBy(x => x)).Distinct();searchInfo;

}

}

}

using MyPet.Domain.Abstract;MyPet.Domain.Entities;MyPet.WebUI.Models;MyPet.WebUI.Models.ViewModels;System;System.Collections.Generic;System.Linq;System.Web;System.Web.Mvc;MyPet.WebUI.Controllers

{

[Authorize]class MessageController: Controller

{

// GET: MessageIMyPetRepository repository;IRepository<Message> msgRepository;

//private int authorisedUserId = User.Identity.GetUserId<int>();MessageController(IMyPetRepository repo, IRepository<Message> mesrepo)

{.repository = repo;.msgRepository = mesrepo;

}ActionResult New(int id)

{authorisedUserId=User.Identity.GetUserId<int>();msgs=msgRepository.GetAll().Where(x => (x.AuthorId == id && x.ReceiverId == authorisedUserId) || (x.ReceiverId == id && x.AuthorId == authorisedUserId))

.OrderByDescending(x => x.DateAndTime).Take(10).OrderBy(x=>x.DateAndTime);<Message> willBeViewedMsgs = msgs.Where(x => x.HasBeenViewed == false && User.Identity.GetUserId<int>() == x.ReceiverId);(var v in willBeViewedMsgs)

{.HasBeenViewed=true;

}.Save();.ReceiverId = id;View(msgs);

}

[HttpPost]ActionResult New(Message msg)

{(msg.Text != null && msg.AuthorId == User.Identity.GetUserId<int>())

{.DateAndTime = DateTime.Now;msgBeforeSavingInDB = msg;.Create(msg);.Save();

}<Message> msgsToShow = msgRepository.Find(x => (x.AuthorId == msg.ReceiverId && x.ReceiverId == msg.AuthorId && x.HasBeenViewed == false)

|| (x.DateAndTime == msg.DateAndTime && x.AuthorId == msg.AuthorId && x.ReceiverId == msg.ReceiverId && x.Text == msg.Text));(var v in msgsToShow)

{(v.AuthorId != msg.AuthorId).HasBeenViewed = true;

}.Save();(msgsToShow != null)

{PartialView("GetNextMsgs", msgsToShow);

}return null;

}ActionResult List()

{authorisedUserId = User.Identity.GetUserId<int>();receivedMsgsLists = msgRepository.Find(x => x.ReceiverId == authorisedUserId)

.GroupBy(x => x.AuthorId).ToArray();<MsgAndUnViewedMsgsCount> lastReceivedMsgsFromDifferentAuthors=new List<MsgAndUnViewedMsgsCount>();(var msgs in receivedMsgsLists)

{count=msgs.Where(x => x.ReceiverId == authorisedUserId && x.HasBeenViewed == false).Count();lastCurMsg=msgs.OrderByDescending(x=>x.DateAndTime).FirstOrDefault();.Add(new MsgAndUnViewedMsgsCount { Message = lastCurMsg, UnviewedMsgsCount = count, ReceiverProfile = repository.Profiles.Where(x => x.UserId == lastCurMsg.ReceiverId).FirstOrDefault()});

}sentMsgsLists = msgRepository.Find(x => x.AuthorId == authorisedUserId)

.GroupBy(x => x.ReceiverId).ToArray();<MsgAndUnViewedMsgsCount> lastSentMsgsFromDifferentAuthors=new List<MsgAndUnViewedMsgsCount>();(var msgs in sentMsgsLists)

{count = 0;lastCurMsg = msgs.OrderByDescending(x => x.DateAndTime).FirstOrDefault();.Add(new MsgAndUnViewedMsgsCount { Message = lastCurMsg, UnviewedMsgsCount = count, ReceiverProfile=repository.Profiles.Where(x=>x.UserId==lastCurMsg.ReceiverId).FirstOrDefault()});

}<MsgAndUnViewedMsgsCount> resultMsgs = new List<MsgAndUnViewedMsgsCount>();(var msg in lastReceivedMsgsFromDifferentAuthors)

{ms = lastSentMsgsFromDifferentAuthors.Where(x =>.Message.AuthorId==msg.Message.ReceiverId

&& x.Message.ReceiverId==msg.Message.AuthorId).FirstOrDefault();dt=(ms!=null?ms.Message.DateAndTime:msg.Message.DateAndTime);(dt > msg.Message.DateAndTime).Add(ms);resultMsgs.Add(msg);

}View(resultMsgs.OrderByDescending(x=>x.Message.DateAndTime));

}JsonResult GetUnviewedMsgsCountJson()

{authorisedUserId=User.Identity.GetUserId<int>();data = msgRepository.Find(x => (x.ReceiverId == authorisedUserId) && x.HasBeenViewed == false).Count();Json(data, JsonRequestBehavior.AllowGet);

}ActionResult GetNextMsgs(PresentedMsgsModelObj presMsgsObj)

{<Message> newNextMsgs = msgRepository.Find(x => (x.ReceiverId == presMsgsObj.ReceiverId && x.AuthorId == presMsgsObj.AuthorId) || (x.ReceiverId == presMsgsObj.AuthorId && x.AuthorId == presMsgsObj.ReceiverId)).(x => x.DateAndTime).(presMsgsObj.PresentedMsgsCount).(10).OrderBy(x=>x.DateAndTime);<Message> willBeViewedMsgs = newNextMsgs.Where(x => x.HasBeenViewed == false && User.Identity.GetUserId<int>() == x.ReceiverId);(var v in willBeViewedMsgs)

{.HasBeenViewed=true;

}.Save();(newNextMsgs.Count() != 0)PartialView(newNextMsgs);return null;

}

// in this method property PresentedMsgsModelObj.PresentedMsgsCount is not nessecary, because the method returns new unViewede msgsActionResult GetNewMsgs(PresentedMsgsModelObj presMsgsObj)

{<Message> newMsgs = msgRepository.(x => x.ReceiverId == presMsgsObj.AuthorId && x.AuthorId == presMsgsObj.ReceiverId && x.HasBeenViewed==false).(x => x.DateAndTime);

// in this foreach we are mark this msgs as they has been viewed(var v in newMsgs)

{.HasBeenViewed = true;

}.Save();(newMsgs.Count() != 0)PartialView("GetNextMsgs",newMsgs);return null;

}

}

}

using System;System.Collections.Generic;System.Linq;System.Web;System.Web.Mvc;MyPet.Domain.Entities;MyPet.Domain.Abstract;MyPet.WebUI.Models;MyPet.WebUI.Models.ViewModels;ImageResizer;System.IO;System.Text.RegularExpressions;MyRepetitor.WebUI.Controllers

{class PetsController: Controller

{IRepository<Pet> petsRepository;IMyPetRepository repository;PetsController(IRepository<Pet> petsRepo, IMyPetRepository repo)

{.petsRepository = petsRepo;.repository = repo;

}ActionResult MyPets(int id=0)

{pets = petsRepository.Find(x => x.UserId ==id);(pets != null)View(pets);return RedirectToAction("List");

}ActionResult List(int id=0)

{(id == 0)View(petsRepository.GetAll());

{pet=petsRepository.Find(x=>x.PetId==id);(pet != null)View(pet);RedirectToAction("Details", "Account", new { id = User.Identity.GetUserId<int>() });

}

}ActionResult Details(int id)

{pet = petsRepository.Get(id);<string[]> petPhotos = null;path = Server.MapPath(String.Concat("~/PetPhotos/", id).Trim());(pet != null)

{dir = new DirectoryInfo(Server.MapPath(String.Concat("~/PetPhotos/", id).Trim()));(dir != null && dir.Exists && dir.GetFiles().Length>0)

{<int> numbers = this.GetPhotoNumberCollection(path);= new List<string[]>();<FileInfo> files = dir.GetFiles();[] curFiles;(var v in numbers)

{= new string[3];[0] = Path.GetFileNameWithoutExtension(files.FirstOrDefault(x => x.Name.Contains(String.Concat("big", v.ToString()).Trim())).Name);[1] = Path.GetFileNameWithoutExtension(files.FirstOrDefault(x => x.Name.Contains(String.Concat("small", v.ToString()).Trim())).Name);[2] = Path.GetFileNameWithoutExtension(files.FirstOrDefault(x => x.Name.Contains(String.Concat("tiny", v.ToString()).Trim())).Name);.Add(curFiles);

}

}["PetPhotos"] = petPhotos;petAvatarPath = String.Concat("small", this.GetMinNumberInFileNames(path).ToString()).Trim();["PetAvatarPath"] = petAvatarPath;View(pet);

}return("Details", "Account", new { id = User.Identity.GetUserId<int>() });

}ActionResult EditPet(int id)

{pet = petsRepository.Get(id);(pet != null)

{.DogBreeds = repository.DogBreeds;View(pet);

}return("Details", "Account", new { id = User.Identity.GetUserId<int>() });

}

[Authorize]

[HttpPost]ActionResult EditPet(Pet pet, HttpPostedFileBase[] uploads)

{id = User.Identity.GetUserId<int>();(ModelState.IsValid && pet.UserId == id)

{(var v in pet.Rewards.Where(x => x.PetOwnerId == 0))

{.PetOwnerId = pet.PetId;

}(uploads.Where(x => x != null).FirstOrDefault() != null && pet.HaveImages == false).HaveImages = true;.Update(pet);.Save();= petsRepository.Get(pet.PetId);

{(uploads != null && uploads.Where(x => x != null).FirstOrDefault() != null)

{path = Server.MapPath(String.Concat("~/PetPhotos/", pet.PetId.ToString().Trim(), "/").Trim());i = this.GetMaxNumberInFileNames(path) + 1;(var upload in uploads)

{(upload != null)

{(upload.ContentLength > 0)

{versions = new Dictionary<string, string>();

//Define the versions to generate.Add("big", "maxwidth=768&maxheight=1024&format=jpg");.Add("small", "maxwidth=240&maxheight=320&format=jpg");.Add("tiny", "&maxheight=108&format=jpg");//maxwidth=81

//Generate each version(var suffix in versions.Keys)

{.InputStream.Seek(0, SeekOrigin.Begin);

//Let the image builder add the correct extension based on the output file type.Current.Build(ImageJob(upload.InputStream,.Concat(path, suffix, i.ToString()),Instructions(versions[suffix]),, true));

}

}++;

}

}

}

}(NullReferenceException) { }RedirectToAction("Details", new { id = pet.PetId });

}

{.DogBreeds = repository.DogBreeds;View(pet);

}

}ActionResult EditPetAjax(int id)

{pet = petsRepository.Get(id);(pet != null)

{.DogBreeds = repository.DogBreeds;PartialView(pet);

}return("Details", "Account", new { id = User.Identity.GetUserId<int>() });

}

[HttpPost]ActionResult EditPetAjax(Pet pet)

{id = User.Identity.GetUserId<int>();(ModelState.IsValid && pet.UserId == id)

{.Update(pet);.Save();= petsRepository.Get(pet.PetId);PartialView("DetailsPartial", pet);

}PartialView(pet);

}ActionResult Create()

{.DogBreeds = repository.DogBreeds;View(new Pet());

}

[HttpPost]ActionResult Create(Pet pet)

{(ModelState.IsValid)

{.UserId = User.Identity.GetUserId<int>();.Create(pet);.Save();View("List", petsRepository.Find(x=>x.Name==pet.Name));

}return View(pet);

}ActionResult GetImg(string id)

{file = new FileInfo(Server.MapPath("~/Avatars/default-dog-avatar.jpg"));(id != null)

{[] strs = id.Split(new char[] { '-' });(strs.Length == 2 && (new FileInfo(Server.MapPath(String.Concat("~/PetPhotos/", strs[0].Trim(), "/", strs[1], ".jpg").Trim()))).Exists)

{= new FileInfo(Server.MapPath(String.Concat("~/PetPhotos/", strs[0].Trim(), "/", strs[1], ".jpg").Trim()));

}

{= new FileInfo(Server.MapPath("~/Avatars/default-dog-avatar.jpg"));

}

}(file.Exists)File(file.FullName, "text/plain", file.Name);return Content("");

}

[Authorize]

[HttpPost]ActionResult DeletePhoto(DeletePhotoModel delPhModel)

{authUserId = User.Identity.GetUserId<int>();(delPhModel.WhoCanDeleteId == authUserId && delPhModel.OwnerId != 0 && delPhModel.PhotoNumber != 0)

{di = new DirectoryInfo(Server.MapPath(String.Concat("~/PetPhotos/", delPhModel.OwnerId.ToString().Trim()).Trim()));(di.Exists)

{(var file in di.GetFiles())

{fotoNumberLenght = delPhModel.PhotoNumber.ToString().Length;fileNameLength = Path.GetFileNameWithoutExtension(file.Name).Length;(file.Name.Contains(delPhModel.PhotoNumber.ToString()) &&

(Path.GetFileNameWithoutExtension(file.Name).LastIndexOf(delPhModel.PhotoNumber.ToString()) == fileNameLength - fotoNumberLenght))

{.Delete();

}

}(di.GetFiles().Length == 0)

{.Delete();pet = petsRepository.Find(x => x.PetId == delPhModel.OwnerId).FirstOrDefault();(pet != null).HaveImages = false;.Save();

}

}

}null;

}

[Authorize]ActionResult Delete(int id)

{pet = petsRepository.Get(id);userid = User.Identity.GetUserId<int>();(pet != null && pet.UserId == userid)

{.Delete(id);.Save();path = Server.MapPath(String.Concat("~/PetsPhotos/", id.ToString().Trim(), "/"));di = new DirectoryInfo(path);(di != null && di.Exists)

{(var file in di.GetFiles())

{.Delete();

}.Delete();

}RedirectToAction("List", "Advertizements");

}return RedirectToAction("DetailsPartial", "Account", new { id = userid });

}int GetMaxNumberInFileNames(string path)

{.CreateDirectory(path);di = new DirectoryInfo(path);r = new Regex(@"\d+");ddd = di.GetFiles().Select(x => x.Name).FirstOrDefault();teg;maxNumberInFileNames = 0;(string fileName in di.GetFiles().Select(x => x.Name))

{= r.Match(fileName);(teg.Success && int.Parse(teg.ToString()) > maxNumberInFileNames)= int.Parse(teg.ToString());

}maxNumberInFileNames;

}int GetMinNumberInFileNames(string path)

{.CreateDirectory(path);di = new DirectoryInfo(path);r = new Regex(@"\d+");ddd = di.GetFiles().Select(x => x.Name).FirstOrDefault();teg;minNumberInFileNames = int.MaxValue;(string fileName in di.GetFiles().Select(x => x.Name))

{= r.Match(fileName);(teg.Success && int.Parse(teg.ToString()) < minNumberInFileNames)= int.Parse(teg.ToString());

}minNumberInFileNames == int.MaxValue ? 0: minNumberInFileNames;

}IEnumerable<int> GetPhotoNumberCollection(string path)

{.CreateDirectory(path);di = new DirectoryInfo(path);(di.Exists && di.GetFiles().Length > 0)

{r = new Regex(@"\d+");teg;<int> numbers = new List<int>();(string fileName in di.GetFiles().Select(x => x.Name))

{= r.Match(fileName);(teg.Success && !(numbers.Contains(int.Parse(teg.ToString())))).Add(int.Parse(teg.ToString()));

}numbers.Count > 0 ? numbers: null;

}return null;

}

}

}

using System;System.Collections.Generic;System.Linq;System.Web;System.Web.Mvc;MyPet.WebUI.Models.ViewModels;MyPet.Domain.Abstract;MyPet.Domain.Concrete;MyPet.Domain.Entities;System.Security.Claims;Microsoft.Owin.Security;MyPet.WebUI.Controllers

{class RegistrationController: Controller

{IRepository<User> userRepository;RegistrationController(IRepository<User> repo)

{.userRepository = repo;

}

// GET: RegistrationActionResult New()

{View(new UserModel());

}

[HttpPost]ActionResult New(UserModel usermodel)

{(ModelState.IsValid)

{user = new User() { RoleId = 1, Email = usermodel.YourEmail, Password = usermodel.YourPassword };profile = new Profile() { FirstName = usermodel.FirstName, LastName = usermodel.LastName, Sex = usermodel.Sex };.Profile = profile;.Create(user);.Save();userId = userRepository.Find(x => x.Email == user.Email).FirstOrDefault().UserId;userDB = userRepository.Find(x => x.UserId == userId).FirstOrDefault();.Authorise(userDB);RedirectToAction("Details", "Account", new { id=userId});

}View(usermodel);

}IAuthenticationManager AuthenticationManager

{

{HttpContext.GetOwinContext().Authentication;

}

}

[ValidateAntiForgeryToken]void Authorise(User user)

{claim = new ClaimsIdentity("ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString(), ClaimValueTypes.String));.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email, ClaimValueTypes.String));.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",

"OWIN Provider", ClaimValueTypes.String));(user.Role != null).AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, user.Role.Name, ClaimValueTypes.String));.SignOut();.SignIn(new AuthenticationProperties { IsPersistent = true }, claim);

}

// method to check that email does not exist in DatabaseJsonResult CheckEmail(string YourEmail)

{Json("Пользователь с таким email уже зарегистрирован", JsonRequestBehavior.AllowGet);

}

{Json(true, JsonRequestBehavior.AllowGet);

}

}

}

}

using System;System.Collections.Generic;System.Linq;System.Web;System.Web.Mvc;MyPet.Domain.Abstract;MyPet.Domain.Entities;MyPet.WebUI.Models;MyRepetitor.WebUI.Controllers

{class ReviewController: Controller

{

// GET: ReviewIMyPetRepository repository;IRepository<Review> revRepository;ReviewController(IMyPetRepository repo, IRepository<Review> revrepo)

{.repository = repo;.revRepository = revrepo;

}ActionResult GetReviews(int id)

{["AdvertizementId"]=id;PartialView(revRepository.Find(x => x.AdvertizementId == id).OrderByDescending(x => x.DateAndTime));

}

[HttpPost]

[Authorize]PartialViewResult AddComment(Review review)

{(review.Text != null && review.AuthorId == User.Identity.GetUserId<int>())

{.DateAndTime = DateTime.Now;.Create(review);.Save();reviews = revRepository.Find(x => x.AdvertizementId == review.AdvertizementId).OrderBy(x => x.DateAndTime);PartialView("Comments", reviews);

}null;

}

}

}

using MyPet.Domain.Entities;System;System.Collections.Generic;System.Linq;System.Web;MyPet.Domain.Abstract;System.ComponentModel.DataAnnotations;System.Web.Mvc;MyPet.WebUI.Models;MyPet.WebUI.Models

{enum SortWay

{,,,,

};class SearchInfo

{

//[HiddenInput(DisplayValue = false)]PagingInfo PagingInfo { get; set; }SearchInfo()

{.Categories = new List<AdvertizementCategory>();.DogBreeds = new List<DogBreed>();.Cities = new List<string>();

}

[Display(Name = "Спрос / предложение")]bool? IsDemand { get; set; }

[Display(Name = "Пол")]bool? Sex { get; set; }

[HiddenInput(DisplayValue = false)]decimal? PriceMin { get; set; }

[HiddenInput(DisplayValue = false)]decimal? PriceMax { get; set; }

[Display(Name = "С выездом на дом")]

public bool? IsWithDeparture { get; set; }

[Display(Name = "Период")]string Period { get; set; }DateTime? FromDate

{

{(this.Period == "за сегодня")DateTime.Now.AddDays(-1);if (this.Period == "за неделю")DateTime.Now.AddDays(-7);if (this.Period == "за месяц")DateTime.Now.AddMonths(-1);return null;

}

}

[Display(Name = "Категория объявления")]int? CategoryId { get; set; }

[Display(Name = "Порода")]int? DogBreedId { get; set; }

[Display(Name = "Город")]string City { get; set; }

[HiddenInput(DisplayValue = false)]SortWay WayToSort { get; set; }IEnumerable<AdvertizementCategory> Categories { get; set; }IEnumerable<DogBreed> DogBreeds { get; set; }IEnumerable<string> Cities { get; set; }IEnumerable<Advertizement> FilterAdvsWithoutPaging(IEnumerable<Advertizement> parentAdvs)

{<Advertizement> advs = parentAdvs.Where((x) =>

{(this.CategoryId == null || this.CategoryId == 0)true;return x.CategoryId == this.CategoryId;

}).Where((x) =>

{(this.PriceMin == null || this.PriceMax == null)true;return (x.Price >= this.PriceMin) && (x.Price <= PriceMax);

}).Where((x) =>

{(this.DogBreedId == null)true;return x.DogBreedId == this.DogBreedId;

}).Where((x) =>

{(this.City == null)true;return x.City == this.City;

}).Where((x) =>

{(this.FromDate == null)true;return x.DateAndTime >= this.FromDate;

}).Where((x) =>

{(this.IsDemand == null)true;return x.IsDemand == this.IsDemand;

}).Where((x) =>

{(this.Sex == null)true;return x.Sex == this.Sex;

}).Where((x) =>

{(this.IsWithDeparture == null || this.IsWithDeparture == false)true;return x.IsWithDeparture == this.IsWithDeparture;

});advs;

}IEnumerable<Advertizement> FilterAdvs(IEnumerable<Advertizement> parentAdvs)

{<Advertizement> advs = this.FilterAdvsWithoutPaging(parentAdvs);.PagingInfo.TotalItems = advs.Count();(this.WayToSort == SortWay.MaxDatesFirst)advs.OrderByDescending(x => x.DateAndTime).Skip((this.PagingInfo.CurrentPage - 1) * this.PagingInfo.ItemsPerPage).Take(this.PagingInfo.ItemsPerPage);(this.WayToSort == SortWay.MinDatesFirst)advs.OrderBy(x => x.DateAndTime).Skip((this.PagingInfo.CurrentPage - 1) * this.PagingInfo.ItemsPerPage).Take(this.PagingInfo.ItemsPerPage);if (this.WayToSort == SortWay.MinPricesFirst)advs.OrderBy(x => x.Price).Skip((this.PagingInfo.CurrentPage - 1) * this.PagingInfo.ItemsPerPage).Take(this.PagingInfo.ItemsPerPage);if (this.WayToSort == SortWay.MaxPricesFirst)advs.OrderByDescending(x => x.Price).Skip((this.PagingInfo.CurrentPage - 1) * this.PagingInfo.ItemsPerPage).Take(this.PagingInfo.ItemsPerPage);if (this.WayToSort == SortWay.SortByRating)advs.OrderByDescending(x => x.DateAndTime).Skip((this.PagingInfo.CurrentPage - 1) * this.PagingInfo.ItemsPerPage).Take(this.PagingInfo.ItemsPerPage);return advs.OrderByDescending(x => x.DateAndTime).Skip((this.PagingInfo.CurrentPage - 1) * this.PagingInfo.ItemsPerPage).Take(this.PagingInfo.ItemsPerPage);

}

}

}

using System;System.Globalization;System.Security.Claims;System.Security.Principal;MyPet.WebUI.Models

{static class IdentityExtensions

{static T GetUserId<T>(this IIdentity identity) where T: IConvertible

{(identity == null)

{new ArgumentNullException("identity");

}ci = identity as ClaimsIdentity;(ci != null)

{id = ci.FindFirst(ClaimTypes.NameIdentifier);(id != null)

{(T)Convert.ChangeType(id.Value, typeof(T), CultureInfo.InvariantCulture);

}

}default(T);

}static string GetUserRole(this IIdentity identity)

{(identity == null)

{new ArgumentNullException("identity");

}ci = identity as ClaimsIdentity;role = "";(ci != null)

{id = ci.FindFirst(ClaimsIdentity.DefaultRoleClaimType);(id != null)= id.Value;

}role;

}

}

}

using System;System.Collections.Generic;System.Linq;System.Web;System.ComponentModel.DataAnnotations.Schema;System.ComponentModel.DataAnnotations;MyPet.WebUI.Models.ViewModels

{class UserModel

{

[Required(ErrorMessage="Поле 'e-mail' является обязательным для заполнения")]

[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage = "Некорректный адрес")]

[System.Web.Mvc.Remote("CheckEmail", "Registration")]

[Display(Name = "Email")]string YourEmail { get; set; }

[Required(ErrorMessage = "Поле 'пароль' является обязательным для заполнения")]

//[DataType(DataType.Password)]

[StringLength(25, MinimumLength = 6, ErrorMessage="Пароль должен быть не менее 6 символов")]

[Display(Name = "Пароль")]

[UIHint("Password")]string YourPassword { get; set; }

[Compare("YourPassword", ErrorMessage = "Пароли не совпадают")]

//[DataType(DataType.Password)]

[Display(Name = "Подтвердите пароль")]

[UIHint("Password")]string YourPasswordConfirm { get; set; }

[Display(Name="Имя")]

[Required(ErrorMessage = "Поле 'Имя' является обязательным для заполнения")]

[StringLength(25, MinimumLength = 2, ErrorMessage = "Поле 'Имя' слишком короткое")]string FirstName { get; set; }

[StringLength(25, MinimumLength = 2, ErrorMessage = "Поле 'Фамилия' слишком короткое")]

[Display(Name = "Фамилия")]

[Required(ErrorMessage = "Поле 'Фамилия' является обязательным для заполнения")]

public string LastName { get; set; }

[Display(Name = "Пол")]bool Sex { get; set; }

[Range(typeof(bool), "true", "true", ErrorMessage="Ознакомьтесь и согласитесь с условиями")]bool Accept { get; set; }

}

}

Приложение Б

Трассировочная таблица

Таблица Б - Трассировочная таблица

Пользовательское требование

Функция системы

Вариант использования

1

Регистрация

1.1 1.2 1.3 1.4

Ввод личных данных Ввод e-mail Ввод пароля Подтверждение пароля

Ввод личных данных Ввод e-mail Ввод пароля Подтверждение пароля

2

Авторизация

2.1 2.2

Ввод электронного адреса Ввод пароля

Ввод электронного адреса Ввод пароля

3

Работа с персональными данными

3.1 3.2

Просмотр личных данных Редактирование и сохранение данных

Просмотр личных данных Редактирование и сохранение данных

4

Работа с питомцами

4.1 4.2 4.34.4

Просмотр данных о питомцах Редактирование и сохранение данных о питомцах Добавление питомца Удаление питомца

Просмотр данных о питомцах Редактирование и сохранение данных о питомцах Добавление питомца Удаление питомца

5

Работа с личными объявлениями

5.1 5.2 5.3 5.4 5.5

Просмотр данных объявлений Редактирование и сохранение данных объявлений Добавление объявления Удаление объявления Просмотр отзывов

Просмотр данных объявлений Редактирование и сохранение данных объявлений Добавление объявления Удаление объявления Просмотр отзывов

6

Работа с сообщениями

6.1 6.2 6.3

Просмотр списка диалогов Отправка сообщения Получение сообщения

Просмотр списка диалогов Отправка сообщения Получение сообщения

7

Поиск объявлений по фильтрам

7.1 7.2 7.3

Задание фильтров Выбор способа сортировки «Сортировать по» Поиск подходящих объявлений

Задание фильтров Выбор способа сортировки «Сортировать по» Поиск подходящих объявлений

8

Просмотр результатов поиска

8.1 8.2 8.3 8.4

Просмотр карты с метками объявлений Просмотр информации об объявлении Оставление отзыва и оценки объявлению Отправка сообщения автору объявления

Просмотр карты с метками объявлений Просмотр информации об объявлении Оставление отзыва и оценки объявлению Отправка сообщения автору объявления


Похожие работы на - Программное средство по обмену услугами для владельцев собак

 

Не нашли материал для своей работы?
Поможем написать уникальную работу
Без плагиата!