转换控制器

现在我们已经解释了数据的表示方式, 我们可以通过查看主控制器来了解它是如何进入我们的程序的。

我们的主控制器是 ConvertController 。它包含了我们的 API 的核心调度逻辑。

[Route("api/[controller]")]
public class ConvertController : Controller
{        
    private IDataRepository _dataRepository;
    public ConvertController(IDataRepository dataRepository)
    {
        _dataRepository = dataRepository;
    }

    [HttpGet]
    public IActionResult Get()
    {
        IEnumerable<InfoData> files =_dataRepository.All();

        return View(files);
    }

    [HttpGet("{id}/{format?}", Name = "GetData")]
    public IActionResult Get(Guid id, string format = "")
    {                        
        InfoData data = _dataRepository.Get(id);
        Pipeline pipeline = new Pipeline();            

        if(!String.IsNullOrEmpty(format))
            data.Format = Enum.Parse<Format>(format);

        string converted = Pipeline.Convert(data.Data, data.Format);

        if(String.IsNullOrEmpty(converted))
             return NoContent();
        else
            return Content(converted);
    }

    [HttpPost]
    public IActionResult Post(InputData value)
    {            
        InfoData result = _dataRepository.Save(value);

        return new JsonResult(result);
    }

    [HttpDelete("{id}")]
    public IActionResult Delete(Guid id)
    {
        _dataRepository.Delete(id);

        return Ok();
    }
}

整个控制器简单直观。我们使用依赖项注入将 IDataRepository 对象传递给控制器。依赖项注入是由 ASP.NET 提供的, 我们将在以后安装它。

唯一有趣的部分是 Get 方法, 我们调用我们的管道转换文件。首先, 我们从存储库 (21 行) 中获取与该id对应的数据, 此时字段数据。格式包含文件的原始格式。如果用户提供了格式值, 则更改数据。格式设置为指定格式。

无论格式是不同的, 还是原始的, 我们总是通过数据和我们需要的格式的管道。通过这样做, 所有数据都经过我们的管道。因此, 例如, 我们可以以相同的格式返回数据, 但是在对它执行了一些操作之后 (例如, 我们确保使用标准的十进制分隔符)。

让我们看看管道是如何工作的。

在本教程中, 我们将创建一个非常简单的管道。我们的管道将:

  • 接收我们内部表示的数据。
  • 执行管线中指定的所有操作。
  • 返回以请求格式转换的数据 (如果格式可以处理数据)。

本教程的想法是创建一个通用系统, 它可以处理不同格式之间的转换文件。为了做到这一点, 我们需要有一个管道, 可以执行简单的数据清理操作, 如合并值, 排序, 等等。

讨论设计

如果我们愿意每次修改代码, 我们只需创建一个接口并使用委托来执行自定义操作。类似这样的东西:

List<Func<DataItem, DataItem>> operations;
operations = new List<Func<DataItem, DataItem>>();
operations.Add(new Func<DataItem, DataItem>(operation));

// later

foreach(var operation in operations)
{
     operation(data);
}

但是, 这会使服务有点麻烦, 除非我们总是想执行相同的标准操作。

这将是使用DSL的理想方案, 因为我们有有限的范围 (操作数据), 它可以通过一个工具来极大地改进, 这有助于我们需要做的少量操作。但是, 这超出了本教程的范围。

因此, 一个好的替代解决方案是包括一种自动发现和执行用户定义的操作的方法。有几种可能的方法可以做到这一点:

表达式解释器对于我们想做的事情来说太简单了。如果我们想给用户充分的语言能力, 那么完整的 Roslyn 编译器将是我们的前进之路。然而, 除了安全风险, 它可能是太多的自由, 用户不需要, 并需要一点点的安装工作, 每次。

因此, 我们选择中间的地方, 并使用脚本解决方案。实际上, 我们将使用 Roslyn 中包含的脚本引擎, 但我们将设置所有内容, 用户只需添加自己的脚本即可。

的管道

我们从简单的public String Convert(DataItem data, Format format) { IConverter<DataItem> converter = null; String converted = String.Empty; if(data != null) { data = PerformOperations(data); switch(format) { case Format.JSON: converter = new ParsingServices.Models.JsonConverter(); break; case Format.CSV: converter = new ParsingServices.Models.CSVConverter(); break; default: break; } if(converter.IsValid(data)) { converted = converter.ToFile(data); } } return converted; }

该函数对数据执行所有操作, 然后在检查目标格式可以支持数据之后, 将数据转换为请求的格式。

操作的处理过程发生在方法中 PerformOperations , 这可能比预期的简单。

public DataItem PerformOperations(DataItem data)
{
    foreach(var file in Directory.EnumerateFiles($"{Location}{Path.DirectorySeparatorChar}"))
    {
        GlobalsScript item = new GlobalsScript { Data = data };

        var script = CSharpScript.EvaluateAsync<DataItem>(
            System.IO.File.ReadAllText(file),
            Microsoft.CodeAnalysis.Scripting.ScriptOptions.Default
            .WithReferences(typeof(ParsingServices.Models.DataItem).Assembly)                    
            .WithImports("System.Collections.Generic")
            .WithImports("System.Linq"),
            globalsType: item.GetType(), globals: item); 

        script.Wait();                    
        data = script.Result;   
    }            

    return data;
}

