I'm currently working on a small project on asp net mvc. The deadlines are rather short, the result is needed as soon as possible, so we began to throw in the functionality and pull the beautiful interface (govnodit). As time went on, it became harder to look at it, it took longer to make edits, and while the customer is testing the application, there is time to think about what can be done with this sheet of code (there was no time to think before).
I'm starting to think about how to solve the problem of long-tired document editing cards. There are several types of documents in the system, fields in which users can edit, depending on the role and status of the document.
I will give an example of what we have at this stage:
- several views for creating a document, editing, viewing (viewing is essentially also editing, but all fields, in most cases, are read-only);
- presentation code consisting of branches with status and role checks, sometimes only on roles, since business logic also does not stand aside and decides that in some cases it is necessary to show the card only for reading.
Code example:
')
@if (User.IsInRole(RolesEnum.Executor.GetDescription())) { @Html.TextBoxFor(model => model.RegNumber) } else { @Html.TextBoxFor(model => model.RegNumber, new { @readonly = "readonly" }) }
Actually, this is not all the problems, because in some places in order to solve the problem more quickly, someone leaves editable fields in the view, which by definition should have only been readable. All this is exacerbated by partial representations that live their lives and all of them were made differently, and in some places the display templates are used.
We are trying to improve.
I decide that it is possible to put the mapping rules into the data model, and to set the rules using attributes by the method of the bugs.
Sample attribute class and rules:
/// <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) }; } }
Here I think that it is enough to set the rules for roles and statuses, which will be available for editing fields, and leave the rest to be read-only mode. So, in our application, some fields are edited for the most part by only one role and on a certain status of the document; therefore, it will be necessary for the model property to prescribe according to one rule in most cases. To create an attribute, you can call the constructor and give it a user role and a set of statuses on which the read mode will be disabled.
To make checking the rules for objects of different classes, you need polymorphism (all documents have no connected classes at all, so far), then you can declare an interface and implement it in document classes to check the compliance of document statuses and roles, but since so far the display logic is all documents depend only on roles and statuses, and we have the status property in all documents, then we make the base class and set the attribute for the model property:
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; } }
In this case, we determine that the RegNumber field will be available to us to the user with the role of “Executor” on the status of “ToWork”. It remains to write a helper to our rules come to life. Helper will be used to display edit fields:
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 left the logic for checking the rules in the same class, it checks the field attributes and makes its decision. The helper itself uses EditorFor to display the field and, if necessary, corrects the output html to make the field readonly.
Everything remains in the submission to call our method:
@Html.RegistratorEditorFor(model => model.RegNumber)
That's how I tried to solve my problems. I would like to know what I might be wrong.