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();

看起来是大功告成了,但是细看就会发现存在问题:

  1. 只能先调用ValidateFile() ,否则后面会空引用.
  2. 当校验失败时,依旧会执行到最后,即ValidateFileName()失败 ,还是会走到ValidateIsFile(),违反了提前中断的编码规范

如何解决呢?
想了半天没什么好的解决方案,那就从业务上解决.

  1. 既然要校验file的属性,那么首先file是要存在的,那么校验file存在的逻辑应该首先执行,所以把ValidateFile()放到构造函数中执行,这样就不用担心后面空引用了
  2. 对于报错一直执行这个问题,首先想到的是一旦校验失败就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();

好吧,看起来也不是很优雅

posted @   飞奔的牛牛  阅读(26)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示