该方法收集在正确位置内的文件中定义的所有操作, 然后按一项执行它们。这些操作在可以上载的文件中指定, 就像要转换的文件上载一样。在存储库中, 类中有一个 OperationsController 和几个方法 Pipeline 来管理操作的创建, 但我们不在此显示它, 因为该代码是初级的。

这一切都发生在方法 EvaluateAsync 。此方法接受代码作为字符串, 以及包含脚本可以访问的数据的对象 (全局)。我们还必须指定代码所需的程序集。这是使我们的脚本解决方案变得脆弱的关键步骤。因为它只是在这里, 而不是在脚本内部, 我们可以设置程序集, 我们必须确保包括所有的程序集, 我们将需要。这样每个脚本都有它所需要的一切。

我们也可以使用脚本内部的语句 (即 WithImports 方法), 但如果我们在这里为我们需要的那些代码提供了方便com/dotnet/roslyn/wiki/脚本-API-示例 “相对 =” nofollow “目标 =” _blank “>> c# 代码内的脚本, 但我们可以做一个公平的位。下面是一个示例脚本, 它也包含在存储库中。

using ParsingServices.Models;

if(Data is DataArray)
{                
    bool simpleValues = true;
    foreach(var v in (Data as DataArray).Values)
    {
        if(!(v is DataValue))
            simpleValues = false;
    }

    if(simpleValues)
    {
        (Data as DataArray).Values = (Data as DataArray).Values.OrderByDescending(v => (v as DataValue).Text).ToList();
    }
}

return Data;

如果该项是数组, 并且所有值都是简单值 (例如或), 则脚本将对值进行排序 5 hello

我们在全局中传递的参数 EvaluateAsync 可以直接访问, 即, 我们使用数据而不是全局变量。数据。很好的一点是, 我们不需要在类或方法中包装代码, 它只是一个语句序列。

存储数据

现在让我们来看看这个 DataRepository 班级。此类存储上载到我们的服务的文件。很明显, 如果我们只想转换文件, 就没有必要存储它。但是, 如果创建转换服务意义很有意义, 则在需要时自动为转换后的文件提供服务可能很有用。为了提供这样的功能, 我们希望保存文件的简单性。因此, 我们必须上传文件一次, 我们可以要求它根据需要。

我们不保存数据库中的数据, 但在目录上。许多数据库都支持存储文件, 但在这种情况下, 最简单的方法就足够了。我们将只看到方法 Save , 因为其余的没有显示任何挑战。与往常一样, 您可以在存储库中看到整个文件。

public InfoData Save(InputData value)
{
    var id = Guid.NewGuid();
    Directory.CreateDirectory($"{Location}{Path.DirectorySeparatorChar}{id}{Path.DirectorySeparatorChar}");            
    IConverter<DataItem> converter = null;                
    DataItem data = new DataItem();            

    switch(value.Format)
    {
        case Format.JSON:
            converter = new ParsingServices.Models.JsonConverter();
        break;
        case Format.CSV:
            converter = new ParsingServices.Models.CSVConverter();
        break;
        default:
            break;
    }

    using(FileStream fs = new FileStream($"{Location}{Path

DirectorySeparatorChar} 文件。值.格式} “, FileMode. OpenOrCreate)) {值。文件. CopyTo (fs);
} 使用 (FileStream fs = 新 FileStream ($ “{位置} {路径. DirectorySeparatorChar} {id} {路径. DirectorySeparatorChar} 文件. {价值。格式} “, FileMode 打开)) {数据 = 转换器。FromFile (fs);
} var infoData = 新 infoData () {id = id, 格式 = 值。格式, LocationFile = $ “{位置} {路径. DirectorySeparatorChar} {id} {路径. DirectorySeparatorChar} 文件。值.格式} “, 数据 = 数据};

JsonSerializerSettings 设置 = 新 JsonSerializerSettings ();
设置。ReferenceLoopHandling = ReferenceLoopHandling。
设置。格式化 = 格式化. 缩进;

系统. IO 文件. WriteAllText ($ “{位置} {路径. DirectorySeparatorChar} {id} {路径. DirectorySeparatorChar} 数据. json”, JsonConvert SerializeObject (infoData, 设置));

返回 infoData;
}

我们创建一个新的目录 (4 行) 对应于一个新的id内的位置(一个类的字段, 其声明没有显示)。在为所请求的格式创建了适当的 *Converter 文件后, 我们都在目录 (20-22 行) 中复制该文档, 并从文件本身创建通用数据格式。最后, 我们将对象保存在 InfoData data.json 要在上载文件旁边的文件中。

我们实际上不会使用存储在文件中的数据字段中的值 data.json 。相反, 当我们被要求加载特定id的数据时, 我们只需再次使用适当的方法 *Converter 就可以直接重新创建数据。我们将它存储在这里是为了进行调试, 以防我们要检查有问题的文件是如何转换的。

要激活类的依赖项注入 DataRepository , 我们只需在文件的方法ConfigureServices中添加一行 Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    // we tell ASP.NET that DataRepository implement IDataRepository
    services.AddTransient<IDataRepository, DataRepository>();
}

2部分!当覆盖将数据转换为 CSV 和 JSON 数据类型时, 在星期日调整。

Comments are closed.