Quantcast
Channel: ClosedXML - The easy way to OpenXML
Viewing all articles
Browse latest Browse all 1877

Closed Issue: Unable to cast object of type 'ClosedXML.Excel.XLFont' to type 'ClosedXML.Excel.XLStyle' [9260]

$
0
0
Hi all,

###The Background
I'm in a bit of a high pressure situation (so sorry if I'm short in this message) and trying to figure out something with the ClosedXML library.

We have an invoice processor that opens an invoice, and uses various validators to check items.

__Most of the time, these rows process fine and I receive successful results.__ However, whenever there's a validation error at the row level (e.g. "this field is supposed to be a date but you entered a non-date", we throw a fluent validation exception with the error message to pass back to the caller (this runs in a WebAPI).

What I can't understand is that, I have the validation error and I'm ready to pass it back, and I can follow the stack trace and see exactly that error message. However, somewhere at the last second, some other exception comes up that says:

>Unable to cast object of type 'ClosedXML.Excel.XLFont' to type 'ClosedXML.Excel.XLStyle'.

I'm hoping to determine how to trap this error, as I've already received the information I receive correctly and just wish to pass back the value I already have.

**I know this is likely something that I'm doing dumb, so please feel free to point out things that you might think I've already checked.**

###Theories
It appears that something may still be hanging around in memory or trying to process, as the error occurs when serializing my final object to JSON, despite the object itself being a purely business object and having nothing to do with the Excel file.

FYI, I have tried this with spreadsheets that are completely blank, spreadsheets with the formatting removed, etc. etc. -- there should be neither fonts nor styles in these workbooks and yet I get an error about the conversion.

##The Code

I'll attempt to explain what we're doing with some code below.

In our `/api/StandardInvoiceDetailsFile` controller, where users submit the file, we:

Save the file:

```C#
var path = HostingEnvironment.MapPath(string.Format("~/App_Data/import/{0}.xlsx", DateTime.Now.Ticks));
Debug.Assert(path != null, "path != null");
file.SaveAs(path);
```

then we start the processing:

```C#
//the happy path -- process a file and return an HttpResponseMessage with the appropriate information.
try
{
IList<IStandardInvoiceDetailItem> processResult;
workbook = new XLWorkbook(path);

processResult =
new InvoiceProcessorFactory(this._clientSiteID, this._firmID).GetInvoiceProcessorInstance()
.ProcessInvoice(workbook);

}

//uh-oh, their workbook items were invalid in some way. Format and return an http OK but with a collection of validation errors and error messages.
catch (FluentValidationException validationEx)
{
workbook.Dispose();
var validationErrors = validationEx.Data["ValidationErrors"] as IList<ValidationFailure>;

return this.CreateResult(false, validationEx.Message, HttpStatusCode.OK, new List<IStandardInvoiceDetailItem>(), validationErrors.ToList());
}
```
This first step gets an invoice processor based on a client site ID and then processes the workbook that is passed in (a ClosedXML XLWorkbook in memory.)

The part where the is actually processed is below, the "ProcessInvoice" method of the invoice processor:

```C#
public IList<IStandardInvoiceDetailItem> ProcessInvoice(IXLWorkbook workbookToValidate)
{
if (workbookToValidate == null) {throw new ArgumentNullException("workbookToValidate");}
_workbookToValidate = workbookToValidate;
this.ValidateWorkbook();
this.ValidateWorksheet();

_invoiceDetailItems = this.ValidateRowsAndReturnBusinessObjects();

this.ValidateBusinessObjects(_invoiceDetailItems);


// If all's well, return the business objects.
return _invoiceDetailItems;
}
```

The exception is initially raised (correctly) in `this.ValidateRowsAndReturnBusinessObjects()`, which does the following:

