HiveBrain v1.2.0
Get Started
← Back to all entries
debugcsharpMinor

Sending an XML file to an SFTP site

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
sftpfilesendingxmlsite

Problem

I have a console application that basically retrieves XML content, writes to the XML file and sends the file to an SFTP site:

public Main(string[] args)
{
 try
 {
   //code to parse arguments to load into DTO to extract stored proc and report name
  List reports = Parse(args);

   foreach(var rpt in reports)
   {
      //retrieve xml content from database 
       XmlDocument doc = new XmlDocument();
       using (SqlConnection con = new SqlConnection(AppConfig.ConnectionString))
        {
            using (SqlCommand cmd = con.CreateCommand())
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandText = rpt.StoredProcedure;
                con.Open();
                using (var reader = cmd.ExecuteXmlReader())
                {
                    doc.Load(reader);
                    reader.Close();
                }
                con.Close();
            }
        }

        //save xml file in a folder
        string filePath = Path.Combine(AppConfig.ReportsFolder, string.Format(rpt.FileName, DateTime.Today.ToString("MMddyyyy")));
        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            using (var xmlWriter = XmlWriter.Create(fileStream,
                      new XmlWriterSettings
                      {
                          OmitXmlDeclaration = false,
                          ConformanceLevel = ConformanceLevel.Document,
                          Encoding = Encoding.UTF8
                      }))
            {
                xmldoc.Save(xmlWriter);
            }
        }

     // third party tool is called to transmit the file 
     SFtpClient client = new SFtpClient("host","user","pwd");
     client.Send(filPpath);
  }
 }
 catch(Exception ex)
 {
    _iLogger.Error(ex);
 }
}


Main() consists of considerable amount of lines. So, I have decided to split functionality into smaller classes similar to SOLID principles like t

Solution

I would start by looking at functional decomposition. Here's crack #1 at it:

internal static class Solid
{
    private static readonly XmlWriterSettings settings = new XmlWriterSettings
    {
        OmitXmlDeclaration = false,
        ConformanceLevel = ConformanceLevel.Document,
        Encoding = Encoding.UTF8
    };

    public static void Main(string[] args)
    {
        try
        {
            ProcessReports(args, AppConfig.ConnectionString, AppConfig.ReportsFolder);
        }
        catch (Exception ex)
        {
            _iLogger.Error(ex);
        }
    }

    private static void ProcessReports(string[] args, string connectionString, string reportsFolder)
    {
        // code to parse arguments to load into DTO to extract stored proc and report name
        foreach (var rpt in Parse(args))
        {
            ProcessSingleReport(connectionString, reportsFolder, rpt);
        }
    }

    private static IEnumerable Parse(string[] args)
    {
        // Do actual argument parsing here...
        return Enumerable.Empty();
    }

    private static void ProcessSingleReport(string connectionString, string reportsFolder, ReportInfo rpt)
    {
        var doc = LoadReportXml(connectionString, rpt);
        var filePath = Path.Combine(reportsFolder, string.Format(rpt.FileName, DateTime.Today.ToString("MMddyyyy")));

        WriteFile(filePath, doc);
        TransmitFile(filePath);
    }

    private static XmlDocument LoadReportXml(string connectionString, ReportInfo rpt)
    {
        using (var con = new SqlConnection(connectionString))
        using (var cmd = con.CreateCommand())
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.CommandText = rpt.StoredProcedure;
            con.Open();
            using (var reader = cmd.ExecuteXmlReader())
            {
                var doc = new XmlDocument();

                doc.Load(reader);
                return doc;
            }
        }
    }

    private static void WriteFile(string filePath, XmlDocument doc)
    {
        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
        using (var xmlWriter = XmlWriter.Create(fileStream, settings))
        {
            doc.Save(xmlWriter);
        }
    }

    private static void TransmitFile(string filePath)
    {
        // third party tool is called to transmit the file
        SFtpClient client = new SFtpClient("host", "user", "pwd");

        client.Send(filePath);
    }
}


Then, you can see there's a definite separation of concerns, and therefore separate classes:

