Provides metadata model, parsing and reporting.
dotnet add package MicroElements.Metadata
Windows: Run build.ps1
Linux: Run build.sh
This project is licensed under the MIT license. See the LICENSE file for more info.
Represents property for describing metadata model.
There are two main interfaces: untyped IProperty and generic IProperty<T>.
IProperty provides Name and Type and optional Description and Alias.
IProperty<T> extends IProperty with DefaultValue, Calculator and Examples
Source: IProperty.cs
Represents property and its value.
Has untyped form: IPropertyValue and strong typed: IPropertyValue<T>.
Source: IPropertyValue.cs
PropertyContainer represents collection that contains properties and values for these properties. IPropertyContainer is an immutable collection of IPropertyValue
IPropertyContainer provides Properties, ParentSource and SearchOptions
IMutablePropertyContainer extends IPropertyContainer with Add* and Set* methods.
public class PropertyContainerUsage
{
public class EntityMeta
{
public static readonly IProperty<DateTime> CreatedAt = new Property<DateTime>("CreatedAt");
public static readonly IProperty<string> Description = new Property<string>("Description");
}
[Fact]
public void simple_set_and_get_value()
{
IPropertyContainer propertyContainer = new MutablePropertyContainer()
.WithValue(EntityMeta.CreatedAt, DateTime.Today)
.WithValue(EntityMeta.Description, "description");
propertyContainer.GetValue(EntityMeta.CreatedAt).Should().Be(DateTime.Today);
propertyContainer.GetValue(EntityMeta.Description).Should().Be("description");
}
[Fact]
public void get_property_value()
{
IPropertyContainer propertyContainer = new MutablePropertyContainer()
.WithValue(EntityMeta.CreatedAt, DateTime.Today)
.WithValue(EntityMeta.Description, "description");
IPropertyValue<string>? propertyValue = propertyContainer.GetPropertyValue(EntityMeta.Description);
propertyValue.Should().NotBeNull();
propertyValue.Property.Should().BeSameAs(EntityMeta.Description);
propertyValue.Value.Should().Be("description");
propertyValue.Source.Should().Be(ValueSource.Defined);
}
}IMetadataProvider represents object that has metadata where Metadata is IPropertyContainer.
MetadataProvider allows to extend any object with additional properties. IMetadataProvider default implementation uses MetadataGlobalCache.GetInstanceMetadata that creates MutablePropertyContainer. If you want mutable metadata - implement Metadata with IMutablePropertyContainer.
Metadata default search mode: ByTypeAndName
/// <summary>
/// Gets metadata of required type.
/// </summary>
/// <typeparam name="TMetadata">Metadata type.</typeparam>
/// <param name="metadataProvider">Metadata provider.</param>
/// <param name="metadataName">Optional metadata name.</param>
/// <param name="defaultValue">Default value to return if not metadata found.</param>
/// <returns>Metadata or default value if not found.</returns>
[return: MaybeNull]
public static TMetadata GetMetadata<TMetadata>(
this IMetadataProvider metadataProvider,
string? metadataName = null,
[AllowNull] TMetadata defaultValue = default)
/// <summary>
/// Sets metadata for target object and returns the same metadataProvider for chaining.
/// </summary>
/// <typeparam name="TMetadataProvider">Metadata provider type.</typeparam>
/// <typeparam name="TMetadata">Metadata type.</typeparam>
/// <param name="metadataProvider">Target metadata provider.</param>
/// <param name="metadataName">Metadata name.</param>
/// <param name="data">Metadata to set.</param>
/// <returns>The same metadataProvider.</returns>
public static TMetadataProvider SetMetadata<TMetadataProvider, TMetadata>(
this TMetadataProvider metadataProvider,
string? metadataName,
TMetadata data)
where TMetadataProvider : IMetadataProvider
/// <summary>
/// Configures metadata with action. Can be called many times.
/// If metadata is not exists then it creates with default constructor.
/// </summary>
/// <typeparam name="TMetadataProvider">Metadata provider type.</typeparam>
/// <typeparam name="TMetadata">Metadata type.</typeparam>
/// <param name="metadataProvider">Target metadata provider.</param>
/// <param name="configureMetadata">Configure action.</param>
/// <param name="metadataName">Optional metadata name.</param>
/// <returns>The same metadataProvider.</returns>
public static TMetadataProvider ConfigureMetadata<TMetadataProvider, TMetadata>(
this TMetadataProvider metadataProvider,
Action<TMetadata> configureMetadata,
string? metadataName = null)
where TMetadataProvider : IMetadataProvider
where TMetadata : new()other methods can be found in MetadataProviderExtensions
Searching is one of the important concept of working with metadata. Most search methods accepts SearchOptions
| Property | DefaultValue | Description |
|---|---|---|
| PropertyComparer | ByTypeAndNameEqualityComparer |
Equality comparer for comparing properties. |
| SearchInParent | true |
Do search in parent if no PropertyValue was found. |
| CalculateValue | true |
Calculate value if value was not found. |
| UseDefaultValue | true |
Use default value from property is property value was not found. |
| ReturnNotDefined | true |
Return fake PropertyValue with and Value set to default if no PropertyValue was found. Returns null if ReturnNotDefined is false. |
- Main search methods provided by
ISearchAlgorithm - Main search algorithm can be get or set in
Search.Algorithm - All search methods from
SearchExtensionsuseSearch.Algorithm SearchOptionscan be composed bySearchclass
/// <summary>
/// Represents search algorithm.
/// </summary>
public interface ISearchAlgorithm
{
/// <summary>
/// Searches <see cref="IPropertyValue{T}"/> by <see cref="IProperty{T}"/> and <see cref="SearchOptions"/>.
/// </summary>
/// <param name="propertyContainer">Property container.</param>
/// <param name="property">Property to search.</param>
/// <param name="searchOptions">Search options.</param>
/// <returns><see cref="IPropertyValue"/> or null.</returns>
IPropertyValue? SearchPropertyValueUntyped(
IPropertyContainer propertyContainer,
IProperty property,
SearchOptions? searchOptions = default);
/// <summary>
/// Gets <see cref="IPropertyValue{T}"/> by <see cref="IProperty{T}"/> and <see cref="SearchOptions"/>.
/// </summary>
/// <typeparam name="T">Property type.</typeparam>
/// <param name="propertyContainer">Property container.</param>
/// <param name="property">Property to search.</param>
/// <param name="searchOptions">Search options.</param>
/// <returns><see cref="IPropertyValue"/> or null.</returns>
IPropertyValue<T>? GetPropertyValue<T>(
IPropertyContainer propertyContainer,
IProperty<T> property,
SearchOptions? searchOptions = null);
}| Method | Description |
|---|---|
| GetPropertyValue | Gets or calculates typed property and value for property using search conditions. It's a full search that uses all search options: SearchOptions.SearchInParent, SearchOptions.CalculateValue, SearchOptions.UseDefaultValue, SearchOptions.ReturnNotDefined. |
| SearchPropertyValueUntyped | Searches property and value for untyped property using search conditions. Search does not use SearchOptions.UseDefaultValue and SearchOptions.CalculateValue. Search uses only SearchOptions.SearchInParent and SearchOptions.ReturnNotDefined. |
| GetPropertyValueUntyped | Gets property and value for untyped property using search conditions. Uses simple untyped search SearchPropertyValueUntyped if CanUseSimpleUntypedSearch or property has type Search.UntypedSearch. Uses full GetPropertyValue{T} based on property.Type in other cases. |
| GetValue | Gets or calculates value for property. |
| GetValueAsOption | Gets or calculates optional not null value. |
| GetValueUntyped | Gets or calculates untyped value for property. |
| GetValueByName | Gets or calculates value by name. |
Source: EntityParseValidateReport.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using DocumentFormat.OpenXml.Packaging;
using FluentAssertions;
using MicroElements.Functional;
using MicroElements.Parsing;
using MicroElements.Reporting.Excel;
using MicroElements.Validation;
using MicroElements.Validation.Rules;
using Xunit;
namespace MicroElements.Metadata.Tests.examples
{
public class EntityParseValidateReport
{
public class Entity
{
public DateTime CreatedAt { get; }
public string Name { get; }
public Entity(DateTime createdAt, string name)
{
CreatedAt = createdAt;
Name = name;
}
}
public class EntityMeta : IPropertySet, IPropertyContainerMapper<Entity>
{
public static readonly EntityMeta Instance = new EntityMeta();
public static readonly IProperty<DateTime> CreatedAt = new Property<DateTime>("CreatedAt");
public static readonly IProperty<string> Name = new Property<string>("Name");
/// <inheritdoc />
public IEnumerable<IProperty> GetProperties()
{
yield return CreatedAt;
yield return Name;
}
/// <inheritdoc />
public IPropertyContainer ToContainer(Entity model)
{
return new MutablePropertyContainer()
.WithValue(CreatedAt, model.CreatedAt)
.WithValue(Name, model.Name);
}
/// <inheritdoc />
public Entity ToModel(IPropertyContainer container)
{
return new Entity(
createdAt: container.GetValue(CreatedAt),
name: container.GetValue(Name));
}
}
public class EntityParser : ParserProvider
{
protected Option<DateTime> ParseDate(string value) => Prelude.ParseDateTime(value);
/// <inheritdoc />
public EntityParser()
{
Source("CreatedAt", ParseDate).Target(EntityMeta.CreatedAt);
Source("Name").Target(EntityMeta.Name);
}
}
public class EntityValidator : IValidator
{
/// <inheritdoc />
public IEnumerable<IValidationRule> GetRules()
{
yield return EntityMeta.CreatedAt.NotDefault();
yield return EntityMeta.Name.Required();
}
}
public class EntityReport : ReportProvider
{
public EntityReport(string reportName = "Entities")
: base(reportName)
{
Add(EntityMeta.CreatedAt);
Add(EntityMeta.Name);
}
}
public Stream ReportToExcel(Entity[] entities)
{
var reportRows = entities.Select(entity => EntityMeta.Instance.ToContainer(entity));
var excelStream = new MemoryStream();
ExcelReportBuilder.Create(excelStream)
.AddReportSheet(new EntityReport("Entities"), reportRows)
.SaveAndClose();
return excelStream;
}
public Entity[] ParseExcel(Stream stream)
{
var document = SpreadsheetDocument.Open(stream, false);
var messages = new List<Message>();
var entities = document
.GetSheet("Entities")
.GetRowsAs(new EntityParser(), list => new PropertyContainer(list))
.ValidateAndFilter(new EntityValidator(), result => messages.AddRange(result.ValidationMessages))
.Select(container => EntityMeta.Instance.ToModel(container))
.ToArray();
return entities;
}
[Fact]
public void UseCase()
{
// Trim DateTime to milliseconds because default DateTime render trimmed to milliseconds
DateTime NowTrimmed()
{
DateTime now = DateTime.Now;
return now.AddTicks(-(now.Ticks % TimeSpan.TicksPerMillisecond));
}
Entity[] entities = {
new Entity(NowTrimmed(), "Name1"),
new Entity(NowTrimmed(), "Name2"),
new Entity(NowTrimmed(), "Name3"),
};
Stream excelStream = ReportToExcel(entities);
Entity[] fromExcel = ParseExcel(excelStream);
fromExcel.Should().HaveCount(3);
fromExcel.Should().BeEquivalentTo(entities);
}
}
}- IPropertySet
- IPropertyContainerMapper
IPropertyContainer can be casted to dynamic object with AsDynamic extension.
[Fact]
public void DynamicContainer()
{
var propertyContainer = new MutablePropertyContainer();
propertyContainer.SetValue("PropertyA", "ValueA");
propertyContainer.SetValue(new Property<int>("PropertyB"), 42);
dynamic dynamicContainer = propertyContainer.AsDynamic();
object valueA = dynamicContainer.PropertyA;
valueA.Should().Be("ValueA");
object valueB = dynamicContainer.PropertyB;
valueB.Should().Be(42);
object notFoundProperty = dynamicContainer.NotFoundProperty;
notFoundProperty.Should().BeNull();
}See: EntityParseValidateReport example
See: EntityParseValidateReport example
See: EntityParseValidateReport example
- ReportProvider
- Renderer
- IPropertyParser