Общая задача: Административный Controller к стандартному MembershipProvider.
Зачем:
К сделанным сайтам нужно дать управление пользователями администратору. Стандартного, простого и на русском не нашел.Для решения созданы несколько кирпичиков и их размещаю в статье.
Кирпичик №1. Индивидуальные шаблоны генератора View (например на русском)
Зачем: Переводить надоело или нужен свой шаблон
Шаблоны лежат тут: «X:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 4\CodeTemplates\AddView\CSHTML\»
Просто копируем нужный шаблон (например Edit.tt в Edit_Ru.tt), корректируем в нем код на нужный и при генерации View выбираем этот
шаблон.
Кирпичик №3,4,5. CheckBoxFor, генерация CheckBoxFor в View, и сортировка полей модели
Зачем: Встроенного нет.
1. Код CheckBoxListFor найден на просторах сети и немного подправлен
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty[]>> expression,
MultiSelectList multiSelectList,
object htmlAttributes = null)
{
MemberExpression body = expression.Body as MemberExpression;
string propertyName = body.Member.Name;
TProperty[] list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
List<string> selectedValues = new List<string>();
if (list != null)
{
selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i)
{ return i.ToString(); });
}
TagBuilder divTag = new TagBuilder("div class=\"CheckBoxListFor\"");
divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
foreach (SelectListItem item in multiSelectList)
{
divTag.InnerHtml += String.Format("<label><input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} />{3}</label>",
propertyName,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(divTag.ToString());
}
2. Attributes
/// <summary>
/// Атрибут для генерации View
/// </summary>
public class CheckBoxListAttribute : Attribute
{
/// <summary>
/// Путь получения всех значений
/// </summary>
public string AllValueLink { get; set; }
public CheckBoxListAttribute(string allValueLink)
{
AllValueLink = allValueLink;
}
}
/// <summary>
/// Атрибут для сортировкаи полей модели
/// </summary>
public class PropertySortAttribute : Attribute
{
/// <summary>
/// Порядок сортировки
/// </summary>
public int SortOrder { get; set; }
public PropertySortAttribute(int sortOrder)
{
SortOrder = sortOrder;
}
}
3. Model
public class AdvAccountModel
{
[Required]
[DataType(DataType.Password)]
[Display(Name = "Текущий пароль")]
[PropertySort(-1)]
public string OldPassword { get; set; }
[Required]
[Display(Name = "Имя пользователя")]
[PropertySort(0)]
public string UserName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email")]
[PropertySort(1)]
public string Email { get; set; }
[StringLength(100, ErrorMessage = "{0} должен быть минимум {2} символов.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Пароль")]
[PropertySort(2)]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Подтверждение пароля")]
[Compare("Password", ErrorMessage = "Новый пароль и подтверждение пароля - не совпадают.")]
[PropertySort(3)]
public string ConfirmPassword { get; set; }
[Display(Name = "Секретный вопрос")]
[PropertySort(4)]
public string PasswordQuestion { get; set; }
[Display(Name = "Ответ на секретный вопрос")]
[PropertySort(5)]
public string PasswordAnswer { get; set; }
}
public class AdvProfileModel : AdvAccountModel
{
/// <summary>
/// Пустой конструктор
/// </summary>
public AdvProfileModel()
{
}
/// <summary>
/// Конструктор с инициализацие по пользователю
/// </summary>
/// <param name="user"></param>
public AdvProfileModel(string user)
{
UserName = user;
MembershipUser mUser = Membership.GetUser(user);
if (user != null)
{
Email = mUser.Email;
PasswordQuestion = mUser.PasswordQuestion;
}
}
[Required(AllowEmptyStrings = true)]
[DataType(DataType.Password)]
[Display(Name = "Текущий пароль")]
[PropertySort(-1)]
new public string OldPassword { get; set; }
#region Roles
string[] _userRoles;
[Display(Name = "Роли")]
[CheckBoxList("Model.AllRoles")]
[ScaffoldColumn(true)]
[PropertySort(11)]
public string[] UserRoles
{
get
{
if (String.IsNullOrEmpty(UserName))
return new string[0];
return Roles.GetRolesForUser(UserName);
}
set
{
_userRoles = value;
}
}
/// <summary>
/// Все роли
/// </summary>
public MultiSelectList AllRoles
{
get
{
string[] all = Roles.GetAllRoles();
List<SelectListItem> ret = new List<SelectListItem>();
foreach(string role in all)
ret.Add(new SelectListItem { Selected = false, Text = role, Value = role});
MultiSelectList r = new MultiSelectList(ret, "Text", "Value");
foreach (SelectListItem item in r)
{
}
return r;
}
}
/// <summary>
/// Сохранить роли в БД
/// </summary>
public void SaveRoles()
{
if (String.IsNullOrEmpty(UserName))
return;
string[] all = Roles.GetAllRoles();
foreach (string role in all)
{
var item = Array.Find(_userRoles, x => x == role);
if ((String.IsNullOrEmpty(item)) && (Roles.IsUserInRole(UserName, role)))
Roles.RemoveUserFromRole(UserName, role);
}
foreach (string role in _userRoles)
if (!Roles.IsUserInRole(UserName, role))
Roles.AddUserToRole(UserName, role);
}
#endregion Roles
}
4. Код для страницы .chtml
@Html.CheckBoxListFor(model => model.UserRoles, Model.AllRoles)
5. Наш шаблон Edit_Ru.tt
Весь приводить тут не буду только измененные части. Шаблон не переписывал, постарался с минимальными изменениями внедрить совй код
1) В районе 84 строки. Определяем есть у свойствамодели атрибут «CheckBoxListAttribute» и узнаем путь к списку для CheckBoxList.
foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
if (property.Scaffold) {
//AS 2012-05-02
string IsCheckbox = "";
foreach(object attr in property.Attributes)
if (attr.GetType().Name == "CheckBoxListAttribute")
foreach(PropertyInfo pi in attr.GetType().GetProperties())
if (pi.Name == "AllValueLink")
IsCheckbox = (string)pi.GetValue(attr, null);
//AS 2012-05-02
2) В районе строки 114. Если поле с атрибутом «CheckBoxListAttribute» то шаблон генерируем по-своему.
<div class="editor-field">
<#
if (property.IsForeignKey) {
#>
@Html.DropDownList("<#= property.Name #>", String.Empty)
<#
//AS 2012-05-02
} else if (IsCheckbox != "") {
#>
@Html.CheckBoxListFor(model => model.<#= property.Name #>, <#= IsCheckbox #>)
<#
//AS 2012-05-02
} else {
#>
@Html.EditorFor(model => model.<#= property.Name #>)
<#
}
#>
@Html.ValidationMessageFor(model => model.<#= property.Name #>)
</div>
3) В районе строки 163. Изменим модель свойств, через который работает шаблон. К сожалению, я не понял, зачем в таком небольшом коде добавили эту прослойку.
class ModelProperty {
public string Name { get; set; }
public string AssociationName { get; set; }
public string ValueExpression { get; set; }
public string ModelValueExpression { get; set; }
public string ItemValueExpression { get; set; }
public Type UnderlyingType { get; set; }
public bool IsPrimaryKey { get; set; }
public bool IsForeignKey { get; set; }
public bool IsReadOnly { get; set; }
public bool Scaffold { get; set; }
//AS 2012-05-02
//Attributes
public object[] Attributes { get; set; }
//Sort Order
public int Sort { get; set; }
//AS 2012-05-02
}
4) В районе строки 284. Меняем метод GetEligibleProperties.
List<ModelProperty> GetEligibleProperties(Type type) {
List<ModelProperty> results = new List<ModelProperty>();
foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) {
Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
if (prop.GetGetMethod() != null && prop.GetIndexParameters().Length == 0 && IsBindableType(underlyingType)) {
string valueExpression = GetValueExpressionSuffix(prop);
results.Add(new ModelProperty {
Name = prop.Name,
AssociationName = GetAssociationName(prop),
ValueExpression = valueExpression,
ModelValueExpression = "Model." + valueExpression,
ItemValueExpression = "item." + valueExpression,
UnderlyingType = underlyingType,
IsPrimaryKey = IsPrimaryKey(prop),
IsForeignKey = IsForeignKey(prop),
IsReadOnly = prop.GetSetMethod() == null,
Scaffold = Scaffold(prop)
//AS 2012-05-02
, Attributes = prop.GetCustomAttributes(false)
, Sort = Sort(prop)
//AS 2012-05-02
});
}
}
//AS 2012-05-02
//Сортировка
var result = results.OrderBy(x => x.Sort).ToList();
//AS 2012-05-02
return result;
}
5) Добавить куда удобно я добавил после метода GetEligibleProperties. Метод возвращает порядок сортировок для свойств модели.
//AS 2012-05-02
int Sort(PropertyInfo propertyInfo) {
object[] attrs = propertyInfo.GetCustomAttributes(true);
foreach(object attr in attrs)
if (attr.GetType().Name == "PropertySortAttribute")
foreach(PropertyInfo pi in attr.GetType().GetProperties())
if (pi.Name == "SortOrder")
return (int)pi.GetValue(attr, null);
return 0;
}
//AS 2012-05-02
6) В конце файла. Чтобы все свойства попали в список на генерацию.
bool IsBindableType(Type type) {
//AS 2012-05-02
return true;
//return type.IsPrimitive || bindableNonPrimitiveTypes.Contains(type);
//AS 2012-05-02
}
Итого:
В результате наш шаблон будет правильно сортировать поля при генерации View, и будет правильно создавать код для «CheckBoxListFor»
На этом первый пост заканчиваю. Инвайта нет — отвечать не смогу. Если пост будет интересен, то оформлю еще пост с кодом (выдержками из кода в пост и исходный код отдельно) для решения основной задачи – «Административный Controller к стандартному MembershipProvider».