ASP.NET MVC控制器遵循开闭原则实现多文件导入方案咨询
这个场景完全符合开闭原则的适用场景,咱们可以通过几个步骤重构,彻底摆脱没完没了的if判断,而且后续加Excel或者其他格式都不用碰原来的Action代码:
实现思路&重构步骤
1. 抽象转换行为,定义统一接口
首先把「文件转Company列表」这个核心行为抽象成接口,让所有类型的文件转换器都遵循同一个契约:
public interface IFileToCompanyConverter { List<Company> Convert(string filePath); // 新增属性标识当前转换器支持的扩展名,方便后续匹配 string SupportedExtension { get; } }
2. 为每种文件类型实现具体转换器
把原来的CSV转换逻辑移到专门的实现类里,未来要支持Excel时,只需要新增一个实现类就行,完全不改动现有代码:
public class CsvToCompanyConverter : IFileToCompanyConverter { public string SupportedExtension => "." + Enums.FileType.csv.ToString(); public List<Company> Convert(string filePath) { // 这里直接复用原来的ConvertCsvToCompanyModel逻辑 return Convertor.ConvertCsvToCompanyModel(filePath); } } // 未来支持Excel时,新增这个类即可,无需修改任何现有代码 public class ExcelToCompanyConverter : IFileToCompanyConverter { public string SupportedExtension => "." + Enums.FileType.excel.ToString(); public List<Company> Convert(string filePath) { // 实现Excel转Company的逻辑,比如调用专门的Excel转换工具 return ExcelConvertor.ConvertExcelToCompanyModel(filePath); } }
3. 用工厂模式(结合DI)根据扩展名获取转换器
我们需要一个「转换器工厂」来负责根据文件扩展名匹配对应的转换器,彻底把Action里的if判断干掉:
public class ConverterFactory { private readonly IEnumerable<IFileToCompanyConverter> _converters; // 利用ASP.NET MVC的依赖注入,直接注入所有已注册的转换器实现 public ConverterFactory(IEnumerable<IFileToCompanyConverter> converters) { _converters = converters; } public IFileToCompanyConverter GetConverter(string fileExtension) { return _converters.FirstOrDefault(c => c.SupportedExtension.Equals(fileExtension, StringComparison.OrdinalIgnoreCase)); } }
如果暂时不用DI,也可以在工厂里用静态字典提前注册转换器(比如在静态构造函数里添加_converterMap.Add(".csv", new CsvToCompanyConverter());),但用DI更符合ASP.NET MVC的最佳实践。
4. 重构Controller的Action方法
现在Action里完全不用判断文件类型了,直接通过工厂拿到对应的转换器,执行转换和保存逻辑即可:
// 记得在Controller构造函数里注入工厂和仓储 private readonly ConverterFactory _converterFactory; private readonly ICompanyRepository _companyRepository; public YourController(ConverterFactory converterFactory, ICompanyRepository companyRepository) { _converterFactory = converterFactory; _companyRepository = companyRepository; } public ActionResult Index(HttpPostedFileBase file) { // 验证文件是否存在 if (file != null && file.ContentLength > 0) { var path = FileHelper.GetFilePath(file); string filePath = path.Item1; string fileExtension = path.Item2; if (!string.IsNullOrEmpty(filePath)) { // 通过工厂获取对应转换器 var converter = _converterFactory.GetConverter(fileExtension); if (converter != null) { List<Company> companiesList = converter.Convert(filePath); if (companiesList != null && companiesList.Count > 0) { try { foreach (var company in companiesList) { _companyRepository.SaveCompanyItem(company); } TempData["Message"] = "The file import was successful!"; } catch (Exception ex) { obj.Handle("Something went wrong at save" + ex.Message); TempData["ErrorMessage"] = "Import failed: " + ex.Message; } } else { TempData["ErrorMessage"] = "No valid company data found in the file."; } } else { TempData["ErrorMessage"] = $"File type {fileExtension} is not supported."; } } else { TempData["ErrorMessage"] = "Failed to get file path."; } } else { TempData["ErrorMessage"] = "Please select file"; } return RedirectToAction("Index"); }
为什么这样符合开闭原则?
- 对扩展开放:未来要支持Word、XML等其他文件类型,只需要新增一个
IFileToCompanyConverter的实现类,然后注册到DI容器里,完全不用修改现有的Controller、工厂或CSV转换器代码。 - 对修改关闭:现有代码的核心逻辑已经固定,不会因为新增文件类型而被改动,大大降低了引入bug的风险。
内容的提问来源于stack exchange,提问作者Orsi
相关产品推荐
相关产品推荐

