Skip to content

Commit 9939f6a

Browse files
Pankratyb0bi79
authored andcommitted
Improve XLTemplate API (#16)
* Improve XLTemplate API * Do not dispose wokbook if it was passed into XLTemplate constructor externally
1 parent cff6950 commit 9939f6a

File tree

3 files changed

+141
-18
lines changed

3 files changed

+141
-18
lines changed

ClosedXML.Report/XLTemplate.cs

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
using System.Linq;
2-
using System.Reflection;
3-
using ClosedXML.Excel;
1+
using ClosedXML.Excel;
42
using ClosedXML.Report.Excel;
53
using ClosedXML.Report.Options;
4+
using System;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Reflection;
68

79
namespace ClosedXML.Report
810
{
9-
public class XLTemplate
11+
public class XLTemplate : IDisposable
1012
{
11-
private readonly XLWorkbook _workbook;
1213
private readonly RangeInterpreter _interpreter;
14+
private bool _disposeWorkbookWithTemplate;
15+
16+
public bool IsDisposed { get; private set; }
17+
18+
public IXLWorkbook Workbook { get; private set; }
1319

1420
static XLTemplate()
1521
{
@@ -46,15 +52,26 @@ static XLTemplate()
4652
TagsRegister.Add<SummaryFuncTag>("VARP");
4753
}
4854

49-
public XLTemplate(XLWorkbook workbook)
55+
public XLTemplate(string fileName) : this(new XLWorkbook(fileName))
56+
{
57+
_disposeWorkbookWithTemplate = true;
58+
}
59+
60+
public XLTemplate(Stream stream) : this(new XLWorkbook(stream))
61+
{
62+
_disposeWorkbookWithTemplate = true;
63+
}
64+
65+
public XLTemplate(IXLWorkbook workbook)
5066
{
51-
_workbook = workbook;
67+
Workbook = workbook ?? throw new ArgumentNullException(nameof(workbook), "Workbook cannot be null");
5268
_interpreter = new RangeInterpreter(null);
5369
}
5470

5571
public void Generate()
5672
{
57-
foreach (var ws in _workbook.Worksheets.Where(sh => sh.Visibility == XLWorksheetVisibility.Visible && !sh.PivotTables.Any()).ToArray())
73+
CheckIsDisposed();
74+
foreach (var ws in Workbook.Worksheets.Where(sh => sh.Visibility == XLWorksheetVisibility.Visible && !sh.PivotTables.Any()).ToArray())
5875
{
5976
ws.ReplaceCFFormulaeToR1C1();
6077
_interpreter.Evaluate(ws.AsRange());
@@ -64,6 +81,7 @@ public void Generate()
6481

6582
public void AddVariable(object value)
6683
{
84+
CheckIsDisposed();
6785
var type = value.GetType();
6886
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => f.IsPublic)
6987
.Select(f => new { f.Name, val = f.GetValue(value), type = f.FieldType })
@@ -78,7 +96,37 @@ public void AddVariable(object value)
7896

7997
public void AddVariable(string alias, object value)
8098
{
99+
CheckIsDisposed();
81100
_interpreter.AddVariable(alias, value);
82101
}
102+
103+
public void SaveAs(string file)
104+
{
105+
CheckIsDisposed();
106+
Workbook.SaveAs(file);
107+
}
108+
109+
public void SaveAs(Stream stream)
110+
{
111+
CheckIsDisposed();
112+
Workbook.SaveAs(stream);
113+
}
114+
115+
public void Dispose()
116+
{
117+
if (IsDisposed)
118+
return;
119+
120+
if (_disposeWorkbookWithTemplate)
121+
Workbook.Dispose();
122+
Workbook = null;
123+
IsDisposed = true;
124+
}
125+
126+
private void CheckIsDisposed()
127+
{
128+
if (IsDisposed)
129+
throw new ObjectDisposedException("Template has been disposed");
130+
}
83131
}
84132
}

tests/ClosedXML.Report.Tests/XlTemplateTests.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using ClosedXML.Excel;
22
using ClosedXML.Report.Tests.TestModels;
33
using FluentAssertions;
4+
using NSubstitute;
45
using System;
56
using System.Collections.Generic;
7+
using System.IO;
68
using System.Linq;
79
using Xunit;
810
using Xunit.Abstractions;
@@ -201,5 +203,83 @@ IEnumerable<dynamic> GenerateItems()
201203
};
202204
}
203205
}
206+
207+
[Fact]
208+
public void XLTemplateWithNoWorkbookFails()
209+
{
210+
IXLWorkbook wb = null;
211+
Assert.Throws<ArgumentNullException>(() => new XLTemplate(wb));
212+
}
213+
214+
[Fact]
215+
public void XLTemplateOpenFromFile()
216+
{
217+
var fileName = Path.Combine(TestConstants.TemplatesFolder, "1.xlsx");
218+
using (var template = new XLTemplate(fileName))
219+
{
220+
template.Workbook.Should().NotBeNull();
221+
template.Workbook.Worksheets.First().FirstCell().Value.Should().Be("{{TestValue1}}");
222+
}
223+
}
224+
225+
[Fact]
226+
public void XLTemplateOpenFromStream()
227+
{
228+
var fileName = Path.Combine(TestConstants.TemplatesFolder, "1.xlsx");
229+
using (var stream = File.Open(fileName, FileMode.Open))
230+
{
231+
var template = new XLTemplate(stream);
232+
233+
template.Workbook.Should().NotBeNull();
234+
template.Workbook.Worksheets.First().FirstCell().Value.Should().Be("{{TestValue1}}");
235+
}
236+
}
237+
238+
[Fact]
239+
public void AutoCreatedWorkbookDisposedWithTemplate()
240+
{
241+
var disposed = false;
242+
var wb = Substitute.For<IXLWorkbook>();
243+
wb.When(w => w.Dispose()).Do(w => disposed = true);
244+
245+
var template = new XLTemplate(wb);
246+
247+
template.Dispose();
248+
disposed.Should().BeFalse("Workbook specified in the constructor should not be disposed");
249+
}
250+
251+
[Fact]
252+
public void WorkbookNotDisposedWithTemplate()
253+
{
254+
var disposed = false;
255+
var wb = Substitute.For<IXLWorkbook>();
256+
wb.When(w => w.Dispose()).Do(w => disposed = true);
257+
258+
var fileName = Path.Combine(TestConstants.TemplatesFolder, "1.xlsx");
259+
var template = new XLTemplate(fileName);
260+
ReplaceWorkbookWithMock(template, wb);
261+
262+
template.Dispose();
263+
264+
disposed.Should().BeTrue("Workbook expected to be disposed");
265+
}
266+
267+
[Fact]
268+
public void AccessToDisposedTemplateThrows()
269+
{
270+
var fileName = Path.Combine(TestConstants.TemplatesFolder, "1.xlsx");
271+
var template = new XLTemplate(fileName);
272+
273+
template.Dispose();
274+
275+
Assert.Throws<ObjectDisposedException>(() => template.AddVariable("Test", "test"));
276+
Assert.Throws<ObjectDisposedException>(() => template.Generate());
277+
}
278+
279+
private void ReplaceWorkbookWithMock(XLTemplate template, IXLWorkbook mock)
280+
{
281+
var property = template.GetType().GetProperty("Workbook");
282+
property.SetValue(template, mock);
283+
}
204284
}
205285
}

