ModelBuilder 8.6.0

dotnet add package ModelBuilder --version 8.6.0                
NuGet\Install-Package ModelBuilder -Version 8.6.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ModelBuilder" Version="8.6.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ModelBuilder --version 8.6.0                
#r "nuget: ModelBuilder, 8.6.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install ModelBuilder as a Cake Addin
#addin nuget:?package=ModelBuilder&version=8.6.0

// Install ModelBuilder as a Cake Tool
#tool nuget:?package=ModelBuilder&version=8.6.0                

ModelBuilder

A library for easy generation of model classes

GitHub license Nuget Nuget

Actions Status Coverage Status

Creating a model

The static Model class provides the entry point for generating models. It supports creating classes, structs and primitive types. It can also create complex object hierarchies.

var model = Model.Create<Person>();

You may want to create a model that ignores setting a property value.

var model = Model.Ignoring<Person>(x => x.FirstName).Create<Person>();

Ignoring a property can also be configured for types that may exist deep in an object hierarchy for the type being created.

var model = Model.Ignoring<Address>(x => x.AddressLine1).Create<Person>();

It also supports providing constructor arguments to the top level type being created.

var model = Model.Create<Person>("Fred", "Smith");

Constructor parameter matching

ModelBuilder will attempt to match constructor parameters to property values when building a new instance to avoid a new random value overwriting the property set by the constructor.

The following rules define how a match is made between a constructor parameter and a property value:

  • No match if property type does not match the constructor parameter type (inheritance supported)
  • No match if property value is the default value for its type
  • Match reference types (except for strings) where the property value matches the same instance as the constructor parameter (using Object.ReferenceEquals)
  • Match value types and strings that have the same value and the constructor parameter name is a case insensitive match to the property name

Populating a model

You may have an object instance that was created somewhere else. The Model class can populate that for you.

var person = new Person
{
    FirstName = "Jane"
};

var model = Model.Ignoring<Person>(x => x.FirstName).Populate(person);

var customer = new Person();

var customerModel = Model.Populate(customer);

Logging the build process

ModelBuilder can write log messages while building values. The easiest way to output a build log is to use the WriteLog extension method. Logging the build process is disabled by default however the WriteLog extension method implicitly enables logging.

This would look like the following when using xUnit.

public class WriteLogTests
{
    private readonly ITestOutputHelper _output;

    public WriteLogTests(ITestOutputHelper output)
    {
        _output = output;
    }

    [Fact]
    public void WriteLogRendersLogFromModel()
    {
        var actual = Model.WriteLog<Names>(_output.WriteLine).Create();

        actual.Should().NotBeNull();
    }
}

This test then outputs the generated build log to the xUnit test output.

Start creating type ModelBuilder.UnitTests.Models.Names using ModelBuilder.TypeCreators.DefaultTypeCreator
    Start populating instance ModelBuilder.UnitTests.Models.Names
        Creating property Gender (ModelBuilder.UnitTests.Models.Gender) on type ModelBuilder.UnitTests.Models.Names
            Start creating type ModelBuilder.UnitTests.Models.Gender using ModelBuilder.ValueGenerators.EnumValueGenerator
            End creating type ModelBuilder.UnitTests.Models.Gender
        Created property Gender (ModelBuilder.UnitTests.Models.Gender) on type ModelBuilder.UnitTests.Models.Names
        Creating property FirstName (System.String) on type ModelBuilder.UnitTests.Models.Names
            Start creating type System.String using ModelBuilder.ValueGenerators.FirstNameValueGenerator
            End creating type System.String
        Created property FirstName (System.String) on type ModelBuilder.UnitTests.Models.Names
        Creating property MiddleName (System.String) on type ModelBuilder.UnitTests.Models.Names
            Start creating type System.String using ModelBuilder.ValueGenerators.MiddleNameValueGenerator
            End creating type System.String
        Created property MiddleName (System.String) on type ModelBuilder.UnitTests.Models.Names
        Creating property LastName (System.String) on type ModelBuilder.UnitTests.Models.Names
            Start creating type System.String using ModelBuilder.ValueGenerators.LastNameValueGenerator
            End creating type System.String
        Created property LastName (System.String) on type ModelBuilder.UnitTests.Models.Names
    End populating instance ModelBuilder.UnitTests.Models.Names
