感谢您的任何想法。
我正在努力学习MVC架构,并且正在开发一个小项目,这个项目对我来说是学习工具而不是项目的要求。
我需要弄清楚什么是好的,可接受的和差的做法,以及为什么。我完全理解没有具体的正确答案,但必须有适合任何良好范围的架构 - >可怕。从某种意义上说,不仅仅是一个问题,我希望良好的设计实践的逻辑流程意味着它们都与一个封装的答案有关。
我正在使用DarkoPečnik的Code First Membership provider
用户实现了一个接口IUser,它隐藏了许多属性,例如主键和密码,这些属性应该通过属于Membership类的方法来访问/更改。它还使用getter和setter作为字符串数组,而不是User.Roles Collection通过:
public virtual String[] RoleNames
{
set
{
this.Roles = (ICollection<Role>)value.Select(r =>
new Role { RoleName = r }).ToList();
问题1.)我怀疑这个属性可能是不好的做法,但我不确定究竟是为什么。这些方法会更好地作为方法GetRoleNames和SetRoleNames,还是Icollection本身会更好地包含在IUser接口中?
存在两个单独的viewModel,它们使用AutoMapper从IUser映射。这些模型与用户是否正在注册/更新有关他们的详细信息或由网站管理员注册/更新有关。
一个viewModel包含角色和部门的IEnumerable。这些属性当前通过automapper进行映射:
internal class RoleStringArrayToSelectListResolver
: ValueResolver<String[], IEnumerable<SelectListItem>>
{
protected override IEnumerable<SelectListItem> ResolveCore(String[] source)
{
return Roles.GetAllRoles().Select(x => new SelectListItem
{
Value = x,
Text = StringExtensions.ToSeparatedWords(x),
Selected = source.Contains(x)
问题2.)autoMapper是否可以放置这样的逻辑,如果不是,它应该去哪里?
问题3.)回发后,通过存储库方法createUser和updateUser验证业务逻辑。这些方法接受IUser实例作为参数是最优的,或者对于接受各种viewModels作为参数的几个重载更好,如果是这样,为什么呢?
非常感谢你提出任何想法并帮助我理解。
答案 0 :(得分:1)
为什么要创建IUser接口?我在你的问题中没有看到任何理由解释它为何有用。你是否期望交换出不同的实现方式?或者1个项目是否依赖于其属性,而无法访问具体的User类?
问题1.)我怀疑这个属性可能是不好的做法,但是 不确定究竟为什么。作为方法GetRoleNames,这些会更好吗? 和SetRoleNames,或者更好地包括Icollection本身 在IUser界面?
在我看来,您希望能够做的是仅使用角色名称字符串访问和操作ICollection Roles属性中的角色项。您可以单独保留类(不要创建属性或方法),只需将其作为扩展方法实现:
public static class UserExtensions
{
public static string[] GetRoleNames(this User user)
{
return user.Roles.Select(r => r.Name).ToArray();
}
public static void SetRoleNames(this User user, params string[] roleNames)
{
user.Roles = roleNames.Select(s => new Role { RoleName = s }).ToList();
}
}
有了这个,您可以相应地获取和设置角色名称。扩展方法只适用于已在User类中定义的内容,而不会因重载而混乱。扩展方法可以很容易地针对IUser接口而不是具体的User类进行编写。您只需撰写this IUser user
而不是this User user
。
var user = MethodToGetOrCreateUser();
string[] roleNames = user.GetRoleNames();
if (!roleNames.Any())
user.SetRoleNames("StandardUser", "SomeOtherRole");
问题2.)autoMapper是否可以放置这样的逻辑,并且 如果不是它应该去哪里?
我想我看到你在做什么:你有一个角色名称的字符串[](可能来自你的User.GetRoleNames属性/方法)。给定字符串数组,您要创建一个IEnumerable的SelectListItems。每个角色都应该有一个SelectListItem,但是应该只选择匹配数组中字符串的那个。由于您的客户端代码没有所有角色名称,因此您将该责任赋予了值解析器。您的客户端代码可能如下所示:
var user = MethodToGetOrCreateUser();
string[] roleNames = user.GetRoleNames();
var rolesMenu = Mapper.Map<IEnumerable<SelectListItem>>(roleNames);
从本质上讲,你正在使自动化程序“智能”足以知道如何获得该用户所不具备的所有其他角色名称。自动映射器不应该这么聪明;有任何类型的自动代理解析器访问数据存储通常不是一个好主意,如果可能你应该避免它。否则,您最终会获得访问数据存储的静态引用。像这样的东西可以进入你的控制器,并且更清晰:
// this should actually go in Application_Start
Mapper.CreateMap<IEnumerable<Role>, IEnumerable<SelectListItem>>()
.ForMember(d => d.Value, o => o.MapFrom(s => s.RoleName))
.ForMember(d => d.Text, o => o.MapFrom(s => s.RoleName.ToSeparatedWords()))
;
// create your menu with all roles
var rolesMenu = Mapper.Map<IEnumerable<SelectListItem>>(Roles.GetAllRoles());
// select only the roles that the user is in
var user = MethodToGetOrCreateUser();
user.GetRoleNames().ToList().ForEach(r =>
{
var match = rolesMenu.SingleOrDefault(i => i.Value == r);
if (match != null) match.Selected = true;
});
我发现你可以完全避免使用ValueResolver类。您可以使用ValueResolver类执行任何操作,也可以使用lambda重载.ResolveUsing()。
问题3.)回发后,业务逻辑通过验证 存储库方法createUser和updateUser。它会是最佳的吗? 这些方法接受IUser实例作为参数,或 最好是接受各种viewModel的几个重载 作为参数,如果是这样,为什么?
您的业务层绝不应接受viewmodels作为参数。它们是视图的模型,而不是业务模型。将业务代码视为您的MVC项目的客户端。如果您曾将业务代码移到MVC项目之外,并且您的业务代码将ViewModels作为参数,则代码将无法编译。为什么?因为视图模型位于MVC项目中,并且MVC项目依赖于业务项目 - 反之亦然。