经验总结
一年多也做了不少项目,遇到不少坑,也遇到很多的麻烦,有苦恼也有喜悦。这里就记载一些较为实用的项目经验
文件上传进度条
上传大文件时,客户端到服务器也是需要时间的,所以也要有一个这样的进度条。使用jquery的ajax,可以利用 xhr方法,创建一个新的xhr对象,然后使用xhr.upload.addEventListener绑定progress事件, e.loaded/e.total*100 得到的值就是我们想要的进度
关于EF
EF用的差,会感觉满是坑。用的好会感觉轻松愉快。EF可以尝试使用CodeFirst模式使用面向对象思维来建立良好的表结构,并且在web项目中的global的application_strat方法中进行EF预热。尝试使用乐观锁控制并发。
Mvc的扩展
- mvc有好用的模型验证,也可以继承ValidationAttribute自定义attribute验证注解,有其它的需求也可以直接继承IValidatableObject接口实现Validate方法来实现更定制化的验证。封装model.isvalid方法到attribute中,去除重复代码
- 用好filter,对于异常处理,日志记录等使用filter来进行处理会显的干净利索。
- 自己实现审计日志时想要拿到方法上的注册,可以在项目属性中选择生成xml文档,但是需要注册的是在发布后不会有xml文件,所以选择把xml文件生成到app_data文件夹下是不错的选择。对于多个项目xml文件名称不一致的的情况通常有两种选择
把名称换成一致的
在config文件中加上节点,value写成项目名称,然后再拿到项目名称。加载xml文件
var proejctName = ConfigurationManager.AppSettings["ProjectName"] as string;
- 封装上下文对象,继承controller类重写Initialize方法。在上下文对象中添加我们的常用信息,比如当前登陆人或是否是ajax请求等,对于cshtml想要使用@上下文对象的方式进行使用,可以创建一个类去继承WebViewPage类重写InitHelpers方法,替换ViewContext.Controller,并在webconfig中进行修改pages节点
<pages pageBaseType="WebWorkContextWebViewPage">
AutoMapper
AutoMapper是个好东西,简单且高效,但是其实大部分使用者都是使用Mapper.Map方法来进行转换,用过EF的人一般都知道,EF生成的语句一般都是select [xxx],[xxx] from xxx,也就是说把所有的字段都查询出来的,但是一般情况下是使用不了这么多字段,就会造成性能浪费。使用AutoMapper针对IQueryable的扩展ProjectTo,可以针对我们的dto里的字段来生成相应的sql语句。
总之就是尽量打造扁平化的Dto
其它
- 对于逻辑问题,避免使用if else、switch case进行判断,并且尝试使用设计模式
比如查询订单是否支付时,一般会有支付宝、银联、微信等支付方式,对于某个项目具有什么支付方式可以提前进行配置,比如使用attribute,使用策略模式进行封装算法来写出良好的代码。使用委托来进行回调
再比如不同项目下的订单可能会分为不同的来源,一般可能在表中使用orderSouceId来进行区分,但是进行获取订单的时候使用if判断来源获得sourceId是不好的行为,三四个项目就会让if else变的无比恶心,可以在项目的枚举使用attribute进行注解,然后再拿到attribute中的sourceId,可以轻松解决这类问题
- 一般的三层架构中,逻辑层因为需求越来越多,类会变的越来越大超过几千行代码都是经常有的事,这就会带来困惑。对象.出的大量方法找起来比较困难不说类也会变的很臃肿,这时可以使用扩展方法来扩展类,并且针对某个类的不同扩展分出不同的命名空间,比如注册的扩展,命名空间可以写成 Bll.UserRole.Register。我们在展现层里只需要引用 Bll.UserRole.Register,点出来的方法简单清爽
- 多写注解
WebApi
写接口的时候,会因为写文档更感到头疼。不同的项目用到相同的接口,还要再写一份相同的文档是件非常恶心的事情 ,而且人工去写还可能会因为一些疏忽而带来麻烦。
使用Swagger来生成在线文档不错的选择, Swagger也为我们留出了大量的扩展、对于请求header中的参数,可以通过继承IOperationFilter来进行实现
/// <summary> /// 设置请求参数,放到header中 /// </summary> public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription) { if (operation.parameters == null) operation.parameters = new List<Parameter>(); operation.parameters.Add(new Parameter { name = "Source", @in = "header", description = "来源", required = true, type = "string" }); } }
对于某个接口用到上传功能,但是swagger没有给我们提供这样的参数(因为它不知道),我们也可以通过配置来实现,这里有一个契约,也就是如果方法名中带有Upload就提供一个file的参数,也是同样继承 IOperationFilter
//如果方法名里包含upload,则提供一个参数名为file类型为file的参数 if (apiDescription.ActionDescriptor.ActionName.Contains("Upload")) { operation.consumes.Add("application/form-data"); operation.parameters.Add(new Parameter { name = "file", @in = "formData", required = true, type = "file" }); }
swagger生成的文档中只是有资源的名称,并没有controller的注释。一个不太熟悉的人看起来也是会有点懵比,网上有相关资料,大家可以针对性的去搜索一下,但是我看到的一些,注释在文档中不是很容易观察到,所以就对js进行了一些修改,让注解更容易看到
//设置控制器注释 _setControllerSummary = function () { $.ajax({ type: "get", async: true, url: $("#input_baseUrl").val(), dataType: "json", success: function (data) { var summaryDict = data.ControllerDesc; var id, controllerName, strSummary; $("#resources_container .resource").each(function (i, item) { id = $(item).attr("id"); if (id) { controllerName = id.substring(9); strSummary = summaryDict[controllerName]; if (strSummary) { var value = $(item).children(".heading").find("h2").find("a").text(); $(item).children(".heading").find("h2").find("a").text(value + " " + strSummary); } } }); } }); },