End creating type ModelBuilder.UnitTests.Models.Names

Changing the model after creation

Sometimes you need to tweak a model after it has been created. This can be done easily using the Set extension method on any object.

var person = Model.Create<Person>()
    .Set(x => x.FirstName = "Joe")
    .Set(x => x.Email = null);

var otherPerson = Model.Create<Person>().Set(x => 
    {
        x.FirstName = "Joe";
        x.Email = null;
    });

The SetEach method does the same as Set but across enumerable objects.

var organisation = Model.Create<Organisation>();

organisation.Staff.SetEach(x => x.Email = null);

Customizing the process

ModelBuilder is designed with extensibility in mind. There are many ways that you can customize the build configuration and the process of building models.

The extensibility points for customizing the build configuration are:

  • IBuildConfiguration
  • IConfigurationModule
  • IExecuteStrategy

The extensibility points defined in IBuildConfiguration that control how to create models are:

  • IConstructorResolver
  • ICreationRule
  • IExecuteOrderRule
  • IIgnoreRule
  • IPostBuildAction
  • IPropertyResolver
  • ITypeCreator
  • TypeMappingRule
  • ITypeResolver
  • IValueGenerator

One way to debug how custom extensibility types get resolved is to output the build log.

IBuildConfiguration

An IBuildConfiguration instance provides access to all the configuration for creating values. The Model class uses a default configuration that is built using the DefaultConfigurationModule. You could however start with an empty BuildConfiguration and modify the configuration from there.

var configuration = new BuildConfiguration();

configuration.Add(new MyCustomTypeCreator());

var value = configuration.Create<MyType>();

The items held by a build configuration can be modified for specific scenarios. For example, generating an Age value using the AgeValueGenerator creates an age between 1 and 100 and generating sets of items using EnumerableTypeCreator creates an enumerable set of data containing a random number of 10 and 30 between items. The logic of these can be modified before creating a value.

var model = Model.UsingDefaultConfiguration()
    .UpdateValueGenerator<AgeValueGenerator>(x => x.MinAge = 18)
    .Create<Person>();

var dataSet = Model.UsingDefaultConfiguration()
    .UpdateTypeCreator<EnumerableTypeCreator>(x => x.MinCount = x.MaxCount = 10)
    .Create<IEnumerable<Person>>();

IConfigurationModule

An IConfigurationModule defines a reusable configuration definition that is applied to IBuildConfiguration. You can write your own by creating a class that implements IConfigurationModule.

public class MyCustomModule : IConfigurationModule
{
    public void Configure(IBuildConfiguration configuration)
    {
        configuration.AddCreationRule<MyCreationRule>();
        configuration.AddExecuteOrderRule<MyExecuteOrderRule>();
        configuration.AddIgnoreRule<MyIgnoreRule>();
        configuration.AddPostBuildAction<MyPostBuildAction>();
        configuration.AddTypeCreator<MyTypeCreator>();
        configuration.AddTypeMappingRule<MyTypeMappingRule>();
        configuration.AddValueGenerator<MyValueGenerator>();
		
        // Or
        configuration.Add(new MyCreationRule());
        configuration.Add(new MyExecuteOrderRule());
        configuration.Add(new MyIgnoreRule());
        configuration.Add(new MyPostBuildAction());
        configuration.Add(new MyTypeCreator());
        configuration.Add(new MyTypeMappingRule());
        configuration.Add(new MyValueGenerator());
    }
}
var model = Model.UsingModule<MyCustomModule>().Create<Person>();

You may want to create multiple configuration modules that support different model construction designs.

var model = Model.UsingModule<MyUnitTestModule>().Create<Person>();
var otherModel = Model.UsingModule<MyIntegrationTestModule>().Create<Person>();

You can also daisy-chain modules to build on top of other modules.

var model = Model.UsingDefaultConfiguration
    .UsingModule<MyCustomModule>()
    .UsingModule<MyOtherModule>()
    .Create<Person>();

