İlerlemeyi bildirirken nesneleri çeşitli formatlarda dışa aktarma

Aug 18 2020

Açıklama

Bir WinForms uygulamasının aşağıdaki türdeki nesneleri çeşitli biçimlerde dışa aktarma işlevi vardır:

class Item
{
    public int id { get; set; }
    public string description { get; set; }
}

Bir penceredeki bir düğmeye tıklandığında, a SaveFileDialoggösterilir ve şu anda verileri .txt, .csv veya .xlsx biçiminde kaydetme seçeneği sunar. Bazen yüzlerce veya binlerce nesne olduğu ve kullanıcı arayüzünün donmaması gerektiğinden, Taskbu işlemi çalıştırmak için a kullanılır. Bu uygulama işe yarıyor, ancak geliştirilebilir.

Kod

public partial class ExportWindow : Form
{
    // objects to be exported
    List<Item> items;

    // event handler for the "Export" button click
    private async void exportButton_click(object sender, System.EventArgs e)
    {
        SaveFileDialog exportDialog = new SaveFileDialog();
        exportDialog.Filter = "Text File (*.txt)|*.txt|Comma-separated values file (*.csv)|*.csv|Excel spreadsheet (*.xlsx)|*.xlsx";
        exportDialog.CheckPathExists = true;
        DialogResult result = exportDialog.ShowDialog();
        if (result == DialogResult.OK)
        {
            var ext = System.IO.Path.GetExtension(saveExportFileDlg.FileName);

            try
            { 
                // update status bar
                // (it is a custom control)
                statusBar.text("Exporting");

                // now export it
                await Task.Run(() =>
                {
                    switch (ext.ToLower())
                    {
                        case ".txt":
                            saveAsTxt(exportDialog.FileName);
                            break;

                        case ".csv":
                            saveAsCsv(exportDialog.FileName);
                            break;
                    
                        case ".xlsx":
                            saveAsExcel(exportDialog.FileName);
                            break;

                        default:
                            // shouldn't happen
                            throw new Exception("Specified export format not supported.");
                    }
                });
            }
            catch (System.IO.IOException ex)
            {
                 statusBar.text("Export failed");
                 logger.logError("Export failed" + ex.Message + "\n" + ex.StackTrace);

                 return;
            }
        }
    }

    private delegate void updateProgressDelegate(int percentage);

    public void updateProgress(int percentage)
    {
        if (statusBar.InvokeRequired)
        {
            var d = updateProgressDelegate(updateProgress);
            statusBar.Invoke(d, percentage);
        }
        else
        {
            _updateProgress(percentage);
        }
    }

    private void saveAsTxt(string filename)
    {
        IProgress<int> progress = new Progress<int>(updateProgress);
        
        // save the text file, while reporting progress....
    }

    private void saveAsCsv(string filename)
    {
        IProgress<int> progress = new Progress<int>(updateProgress);
        
        using (StreamWriter writer = StreamWriter(filename))
        {
            // write the headers and the data, while reporting progres...
        }
    }

    private void saveAsExcel(string filename)
    {
        IProgress<int> progress = Progress<int>(updateProgress);

        // EPPlus magic to write the data, while reporting progress...
    }
}

Sorular

Daha genişletilebilir hale getirmek için bu nasıl yeniden düzenlenebilir? Diğer bir deyişle, daha fazla dosya türü için destek eklemek istersem, değiştirmeyi kolaylaştırın ve hızlandırın. Switch ifadesi çok uzun sürebilir. Esasen, Açık / Kapalı prensibine nasıl uyulur?

Yanıtlar

5 CharlesNRice Aug 18 2020 at 20:04

Gerçek ihracat (lar) ı kendi sınıflarına taşımayı öneririm. İhracat için bir arayüz oluşturabiliriz. Çizgisinde bir şey

public interface IExport<T>
{
    Task SaveAsync(string fileName, IEnumerable<T> items, IProgress<int> progress = null);
    string ExportType { get; }
}

Daha sonra her dışa aktarma türü bu arabirimi uygulayabilir.

public class ExportItemsToText : IExport<Item>
{
    public Task SaveAsync(string fileName, IEnumerable<Item> items, IProgress<int> progress = null)
    {
        throw new NotImplementedException();
    }

    public string ExportType => "txt";
}

Ardından ExportWindow kurucunuzda

public ExportWindow(IEnumerable<IExport<Item>> exports)
{
    // if using DI otherwise could just fill in dictionary here
    ExportStrategy = exports.ToDictionary(x => x.ExportType, x => x);
}

