Skip to content

Commit e54f121

Browse files
GersonDiasGerson Dias
andauthored
fix: do not throw when access null item property (#290)
* fix: do not throw when access null item property if your model has a complex property that is null and your template has {{item.property.subproperty}} then it will NOT throw an error letting the template continues to render * fix: go to next cell after error getting data after TargetInvocationException should go to next cell, otherwise next values will be printed in wrong column Co-authored-by: Gerson Dias <gersondias@outlook.com>
1 parent c4a7439 commit e54f121

File tree

2 files changed

+102
-13
lines changed

2 files changed

+102
-13
lines changed

ClosedXML.Report/RangeTemplate.cs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Collections.Generic;
44
using System.Linq;
55
using System.Linq.Dynamic.Core.Exceptions;
6+
using System.Reflection;
7+
68
using ClosedXML.Excel;
79
using ClosedXML.Report.Excel;
810
using ClosedXML.Report.Options;
@@ -94,7 +96,7 @@ private static RangeTemplate Parse(string name, IXLRange range, TempSheetBuffer
9496
result._cells.AddNewRow();
9597
}
9698

97-
result._mergedRanges = sheet.MergedRanges.Where(x => range.Contains(x) && !innerRanges.Any(nr=>nr.Ranges.Any(r=>r.Contains(x)))).ToArray();
99+
result._mergedRanges = sheet.MergedRanges.Where(x => range.Contains(x) && !innerRanges.Any(nr => nr.Ranges.Any(r => r.Contains(x)))).ToArray();
98100
sheet.MergedRanges.RemoveAll(result._mergedRanges.Contains);
99101

100102
result.ParseTags(range);
@@ -132,21 +134,21 @@ private static IEnumerable<IXLNamedRange> GetInnerRanges(IXLRange prng)
132134
{
133135
var containings = prng.GetContainingNames().ToArray();
134136
return from nr in containings
135-
let br = nr.Ranges
136-
.Any(rng => containings
137-
.Where(rr => rr != nr)
138-
.SelectMany(rr => rr.Ranges)
139-
.Any(r => r.Contains(rng)))
140-
where !br
141-
select nr;
137+
let br = nr.Ranges
138+
.Any(rng => containings
139+
.Where(rr => rr != nr)
140+
.SelectMany(rr => rr.Ranges)
141+
.Any(r => r.Contains(rng)))
142+
where !br
143+
select nr;
142144
}
143145

144146
public IReportBuffer Generate(object[] items)
145147
{
146148
_evaluator.AddVariable("items", items);
147149
foreach (var v in _globalVariables)
148150
{
149-
_evaluator.AddVariable("@"+v.Key, v.Value);
151+
_evaluator.AddVariable("@" + v.Key, v.Value);
150152
}
151153
_rangeTags.Reset();
152154

@@ -208,7 +210,7 @@ private void VerticalTable(object[] items, FormulaEvaluator evaluator)
208210
}
209211

210212
var newRowRng = _buff.GetRange(startAddr, rowEnd);
211-
foreach (var mrg in _mergedRanges.Where(r=>!_optionsRow.Contains(r)))
213+
foreach (var mrg in _mergedRanges.Where(r => !_optionsRow.Contains(r)))
212214
{
213215
var newMrg = mrg.Relative(_rowRange, newRowRng);
214216
newMrg.Merge(false);
@@ -258,7 +260,7 @@ private void VerticalTable(object[] items, FormulaEvaluator evaluator)
258260
{
259261
_rangeTags.Execute(new ProcessingContext(resultRange, new DataSource(items), evaluator));
260262
// if the range was increased by processing tags (for example, Group), move the buffer to the last cell
261-
_buff.SetPrevCellToLastUsed();
263+
_buff.SetPrevCellToLastUsed();
262264
}
263265
}
264266

@@ -284,6 +286,28 @@ private void RenderCell(FormulaEvaluator evaluator, TemplateCell cell, params Pa
284286
_errors.Add(new TemplateError(ex.Message, cell.XLCell.AsRange()));
285287
return;
286288
}
289+
catch (TargetInvocationException)
290+
{
291+
/*
292+
* item null complex objects results on TargetInvocationException when evaluating the lambda expression
293+
* eg: Given an Array {
294+
* items: {
295+
* name: string,
296+
* foo?: {
297+
* name: string
298+
* }
299+
* }[]
300+
*
301+
* if in the template we have {{item.foo.name}} and in some of the items material property is null,
302+
* this exception will be thrown.
303+
*
304+
* just add to the error list for future use and keep doing the work, other items may have the material property.
305+
* No need to write the error in the cell since it might be a desired behaviour, but needs to go to next cell.
306+
*/
307+
_buff.WriteValue(string.Empty, cell.XLCell);
308+
_errors.Add(new TemplateError(string.Format("TargetInvocationException: {0}", cell.Value), cell.XLCell.AsRange()));
309+
return;
310+
}
287311

288312
IXLCell xlCell;
289313
if (cell.CellType == TemplateCellType.Formula)
@@ -364,8 +388,8 @@ private void RenderSubrange(RangeTemplate ownRng, object item, FormulaEvaluator
364388
else
365389
{
366390
// move current template cell to next (skip subrange)
367-
row += ownRng._rowCnt+1;
368-
while (_cells[iCell].Row <= row-1)
391+
row += ownRng._rowCnt + 1;
392+
while (_cells[iCell].Row <= row - 1)
369393
iCell++;
370394

371395
iCell--; // roll back. After it became clear that it was too much, we must go back.

tests/ClosedXML.Report.Tests/RangeInterpreterTests.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,33 @@ public void CanBindRangesToAliasedVariableFields()
8787
AssertResultIsCorrect(ws);
8888
}
8989

90+
[Fact]
91+
public void ShouldNotThrowExceptionIfAccessSomeChildrenNullProp()
92+
{
93+
var entity = new Order();
94+
95+
var template = CreateOrderTemplate();
96+
var ws = template.Workbook.Worksheets.First();
97+
98+
ws.Range("A3:B4").AddToNamed("Items");
99+
100+
template.AddVariable(entity);
101+
template.Generate();
102+
103+
ws.Cell("B3").Value.Should().Be(string.Empty);
104+
ws.Cell("B4").Value.Should().Be("Material 1");
105+
}
106+
107+
private XLTemplate CreateOrderTemplate()
108+
{
109+
var wbTemplate = new XLWorkbook();
110+
var ws = wbTemplate.AddWorksheet();
111+
112+
ws.Cell("B3").Value = "{{item.Material.Name}}";
113+
114+
return new XLTemplate(wbTemplate);
115+
}
116+
90117
private XLTemplate CreateBaseTemplate()
91118
{
92119
var wbTemplate = new XLWorkbook();
@@ -123,6 +150,44 @@ private class Parent
123150
};
124151
}
125152

153+
private class Order
154+
{
155+
public string OrderNumber => "Order Number";
156+
public List<Item> Items { get; } = new List<Item>
157+
{
158+
new Item("noMaterial", null),
159+
new Item("withMaterial", new Material("Material 1"))
160+
};
161+
}
162+
163+
private class Item
164+
{
165+
public string Name { get; private set; }
166+
public Material Material { get; private set; }
167+
168+
public Item(string name, Material material)
169+
{
170+
Name = name;
171+
Material = material;
172+
}
173+
174+
public Item AddMaterial(Material material)
175+
{
176+
Material = material;
177+
return this;
178+
}
179+
}
180+
181+
private class Material
182+
{
183+
public string Name { get; }
184+
185+
public Material(string name)
186+
{
187+
Name = name;
188+
}
189+
}
190+
126191
public class Child
127192
{
128193
public string ChildName { get; }

0 commit comments

Comments
 (0)