NOTE: The Model.UsingModule<T>() method implicitly includes the DefaultConfigurationModule which provides the configuration that Model.Create and Module.Populate uses.

Configuration modules are useful when you want to provide some consistent defaults for creating values. For example, you may have an Account entity with an IsActive boolean property and those should always be created with a value of true rather than a random value.

public class TestModule : IConfigurationModule
{
    /// <inheritdoc />
    public void Configure(IBuildConfiguration configuration)
    {
        if (configuration == null)
        {
            throw new ArgumentNullException(nameof(configuration));
        }

        configuration.AddCreationRule<Account>(x => x.IsActive, true, 100)
    }
}

You can then use this common build configuration across unit tests by referencing the module.

[Fact]
public void GetReturnsAccountWhenActiveIsTrue()
{
    var account = Model.UsingModule<TestModule>.Create<Account>();

    // account.IsActive will always be true
}

IExecuteStrategy

An IExecuteStrategy provides the logic that creates a model instance from the configuration in a provided IBuildConfiguration.

IConstructorResolver

An IConstructorResolver assists in resolving the constructor to execute when creating instances of a model.

The DefaultConstructorResolver provides the logic for selecting the constructor with the least number of parameters unless constructor arguments have been supplied. In that case it selects a constructor that matches the argument list.

ICreationRule

An ICreationRule provides a simple implementation for returning a model value that bypasses the complexity of creating values using an IValueGenerator or an ITypeCreator. Most often they are used when creating a custom IBuildConfiguration.

For example:

public class TestModule : IConfigurationModule
{
    /// <inheritdoc />
    public void Configure(IBuildConfiguration configuration)
    {
        if (configuration == null)
        {
            throw new ArgumentNullException(nameof(configuration));
        }

        configuration.AddCreationRule(typeof(bool), "^IsActive$", true, 100); // Make all boolean IsActive properties true across all types
        configuration.AddCreationRule<Account>(x => x.IsReseller, false, 100);
        configuration.AddCreationRule<MyCustomCreationRule>();
    }
}

Implementing a custom IValueGenerator or ITypeCreator is the preferred method if generating values is more complex than assigning simple values.

IExecuteOrderRule

Generating random or pseudo-random data for a model dynamically is never going to be perfect. We can however provide better data when some context is available. An IExecuteOrderRule helps here by defining the order in which a property or parameter value is generated when the model is being created. This means that creating one value may then be dependent on another by controlling the order in which they are created.

For example, if a Person type exposes an Email, FirstName and LastName properties then the email value should include the first and last names. The DefaultConfigurationModule supports this by defining that the execute order (descending integer priority) for this scenario is FirstName, LastName, Domain, Email and then other string properties or parameters.

configuration.AddExecuteOrderRule(NameExpression.FirstName, 9580);
configuration.AddExecuteOrderRule(NameExpression.LastName, 9560);
configuration.AddExecuteOrderRule(NameExpression.Domain, 9550);
configuration.AddExecuteOrderRule(NameExpression.Email, 9540);

configuration.AddExecuteOrderRule(x => x.PropertyType == typeof(string), 2000);
configuration.AddExecuteOrderRule(x => x.PropertyType.IsClass, 1000);

Another example of ordering priority is that the default configuration defines the enum properties will be assigned before other property types because they tend to be reference data that might define how other properties are assigned.

configuration.AddExecuteOrderRule(x => x.PropertyType.IsEnum, 4000);
configuration.AddExecuteOrderRule(x => x.PropertyType.IsValueType, 3000);
configuration.AddExecuteOrderRule(x => x.PropertyType == typeof(string), 2000);
configuration.AddExecuteOrderRule(x => x.PropertyType.IsClass, 1000);

IIgnoreRule

An IIgnoreRule identifies a property that should not have a value generated for it.

Ignore rules can be added to a IConfigurationModule so that the configuration is reusable.

public class TestModule : IConfigurationModule
{
    /// <inheritdoc />
    public void Configure(IBuildConfiguration configuration)
    {
        if (configuration == null)
        {
            throw new ArgumentNullException(nameof(configuration));
        }

        configuration.AddIgnoreRule<Request>(x => x.Data);
    }
}

