C# 鏈式程式設計實踐之檔案校驗

飞奔的牛牛發表於2024-09-12

最近有個檔案上傳的功能,需要做很多判斷,以前的程式碼是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();

好吧,看起來也不是很優雅

相關文章