在ViewModel和entity转换时,大量的重复代码,比如:
return new _LogOnStatusModel { Id = current.Id, UserName = current.UserName };
可以使用工具简化。演示:
MapperConfiguration config = new MapperConfiguration( cfg => cfg.CreateMap<User, _LogOnStatusModel>() );
IMapper mapper = config.CreateMapper();
_LogOnStatusModel model = mapper.Map<_LogOnStatusModel>(current);
但是,注意:MapperConfiguration的生成是比较消耗资源的!而且整个项目只需要使用一个MapperConfiguration即可,怎么办?
在SRV的基类 BaseService 中引入静态构造函数,生成静态只读的MapperConfiguration对象即可:(复习:为什么这样就行?)
protected readonly static MapperConfiguration config; static BaseService() { config = new MapperConfiguration( cfg => { cfg.CreateMap<User, _LogOnStatusModel>(); cfg.CreateMap<User, _UserInfoModel>();
断点演示:不关闭IIS,哪怕是停止调试,都不会重新运行该静态构造函数
然后,在 BaseService 中引入一个 mapper 属性,供所有子类使用:
protected IMapper mapper => config.CreateMapper();mapper是“轻量级”的,可以直接使用,一般不需要特别处理。(估计AutoMapper内部也进行了相应的处理,不是每次CreateMapper()就真的new一个)
这样,在SRV的子类中就可以直接使用mapper了。
另外,根据我们PerViewPerModel的原则(复习),ViewModel的(短)命名可能大量重复,比如:
@想一想@:这时候使用using管用不?
我们可以使用全名来区分,但也可以取个巧:
using VM = SRV.ViewModel.Models;这样,下面就可以这样写:
cfg.CreateMap<VM.Article.SingleModel, Article>(); cfg.CreateMap<VM.Problem.SingleModel, Problem>(); cfg.CreateMap<VM.Suggest.SingleModel, Suggest>();
AutoMapper可以自动的映射:
public class User : BaseEntity { public string UserName { get; set; } public string Password { get; set; } |
public class IndexModel { public string UserName { get; set; } public string Password { get; set; } |
1 |
public User InvitedBy { get; set; } |
public string InvitedByUserName { get; set; } |
2 |
public User InvitedBy { get; set; } |
public IndexModel InvitedBy { get; set; } |
3.1 |
public IList<Comment> Comments { get; set; } |
public IList<CommentModel> Comments { get; set; } |
3.2 |
如果名称不同,需要进行配置。比如,_LogOnStatusModel的Name属性,由User的UserName获得:
cfg.CreateMap<User, _LogOnStatusModel>() .ForMember(l => l.Name, opt => opt.MapFrom(u => u.UserName))
MapFrom()里的Func可以自由扩展,比如:
.ForMember(l => l.Name, opt => opt.MapFrom(u => u.UserName.Trim()))
还可以配置忽略某个属性。比如,_LogOnStatusModel的Password,不要映射(忽略之):
cfg.CreateMap<User, _LogOnStatusModel>() .ForMember(l => l.Password, opt => opt.Ignore())以及,如果某个属性为null值时的处理方案(AutoMapper不会报NullReferenceException)。比如,如果_LogOnStatusModel的Name映射过后值为null的话,将其替换成“匿名用户”:
cfg.CreateMap<User, _LogOnStatusModel>() .ForMember(l => l.Name, opt => opt.NullSubstitute("匿名用户"))
一般来说,AutoMapper中只需要执行上述这些简单的“映射”逻辑,复杂逻辑不应该由MapFrom()实现。
最后,通过添加ReverseMap(),可以形成“双向”映射(之前的映射都是单向的):
cfg.CreateMap<VM.Register.IndexModel, User>().ReverseMap();
默认的,AutoMapper只映射它能够映射的属性。
有时候我们希望AutoMapper能够检查是否“所有的属性”都可以映射,这就需要添加语句:
config.AssertConfigurationIsValid();
这样如果DestinationType中还有没有被映射的属性,AutoMapper就会抛出异常。(演示)
还可以在映射时添加一个参数MemberList,通过枚举指定:
cfg.CreateMap<User, _LogOnStatusModel>(MemberList.None)
cfg.CreateMap<User, _LogOnStatusModel>(MemberList.Source)
之前的mapper.Map(model)方法是利用传入的model,全新的new出来一个对象。
但有时候我们希望能利用model“修改”一个对象,比如:
public class User { public int Id { get; set; } //有Id public string UserName { get; set; }
public class IndexModel { //没有Id public string UserName { get; set; }注意User有Id而IndexModel没有Id,这是前提。
接下来,User和IndexModel对象都是已有的:
User current = new User { UserName = "atai", Id = 10
IndexModel model = new IndexModel { UserName = "fg",
演示对比:
current = mapper.Map<User>(model);
mapper.Map(model, current);
更多的特性,留待同学们自己去发掘……o(* ̄︶ ̄*)o
复习:DI和IoC,现在Controller依赖Service……
演示:额外引入了MockService和ServiceInterface
#体会#:为什么需要接口?
在不断的开发迭代过程中,我们需要不断的在MockService和ProdService之间进行切换,怎么才能方便的进行呢?
在C#中我们可以使用:
public ArticleController() { #if UI articleService = new MockService.ArticleService(); #else articleService = new ProdService.ArticleService(); #endif }
但更优雅的方式是使用Autofac这种第三方依赖注入工具(官网地址)
因为其在MVC中非常流行,所以除了Autofac组件以外,还有专门的MVC integration组件:
通过nuget下载了上述两个Autofac类库之后,在Global.asax文件中,MvcApplication.cs的Application_Start()方法中:
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly); builder.RegisterFilterProvider();
然后,指示使用何种interface/基类的实现:
builder.RegisterType<MockService.UserService>().As<IUserService>();
builder.RegisterAssemblyTypes(typeof(ProdService.UserService).Assembly) .AsImplementedInterfaces();
注意:AsImplementedInterfaces()不能注册(非接口)的基类,比如DbContext。
这里一样可以利用条件编译符:
#if Mock using MockService; #else using ProdService; #endif
注意:需要开发者自定义设置的就这点代码,其他的都只需要原样copy就是了。
通过ContainerBuilder得到一个IContainer容器对象,并为MVC自动设定解析(resolve:获取“接口对象”)IContainer container = builder.Build(); DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
#体会#:Builder模式
可以通过构造函数注入:
private IUserService userService; public SharedController(IUserService userService) { this.userService = userService; }
#理解#:
注意:
如果是继承的Attribute,可以使用属性注入:(因为已经在Golbal.asax.cs中:builder.RegisterFilterProvider();)
public class NeedLogOnAttribute : AuthorizeAttribute { public IUserService Service { get; set; } public override void OnAuthorization(AuthorizationContext filterContext) { if (Service.GetLogonStatus() == null)
如果filter实现的是接口,就只能通过Container来Resolve():
DbContext context = AutofacDependencyResolver.Current.GetService<DbContext>();
IUserService service = AutofacConfig.Container.Resolve<IUserService>();//老版
注意方法InstancePerRequest():
builder.RegisterAssemblyTypes(typeof(BaseService).Assembly).InstancePerRequest().AsSelf(); builder.RegisterType(typeof(MysqlDbContext)).InstancePerRequest().As<DbContext>();
另:AsSelf()在没有基类/接口时使用。
是一个专门用于记录错误日志的第三方组件(官网)。
理论上我们也可以自己记录(比如利用HandleErrorAttribute.OnException()使用log4net),但是……,你懂的,(^_-)
MVC中应用,原文4步,实际上只需要2步:
<elmah> <errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/App_Data" /> </elmah>即将log文件存入到根目录下/App_Data文件夹。
演示:
出于安全考虑(比如cookie消息可能会被泄露等),elmah默认禁止了远程访问/elmah.axd。
演示访问:https://17bang.ren/elmah.axd,403禁止访问。
这是在web.config中有配置的:
<security allowRemoteAccess="false" />
PS:不要放开权限(不建议),确实需要对特定人员放开权限的,可以配置authorization。但这需要配合ASP.NET自带的权限管理机制才能生效,我们没讲……
另外,还可以进行错误过滤(某些异常不进行处理),可以在Global.asax中添加://和Application_Start()并列,由Elmah自行调用 void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e) { //通过e.Exception可以获得诸多有用信息,比如Message、StackTrace等…… if (e.Exception.GetBaseException() is HttpRequestValidationException) { e.Dismiss(); //忽略当前异常
无论是出于安全,还是“用户友好”的考虑,我们都不能把包含源代码/堆栈信息的“报错页面(黄页)”直接呈现给用户的。
在web.config中开启“自定义错误页面”配置:
<customErrors mode="On"></customErrors>
默认是Off,所以有报错就直接黄页。
还可以设置RemoteOnly:只在远程访问(非localhost,浏览器和ASP.NET项目不在同一地址)时使用自定义的报错页面。这样,开发/维护人员就可以在项目发布以后,还可以登陆远程服务器,查看到黄页中的报错信息。
MVC默认在~/App_Start/FilterConfig.cs下注册了HandleErrorAttribute。
当customErrors开启生效之后,程序运行异常就会呈现(不是重定向)默认的错误页面内容:~/Views/Shared/Error.cshtml(演示)
注意错误页面全是静态内容。这样可以避免“循环错误”,即:自定义错误页面上面还有报错!
HandleErrorAttribute是不会处理404错误的(因为此时根本就没有进入Controller/Action可控的Filter领域)。
404错误的处理需要在customErrors中配置:
<error statusCode="404" redirect="/404.html"/>即:404的错误重定向到/404.html页面。
注意:404.html是一个静态HTML页面,直接放在项目根目录下,而不是要放在Views下。
customErrors的配置效力在HandleErrorAttribute之后,所以500的配置不会生效:
<error statusCode="500" redirect="/500.html"/>
除非注释掉HandleErrorAttribute的注册代码……(演示)
重定向到自定义错误页面后,MVC会自动在url后面加上参数aspxerrorpath,如:
多快好省!前端后端,线上线下,名师精讲
更多了解 加: