导入Excel工作表并使用松散耦合验证导入的数据 [英] Importing an Excel Sheet and Validate the Imported Data with Loosely Coupled

查看:138
本文介绍了导入Excel工作表并使用松散耦合验证导入的数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试开发一个将阅读excel表(可能来自其他数据源,因此应该松散耦合)的模块,并将其转换为实体以便保存。



逻辑将是这样的:


  1. Excel表格可以是不同的格式,例如Excel表中的列名称可以不同的,所以我的系统需要能够将不同的字段映射到我的实体。

  2. 现在我将假定上面定义的格式现在将是相同的并且硬编码,而不是动态地从数据库之后设置配置映射UI有点事情。

  3. 数据需要在映射之前进行验证。所以我应该能够事先验证一些东西。我们不像XSD或其他东西那样使用,所以我应该使用我用作导入模板的对象结构进行验证。

问题是,我把一些东西放在一起,但我不说我喜欢我做的。 我的问题是我如何改进下面的代码,使事情更加模块化,并修复验证问题。



下面的代码是一个模型,并不期望工作,只是为了看到一些结构的设计。



这是迄今为止已经提出的代码,我已经意识到有一件事我需要提高我的设计模式技能,但现在我需要你的帮助,如果你能帮助我:

  //控制器,占位符
class UploadController
{
//在这里我们称之为适当的类和方法,以便将
// excel表转换为数据集
}

在使用MVC控制器上传文件后,可能会有不同的控制器要导入某些行为,在这个例子中我将上传人员相关的表,

 接口IDataImporter 
{
void Import(DataSet数据集);
}

//我们可以使用除PersonImporter之外的许多其他进口商
class PersonImporter :IDataImporter
{
//我们将数据集划分为适当的数据表,并调用所有IImportActions
//与Person数据导入相关
//我们调用插入数据库函数DataContext因为这样的方式
//我们可以减少db roundtrip。

  public string PersonTableName {get; set; 
public string DemographicsTableName {get; set;}

public Import(Dataset dataset)
{
CreatePerson();
CreateDemograhics();
}

//我们把不同的东西放在不同的方法来清除字段。高度凝聚力
private void CreatePerson(DataSet dataset)
{
var personDataTable = GetDataTable(dataset,PersonTableName);
IImportAction addOrUpdatePerson = new AddOrUpdatePerson();
addOrUpdatePerson.MapEntity(personDataTable);
}

private void CreateDemograhics(DataSet dataset)
{
var demographicsDataTable = GetDataTable(dataset,DemographicsTableName);
IImportAction demoAction = new AddOrUpdateDemographic(demographicsDataTable);
demoAction.MapEntity();
}

private DataTable GetDataTable(DataSet dataset,string tableName)
{
return dataset.Tables [tableName];
}

}



我有 IDataImporter 和专门的具体类 PersonImporter 。但是,由于事情应该是SOLID,所以基本上很容易在项目周期内进行扩展,所以我不太清楚,这将是今后改进的基础,让我们继续努力:



IImportActions 是魔术主要发生的地方。我不是设计基于表的方式,而是开发它基于行为,所以可以调用其中任何一个以更加模块化的模型导入东西。例如,一个表可能有两个不同的操作。

  interface IImportAction 
{
void MapEntity(DataTable table );
}

//样本导入操作,AddOrUpdatePerson
类AddOrUpdatePerson:IImportAction
{
//也考虑使用默认值?
public string FirstName {get; set;}
public string LastName {get; set;}
public string EmployeeId {get; set;}
public string Email {get; set ;}

public void MapEntity(DataTable table)
{
//每个动作都生成自己的数据上下文,因为它们使用
//不同的动作。
using(var dataContext = new DataContext())
{
foreach(Table.Rows中的DataRow行)
{
if(!emailValidate(row [email] ))
{
LoggingService.LogWarning(emailValidate.ValidationMessage);
}

var person = new Person(){
FirstName = row [FirstName],
LastName = row [LastName],
EmployeeId = row [EmployeeId],
Email = row [Email]
};

dataContext.SaveObject(person);
}

dataContext.SaveChangesToDatabase();
}
}
}

class AddOrUpdateDemographic:IImportAction
{
static string Name {get; set;}
static string EmployeeId {get; set;}

//所以在这里例如,我们需要先保存dataContext,然后再传入
//才能从Person获取PersonId(我们是假设我们需要Demograhics的PersonId
public void MapEntity(DataTable table)
{
using(var dataContext = new DataCOntext())
{
foreach(DataRow row in table.Rows)
{
var demograhic = new Demographic(){
Name = row [Name],
PersonId = dataContext.People.First(t => t.EmployeeId = int.Parse(row [EmpId]))
};

dataContext.SaveObject(person);
}

dataContext.SaveChangesToDatabase();
}
}
}

不幸的是我在哪里吮吸验证需要易于扩展和松散耦合,并且我需要能够提前调用此验证,而不是添加所有内容。

  public static class ValidationFactory 
{
public static Lazy< IFieldValidation> PhoneValidation = new Lazy< IFieldValidation>(()=> new PhoneNumberValidation());
public static Lazy< IFieldValidation> EmailValidation = new Lazy< IFieldValidation>(()=> new EmailValidation());
//等。
}

接口IFieldValidation
{
string ValidationMesage {get; set;}
bool验证(对象值);
}

类PhoneNumberValidation:IFieldValidation
{
public string ValidationMesage {get; set;}
public bool验证(对象值)
{
var validated = true; // let say ...
var innerValue =(string)value;
//使用Regex或某些东西
验证innerValue //如果验证失败,则设置ValidationMessage属性进行日志记录。
返回验证;
}
}

class EmailValidation:IFieldValidation
{
public string ValidationMesage {get; set;}
public bool验证(对象值)
{
var validated = true; // let say ...
var innerValue =(string)value;
//使用Regex或某些东西
验证innerValue //如果验证失败,则设置ValidationMessage属性进行日志记录。
返回验证;
}
}


解决方案

在一个项目上做了同样的事情。不同之处在于我不需要导入Excel表单,而是CSV文件。我创建了一个CSVValueProvider。因此,CSV数据自动绑定到我的IEnumerable模型。对于验证,我认为,通过所有行和单元格并逐个验证它们并不是非常有效,特别是当CSV文件有数千条记录时。那么我所做的就是创建了一些验证方法,它通过列逐行列出CSV数据,而不是按行排列,并对每列执行了一个linq查询,并返回了具有无效数据的单元格的行号。然后,将无效的行号/列名添加到ModelState中。



更新:



这是我做的...



CSVReader类:

