C# 链式编程实践之文件校验
最近有个文件上传的功能,需要做很多判断,以前的代码是if-else的判断,正好可以练练手
老代码如下:
public class UploadFile { public void Upload() { var files = Request.Form.Files[idValue]; if (files == null) return Json(new { isok = false, msg = "未上传任何附件,请重试", filepath = "" }); string filename = files.FileName; if (string.IsNullOrEmpty(filename)) return Json(new { isok = false, msg = "文件名不能为空", filepath = "" }); filename = filename.TrimAll(); string fileExt = Path.GetExtension(filename); var isImges = WebProvider.CheckFileImges(fileExt); if (isImges) return Json(new { isok = false, filepath = "", msg = "请在下方直接上传图片!" }); isImges = WebProvider.CheckFileExtension(fileExt); if (!isImges) return Json(new { isok = false, filepath = "", msg = "文件仅限于上传(.DOC、.DOCX、.PDF)" }); // more code .... }
链式编程就是实例方法后面可以点出来,那么意味着每个方法都要返回当前的类实例,也就是this
public class Demo { private readonly CheckResult = new CheckResult(); public Demo CheckName() { //more coding return this; } public Deme CheckAge() { //more coding return this; } public CheckResult GetCheckResult() { return _result; } }
然后调用的地方就可以这样操作
var demo = new Demo().CheckName().CheckAge().GetCheckResult();
ok 照猫画虎来一版:
public class Result { public bool IsSuccess { get; set; } public string FilePath { get; set; } public string ErrorMsg { get; set; } public string FileName { get; set; } }
public class FileValidator { private readonly IFormFile _file; private readonly Result _result = new Result(); public FileValidator(IFormFile file) { _file = file; } /// <summary> /// 校验是否存在文件 /// </summary> /// <returns></returns> private FileValidator ValidateFile() { if (_file == null) { _result.ErrorMsg = "未上传任何附件,请重试"; } else { _result.IsSuccess = true; } return this; } private FileValidator ValidateFileName() { if (_file == null) { _result.ErrorMsg = "文件名不能为空"; } else { _result.IsSuccess = true; } return this; } /// <summary> /// 校验是否文件 /// </summary> /// <returns></returns> public FileValidator ValidateIsFile() { if (CheckFormat.CheckIsFile(_file.FileName)) { _result.ErrorMsg = "请在下方直接上传图片!"; } else { _result.IsSuccess = true; } return this; } /// <summary> /// 校验文件格式 /// </summary> /// <returns></returns> public FileValidator ValidateFileExtension() { if (CheckFormat.CheckFileExtension(_file.FileName)) { _result.ErrorMsg = "文件仅限于上传(.DOC、.DOCX、.PDF)"; } else { _result.IsSuccess = true; } return this; } /// <summary> /// 获取校验结果 /// </summary> /// <returns></returns> public Result GetVaildResult() { return _result; } }
调用的地方是这样的
var checkResult = new FileValidator(file) .ValidateFile() .ValidateFileName() .ValidateIsFile() .ValidateFileExtension() .GetVaildResult();
看起来是大功告成了,但是细看就会发现存在问题:
- 只能先调用
ValidateFile()
,否则后面会空引用. - 当校验失败时,依旧会执行到最后,即
ValidateFileName()
失败 ,还是会走到ValidateIsFile()
,违反了提前中断的编码规范
如何解决呢?
想了半天没什么好的解决方案,那就从业务上解决.
- 既然要校验file的属性,那么首先file是要存在的,那么校验file存在的逻辑应该首先执行,所以把
ValidateFile()
放到构造函数中执行,这样就不用担心后面空引用了 - 对于报错一直执行这个问题,首先想到的是一旦校验失败就throw Exception,然后全局异常处理,但是考虑到这个是给前端的一个接口中的校验,未免有点小题大作了,那么就只能在每个校验中加入判断,如果上一个是失败的则,直接返回,不进入当前判断
那么开始第二版本:
public class FileValidator { private readonly IFormFile _file; private readonly Result _result = new Result(); public FileValidator(IFormFile file) { _file = file; ValidateFile();//先执行校验文件和文件名是否合法 } /// <summary> /// 校验是否文件 /// </summary> /// <returns></returns> public FileValidator ValidateIsFile() { if (!_result.IsSuccess) //如果校验失败,则直接返回 { return this; } if (CheckFormat.CheckIsFile(_file.FileName)) { _result.ErrorMsg = "请在下方直接上传图片!"; } else { _result.IsSuccess = true; } return this; } /// <summary> /// 校验文件格式 /// </summary> /// <returns></returns> public FileValidator ValidateFileExtension() { if (!_result.IsSuccess) { return this; } if (CheckFormat.CheckFileExtension(_file.FileName)) { _result.ErrorMsg = "文件仅限于上传(.DOC、.DOCX、.PDF)"; } else { _result.IsSuccess = true; } return this; } /// <summary> /// 获取校验结果 /// </summary> /// <returns></returns> public Result GetVaildResult() { return _result; } /// <summary> /// 校验是否存在文件 /// </summary> /// <returns></returns> private FileValidator ValidateFile() //直觉上来说,文件应该是有文件名的 所以放到一起校验 { if (_file == null) { _result.ErrorMsg = "未上传任何附件,请重试"; } else if (string.IsNullOrEmpty(_file.Name)) { _result.ErrorMsg = "文件名不能为空"; } else { _result.IsSuccess = true; } return this; } }
那么再调用一下看看
var checkResult = new FileValidator(file) .ValidateIsFile() .ValidateFileExtension() .GetVaildResult();
这样修改的话,看上去没啥问题了
但是
将提示文本写死到校验器内部,应该不是什么好的处理方式,如果某天要修改的话只能修改校验器.那么提示文本还是从调用方传进来比较好
好的,开始第三版:
首先定义一个提示文本的类,这样的话,要修改直接提示类就行,然后做多语言的话好做
public static class CheckFileErrorMsg { public const string NoAttachmentHasBeenUploadedPleaseTryAgain = "未上传任何附件,请重试"; public const string FILENAMENOEMPTY = "文件名不能为空"; public const string FileEXTENSIONONLY = "文件仅限于上传(.DOC、.DOCX、.PDF)"; public const string UPLOADIMAGEBELOW = "请在下方直接上传图片!"; }
public class FileValidator { private readonly IFormFile _file; private readonly Result _result = new Result(); public FileValidator(IFormFile file,string[] errorMsgs) { _file = file; ValidateFile(); } /// <summary> /// 校验是否文件 /// </summary> /// <returns></returns> public FileValidator ValidateIsFile(string errorMsg) //传入提示文本 { if (!_result.IsSuccess) { return this; } if (CheckFormat.CheckIsFile(_file.FileName)) { _result.ErrorMsg = errorMsg; } else { _result.IsSuccess = true; } return this; } /// <summary> /// 校验文件格式 /// </summary> /// <returns></returns> public FileValidator ValidateFileExtension(string errorMsg) { if (!_result.IsSuccess) { return this; } if (CheckFormat.CheckFileExtension(_file.FileName)) { _result.ErrorMsg = errorMsg; } else { _result.IsSuccess = true; } return this; } /// <summary> /// 获取校验结果 /// </summary> /// <returns></returns> public Result GetVaildResult() { return _result; } /// <summary> /// 校验是否存在文件 /// </summary> /// <returns></returns> private FileValidator ValidateFile(string[] errorMsg) { if (_file == null) { _result.ErrorMsg = errorMsg[0]; } else if (string.IsNullOrEmpty(_file.Name)) { _result.ErrorMsg = errorMsg[1]; } else { _result.IsSuccess = true; } return this; } }
调用的地方是这样的:
var vaildResult = new FileValidator(files, new string[] { CheckFileErrorMsg.NoAttachmentHasBeenUploadedPleaseTryAgain }) .ValidateIsFile(CheckFileErrorMsg.UPLOADIMAGEBELOW) .ValidateFileExtension(CheckFileErrorMsg.FileEXTENSIONONLY) .GetVaildResult();
这样看起来是没啥毛病了,但是看起来比较丑陋.
我们尝试下使用委托,类似于where(x=>x.name!=null) 这种写法
Plan B
/// <summary> /// 校验文件 使用委托 /// </summary> /// <returns></returns> public FileValidator ValidateFile(Predicate<IFormFile> checkRule, string errorMsg) { if (!_result.IsSuccess) { return this; } if (!checkRule(_file)) { _result.ErrorMsg = errorMsg; } else { _result.IsSuccess = true; } return this; } }
就可以这样使用
var fileValidator = new FileValidator(formFile,new string[] { CheckFileErrorMsg.NoAttachmentHasBeenUploadedPleaseTryAgain,CheckFileErrorMsg.FILENAMENOEMPTY }) .ValidateFile(x =>CheckFormat.CheckIsFile(x.Name),CheckFileErrorMsg.UPLOADIMAGEBELOW) .ValidateFile(x =>CheckFormat.CheckFileExtension(x.Name),CheckFileErrorMsg.FileEXTENSIONONLY) .GetVaildResult();
好吧,看起来也不是很优雅
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步