If a specific scenario needs to ignore a property then this can be inlined in the create call.

var model = Model
    .Ignoring<PersonalDetails>(x => x.Age)
    .Create<CreateAccountRequest>();

IPostBuildAction

An IPostBuildAction is like the Set and SetEach extension methods. It provides the opportunity to tweak an instance after it has been created or populated and are evaluated in descending priority order.

IPropertyResolver

An IPropertyResolver is assists in resolving the properties on a type when populating instances of a model.

The DefaultPropertyResolver provides the logic for identifying the properties on a type that can be populated and using IExecuteOrderRule values to determine the priority order in which they should be populated. It also supports identifying whether a property should be populated even though it can be. The typcial scenario here is that a property that returns a value provided in a constructor parameter should not be overwritten with a new value.

ITypeCreator

An ITypeCreator creates instances of class or struct types. There are several type creators that ModelBuilder provides out of the box. These are:

  • StructTypeCreator
  • EnumerableTypeCreator
  • ArrayTypeCreator
  • DefaultTypeCreator

StructTypeCreator provides the logic for creating struct values that either do not have a constructor defined or define a constructor with parameters.

ArrayTypeCreate will create instances of Array types and populates the instance with data.

EnumerableTypeCreate will create instances of most types derived from IEnumerable<T> and populates the instance with data.

DefaultTypeCreator will create a new instance of any type using Activator.CreateInstance and supports the optional provision of constructor arguments. Any parameters required by the constructor will also be created.

TypeMappingRule

A TypeMappingRule can identify a mapping between types when one needs to be created. For example, a Stream property on a type can not be created as it is an abstract type. A type mapping can identify the type of Stream to create however.

This can be defined in a configuration module.

public class TestModule : IConfigurationModule
{
    /// <inheritdoc />
    public void Configure(IBuildConfiguration configuration)
    {
        if (configuration == null)
        {
            throw new ArgumentNullException(nameof(configuration));
        }

        configuration.AddTypeMappingRule<Stream, MemoryStream>();
    }
}

A type mapping could also be inlined.

var model = Model.Mapping<Stream, MemoryStream>().Create<RequestPayload>();

ITypeResolver

An ITypeResolver identifies a type to create.

The DefaultTypeResolver provides some automatic detection of types to create when the type requested is an interfaces or abstract type. This is helpful for testing code that uses interfaces and abstract classes for models and their properties/parameters. It reduces the number of times that a manual type mapping will be required however results may be non-deterministic.

The DefaultTypeResolver will scan all the types in the assembly that defines the requested type. It will try to create an object using the first matching type it identifies.

For example, you may have a class that returns an interface property that is not already supported by a ITypeCreator or TypeMappingRule.

public interface IManifest
{
    bool IsCurrent();

    DateTimeOffset Sent { get; set; }

    DateTimeOffset ExpectedAt { get; set; }
}

public class Manifest : IManifest
{
    public bool IsCurrent()
    {
        var now = DateTimeOffset.UtcNow;

        return (Sent < now && ExpectedAt > now);
    }

    public DateTimeOffset Sent { get; set; }

    public DateTimeOffset ExpectedAt { get; set; }
}

public class ShippingContainer
{
    public IManifest Manifest { get; set; }
}

The ShippingContainer model here can then be created without an specific type mapping as DefaultTypeResolve will automatically identify that a Manifest instance should be created when creating an IManifest.

var model = Model.Create<ShippingContainer>();

IValueGenerator

An IValueGenerator typically creates values that do not require constructor parameters. There are many value generators that come out of the box. These are:

  • AddressValueGenerator
  • AgeValueGenerator
  • BooleanValueGenerator
  • CityValueGenerator
  • CompanyValueGenerator
  • CountryValueGenerator
  • CountValueGenerator
  • DateOfBirthValueGenerator
  • DateTimeValueGenerator
  • DomainNameValueGenerator
  • EmailValueGenerator
  • EnumValueGenerator
  • FirstNameValueGenerator
  • GenderValueGenerator
  • GuidValueGenerator
  • IPAddressValueGenerator
  • LastNameValueGenerator
  • MailinatorEmailValueGenerator (NOTE: This generator is not included in DefaultBuildConfiguration by default)
  • MiddleNameValueGenerator
  • NumericValueGenerator
  • PhoneValueGenerator
  • PostCodeValueGenerator
  • StateValueGenerator
  • StringValueGenerator
  • SuburbValueGenerator
  • TimeZoneInfoValueGenerator
  • TimeZoneValueGenerator
  • UriValueGenerator

Value generators create values either using random data (string, guid, numbers etc) or use embedded resource data for entity type properties (names, addresses etc) to provide pseudo-random values that are appropriate for their purpose.

Some generators also use relative data on the object being created to determine more appropriate values to generate. For example, a Gender parameter or property will restrict the value created for FirstName which will then be used to populate EmailAddress.

Supporters

This project is supported by JetBrains

Upgrading to 6.0.0

The package had some large design changes that introduce breaking changes to the API if you were customising the model creation process. The following changes have been made.

  • All ValueGenerator types have been moved into a ValueGenerators namespace.
  • All TypeCreator types have been moved into a TypeCreators namespace.
  • CreationRule has been replaced with ExpressionCreationRule, ParameterPredicateCreationRule, PropertyPredicateCreationRule, TypePredicateCreationRule and RegexCreationRule which are in the CreationRules namespace.
  • The combination of build strategy, compiler and configuration have been replaced with just IBuildConfiguration.
  • IBuildConfiguration is now mutable. A new IBuildConfiguration instance is created for each call to a static method on the Model class and that configuration instance is used for that entire creation process. Any mutations to the build configuration will apply until the entire build tree has completed.
  • ICompilerModule has been renamed to IConfigurationModule and now configures IBuildConfiguration.
  • The IgnoreRule class has been replaced with the IIgnoreRule interface in the IgnoreRules namespace. The ignore rules provided include ExpressionIgnoreRule, PredicateIgnoreRule and RegexIgnoreRule. The logic for processing rule matches moves from DefaultPropertyResolver to the rule itself.
  • Renamed IValueGenerator.IsSupported to IsMatch
  • Renamed IPostBuildAction.IsSupported to IsMatch
  • Added IBuildConfiguration.TypeResolver
  • Added IBuildConfiguration parameter to ITypeCreator.CanCreate
  • ICreationRule, ITypeCreator, IValueGenerator, IPostBuildAction and IBuildLog now have overloads for ParameterInfo, PropertyInfo and Type rather than (Type, string)
  • IExecuteOrderRule and IIgnoreRule now have overloads for PropertyInfo rather than (Type, string)
  • RelativeValueGenerator no longer supports targeting a specific property as defined by the constructor. Getting a value from another property is now only available with an explicit call to GetPropertyValue.
  • Added caching support to DefaultPropertyResolver and DefaultConstructorResolver
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on ModelBuilder:

