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 @ 2024-09-12 01:38  飞奔的牛牛  阅读(85)  评论(0)    收藏  举报