```C#
var invoiceDetailItems = new List<IStandardInvoiceDetailItem>();
foreach (var row in this._workbookToValidate.Worksheet(1).RowsUsed())
{
// we don't need to worry about the header columns, hence row > 1
if (row.RowNumber() > 1 && !row.IsEmpty())
{
var rowResult = this._rowsValidator.Validate(row);
if (!rowResult.IsValid)
{
var rowValidationErrorMessage = string.Format(
"Row {0} is invalid. Validation errors:", row.RowNumber());
throw new FluentValidationException(rowValidationErrorMessage, rowResult.Errors);
}

try
{
var newItem = new StandardInvoiceDetailRow()
{
ClaimantName = row.Cell("A").GetValue<string>(),
ClaimantId = row.Cell("B").GetValue<int>(),
Matter = row.Cell("C").GetValue<string>(),
InvoiceDate = row.Cell("D").GetValue<DateTime>(),
InvoiceNumber = row.Cell("E").GetValue<string>(),
Fees = row.Cell("F").GetValue<decimal>(),
Costs = row.Cell("G").GetValue<decimal>(),
Adjustments = row.Cell("H").GetValue<decimal>(),
Total = row.Cell("I").GetValue<decimal>()
};
invoiceDetailItems.Add(newItem);
}

catch (Exception e)
{
throw new Exception("An exception occurred while trying to creatre a business object from an excel row.", e);
}
}
}

return invoiceDetailItems;
}
```

The `RowsValidator` uses FluentValidation to do the following:

```C#
public class InvoiceDetailsWorksheetRowValidator : AbstractValidator<IXLRow>, IRowsValidator
{
public InvoiceDetailsWorksheetRowValidator()
{
this.RuleFor(x => x.Cell("B"))
.Must(ExcelValidationHelpers.BeAnInt).WithMessage("ClaimantID column value is not a valid number.")
.OverridePropertyName("ClaimantIDColumn");

this.RuleFor(x => x.Cell("D"))
.Must(ExcelValidationHelpers.BeADate).WithMessage("InvoiceDate column value is not a valid date.")
.OverridePropertyName("InvoiceDateColumn");

this.RuleFor(x => x.Cell("F"))
.Must(ExcelValidationHelpers.BeADecimal).WithMessage("Fees column value is not a valid decimal.")
.OverridePropertyName("FeesColumn");

this.RuleFor(x => x.Cell("G"))
.Must(ExcelValidationHelpers.BeADecimal).WithMessage("Costs column value is not a valid decimal.")
.OverridePropertyName("CostsColumn");

this.RuleFor(x => x.Cell("H"))
.Must(ExcelValidationHelpers.BeADecimal).WithMessage("Adjustments column value is not a valid decimal.")
.OverridePropertyName("AdjustmentsColumn");

this.RuleFor(x => x.Cell("I"))
.Must(ExcelValidationHelpers.BeADecimal).WithMessage("Total column value is not a valid decimal.")
.OverridePropertyName("TotalColumn");
}


}
```


My Excel Validation helpers are as follows:
```C#
public static bool BeADecimal(IXLCell cellToCheck)
{
try
{
// ReSharper disable UnusedVariable -- this is OK; the value will never be used in this case but needs to be assigned.
var test = cellToCheck.GetValue<decimal>();
// ReSharper restore UnusedVariable
return true;
}
catch (Exception)
{
return false;
}
}

public static bool BeADate(IXLCell cellToCheck)
{
try
{
// ReSharper disable UnusedVariable -- this is OK; the value will never be used in this case but needs to be assigned.
var test = cellToCheck.GetValue<DateTime>();
// ReSharper restore UnusedVariable
return true;
}
catch (Exception)
{
return false;
}
}

public static bool BeALong(IXLCell cellToCheck)
{
try
{
// ReSharper disable UnusedVariable -- this is OK; the value will never be used in this case but needs to be assigned.
var test = cellToCheck.GetValue<long>();
// ReSharper restore UnusedVariable
return true;
}
catch (Exception)
{
return false;
}
}

public static bool BeAnInt(IXLCell cellToCheck)
{
try
{
// ReSharper disable UnusedVariable -- this is OK; the value will never be used in this case but needs to be assigned.
var test = cellToCheck.GetValue<int>();
// ReSharper restore UnusedVariable
return true;
}
catch (Exception)
{
return false;
}
}

}
```

##I know it's a lot -- please ask questions!

The complicated nature of the code means it's tough for me to succinctly show the problem, especially since it involves an exception that I can't (yet) trap or trace in any way. I will do my best to rip this code out and get it into an exe of some kind that can be studied. I am happy to answer any questions you may have.
Comments: This issue ended up being something else. Will be posting a new issue for clarity (and a patch from a coworker -- his first contribution to ClosedXML).

Viewing all articles
Browse latest Browse all 1877

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>