В большинстве часто используемых entity в базе данных, есть определённые сервисные поля, которые независимы от других полей и содержат похожую информацию. DateCreated, DateModified, UserCreated, UserModified, UrlName – могут не вводиться при каждом добавлении в бизнесс обьектах, они могут быть сгенерированы.
Перехват удобней всего производить в override версии SubmitChanges. Там можно перед произведением добавления или изменений, получать все обьекты и производить над ними операции.
namespace Yabeda.Med.Data { public partial class MedData { public override void SubmitChanges(System.Data.Linq.ConflictMode failureMode) { foreach(object ent in this.GetChangeSet().Inserts) { AutoValues auto = new AutoValues(ent); auto.AutoUrlName(); auto.AutoDateCreated(); auto.AutoDateModified(); auto.AutoUserCreated(); auto.AutoUserModified(); } foreach (object ent in this.GetChangeSet().Updates) { AutoValues auto = new AutoValues(ent); auto.AutoDateModified(); auto.AutoUserModified(); } base.SubmitChanges(failureMode); } } } |
Рисунок 2.7 – Переопределение SubmitChanges, для автозаполнения
|
|
Как видно, для Inserts, вставляються Created и UrlName значений, при Update, только Modified поля. AutoValues – это специальный класс принимающий обьект как конструктор, и производит с ним заполнения полей. Политика заполнения представляют собой осторожный подход. Поле вставляеться только в случае его существования и пустого значения внутри.
public object Entity { get; set; } public Type EntityType { get; set; } public AutoValues(object ent) { Entity = ent; EntityType = ent.GetType(); } |
private bool CheckIfValueExists(string property) { var name = EntityType.GetProperty(property); if (name == null) return true; object value = name.GetValue(Entity, null); if (value is Guid) { return ((Guid)value)!= Guid.Empty; } return value!= null; } |
Рисунок 2.8 – Проверка заполненности Reflection свойства
Данные функции широко используют Reflection механизмы. Для ValueType таких как Guid, производяться специальные проверки на пустоту значения.
private void AutoDefaultPut(string propertyName, object value) { var user = EntityType.GetProperty(propertyName); if (user == null) return; user.SetValue(Entity, value, null); } |
Рисунок 2.9 – Установка значения в Reflection свойство
AutoDefaultPut – проверяет указанное свойство на существование и заполняет его. Метод используеться AutoDate и AutoUser методами.
public void AutoDateModified() { AutoDefaultPut("DateModified", DateTime.UtcNow); } public void AutoUserCreated() { if (CheckIfValueExists("UserCreated")) return; AutoDefaultPut("UserCreated", Data.Ctx.User.UserId); } |
Рисунок 2.10 – Пример автозаполнения Id пользователя и DateTime.
Поле с датой заполняеться текущим временем в формате универсальном формате UTC. User заполняеться текущим UserId. В случае с Url происходят дополнительные проверки и конвертация через транслит метод.
|
|
public void AutoUrlName() { var name = EntityType.GetProperty("DisplayName"); var url = EntityType.GetProperty("UrlName"); if (url == null || name == null) return; string nameValue = name.GetValue(Entity, null) as string; string urlValue = url.GetValue(Entity, null) as string; if (nameValue!= null && urlValue == null) { url.SetValue(Entity, nameValue.Translit(), null); } } |
Рисунок 2.11 – Заполнение Url, из расчёта на другое свойство
Это специальный созданный для данной системы транслит, удобный для внедрения в Url. Транслит сделан в виде Extension метода [extension метод в C# и статья из моего блога].
Json модели
Так как множество данных в результате передаёться на клиент в форме json, то вполне не лишним может оказаться дополнительный уровень абстракции в виде набора классов для сериализации. По стилю кодированию они заканчиваются на Json и находяться среди моделей. Их задача есть передавать на клиент только нужную информацию без лишних зацикленных элементов.
В целом это обычные пустые классы, как пример снизу.
namespace Yabeda.Med.Mvc.Models.Company { public class ShortJson { public string name { get; set; } public string url { get; set; } public Guid id { get; set; } } } |
Рисунок 2.12 – Пример промежуточной структуры для модели данных
Они могут также содержать в одном из конструкторов аргументом класс Company из ORM модели для клонирования данных.
public ShortJson(Data.Company comp) { id = comp.CompanyId; name = comp.LongName; } |
Рисунок 2.13 – Конструктор заполнения структуры ShortJson
Конструктор как метод заполнения выбран не случайно, он удобен при создании объектов в Linq методе Select.
Company.All().Select(s => new Models.Company.ShortJson(s)); |
Рисунок 2.14 – Пример заполнение ShortJson
Кроме того, оказалось проблемой воспользоваться механизмом UrlRouting не внутри View, а внутри Action Controller. Для этого пришлось создавать специальную заглушку.
namespace Yabeda.Med.Mvc.Controllers { public class BaseController<C>: Controller where C: Controller { …. private UrlHelper _url; public UrlHelper Url { get { if (_url == null) { _url = new UrlHelper(this.ControllerContext.RequestContext); } return _url; } } protected override void OnActionExecuting(ActionExecutingContext filterContext) { Ctx.Url = Url; } … } } |
Рисунок 2.15 – Загрузка Ctx.Url
Таким образом в каждом Action контроллеров, которые наследуются от BaseController, есть возможность использования Ctx.Url. А в конструкторе CompanyJson создавать целый url.
url = Ctx.Url.Action("Show", "Company", new { name = comp.UrlName}); |
Рисунок 2.16 – Доступ к Ctx.Url
Безопасность доступа
Анонимный доступ
Любой пользователь, производящий запрос к странице, не имеющий в запросе cookies аутентификации, является для системы анонимом.
Суть анонимов достаточно проста, это неопределённый пользователь, однако он имеет права производить такие операции, как комментирование и создание анонимных жалоб. Однако в действительности, они не столь анонимны относительно внутренней структуры системы.
Каждый аноним, заходя первый раз передаёт свой IP адрес, что не мало важно, и даёт информацию о местоположении. Эта информация может быть использована в целях персонализации вывода страниц. Например, показывая жалобы или новости из своего района, ближайшие компании, статистика, плотность пользователей и тому подобную информацию.
Анонимы записываются как самые настоящие пользователи, в таблицу пользователей, однако с маркером в виде колонки IsAnonym. Такая запись является первой стадией, и её задача есть поддержание последовательности действий будущего зарегистрированного пользователя. Конечно, такой подход не лишён недостатков - меняя браузер, удаляя cookies, производя вход другим аккаунтом, отметка анонима будет очищена и создана заново. Следовательно, введённая информация так и останется ссылающейся на пользователя анонима. В случае успешной регистрации, UserId анонима уже использованная при создании комментариев, будет преобразована в нового пользователя, не меняя UserId.
|
|
Реализация механизма работы, такого анонима, производится в ручную. В asp.net 2.0, был включёна реализация AnonymousModule, однако он работает с более простой схемой, когда маркер аноним остаётся постоянно[почему?]. Проблема начинается в момент выхода из аккаунта зарегистрированного пользователя, так как UserId анонима и пользователя совпадают, то при выходе нужно удалять и анонимную, и пользовательскую cookie, а встроенный механизм этого не позволяет. Поэтому для нашей схемы реализуется свой модуль анонимных пользователей.
partial class User { public static User Current { get { return HttpContext.Current.User.Identity.IsAuthenticated? User.Get(HttpContext.Current.User.Identity.Name): RegisterAnonym(); } } public static User RegisterAnonym() { return RegisterAnonym(Anonymous.Get()); } public static User RegisterAnonym(Guid anonymId) { User user = Rep<User>.GetById(anonymId); if (user == null) { user = new User { UserId = anonymId, IsActivated = false, IsDeleted = false, IsGuest = true }; Ctx.Model.Users.InsertOnSubmit(user); } Ctx.Model.SubmitChanges(); Ctx.User = user; return user; } |
Рисунок 2.17 – Регистрация анонима и текущий пользователь
Как видно, текущего пользователь получают через статическое свойство User.Current. Это свойство не кеширует пользователя, загружая его, либо анонима через RegisterAnonym. Он производит два действия: как добавление анонима, так и загрузку по UserId в случае не существования и в результате в любом случае выдаёт некого пользователя.
public class Anonymous { public static Guid Get() { HttpCookie cookie = HttpContext.Current.Request.Cookies[CookieName]; try { return cookie == null? New(): new Guid(cookie.Value); } catch(Exception exc) { return New(); } } public static Guid New() { Guid anonymId = Guid.NewGuid(); HttpCookie cookie = HttpContext.Current.Response.Cookies[CookieName]; cookie.Expires = DateTime.Now.AddDays(30); cookie.Value = anonymId.ToString(); return anonymId; } public static readonly string CookieName = "Yabeda.AnonId"; } |
Рисунок 2.18 – Класс Anonym
|
|
Этот статический класс отвечает за работу с cookie: запрос идентификатора анонима, соответствующего идентификатору UserId, генерация и установка на клиент, если cookie не найден. Сгенерированный Id затем будет использован в RegisterAnonym, как видно на прошлом изображении.
Регистрация
Регистрация – это процесс перехода состояния пользователя в более постоянную величину. Основные задачи достижимые при использовании регистрации: улучшенный уровень персонализации и более упрощённый ввод идентификационный информации, во время пользования системой. Для создателей, регистрация, прежде всего трассировка движения, и более простой и точный способ просмотра статистики.
Рисунок 2.19 – Процесс регистрации
Сверху диаграмма статусов, для процесса регистрации. Как видно процесс, состоит из этапа валидации информации, отсылки email и активации по ссылке из email. Достаточно стандартный процесс в веб-приложениях. Внешне работа формы регистрации описывается в пункте [веб ресурс – сценарий – регистрация]. Здесь же далее будет описан UserController и клиентские компоненты, использованные на странице регистрации выполняющие функциональность.
UserController – это управляющий элемент таких действий, как логин, логаут, регистрация, активизация и профайл пользователя. Как и задача любого mvc контроллера, он рассматривает присланные данные, наполняет в зависимости от них модель и пользуясь View отображает её. В случае с UserController, результатом некоторых действий есть json, тоесть он выступает как rest-сервис [ссылки на rest и json] вызываемый при помощи ajaj. В данном случае, регистрация и логин переносит валидацию в асинхронные вызовы, и на клиенте обрабатывает ответ. На сервере находиться входное действие Register и RegisterJson выполняющее функцию сервиса.
Register выглядит как самый простой Action:
[UserFilter(IsGuest = true)] public ActionResult Register() { ViewData.Model = new PageModel { Title = "Регистрация" }; return View(); } |
Рисунок 2.20 – Action регистрации
UserFilter указывает как видно, что доступ к регистрации имеет любой пользователь. Ниже листинг разметки отображаемой страницы:
<div class="register container"> <div class="inputs"> Имя: <input type="text" id="name" /> <br /> Email: <input type="text" id="email" /> <br /> Пароль: <input type="password" id="pass" /> <br /> <input type="button" class="submit" value="Регистрация" /> </div> <div class="validation"> <div class="email"> <span class="status failed"> </span> 1. Проверяем уникальность email <div class="result"></div> </div> <div class="pass"> <span class="status failed"> </span> 2. Проверяем качество пароля <div class="result"></div> </div> <div class="send"> <span class="status failed"> </span> 3. Отсылаем письмо на ваш email <div class="result"></div> </div> </div> </div> |
Рисунок 2.21 – Разметка для страницы регистрации
Слева вводимые поля – имя, email и пароль. Справа валидационная часть. Реализация достаточно гибкая и чистая маркировка.
.register.inputs,.register.validation { float: left; margin: 20px; padding: 10px; border: dotted 1px red; } .register.validation > div { margin: 20px; } .register.validation.result { font-size: small; margin-left: 40px; } .register.status { padding: 10px; margin: 5px; } .register.status.failed { background-color: red } .register.status.passed { background-color: green } |
Рисунок 2.22 – CSS блоков
Первые три отвечают за оформление двух блоков и относительное позиционирование друг к другу и элементов внутри. Status класс, выражает квадрат, метку выполненности каждой из валидаций. Указанные рядом failed или passed соответствует результату, который пришёл от RegisterJson.
$(function() { $('.register.submit').click(function() { var params = { name: $('.register.inputs #name').val(), email: $('.register.inputs #email').val(), pass: $('.register.inputs #pass').val() }; $.getJSON(Yabeda.Services.User.RegisterJson, params, function(json) { $('.register.validation.result').empty(); $.each(json, function(i, n) { $('.register.validation.' + i + '.status') .removeClass(n.length == 0? 'failed': 'passed') .addClass(n.length == 0? 'passed': 'failed'); $.each(n, function(ir, r) { $('.register.validation.' + i + '.result') .append('-').append(r).append('<br />'); }) }) if ($('.register.validation.failed').size() == 0) { setTimeout(function() { window.location = Yabeda.Services.User.Profile; }, 700); } }); }); }); |
Рисунок 2.23 – Код управления валидацией регистрационной информацией
На кнопку «Регистрация» устанавливается обработчик, который собирает параметры на проверку и передаёт их в RegisterJson. Если например отправить все пустые значение, то ответом Json будет строка снизу.
{"email":["Email введён неверно"], "pass":[], "send":["Без email, не могу послать письмо активации"]} |
Рисунок 2.24 – Пример присланного RegisterJson
Формат Json в данном случае выглядит как вид валидации, и массив ошибок соответствующий ему. В Csharp для сериализации используется следующий класс.
public class ValidationJson { public List<string> email = new List<string>(); public List<string> pass = new List<string>(); public List<string> send = new List<string>(); } |
Рисунок 2.25 – Версия ValidationJson
RegisterJson должен заполнить этот класс по мере валидации.
public ActionResult RegisterJson(string name, string email, string pass) { var validation = new Models.User.ValidationJson(); // 1. Check similar if (!Emails.Check(email)){ validation.email.Add("Email введён неверно"); } else { if (Data.User.Get(email)!= null) { validation.email.Add("Такой пользователь существует"); } } // 2. Send email if (validation.email.Count == 0) { // код отсылки email } return Json(validation); } |
Рисунок 2.26 – Проверка Email в RegisterJson Action
Сначало, как видно, идёт проверка возможности отсылки Email. Emails класс использует для проверки регулярное выражание.
public static bool Check(string email) { if (email == null) return false; string reg_email = @"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])* @([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$"; return new Regex(reg_email).IsMatch(email); } |
Рисунок 2.27 – Проверка Email на правильность составления
Затем проверяется уникальность email, в списке пользователей ищутся с таким же email. Если email верен, проверяется пароль, здесь данная проверка опущена. Далее, если ошибок не найдено, происходит подготовка письма и отсылка. Для создания тела письма используется компонент StringTemplate, позволяющий совмещать списки переменных с шаблоном. Это стандартный подход, среди подобных вспомогательных шаблонных систем.
Try { using (var tran = new TransactionScope()) { Data.User.RegisterUser(name, email, pass); StringTemplateGroup group = new StringTemplateGroup("register", Server.MapPath("~/Views/User")); StringTemplate st = group.GetInstanceOf("RegisterAnonym"); st.SetAttribute("user", Ctx.User); Email("Письмо активации", st.ToString()); tran.Complete(); } FormsAuthentication.SetAuthCookie(email, true); } catch (DataException dexc) { validation.send.Add("Проблема с базой данных"); } catch (Exception exc) { validation.send.Add("Ошибка отсылки активации"); } |
Рисунок 2.28 – Составление Email и отсылка
Несколько особенностей. Во-первых, здесь использована транзакция из пространства имён транзакций. По умолчанию данная транзакция после окончания using производит откат, если не был произведён коммит. В данном случае схема очень удобна, так как не требуется передавать в функции обработки переменную транзакции.
StringTemplateGroup – это класс абстракция, позволяет указать группу поиска для списка темплейтов. В нашем случае это ссылка на каталог во View. Далее идёт поиск нужного темплейта, и заполнение переменных. Пример, темплейта снизу.
<div> Активационный код: $user.UserId$ Введите его в поле на странице регистрации </div> |
Рисунок 2.29 – Пример StringTemplate шаблона для активации
После проведения всех участков проверки, приходит ответ в виде сериализованного ValidationJson. Просмотр идёт двумя циклами, и затем в случае всех успешных, происходит перенаправление на Profile. В принципе попасть туда в любом случае пользователь не сможет, пока не проведёт активацию, куда его автоматически и перенаправят.
ВЫВОДЫ
В результате выполненных работ, была создана современная система регистрации и оценки обращений и отзывов. Система поиска и добавления превосходящая всех конкурентов в удобности и быстроте.
За время разработки, было найдено множество подходов для создания и взаимодействия клиентских компонентов.
СПИСОК ИСТОЧНИКОВ
1. Иван Блинков Масштабируемые веб-архитектуры [Электронный ресурс] /. - Режим доступа: www/ URL: http://www.insight-it.ru/net/scalability/masshtabiruemye-veb-arkhitektury/ - 12.05.2008 г. - Загл. с экрана.
2. Иван Блинков Сегментирование базы данных [Электронный ресурс] /. - Режим доступа: www/ URL: http://www.insight-it.ru/net/scalability/segmentirovanie-bazy-dannykh/ - 12.05.2008 г. - Загл. с экрана.
3. ORGs for Scalable, Robust, Privacy-Friendly Client Cloud Computing [Электронный ресурс] /. - Режим доступа: www/ URL: http://doi.ieeecomputersociety.org/10.1109/MIC.2008.107 - 10.2008 г. - Загл. с экрана.
4. Система управления базами данных [Электронный ресурс] /wikipedia. - Режим доступа: www/ URL: http://ru.wikipedia.org/wiki/Система_управления_базами_данных.
5. Объектно-ориентированная_база_данных [Электронный ресурс] /wikipedia. - Режим доступа: www/ URL: http://ru.wikipedia.org/wiki/Объектно-ориентированная_база_данных.
6. Реляционная СУБД [Электронный ресурс] /wikipedia. - Режим доступа: www/ URL: http://ru.wikipedia.org/wiki/Реляционная_СУБД
7. Объектно-реляционная СУБД [Электронный ресурс] /wikipedia. - Режим доступа: www/ URL: http://ru.wikipedia.org/wiki/Объектно-реляционная_СУБД
8. Web 2.0 and Cloud Computing [Электронный ресурс] /. - Режим доступа: www/ URL: http://radar.oreilly.com/2008/10/web-20-and-cloud-computing.html - 26.10.2008 г. - Загл. с экрана.
9. Amazon EC2 [Электронный ресурс] /wikipedia. - Режим доступа: www/ URL: http://ru.wikipedia.org/wiki/Amazon_EC2
10. Amazon EC2 [Электронный ресурс] /wikipedia. - Режим доступа: www/ URL: http://ru.wikipedia.org/wiki/Windows_Azure
11. Amazon Web Services [Электронный ресурс] /. - Режим доступа: www/ URL: http://aws.amazon.com/
12. Что такое Google App Engine? [Электронный ресурс] /. - Режим доступа: www/ URL:
http://www.googleappengine.ru/docs/whatisgoogleappengine.html
13. Windows Azure: официальный анонс "облачной" операционной системы [Электронный ресурс] /wikipedia. - Режим доступа: www/ URL: http://www.hardwareportal.ru/news/Windows_Azure_ofitsialniy_anons_oblachnoy_operatsionnoy_sistemi_/
14. Cloud computing [Электронный ресурс] /wikipedia. - Режим доступа: www/ URL: http://en.wikipedia.org/wiki/Cloud_computing
15. Develop. Deploy. Scale. [Электронный ресурс] /linode. - Режим доступа: www/ URL:
http://www.linode.com/
16. Microsoft Azure vs Amazon, Google, and VMware [Электронный ресурс] /linode. - Режим доступа: www/ URL:
http://cloudenterprise.info/2008/10/29/microsoft-azure-vs-amazon-google-and-vmware/
17. Prado Framework [Электронный ресурс] /. - Режим доступа: www/ URL:
http://www.xisc.com/
18. MonoRail [Электронный ресурс] /. - Режим доступа: www/ URL:
http://www.castleproject.org/MonoRail/
19. Ruby On Rails [Электронный ресурс] /. - Режим доступа: www/ URL:
http://rubyonrails.org/
20. Asp.Net Mvc [Электронный ресурс] /microsoft. - Режим доступа: www/ URL:
http://www.asp.net/mvc/
21. ASP.NET MVC vs. WebForms[Электронный ресурс] /. - Режим доступа: www/ URL:
http://habrahabr.ru/blogs/net/47249/
22. JavaScript Object Inheritance[Электронный ресурс] /. - Режим доступа: www/ URL:
http://javascript.ru/tutorial/object/inheritance
23. Search Engine Friendly URLs - URL Rewriting[Электронный ресурс] /. - Режим доступа: www/ URL: http://www.seoconsultants.com/articles/1000/urls.asp
24. RFC 1738 [Электронный ресурс] /. - Режим доступа: www/ URL:
http://www.ietf.org/rfc/rfc1738.txt