Repository Stars
roryprimrose/Neovolve.Logging.Xunit
A Microsoft.Extensions.Logging provider for xUnit test output
Version Downloads Last updated
8.6.0 4,763 3/17/2024
8.5.0 148 2/27/2024
8.4.0 12,081 8/9/2022
8.4.0-beta0001 123 8/9/2022
8.3.0 1,430 6/22/2022
8.2.0 15,625 8/29/2021
8.1.1-beta0001 223 8/29/2021
8.1.0 2,374 5/7/2021
8.1.0-beta0001 273 5/7/2021
8.0.0 657 4/17/2021
8.0.0-beta0087 206 4/17/2021
8.0.0-beta0057 223 4/17/2021
8.0.0-beta0028 243 4/17/2021
7.4.2-beta0001 244 4/17/2021
7.4.1 1,394 3/30/2021
7.4.1-beta0001 227 3/30/2021
7.4.0 1,047 1/2/2021
7.3.1-beta0001 286 1/2/2021
7.3.0 693 12/21/2020
7.2.1-beta0012 327 12/21/2020
7.2.1-beta0007 317 12/21/2020
7.2.1-beta0003 334 12/21/2020
7.2.1-beta0001 290 11/23/2020
7.2.0 656 11/21/2020
7.1.1-beta0035 404 11/21/2020
7.1.1-beta0017 357 11/14/2020
7.1.1-beta0016 276 11/13/2020
7.1.1-beta0015 280 11/13/2020
7.1.1-beta0014 292 11/12/2020
7.1.1-beta0013 273 11/12/2020
7.1.1-beta0012 334 10/12/2020
7.1.1-beta0011 388 9/27/2020
7.1.1-beta0009 316 9/9/2020
7.1.1-beta0008 339 9/2/2020
7.1.1-beta0007 308 8/21/2020
7.1.1-beta0006 326 8/12/2020
7.1.1-beta0005 356 8/8/2020
7.1.1-beta0003 373 8/8/2020
7.1.1-beta0001 382 8/4/2020
7.1.0 1,196 8/4/2020
7.1.0-beta0001 334 8/4/2020
7.0.0 789 6/16/2020
6.1.0-beta0012 327 6/7/2020
6.1.0-beta0011 349 6/6/2020
6.1.0-beta0010 346 5/31/2020
6.1.0-beta0009 354 5/31/2020
6.1.0-beta0004 426 5/30/2020
6.0.2-beta0006 351 5/28/2020
6.0.2-beta0003 373 5/27/2020
6.0.2-beta0001 391 5/25/2020
6.0.1 962 5/13/2020
6.0.1-beta0001 352 5/13/2020
6.0.0 571 5/11/2020
6.0.0-beta0014 362 5/11/2020
6.0.0-beta0013 361 5/10/2020
6.0.0-beta0012 374 5/9/2020
6.0.0-beta0011 350 5/6/2020
6.0.0-beta0010 351 5/5/2020
6.0.0-beta0009 369 5/2/2020
6.0.0-beta0008 339 4/25/2020
6.0.0-beta0007 363 4/18/2020
6.0.0-beta0006 453 4/5/2020
6.0.0-beta0005 386 4/3/2020
6.0.0-beta0004 398 3/28/2020
6.0.0-beta0003 416 3/28/2020
6.0.0-beta0002 387 3/16/2020
6.0.0-beta0001 437 1/14/2020
5.0.0 2,464 4/5/2019
5.0.0-pullrequest0064-0025 608 4/1/2019
5.0.0-beta0004 596 4/2/2019
4.1.1 1,540 1/23/2018
4.1.0 1,265 1/20/2018
4.0.0 1,234 1/19/2018
4.0.0-beta0059 377 8/8/2020
4.0.0-beta0057 384 8/4/2020
4.0.0-beta0054 353 6/16/2020
4.0.0-beta0053 373 6/16/2020
4.0.0-beta0052 358 6/15/2020
4.0.0-beta0051 365 6/14/2020
4.0.0-beta0050 403 6/13/2020
4.0.0-beta0049 336 6/11/2020
3.2.0 1,346 8/11/2017
3.1.1 1,272 8/11/2017
3.1.0 1,262 4/13/2017
3.0.2 1,237 3/3/2017
3.0.1 1,213 1/28/2017
3.0.0 1,191 12/9/2016
2.0.1 1,159 11/25/2016
2.0.0 1,194 11/16/2016
1.0.1 1,502 11/2/2016
1.0.0 1,223 10/19/2016
0.17.0 1,328 8/24/2016
0.16.1 1,177 8/24/2016
0.16.0 1,174 8/24/2016
0.14.0 1,172 8/19/2016
0.13.0 1,166 8/18/2016
0.12.0 1,252 6/22/2016
0.11.0 1,190 6/21/2016
0.10.0 1,195 6/20/2016
0.9.0 1,194 6/14/2016
0.8.0 1,222 5/25/2016
0.7.0 1,206 5/24/2016
0.6.0 1,184 5/20/2016
0.5.0 1,173 5/19/2016
0.4.0 1,208 5/13/2016
0.3.0 1,209 5/5/2016
0.2.0 1,530 4/28/2016
0.1.0 1,235 4/22/2016