Home > front end >  How to write a text file with specific positions with FileStream?
How to write a text file with specific positions with FileStream?

Time:12-02

I am trying to write a text file with this structure

enter image description here

For this I am working with Seek() and Write(), but every time I want to add a new data it is overwritten, and in the end only one line is written to the file

enter image description here

This is the code I am using

private async void CreacionArchivoBCD(ArchivoPerfil contenidoPerfil, string nombreFinalBCD, bool checkPrevia)
{
    var result = await ensayoDataProvider.ObtenerUexCapturador(checkPrevia, contenidoPerfil, codensSeleccionado.Id);
    var cant = System.Text.Encoding.UTF8.GetBytes(Convert.ToString(result.Count()));


    ////TextWriter archivoExportar = new StreamWriter(nombreFinalBCD);
    ////archivoExportar.Close();

    var file = new FileStream(nombreFinalBCD, FileMode.OpenOrCreate, FileAccess.Write);
    file.Seek(1, SeekOrigin.Begin);
    file.Write(cant, 0, cant.Length);
    byte[] newline = Encoding.ASCII.GetBytes(Environment.NewLine);
    file.Write(newline, 0, newline.Length);

    int nroMedAux = 0;
    long rutaAux = 0;
    var cont = 0;
    var bandera = 0;
    //var posicion = 0;
    foreach (var item in result)
    {
        var primerByte = System.Text.Encoding.UTF8.GetBytes("00");
        file.Seek(0, SeekOrigin.Begin);
        file.Write(primerByte, 0, primerByte.Length);

        if (bandera == 0)
        {
            nroMedAux = item.IdMed;
            rutaAux = item.Ruta;
            bandera = 1;
        }

        var tipo = item.GetType();

        if (nroMedAux != item.IdMed || rutaAux != item.Ruta)
        {
            nroMedAux = item.IdMed;
            rutaAux = item.Ruta;
            file.Write(newline, 0, newline.Length);
        }

        foreach (var pi in tipo.GetProperties())
        {
            var propName = pi.Name;
            var propValue = pi.GetValue(item, null)?.ToString();
            var propValueAux = propName == "Campo" ? item.GetType().GetProperty("Valor").GetValue(item, null) : propValue;

            if (propName == "IdMed" || propName == "Valor")
            {
                continue;
            }

            //TODO: buscar posicion y ancho por nombre campo
            var posicion = BuscarPosicion(propName, propValue, contenidoPerfil);
            //var ancho = BuscarAncho(propName, contenidoPerfil);

            Debug.WriteLine(propName);
            Debug.WriteLine(propValueAux);
            Debug.WriteLine(posicion);

            if (posicion == -1)
            {
                continue;
            }

            var lacadena = propValue;
            var lacadenAux = propName == "Campo" ? propValueAux : propValue;
            var cadena = System.Text.Encoding.UTF8.GetBytes(lacadenAux.ToString());
            file.Seek(posicion, SeekOrigin.Begin);
            file.Write(cadena, 0, cadena.Length);
        }
    }

    file.Close();

}

CodePudding user response:

Before file.Close(), add file.Flush() or await file.FlushAsync() -> this flushes the buffer to the output.

See https://docs.microsoft.com/en-us/dotnet/api/system.io.filestream.flush?view=net-6.0.

CodePudding user response:

This is not how you should do it. Since you are writing text to a file, you should use a StreamWriter.
Then to create the column formatting, simply use string.PadLeft (or string.PadRight).
To handle streams (or IDisposable implementations in general) use the using statement. Don't close resources explicitly.
Also use the async API to improve the performance.

The following example shows the pattern you should use:

  1. Generate the data structure (rows)
  2. Format the data
  3. Write all data at once to the file

The algorithm takes different cell value lengths into account to generate even columns.

If you use .NET Standard 2.1 (.NET Core 3.0, .NET 5) you can even make use of IAsyncDisposable.

private async Task WriteDataTableToFileAsync(string filePath)
{
  List<List<string>> rows = GenerateData();
  string fileContent = FormatData("4080", rows, 4);
  await WriteToFileAsync(fileContent, filePath);
}

private List<List<string>> GenerateData()
{
  // Generate the complete data first
  var rows = new List<List<string>>
  {
    // Row 1
    new List<string>
    {
      // Cells
      "00",
      "1",
      "LD126",
      "NN",
      "1",
      "0",
      "0 0",
      "49",
      "2"
    },
    // Row 2
    new List<string>
    {
      // Cells
      "00",
      "1",
      "Rell",
      "NN",
      "1",
      "0",
      "0 0",
      "49",
      "2"
    }
  };

  return rows;
}

private string FormatData(string preamble, List<List<string>> rows, int columnGapWidth)
{
  preamble = preamble.PadLeft(preamble.Length   1)   Environment.NewLine;
  var fileContentBuilder = new StringBuilder(preamble);
  var columnWidths = new Dictionary<int, int>();
  int columnCount = rows.First().Count;

  foreach (List<string> cells in rows)
  {
    var rowBuilder = new StringBuilder();
    for (int columnIndex = 0; columnIndex < columnCount; columnIndex  )
    {
      if (!columnWidths.TryGetValue(columnIndex, out int columnWidth))
      {
        int maxCellWidthOfColumn = rows
          .Select(cells => cells[columnIndex])
          .Max(cell => cell.Length);
        columnWidth = maxCellWidthOfColumn   columnGapWidth;
        columnWidths.Add(columnIndex, columnWidth);
      }

      string cell = cells[columnIndex];
      rowBuilder.Append(cell.PadRight(columnWidth));
    }
     
    fileContentBuilder.Append(rowBuilder.ToString().TrimEnd());
    fileContentBuilder.Append(Environment.NewLine);
  }

  string fileContent = fileContentBuilder.ToString().TrimEnd();
  return fileContent;
}

private async Task WriteToFileAsync(string fileContent, string filePath)
{
  await using var destinationFileStream = File.Open(filePath, FileMode.Create, FileAccess.Write);
  await using var streamWriter = new StreamWriter(destinationFileStream);
  await streamWriter.WriteAsync(fileContent);
}

You can further improve the implementation: add a Data, Row and a Cell class. The Row class has a Cell collection. The Data class has a Row collection and additional data like the preamble or formatting info like the column gap width. This way you eliminate method parameters to improve readability. Then replace the current List<List<string>> data structure with the Data class.

  • Related