Universal Report Core: Implementing Pipeline Pattern for FieldValueDisplayViewModel
Pipeline Pattern Update: Refactored FieldValueDisplayViewModel
to use a pipeline pattern, improving maintainability and extensibility for field value formatting.
In the Universal Report Core (URC) project, we recently refactored the FieldValueDisplayViewModel
to replace complex if-then and switch-case logic with a pipeline pattern. This change streamlines the formatting of dynamic field values, making the code more modular and easier to extend for future requirements.
Why Pipeline Pattern?
The original implementation relied on nested conditional statements to format field values based on their type (e.g., integers, decimals) or property type when null. While functional, this approach was hard to maintain and extend. The pipeline pattern addresses these issues by breaking down the formatting logic into a series of independent, reusable formatters that process the input in a defined order.
Key benefits include:
- Modularity: Each formatter handles a specific type or case, making the code easier to understand and test.
- Extensibility: New formatters can be added without modifying existing logic, adhering to the Open-Closed Principle.
- Simplified View: The Razor view is cleaner, delegating all formatting logic to the view model.
Implementation Details
The refactored FieldValueDisplayViewModel
introduces an IFieldFormatter
interface and a pipeline of formatter classes (IntegerFormatter
, DecimalFormatter
, DefaultFormatter
). Each formatter implements two methods:
CanHandle
: Determines if the formatter applies to the given value or property type.Format
: Produces the formatted string output.
The pipeline is executed in the FormatFieldValue
method, which iterates through the formatters until one handles the input. Here’s a simplified example of the implementation:
public string FormatFieldValue(object value, Type propertyType)
{
foreach (var formatter in Formatters)
{
if (formatter.CanHandle(value, propertyType))
{
return formatter.Format(value, propertyType);
}
}
return string.Empty;
}
The formatters handle:
- Integers (
int
,long
): Display as-is or “0” if null. - Decimals (
double
,float
,decimal
): Format with two decimal places (e.g.,123.45
) or “0.00” if null. - Default: Return the value’s string representation or an empty string if null.
The Razor view now simply calls Model.FormatFieldValue
, reducing its complexity:
@model UniversalReportCore.Ui.ViewModels.FieldValueDisplayViewModel
@{
var fieldVal = Model.GetValue();
var propertyType = Model.Item?.GetType().GetProperty(Model.PropertyName)?.PropertyType;
var formattedValue = Model.FormatFieldValue(fieldVal, propertyType);
}
@formattedValue
Testing the Pipeline
To ensure the pipeline works as expected, we added unit tests using xUnit. Below is an example test case for the IntegerFormatter
:
[Fact]
public void IntegerFormatter_HandlesIntValue_ReturnsCorrectString()
{
var formatter = new IntegerFormatter();
var value = 42;
var result = formatter.Format(value, typeof(int));
Assert.Equal("42", result);
}
Run the tests and generate a coverage report with:
# Running unit tests and code coverage
dotnet test /p:CollectCoverage=true --collect:"XPlat Code Coverage"
# Generate HTML coverage report
reportgenerator "-reports:**/coverage.cobertura.xml" "-targetdir:CoverageReport" -reporttypes:Html
The pipeline pattern has improved our code coverage for FieldValueDisplayViewModel
, as the modular formatters are easier to test in isolation.
Next Steps
We plan to extend the pipeline to support additional types, such as DateTime
or custom formats defined in IReportColumnDefinition
. Additionally, we’ll optimize the formatter initialization to reduce memory overhead in high-throughput scenarios.
This refactoring demonstrates how pipeline pattern can simplify complex logic while enhancing maintainability and testability in URC. Stay tuned for more updates as we continue to improve the Universal Report Core!