  //可以读取和解析CSV文件中的数据的类。 
public class CSVReader
{
//用于解析CSV文件行中的数据的Regex表达式
private const string ESCAPE_SPLIT_REGEX =({1} [^ { 1}] * {1})*(小于?隔板> {0})({1} [^ {1}] * {1})*;
//保存标题的字符串数组(列名)
private string [] _headers;
//将数据保存在CSV文件中的字符串数组列表。列表中的每个字符串数组表示一行(行)。
private List< string []> _rows;
//用于读取CSV文件的StreamReader类。
私人StreamReader _reader;

public CSVReader(StreamReader reader)
{
_reader = reader;

解析();
}

//从CSV文件中读取和解析数据
private void Parse()
{
_rows = new List< string [] >();
string [] row;
int rowNumber = 1;

var headerLine =RowNumber,+ _reader.ReadLine();
_headers = GetEscapedSVs(headerLine);
rowNumber ++;

while(!_reader.EndOfStream)
{
var line = rowNumber +,+ _reader.ReadLine();
row = GetEscapedSVs(line);
_rows.Add(row);
rowNumber ++;
}

_reader.Close();
}

私有字符串[] GetEscapedSVs(字符串数据)
{
if(!data.EndsWith(,))
data = data +,;

返回GetEscapedSVs(data,,,\);
}

//通过使用给定的分隔符和转义字符
private string [] GetEscapedSVs(string data,string separator,string escape)
{
string [] result = null;

int priorMatchIndex = 0;
MatchCollection matches = Regex.Matches(data,string.Format(ESCAPE_SPLIT_REGEX,separator,escape));

//跳过空行...
if(matches.Count> 0)
{
result = new string [matches.Count];

for(int index = 0; index< = result.Length - 2; index ++)
{
result [index] = data.Substring(beforeMatchIndex,matches [index] .Groups [Separator]。Index - priorMatchIndex);
priorMatchIndex = matches [index] .Groups [Separator ] .Index + separator.Length;
}
result [result.Length - 1] = data .Substring(beforeMatchIndex,data.Length - priorMatchIndex - 1);

for(int index = 0; index< = result.Length - 1; index ++)
{
if(Regex.IsMatch(result [index],string.Format (^ {0}。* [^ {0}] {0} $,escape)))
result [index] = result [index] .Substring(1,result [index] .Length - 2 );

result [index] = result [index] .Replace(escape + escape,escape);

if(result [index] == null || result [index] == escape)
result [index] =;
}
}

返回结果;
}

//返回行数
public int RowCount
{
get
{
if(_rows = = null)
return 0;
return _rows.Count;
}
}

//返回标题(列)的数量
public int HeaderCount
{
get
{
if(_headers == null)
return 0;
return _headers.Length;
}
}

//返回给定列名和行索引中的值
public object GetValue(string columnName,int rowIndex)
{
if(rowIndex> = _rows.Count)
{
return null;
}

var row = _rows [rowIndex];

int colIndex = GetColumnIndex(columnName);

if(colIndex == -1 || colIndex> = row.Length)
{
return null;
}

var value = row [colIndex];
返回值;
}

//返回提供的列名的列索引
public int GetColumnIndex(string columnName)
{
int index = -1;

for(int i = 0; i< _headers.Length; i ++)
{
if(_headers [i] .Replace(,)等于(columnName,StringComparison.CurrentCultureIgnoreCase))
{
index = i;
返回索引;
}
}

返回索引;
}
}

CSVValueProviderFactory类:

  public class CSVValueProviderFactory:ValueProviderFactory 
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
var uploadedFiles = controllerContext.HttpContext.Request.Files;

if(uploadedFiles.Count> 0)
{
var file = uploadedFiles [0];
var extension = file.FileName.Split('。')。Last();

if(extension.Equals(csv,StringComparison.CurrentCultureIgnoreCase))
{
if(file.ContentLength> 0)
{
var stream = file.InputStream;
var csvReader = new CSVReader(new StreamReader(stream,Encoding.Default,true));

返回新的CSVValueProvider(controllerContext,csvReader);
}
}
}

返回null;
}
}

CSVValueProvider类:

  //表示上传的CSV文件中的数据的值提供程序。 
public class CSVValueProvider:IValueProvider
{
private CSVReader _csvReader;

public CSVValueProvider(ControllerContext controllerContext,CSVReader csvReader)
{
if(controllerContext == null)
{
throw new ArgumentNullException(controllerContext) ;
}

if(csvReader == null)
{
throw new ArgumentNullException(csvReader);
}

_csvReader = csvReader;
}

public bool ContainsPrefix(string prefix)
{
if(prefix.Contains('[')&& prefix.Contains(']' ))
{
if(prefix.Contains('。'))
{
var header = prefix.Split('。')。
if(_csvReader.GetColumnIndex(header)== -1)
{
return false;
}
}

int index = int.Parse(prefix.Split('[')。Last()Split('
if(index> = _csvReader.RowCount)
{
return false;
}
}

返回true;
}

public ValueProviderResult GetValue(string key)
{
if(!key.Contains('[')||!key.Contains(']' )||!key.Contains('。'))
{
return null;
}

对象值= null;
var header = key.Split('。')。Last();

int index = int.Parse(key.Split('[')。Last()Split(']')First());
value = _csvReader.GetValue(header,index);

if(value == null)
{
return null;
}

返回新的ValueProviderResult(value,value.ToString(),CultureInfo.CurrentCulture);
}
}

对于验证,如前所述,我想使用DataAnnotation属性执行它不是很有效。对于数千行的CSV文件,数据的逐行验证将需要很长时间。所以,我决定在模型绑定完成后验证控制器中的数据。我还应该提到,我需要根据数据库中的某些数据验证CSV文件中的数据。如果您只需要验证电子邮件地址或电话号码,您也可以使用DataAnnotation。



以下是验证电子邮件地址列的示例方法:

  private void ValidateEmailAddress(IEnumerable< CSVViewModel> csvData)
{
var invalidRows = csvData.Where(d => ValidEmail(d.EmailAddress)== false).ToList();

foreach(var invalidRow in invalidRows)
{
var key = string.Format(csvData [{0}]。{1},invalidRow.RowNumber - 2, 电子邮件地址);
ModelState.AddModelError(key,Invalid Email Address);
}
}

private static bool ValidEmail(string email)
{
if(email ==)
return false;
else
返回新的System.Text.RegularExpressions.Regex(@^ [\w-\。] + @([\w - ] + \。)+ [\w - ] {2,6} $)IsMatch(电子邮件)。
}

更新2:



要使用DataAnnotion进行验证,您只需像以下那样在CSVViewModel中使用DataAnnotation属性(CSVViewModel是您的Controller操作中CSV数据绑定的类):

  public class CSVViewModel 
{
//您的CSV列的用户专有名称,这些只是示例...

[必需]
public int Column1 {get;组;
[必需]
[StringLength(30)]
public string Column2 {get;组; }
}


I am trying to develop a module which will read excel sheets (possibly from other data sources too, so it should be loosely coupled) and convert them into Entities so to save.

The logic will be this:

  1. The excel sheet can be in different format, for example column names in Excel sheet can be different so my system needs to be able to map different fields to my entities.
  2. For now I will be assuming the format defined above will be same and hardcoded for now instead of coming from database dynamically after set on a configuration mapping UI kinda thing.
  3. The data needs to be validated before even get mapped. So I should be able validate it beforehand against something. We're not using like XSD or something else so I should validate it against the object structure I am using as a template for importing.

The problem is, I put together some things together but I don't say I liked what I did. My Question is how I can improve the code below and make things more modular and fix the validation issues.

The code below is a mock-up and is not expected to work, just to see some structure of the design.

This is code I've come up with so far, and I've realized one thing that I need to improve my design patterns skills but for now I need your help, if you could help me:

//The Controller, a placeholder
class UploadController
{
    //Somewhere here we call appropriate class and methods in order to convert
    //excel sheet to dataset
}

After we uploaded file using an MVC Controller, there could be different controllers specialized to import certain behaviors, in this example I will uploading person related tables,

interface IDataImporter
{
    void Import(DataSet dataset);
}

//We can use many other importers besides PersonImporter class PersonImporter : IDataImporter { //We divide dataset to approprate data tables and call all the IImportActions //related to Person data importing //We call inserting to database functions here of the DataContext since this way //we can do less db roundtrip.

public string PersonTableName {get;set;}
public string DemographicsTableName {get;set;}

public Import(Dataset dataset)
{
    CreatePerson();
    CreateDemograhics();
}

//We put different things in different methods to clear the field. High cohesion.
private void CreatePerson(DataSet dataset)
{   
    var personDataTable = GetDataTable(dataset,PersonTableName);
    IImportAction addOrUpdatePerson = new AddOrUpdatePerson();
    addOrUpdatePerson.MapEntity(personDataTable);
}

private void CreateDemograhics(DataSet dataset)
{
    var demographicsDataTable = GetDataTable(dataset,DemographicsTableName);
    IImportAction demoAction = new AddOrUpdateDemographic(demographicsDataTable);
    demoAction.MapEntity();
}

private DataTable GetDataTable(DataSet dataset, string tableName)
{
    return dataset.Tables[tableName];
}

}

I have IDataImporter and specialized concrete class PersonImporter. However, I am not sure it looks good so far since things should be SOLID so basically easy to extend later in the project cycle, this will be a foundation for future improvements, lets keep going:

IImportActions are where the magic mostly happens. Instead of designing things table based, I am developing it behavior based so one can call any of them to import things in more modular model. For example a table may have 2 different actions.

interface IImportAction
{
    void MapEntity(DataTable table);
}

//A sample import action, AddOrUpdatePerson
class AddOrUpdatePerson : IImportAction
{
    //Consider using default values as well?
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public string EmployeeId {get;set;}
    public string Email {get;set;}

    public void MapEntity(DataTable table)
    {
        //Each action is producing its own data context since they use
        //different actions.
        using(var dataContext = new DataContext())
        {
            foreach(DataRow row in table.Rows)
            {
                if(!emailValidate(row[Email]))
                {
                    LoggingService.LogWarning(emailValidate.ValidationMessage);
                }

                var person = new Person(){
                    FirstName = row[FirstName],
                    LastName = row[LastName],
                    EmployeeId = row[EmployeeId],
                    Email = row[Email]
                };

                dataContext.SaveObject(person);
            }

            dataContext.SaveChangesToDatabase();
        }
    }   
}

class AddOrUpdateDemographic: IImportAction
{
    static string Name {get;set;}
    static string EmployeeId {get;set;}

    //So here for example, we will need to save dataContext first before passing it in 
    //to get the PersonId from Person (we're assuming that we need PersonId for Demograhics)    
    public void MapEntity(DataTable table)
    {
        using(var dataContext = new DataCOntext())
        {
            foreach(DataRow row in table.Rows)
            {
                var demograhic = new Demographic(){
                    Name = row[Name],
                    PersonId = dataContext.People.First(t => t.EmployeeId = int.Parse(row["EmpId"]))
                };

                dataContext.SaveObject(person);
            }

            dataContext.SaveChangesToDatabase();
        }
    }
}

And the validation, which mostly where I suck at unfortunately. The validation needs to be easy to extend and loosely coupled and also I need to be able to call this validation beforehand instead of adding everything.

public static class ValidationFactory
{
    public static Lazy<IFieldValidation> PhoneValidation = new Lazy<IFieldValidation>(()=>new PhoneNumberValidation());
    public static Lazy<IFieldValidation> EmailValidation = new Lazy<IFieldValidation>(()=>new EmailValidation());
    //etc.
}

interface IFieldValidation
{
    string ValidationMesage{get;set;}
    bool Validate(object value);
}

class PhoneNumberValidation : IFieldValidation
{
    public string ValidationMesage{get;set;}
    public bool Validate(object value)
    {   
        var validated = true; //lets say...
        var innerValue = (string) value;
        //validate innerValue using Regex or something
        //if validation fails, then set ValidationMessage propert for logging.
        return validated;
    }
}

class EmailValidation : IFieldValidation
{
    public string ValidationMesage{get;set;}
    public bool Validate(object value)
    {   
        var validated = true; //lets say...
        var innerValue = (string) value;
        //validate innerValue using Regex or something
        //if validation fails, then set ValidationMessage propert for logging.
        return validated;
    }
}

解决方案

I have done the same thing on a project. The difference is that I didn't have to import Excel sheets, but CSV files. I created a CSVValueProvider. And, therefore, the CSV data was bound to my IEnumerable model automatically.

As for validation, I figured that going through all rows, and cells, and validating them one by one is not very efficient, especially when the CSV file has thousands of records. So, what I did was that I created some validation methods that went through the CSV data column by column, instead of row by row, and did a linq query on each column and returned the row numbers of the cells with invalid data. Then, added the invalid row number/column names to ModelState.

UPDATE:

Here is what I have done...

CSVReader Class:

// A class that can read and parse the data in a CSV file.
public class CSVReader
{
    // Regex expression that's used to parse the data in a line of a CSV file
    private const string ESCAPE_SPLIT_REGEX = "({1}[^{1}]*{1})*(?<Separator>{0})({1}[^{1}]*{1})*";
    // String array to hold the headers (column names)
    private string[] _headers;
    // List of string arrays to hold the data in the CSV file. Each string array in the list represents one line (row).
    private List<string[]> _rows;
    // The StreamReader class that's used to read the CSV file.
    private StreamReader _reader;

    public CSVReader(StreamReader reader)
    {
        _reader = reader;

        Parse();
    }

    // Reads and parses the data from the CSV file
    private void Parse()
    {
        _rows = new List<string[]>();
        string[] row;
        int rowNumber = 1;

        var headerLine = "RowNumber," + _reader.ReadLine();
        _headers = GetEscapedSVs(headerLine);
        rowNumber++;

        while (!_reader.EndOfStream)
        {
            var line = rowNumber + "," + _reader.ReadLine();
            row = GetEscapedSVs(line);
            _rows.Add(row);
            rowNumber++;
        }

        _reader.Close();
    }

    private string[] GetEscapedSVs(string data)
    {
        if (!data.EndsWith(","))
            data = data + ",";

        return GetEscapedSVs(data, ",", "\"");
    }

    // Parses each row by using the given separator and escape characters
    private string[] GetEscapedSVs(string data, string separator, string escape)
    {
        string[] result = null;

        int priorMatchIndex = 0;
        MatchCollection matches = Regex.Matches(data, string.Format(ESCAPE_SPLIT_REGEX, separator, escape));

        // Skip empty rows...
        if (matches.Count > 0) 
        {
            result = new string[matches.Count];

            for (int index = 0; index <= result.Length - 2; index++)
            {
                result[index] = data.Substring(priorMatchIndex, matches[index].Groups["Separator"].Index - priorMatchIndex);
                priorMatchIndex = matches[index].Groups["Separator"].Index + separator.Length;
            }
            result[result.Length - 1] = data.Substring(priorMatchIndex, data.Length - priorMatchIndex - 1);

            for (int index = 0; index <= result.Length - 1; index++)
            {
                if (Regex.IsMatch(result[index], string.Format("^{0}.*[^{0}]{0}$", escape))) 
                    result[index] = result[index].Substring(1, result[index].Length - 2);

                result[index] = result[index].Replace(escape + escape, escape);

                if (result[index] == null || result[index] == escape) 
                    result[index] = "";
            }
        }

        return result;
    }

    // Returns the number of rows
    public int RowCount
    {
        get
        {
            if (_rows == null)
                return 0;
            return _rows.Count;
        }
    }

    // Returns the number of headers (columns)
    public int HeaderCount
    {
        get
        {
            if (_headers == null)
                return 0;
            return _headers.Length;
        }
    }

    // Returns the value in a given column name and row index
    public object GetValue(string columnName, int rowIndex)
    {
        if (rowIndex >= _rows.Count)
        {
            return null;
        }

        var row = _rows[rowIndex];

        int colIndex = GetColumnIndex(columnName);

        if (colIndex == -1 || colIndex >= row.Length)
        {
            return null;
        }

        var value = row[colIndex];
        return value;
    }

    // Returns the column index of the provided column name
    public int GetColumnIndex(string columnName)
    {
        int index = -1;

        for (int i = 0; i < _headers.Length; i++)
        {
            if (_headers[i].Replace(" ","").Equals(columnName, StringComparison.CurrentCultureIgnoreCase))
            {
                index = i;
                return index;
            }
        }

        return index;
    }
}

CSVValueProviderFactory Class:

public class CSVValueProviderFactory : ValueProviderFactory
{
    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        var uploadedFiles = controllerContext.HttpContext.Request.Files;

        if (uploadedFiles.Count > 0)
        {
            var file = uploadedFiles[0];
            var extension = file.FileName.Split('.').Last();

            if (extension.Equals("csv", StringComparison.CurrentCultureIgnoreCase))
            {
                if (file.ContentLength > 0)
                {
                    var stream = file.InputStream;
                    var csvReader = new CSVReader(new StreamReader(stream, Encoding.Default, true));

                    return new CSVValueProvider(controllerContext, csvReader);
                }
            }
        }

        return null;
    }
}

CSVValueProvider Class:

// Represents a value provider for the data in an uploaded CSV file.
public class CSVValueProvider : IValueProvider
{
    private CSVReader _csvReader;

