我正在尝试使用最佳实践进行编码,我对此有疑问。我正在WebForms上测试它。
我有一个UserService层,我有一个方法将用户传递给RepositoryLayer:
public AddUserResponse AddUserResponse(AddUserRequest addUserRequest)
{
AddUserResponse response = new AddUserResponse();
User objUser = new User();
objUser.Names = addUserRequest.Names;
objUser.LastName = addUserRequest.LastName;
objUser.Email = addUserRequest.Email;
objUser.Alias = addUserRequest.Alias;
objUser.Profile.IdProfile = addUserRequest.Profile.IdProfile;
objUser.Password = addUserRequest.Password;
objUser.Active = addUserRequest.Active;
short OperationState=_userRepository.Add(objUser);
if (OperationState==0)
{
response.State=true;
response.Message="User inserted";
}
else if (OperationState==2)
{
response.State=false;
response.Message="Alias or Email already exist. Cannot insert User";
}
else
{
response.State=false;
response.Message="Error in User insertion";
}
return response;
}
然后我有一个UserRepository Layer,我有一个函数添加从我的服务层提交的用户:
public short Add(User objUser)
{ ... return OperationState }
如图所示,此函数在存储过程调用中继续插入用户记录。如果用户电子邮件或别名不存在,则它会插入并返回0,如果确实返回2,则操作失败返回1.
我在一次调用中执行检查和插入以保存数据库往返。
我是否在服务和存储库类上以正确的方式执行检查?或者如果不是,我应该如何抽象逻辑以让系统确定何时是重复用户?我应该使用模型或服务来放置验证逻辑并在发生这种情况时引发自定义异常吗?
非常感谢您的洞察力。
更新
为了普遍的兴趣,我现在发布了如何在我的应用程序上实现这一点,一旦我得到Jason的IoC解决方案也将对此进行更新。
模特课程:
using ABC.DEF.Infrastructure.Domain;
namespace ABC.DEF.Model
{
public class AliasOrEmailAreUnique
{
private readonly IRepository<User, int> repository;
public AliasOrEmailAreUnique(IRepository<User,int> repository)
{
this.repository = repository;
}
//If the user is added has Id 0 so search among all the existing users for one that could have the alias or email registered already
//else if the user is being edit then search among all the user except the user with such Id(itself)
public bool IsBroken(User model)
{
if (model.IdUser == 0)
{
return (
repository.List().Where(x => x.Alias == model.Alias).Any()
|| repository.List().Where(x => x.Email == model.Email).Any()
);
}
else
{
return (
repository.List().Where(x => x.Alias == model.Alias && x.IdUser != model.IdUser).Any()
|| repository.List().Where(x => x.Email == model.Email && x.IdUser != model.IdUser).Any()
);
}
}
public ErrorMessage ErrorMessage
{
get { return new ErrorMessage { Property = "AliasEmail", Message = "Alias or Email exists already" }; }
}
}
}
服务类:
using ABC.DEF.Repository;
using ABC.DEF.Model;
using ABC.DEF.Service.Messaging.User;
namespace ABC.DEF.Service
{
public class UsuarioService
{
public AddUserResponse AddUserResponse(AddUserRequest addUserRequest)
{
AddUserResponse response = new AddUserResponse();
User objUser = new User();
objUser.Names = addUserRequest.Names;
objUser.LastName = addUserRequest.LastName;
objUser.Email = addUserRequest.Email;
objUser.Alias = addUserRequest.Alias;
objUser.Profile.IdProfile = addUserRequest.Profile.IdProfile;
objUser.Password = addUserRequest.Password;
objUser.Active = addUserRequest.Active;
//Determine if the Alias or Email are unique
Model.AliasOrEmailAreUnique aliasOrEmailAreUnique = new Model.AliasOrEmailAreUnique(_userRepository);
if (!aliasOrEmailAreUnique.IsBroken(objUser))
{
_usuarioRepository.Add(objUser);
response.State = true;
response.Message = "User added succesfully";
}
else
{
response.State = false;
response.Message = aliasOrEmailAreUnique.ErrorMessage.Message;
}
return response;
}
}
}
答案 0 :(得分:2)
我喜欢在工作单元开头验证输入。对于Web应用程序,请求是工作单元。在触发控制器操作之前,我验证了用户输入。行动本身就是“快乐的道路”。如果它到目前为止我知道我的操作会成功。最后,请求(响应)我将任何更改提交回数据库。
我还想明确保持我的操作,因此添加实体的调用与编辑实体与删除实体的调用不同。
在您的方案中,您有一个服务层而不是控制器操作,但该过程仍然相同。在调用服务层之前验证模型。然后将模型传递给服务层以执行您想要的操作。
...更新1 ...
回应你的评论.....
我一直只在服务层调用我的存储库
不要陷入认为有一种线性模式进行通话的陷阱。通过申请。而是将其视为具有多个图层的onion or sphere。
该模型只是一个POCO / DTO。还有其他组件负责验证模型。通常我有一个看起来像这样的业务规则引擎......写在我的头顶。
interface IRule<T>
{
bool IsBroken(T model);
ErrorMessage Message {get;}
}
interface IRulesEngine
{
IEnumerable<ErrorMessage> Validate<T>(T model);
}
class ErrorMessage
{
public string Property {get;set;}
public string Message {get;set;}
}
class RulesEngine : IRulesEngine
{
private readonly IContainer container;
public RulesEngine(IContainer container)
{
this.container = container;
}
public IEnumerable<ErrorMessage> Validate<T>(T model)
{
return container
.GetInstances<IRule<T>>()
.Where(rule => rule.IsBroken(model))
.Select(rule => rule.Message);
}
}
此实现假定使用IoC容器,但可以在没有容器的情况下实现。规则可能如下所示
class NameIsUnique<MyClass> : IRule<MyClass>
{
private readonly IRepository<TheEntity> repository;
public NameIsUnique<MyClass>(IRepository<TheEntity> repository)
{
this.repository = repository;
}
public bool IsBroken(MyClass model)
{
return repository.Where(x => x.Name == model.Name).Any();
}
public ErrorMessage
{
get { return new ErrorMessage { Property = "Name", Message = "Name is not unique" }; }
}
}
最后,如何使用它。
var errors = engine.Validate(model);
LoadErrorsInToView(errors);
if(errors.Any()) return;
//call service to process the happy path...
...更新2 ...
首先我们重构我们的接口
//this is just a marker interface. don't directly imeplement this.
interface IRule
{
}
interface IRule<T> : IRule
{
bool IsBroken(T model);
ErrorMessage Message {get;}
}
class RulesEngine : IRulesEngine
{
public reasdonly ICollection<IRule> Rules = new List<IRule>();
public IEnumerable<ErrorMessage> Validate<T>(T model)
{
return Rules
.Where(x => typeof(IRule<T>).IsAssignableFrom(x.GetType()))
.Cast<IRule<T>>()
.Where(rule => rule.IsBroken(model))
.Select(rule => rule.Message);
}
}