Bir anahtar ifadesi yerine, artık hangi dışa aktarmanın çalıştırılması gerektiğini bulmak için sözlükte anahtara bakabilirsiniz ve bulunamazsa varsayılan durumunuzla aynı olacaktır.

IExport<Item> exporter;
if (ExportStrategy.TryGetValue(ext.ToLower(), out exporter))
{
    await exporter.SaveAsync(exportDialog.FileName, items, new Progress<int>(updateProgress))
}
else
{
    throw new Exception("Specified export format not supported.");
}

Artık gelecekte daha fazla tür için destek eklerseniz, yalnızca arayüzü uygular ve DI kapsayıcınızı güncellersiniz. Veya DI kullanmıyorsanız, bunu ExportWindow'unuzun yapıcısına eklemeniz gerekir.

Bunun harika bir fikir olduğunu sanmıyorum ama eğer ihracat başına gerçekten bir sınıf oluşturmak istemiyorsan, ki bunu yapman gerektiğini düşünüyorum, sözlüğü yapabilir ve IDictionary<string, Action<string>>sonra metotlarınızı oraya koyabilir ve yeni bir tür eklerken yöntemi ve sözlüğü güncelleyin.

2 iSR5 Aug 21 2020 at 06:22

Bunu daha önceki projelerimden birinde (ASP.NET'teydi) zaten uyguladığımdan beri sahip olduğum şeyleri paylaşmak istiyorum, ancak başka herhangi bir ortamda da uygulanabilir. Uygulama CharlesNRice önerisine benzerdi. Ancak, sistem raporlarını (yalnızca bir rapor şablonu kullanılan), gelecekte daha fazla dışa aktarma seçeneğine sahip olmanın müzakeresiyle Pdf, Excel ve Word'e dışa aktarma seçeneklerine sahip olmak gerekiyordu. İşte böyle yaptım:

Önce arayüz:

public interface IExportTo<T>
{
    IExportTo<T> Generate();

    void Download(string fileName);

    void SaveAs(string fileFullPath);
}

ardından konteyner sınıfı:

public class ExportTo : IDisposable
{
    private readonly IList<T> _source;

    public ExportTo(IList<T> source)
    {
        _source = source;
    }

    public ExportExcel Excel()
    {
        return new ExportExcel(_source);
    }

    public ExportPdf Pdf()
    {
        return new ExportPdf(_source);
    }
    
    public ExportWord Word()
    {
        return new ExportPdf(_source);
    }
    

    #region IDisposable

    private bool _disposed = false;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                Dispose();
            }

            _disposed = true;
        }
    }


    ~ExportTo()
    {
        Dispose(false);
    }

    #endregion
}

Yukarıdaki sınıfta görebileceğimiz gibi, her ihracat türü için bir sınıf uyguladım. Bir sınıfı paylaşacağım (asıl dersten olsa da basitleştireceğim).

public sealed class ExportPdf : IExportTo<T>, IDisposable
{
    private readonly IList<T> _source;

    private ExportPdf() { }

    public ExportPdf(IList<T> source) : this() => _source = source ?? throw new ArgumentNullException(nameof(source));

    public IExportTo<T> Generate()
    {
        // some implementation 
        return this;
    }

    // another overload to generate by Id 
    public IExportTo<T> Generate(long reportId)
    {
        // do some work 
        return this;
    }

    // Download report as file 
    public void Download(string fileName)
    {
       // do some work 
    }

    public void SaveAs(string fileFullPath)
    {
        throw new NotImplementedException("This function has not been implemented yet. Only download is available for now.");
    }


    #region IDisposable

    private bool _disposed = false;

    public void Dispose()
    {   
        Dispose(true);
        GC.SuppressFinalize(this);
    }


    private void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                Dispose();
            }

            _disposed = true;
        }
    }


    ~ExportPdf()
    {
        Dispose(false);
    }

    #endregion
}

Downloadve SaveAsfarklıdır (aynı değil). Downloaddışa aktarılan dosyayı indirirken SaveAsnesne örneğini kaydeder . Ancak bu, bağımlılıklar kullanıldığı için böyle uygulandı.

Şimdi kullanım şu şekilde olacaktır:

new ExportTo(someList)
.Pdf()
.Generate()
.Download(fileName);

Bu projede böyle uyguladım, geliştirilebilir, ancak iş gereksinimleri için yeterli.

Yeni bir dışa aktarma türü eklemeniz gerektiğinde, sadece yeni bir sealedsınıf oluşturun, ardından IExportTo<T>, IDisposableo sınıfta uygulayın. Son olarak, kapsayıcı sınıfını yeni türle güncelleyin (bu yöntemin yeni bir örneğini açmak için bir yöntem ekleyin) ve gitmeniz iyi olur.