Авто заполнение сервисных колонок

В большинстве часто используемых 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">&nbsp;</span> 1. Проверяем уникальность email <div class="result"></div> </div>    <div class="pass"> <span class="status failed">&nbsp;</span> 2. Проверяем качество пароля   <div class="result"></div> </div>    <div class="send"> <span class="status failed">&nbsp;</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












Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: