Trying to make logical edit forms behavior in asp mvc
I am currently working on a small project on asp net mvc. The terms are quite short, the result is needed as soon as possible, so we started to sketch out the functionality and pull on a beautiful interface (shit-code). Time passed, looking at it became harder, making corrections longer, 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:
Code example:
Actually, this is not all the problems, because in some places, in order to solve the problem faster, someone leaves editable fields in the view, which by definition should have been read-only. All this is aggravated by partial representations that live their own lives and they did everything differently, and in some places display templates are used.
Trying to improve.
I decide that it is possible to set the display rules to the data model, and to take the setting of the rules through the way of setting the attributes.
An example attribute class and rule:
Here I believe that it is enough to set the rules for roles and statuses, which will be available for editing fields, and leave the read-only mode for everyone else. So, in our application, one field is edited mainly with only one role and on a certain status of the document, in this connection it will be necessary to register the model property according to one rule in most cases. To create an attribute, you can call the constructor and pass it the user role and a set of statuses on which the reading mode will be disabled.
To check the rules for objects of different classes, you need polymorphism (all the documents we have are in no way related classes, for now), here you can declare an interface and implement it in document classes to check the compliance of the statuses and roles of documents, but since the display logic in of all documents depends only on roles and statuses, and we have a status property in all documents, then we make a base class and set an attribute for the model property:
In this case, we determine that the RegNumber field will be available to a user with the "Executor" role with the "ToWork" status. It remains to write a helper so that our rules come to life. We will use the helper to display edit fields:
IsReadOnly left the logic of checking rules in the same class, it checks the attributes of the field and makes its decision. The helper itself uses EditorFor to display the field and, if necessary, corrects the output html to make the readonly field.
Everything remains in the view to call our method:
So I tried to solve my problems. I would like to know what I may not be right about.
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 checking for statuses and roles, sometimes only for roles, as business logic also does not stand aside and decides that in some cases it is necessary to show the card read-only.
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 faster, someone leaves editable fields in the view, which by definition should have been read-only. All this is aggravated by partial representations that live their own lives and they did everything differently, and in some places display templates are used.
Trying to improve.
I decide that it is possible to set the display rules to the data model, and to take the setting of the rules through the way of setting the attributes.
An example attribute class and rule:
///
/// Правило отображения поля для роли пользователя
///
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;
}
}
///
/// Атрибут задания правил отображения полей формы
///
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 believe that it is enough to set the rules for roles and statuses, which will be available for editing fields, and leave the read-only mode for everyone else. So, in our application, one field is edited mainly with only one role and on a certain status of the document, in this connection it will be necessary to register the model property according to one rule in most cases. To create an attribute, you can call the constructor and pass it the user role and a set of statuses on which the reading mode will be disabled.
To check the rules for objects of different classes, you need polymorphism (all the documents we have are in no way related classes, for now), here you can declare an interface and implement it in document classes to check the compliance of the statuses and roles of documents, but since the display logic in of all documents depends only on roles and statuses, and we have a status property in all documents, then we make a base class and set an 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 a user with the "Executor" role with the "ToWork" status. It remains to write a helper so that our rules come to life. We will use the helper to display edit fields:
public static class ProertyExtensions
{
public static MvcHtmlString RegistratorEditorFor(this HtmlHelper html, Expression> expression)
{
return RegistratorEditorFor(html, expression, null);
}
public static MvcHtmlString RegistratorEditorFor(this HtmlHelper html, Expression> expression, object htmlAttributes)
{
return RegistratorEditorFor(html, expression, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString RegistratorEditorFor(this HtmlHelper html, Expression> expression, IDictionary 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 of checking rules in the same class, it checks the attributes of the field and makes its decision. The helper itself uses EditorFor to display the field and, if necessary, corrects the output html to make the readonly field.
Everything remains in the view to call our method:
@Html.RegistratorEditorFor(model => model.RegNumber)
So I tried to solve my problems. I would like to know what I may not be right about.