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