```
internal static class Solid
{
public static void Main(string[] args)
{
try
{
var argumentParser = new ArgumentParser(args);
var reportLoader = new ReportLoader(AppConfig.ConnectionString);
var reportProcessor = new ReportProcessor(AppConfig.ReportsFolder, reportLoader);
var reportsProcessor = new ReportsProcessor(argumentParser, reportProcessor);

reportsProcessor.ProcessReports();
}
catch (Exception ex)
{
_iLogger.Error(ex);
}
}
}

///
/// code to parse arguments to load into DTO to extract stored proc and report name
///
internal sealed class ArgumentParser
{
private readonly string[] args;

public ArgumentParser(string[] args)
{
this.args = args;
}

public IEnumerable Parse()
{
// Do actual argument parsing here...
return Enumerable.Empty();
}
}

internal sealed class ReportLoader
{
private readonly string connectionString;

public ReportLoader(string connectionString)
{
this.connectionString = connectionString;
}

public XmlDocument LoadReportXml(ReportInfo rpt)
{
using (var con = new SqlConnection(this.connectionString))
using (var cmd = con.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = rpt.StoredProcedure;
con.Open();
using (var reader = cmd.ExecuteXmlReader())
{
var doc = new XmlDocument();

doc.Load(reader);
return doc;
}
}
}
}

internal sealed class ReportProcessor
{
private readonly string reportsFolder;

private readonly ReportLoader reportLoader;

public ReportProcessor(string reportsFolder, ReportLoader reportLoader)
{
this.reportsFolder = reportsFolder;
this.reportLoader = reportLoader;
}

public void ProcessSingleReport(ReportInfo rpt)
{
var doc = this.reportLoader.LoadReportXml(rpt);
var filePath = Path.Combine(this.reportsFolder, string.Format(rpt.FileName, DateTime.Today.ToString("MMddyyyy")));

new ReportWriter(filePath, doc).WriteFile();
new Transmitter(filePath).TransmitFile();
}
}

internal sealed class ReportsProcessor
{
priv

Code Snippets

internal static class Solid
{
    private static readonly XmlWriterSettings settings = new XmlWriterSettings
    {
        OmitXmlDeclaration = false,
        ConformanceLevel = ConformanceLevel.Document,
        Encoding = Encoding.UTF8
    };

    public static void Main(string[] args)
    {
        try
        {
            ProcessReports(args, AppConfig.ConnectionString, AppConfig.ReportsFolder);
        }
        catch (Exception ex)
        {
            _iLogger.Error(ex);
        }
    }

    private static void ProcessReports(string[] args, string connectionString, string reportsFolder)
    {
        // code to parse arguments to load into DTO to extract stored proc and report name
        foreach (var rpt in Parse(args))
        {
            ProcessSingleReport(connectionString, reportsFolder, rpt);
        }
    }

    private static IEnumerable<ReportInfo> Parse(string[] args)
    {
        // Do actual argument parsing here...
        return Enumerable.Empty<ReportInfo>();
    }

    private static void ProcessSingleReport(string connectionString, string reportsFolder, ReportInfo rpt)
    {
        var doc = LoadReportXml(connectionString, rpt);
        var filePath = Path.Combine(reportsFolder, string.Format(rpt.FileName, DateTime.Today.ToString("MMddyyyy")));

        WriteFile(filePath, doc);
        TransmitFile(filePath);
    }

    private static XmlDocument LoadReportXml(string connectionString, ReportInfo rpt)
    {
        using (var con = new SqlConnection(connectionString))
        using (var cmd = con.CreateCommand())
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.CommandText = rpt.StoredProcedure;
            con.Open();
            using (var reader = cmd.ExecuteXmlReader())
            {
                var doc = new XmlDocument();

                doc.Load(reader);
                return doc;
            }
        }
    }

    private static void WriteFile(string filePath, XmlDocument doc)
    {
        using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
        using (var xmlWriter = XmlWriter.Create(fileStream, settings))
        {
            doc.Save(xmlWriter);
        }
    }

    private static void TransmitFile(string filePath)
    {
        // third party tool is called to transmit the file
        SFtpClient client = new SFtpClient("host", "user", "pwd");

        client.Send(filePath);
    }
}
internal static class Solid
{
    public static void Main(string[] args)
    {
        try
        {
            var argumentParser = new ArgumentParser(args);
            var reportLoader = new ReportLoader(AppConfig.ConnectionString);
            var reportProcessor = new ReportProcessor(AppConfig.ReportsFolder, reportLoader);
            var reportsProcessor = new ReportsProcessor(argumentParser, reportProcessor);

            reportsProcessor.ProcessReports();
        }
        catch (Exception ex)
        {
            _iLogger.Error(ex);
        }
    }
}

/// <summary>
/// code to parse arguments to load into DTO to extract stored proc and report name
/// </summary>
internal sealed class ArgumentParser
{
    private readonly string[] args;

    public ArgumentParser(string[] args)
    {
        this.args = args;
    }

    public IEnumerable<ReportInfo> Parse()
    {
        // Do actual argument parsing here...
        return Enumerable.Empty<ReportInfo>();
    }
}

internal sealed class ReportLoader
{
    private readonly string connectionString;

    public ReportLoader(string connectionString)
    {
        this.connectionString = connectionString;
    }

    public XmlDocument LoadReportXml(ReportInfo rpt)
    {
        using (var con = new SqlConnection(this.connectionString))
        using (var cmd = con.CreateCommand())
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.CommandText = rpt.StoredProcedure;
            con.Open();
            using (var reader = cmd.ExecuteXmlReader())
            {
                var doc = new XmlDocument();

                doc.Load(reader);
                return doc;
            }
        }
    }
}

internal sealed class ReportProcessor
{
    private readonly string reportsFolder;

    private readonly ReportLoader reportLoader;

    public ReportProcessor(string reportsFolder, ReportLoader reportLoader)
    {
        this.reportsFolder = reportsFolder;
        this.reportLoader = reportLoader;
    }

    public void ProcessSingleReport(ReportInfo rpt)
    {
        var doc = this.reportLoader.LoadReportXml(rpt);
        var filePath = Path.Combine(this.reportsFolder, string.Format(rpt.FileName, DateTime.Today.ToString("MMddyyyy")));

        new ReportWriter(filePath, doc).WriteFile();
        new Transmitter(filePath).TransmitFile();
    }
}

internal sealed class ReportsProcessor
{
    private readonly ArgumentParser parser;

    private readonly ReportProcessor reportProcessor;

    public ReportsProcessor(ArgumentParser parser, ReportProcessor reportProcessor)
    {
        this.parser = parser;
        this.reportProcessor = reportProcessor;
    }

    public void ProcessReports()
    {
        foreach (var rpt in this.parser.Parse())
        {
            this.reportProcessor.ProcessSingleReport(rpt);
        }
    }
}

internal sealed class ReportWriter
{
    private static readonly XmlWriterSettings settings = new XmlWriterSettings
    {
        OmitXmlD

Context

StackExchange Code Review Q#29791, answer score: 6

Revisions (0)

No revisions yet.