В данный момент я работаю над небольшим проектом на asp net mvc. Сроки достаточно короткие, результат нужен как можно скорее, вот мы и начали набрасывать функционал и натягивать красивый интерфейс (говнокодить). Время шло, смотреть на это становилось все тяжелее, вносить правки все дольше, и вот пока заказчик проводит тестирование приложения, есть время подумать, что можно сделать с этой простыней кода (раньше подумать было лень и некогда).
Начинаю думать, как можно решить проблему давно надоевших карточек редактирования документов. В системе есть несколько типов документов, поля в которых могут редактировать пользователи, в зависимости от роли и статуса документа.
Приведу пример того, что имеем на данном этапе:
Пример кода:
Собственно, это еще не все проблемы, так как в некоторых местах для того, чтобы решить задачу быстрее, кто-то оставляет редактируемые поля в представлении, которое по определению должно было быть только на чтение. Все это усугубляется частичными представлениями, которые живут своей жизнью и все их делали по разному, а местами используются шаблоны отображения.
Пытаемся улучшить.
Решаю, что можно задание правил отображения вынести в модель данных, а способом задания взять установку правил через атрибуты.
Примерный класс атрибута и правила:
Тут я считаю, что достаточно задавать правила для ролей и статусов, которым будет доступно редактирование полей, а всем остальным оставлять режим только для чтения. Так, в нашем приложении одни поля редактируются в основном только одной ролью и на определенном статусе документа, в связи с этим придется у свойства модели прописывать по одному правилу в большинстве случаев. Для создания атрибута можно вызвать конструктор и передать ему роль пользователя и набор статусов, на которых будет отключен режим для чтения.
Чтобы сделать проверку правил для объектов разных классов, нужен полиморфизм (все документы у нас никак не связанные классы, пока что), тут можно объявить интерфейс и реализовывать его в классах документов для проверки соответствия статусов и ролей документов, но так как пока логика отображения во всех документах зависит только от ролей и статусов, а свойство статуса у нас есть во всех документах, то делаем базовый класс и задаем атрибут для свойства модели:
В данном случае определяем, что поле RegNumber будет доступно у нас пользователю с ролью «Executor» на статусе «ToWork». Осталось написать хелпер, чтобы наши правила ожили. Хелпер будем использовать для отображения полей редактирования:
Логику проверки правил IsReadOnly оставил в этом же классе, он проверяет атрибуты поля и выносит свое решение. Сам хелпер для вывода поля использует EditorFor и в случае необходимости подправляет выходной html, чтобы сделать поле readonly.
Всё, остается в представлении вызвать наш метод:
Вот так я пытался решать свои проблемы. Хотелось бы узнать, в чем я, возможно, не прав.
Начинаю думать, как можно решить проблему давно надоевших карточек редактирования документов. В системе есть несколько типов документов, поля в которых могут редактировать пользователи, в зависимости от роли и статуса документа.
Приведу пример того, что имеем на данном этапе:
- несколько представлений для создания документа, редактирования, просмотра (просмотр по сути тоже редактирование но все поля, в большинстве случаев, только для чтения);
- код представления, состоящий из ветвлений с проверкой на статусы и роли, иногда только на роли, так как бизнес логика тоже не остается в стороне и принимает решение о том, что в некоторых случаях нужно показывать карточку только на чтение.
Пример кода:
@if (User.IsInRole(RolesEnum.Executor.GetDescription())) { @Html.TextBoxFor(model => model.RegNumber) } else { @Html.TextBoxFor(model => model.RegNumber, new { @readonly = "readonly" }) }
Собственно, это еще не все проблемы, так как в некоторых местах для того, чтобы решить задачу быстрее, кто-то оставляет редактируемые поля в представлении, которое по определению должно было быть только на чтение. Все это усугубляется частичными представлениями, которые живут своей жизнью и все их делали по разному, а местами используются шаблоны отображения.
Пытаемся улучшить.
Решаю, что можно задание правил отображения вынести в модель данных, а способом задания взять установку правил через атрибуты.
Примерный класс атрибута и правила:
/// <summary> /// Правило отображения поля для роли пользователя /// </summary> public class PropertyPermission { public RolesEnum Role { get; set; } public int[] Satuses { get; set; } public bool IsReadOnly { get; set; } public PropertyPermission(RolesEnum role, int[] statuses) { this.Role = role; this.Satuses = statuses; } } /// <summary> /// Атрибут задания правил отображения полей формы /// </summary> public class PropertyPermissionAttribute : Attribute { public PropertyPermission[] Permissons { get; private set; } public PropertyPermissionAttribute(PropertyPermission[] permissons) { this.Permissons = permissons; } public PropertyPermissionAttribute(RolesEnum role, params int[] statuses) { this.Permissons = new PropertyPermission[] { new PropertyPermission(role, statuses) }; } }
Тут я считаю, что достаточно задавать правила для ролей и статусов, которым будет доступно редактирование полей, а всем остальным оставлять режим только для чтения. Так, в нашем приложении одни поля редактируются в основном только одной ролью и на определенном статусе документа, в связи с этим придется у свойства модели прописывать по одному правилу в большинстве случаев. Для создания атрибута можно вызвать конструктор и передать ему роль пользователя и набор статусов, на которых будет отключен режим для чтения.
Чтобы сделать проверку правил для объектов разных классов, нужен полиморфизм (все документы у нас никак не связанные классы, пока что), тут можно объявить интерфейс и реализовывать его в классах документов для проверки соответствия статусов и ролей документов, но так как пока логика отображения во всех документах зависит только от ролей и статусов, а свойство статуса у нас есть во всех документах, то делаем базовый класс и задаем атрибут для свойства модели:
public class BaseDocumentModel { [DisplayName("ID")] public int ID { get; set; } [DisplayName("Статус")] public int? Status { get; set; } [PropertyPermission(RolesEnum.Executor, (int)StatusComplaint.ToWork)] [DisplayName("Входящий №")] public string RegNumber { get; set; } }
В данном случае определяем, что поле RegNumber будет доступно у нас пользователю с ролью «Executor» на статусе «ToWork». Осталось написать хелпер, чтобы наши правила ожили. Хелпер будем использовать для отображения полей редактирования:
public static class ProertyExtensions { public static MvcHtmlString RegistratorEditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) { return RegistratorEditorFor(html, expression, null); } public static MvcHtmlString RegistratorEditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes) { return RegistratorEditorFor(html, expression, new RouteValueDictionary(htmlAttributes)); } public static MvcHtmlString RegistratorEditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes) { var member = (expression.Body as MemberExpression).Member; if (html.ViewData.Model is BaseDocumentModel) { if (IsReadOnly(member as PropertyInfo, html.ViewData.Model as BaseDocumentModel)) { return html.TextBoxFor(expression, new { @readonly = "readonly" }); } } return html.EditorFor(expression); } static bool IsReadOnly(System.Reflection.PropertyInfo property, BaseDocumentModel document) { var attr = property.GetCustomAttributes(typeof(PropertyPermissionAttribute), false); bool result = true; foreach (PropertyPermissionAttribute a in attr) { foreach (var p in a.Permissons) { if (HttpContext.Current.User.IsInRole(p.Role.GetDescription()) && ((document.Status != null && p.Satuses.Contains((int)document.Status)) || p.Satuses.Length == 0)) { result = p.IsReadOnly; } } } return result; } }
Логику проверки правил IsReadOnly оставил в этом же классе, он проверяет атрибуты поля и выносит свое решение. Сам хелпер для вывода поля использует EditorFor и в случае необходимости подправляет выходной html, чтобы сделать поле readonly.
Всё, остается в представлении вызвать наш метод:
@Html.RegistratorEditorFor(model => model.RegNumber)
Вот так я пытался решать свои проблемы. Хотелось бы узнать, в чем я, возможно, не прав.