tests/ClosedXML.Report.Tests/XlsxTemplateTestsBase.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,8 @@ protected void XlTemplateTest(string tmplFileName, Action<XLTemplate> arrangeCal
4141

4242
var fileName = Path.Combine(TestConstants.TemplatesFolder, tmplFileName);
4343
using (var stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
44+
using (var template = new XLTemplate(stream))
4445
{
45-
var workbook = new XLWorkbook(stream);
46-
var template = new XLTemplate(workbook);
47-
4846
// ARRANGE
4947
arrangeCallback(template);
5048

@@ -56,7 +54,7 @@ protected void XlTemplateTest(string tmplFileName, Action<XLTemplate> arrangeCal
5654
template.Generate();
5755
Output.WriteLine(DateTime.Now.Subtract(start).ToString());
5856
//MemoryProfiler.Dump();
59-
workbook.SaveAs(file);
57+
template.SaveAs(file);
6058
//MemoryProfiler.Dump();
6159
file.Position = 0;
6260

@@ -66,13 +64,10 @@ protected void XlTemplateTest(string tmplFileName, Action<XLTemplate> arrangeCal
6664
assertCallback(wb);
6765
}
6866
}
69-
70-
workbook.Dispose();
71-
workbook = null;
72-
template = null;
73-
GC.Collect();
74-
//MemoryProfiler.Dump();
7567
}
68+
69+
GC.Collect();
70+
//MemoryProfiler.Dump();
7671
}
7772

7873
protected void CompareWithGauge(XLWorkbook actual, string fileExpected)

0 commit comments

Comments
 (0)