    public CSVValueProvider(ControllerContext controllerContext, CSVReader csvReader)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }

        if (csvReader == null)
        {
            throw new ArgumentNullException("csvReader");
        }

        _csvReader = csvReader;
    }

    public bool ContainsPrefix(string prefix)
    {
        if (prefix.Contains('[') && prefix.Contains(']'))
        {
            if (prefix.Contains('.'))
            {
                var header = prefix.Split('.').Last();
                if (_csvReader.GetColumnIndex(header) == -1)
                {
                    return false;
                }
            }

            int index = int.Parse(prefix.Split('[').Last().Split(']').First());
            if (index >= _csvReader.RowCount)
            {
                return false;
            }
        }

        return true;
    }

    public ValueProviderResult GetValue(string key)
    {
        if (!key.Contains('[') || !key.Contains(']') || !key.Contains('.'))
        {
            return null;
        }

        object value = null;
        var header = key.Split('.').Last();

        int index = int.Parse(key.Split('[').Last().Split(']').First());
        value = _csvReader.GetValue(header, index);

        if (value == null)
        {
            return null;
        }

        return new ValueProviderResult(value, value.ToString(), CultureInfo.CurrentCulture);
    }
}

For the validation, as I mentioned before, I figured that it would not be efficient to do it using DataAnnotation attributes. A row by row validation of the data would take a long time for CSV files with thousands of rows. So, I decided to validate the data in the Controller after the Model Binding is done. I should also mention that I needed to validate the data in the CSV file against some data in the database. If you just need to validate things like Email Address or Phone Number, you might as well just use DataAnnotation.

Here is a sample method for validating the Email Address column:

private void ValidateEmailAddress(IEnumerable<CSVViewModel> csvData)
{
    var invalidRows = csvData.Where(d => ValidEmail(d.EmailAddress) == false).ToList();

    foreach (var invalidRow in invalidRows)
    {
        var key = string.Format("csvData[{0}].{1}", invalidRow.RowNumber - 2, "EmailAddress");
        ModelState.AddModelError(key, "Invalid Email Address"); 
    }        
}

private static bool ValidEmail(string email)
{
    if(email == "")
        return false;
    else
        return new System.Text.RegularExpressions.Regex(@"^[\w-\.]+@([\w-]+\.)+[\w-]{2,6}$").IsMatch(email);
}

UPDATE 2:

For validation using DataAnnotaion, you just use DataAnnotation attributes in your CSVViewModel like below (the CSVViewModel is the class that your CSV data will be bound to in your Controller Action):

public class CSVViewModel
{
    // User proper names for your CSV columns, these are just examples...   

    [Required]
    public int Column1 { get; set; } 
    [Required]
    [StringLength(30)]
    public string Column2 { get; set; }
}

这篇关于导入Excel工作表并使用松散耦合验证导入的数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