Lightmap for .Net

I've been working on a new open source project called Lightmap that provides developers with a way to model their databases, in a database agnostic manor. This is done by creating migration objects that define what the table layout (along with other database objects) would look like. Lightmap supports several different ways of doing this - the following example shows just one. We'll use a couple different C# model classes to represent tables, related to each other.

Migrations

We'll use the following two classes as our table definitions.

public class User
{
    public int UserId { get; set; }
    public string Email { get; set; }
    public int LockoutEnabled { get; set; }
    public string PasswordHash { get; set; }
    public string UserName { get; set; }
}

public class UserLogins
{
    public string LoginProvider { get; set; }
    public int ProviderId { get; set; }
    public string ProviderDisplayName { get; set; }
    public string UserId { get; set; }
}

Next we'll create our database model with an initial migration object.

public class InitialMigration : IMigration
{
    public InitialMigration(IDataModel dataModel) => this.DataModel = dataModel;

    public IDataModel DataModel { get; }

    public void Apply()
    {
        this.DataModel.AddTable<User>()
            .AlterColumn(table => table.UserId).IsPrimaryKey();

        this.DataModel.AddTable<UserLogin>()
            .AlterColumn(table => table.ProviderId).IsPrimaryKey()
            .WithForeignKey<User>(
                (userTable, loginTable) => userTable.UserId == loginTable.UserId);
    }

    public void Revert() { } // Revert support not implemented yet.
}

The API is fairly straight forward, and has hook points for database provider specific implementations to expose additional constraints and database features via extension methods.

Wiring it up

To get it wired up in your application, you need to create an instance of the IDataModel implementation, IDatabaseManager implementation for the database of your choice, along with an instance of an IDatabaseMigrator that works with your database.

Currently only Sqlite has working implementations. The Lightmap API and base feature set is still being developed; until it's stabilized, I won't be adding other databases myself. Sql Server and MySql are the next two I plan on supporting as soon as the API churn quiets down.

The wire up for Lightmap will be simplified in the future as well, for apps that use ASP.Net Core. For now, you would wire it all up manually like this:

IDataModel dataModel = new DataModel();
IDatabaseManager databaseManager = new SqliteDatabaseManager("MyDb", "Data Source=MyDb.sqlite");
IMigration migration = new InitialMigration(dataModel);
IDatabaseMigrator migrator = new SqliteMigrator(migration);
migrator.Apply(databaseManager);

Ideally, all you would have to do is use the ASP.Net Core middleware to wire this up. I'm thinking of something along the lines of:

appBuilder.UseLightmap(config => config.WithSqlite());

There will be additional things on the configuration callback instance that you can use to setup various options, like default schema names.

Wrapping up

The project is still under active development and isn't ready for a release yet. If anyone would like to take it for a spin, they can clone it from GitHub, compile and add it to their projects. It currently only runs on NetStandard 1.4. The target framework will probably change to NetStandard 2.0 when that ships and I'll also work towards supporting Net46 as well so that the library can run on Full Framework, Mono and Core.

I'm not to far out from having an initial beta shipped on NuGet.

Localization Support for ValidatableBase

Localization Support

The included Validation Attributes now all support localization, allowing your failure messages to be presented to the user in the native language for global applications. In order to provide this in a cross-platform manor (iOS, Android and Windows), localization support must be handled with a couple levels of indirection.

Without localization, you would manually specify the FailureMessage like this:

[ValidateObjectHasValue(
    FailureMessage = "Email address must not be blank.",
    ValidationMessageType = typeof(ValidationErrorMessage))]
public string Email
{
    get
    {
        return this.email;
    }

    set
    {
        this.email = value;
        this.OnPropertyChanged("Email");
    }
}

Localization is done by specifying a localization key on your validation rules and omitting the FailureMessage property assignment like the following.

[ValidateObjectHasValue(
    LocalizationKey = "User-Email-Validation-Failure-Cannot-be-blank",
    ValidationMessageType = typeof(ValidationErrorMessage))]
public string Email
{
    get
    {
        return this.email;
    }

    set
    {
        this.email = value;
        this.OnPropertyChanged("Email");
    }
}

When validation is performed, and the Email property is null, a localized failure message will be assigned to the FailureMessage property.

In order to facilitate this, the validation rules fetch an implementation of IValidationLocalizationService from a new Factory, the ValidationLocalizationFactory. The API ships with a Windows App Store supported localization service in the Universal App Shared project.

The factory must provide this service to the validation rules. In order to do that, you must initialize the factory. This is demonstrated in the sample projects as well, in the App.Xaml.cs file.

public App()
{
    this.InitializeComponent();
    this.Suspending += this.OnSuspending;

    ValidationLocalizationFactory.Initialize<ValidationLocalizationService>();
}

The factory is initialized with our Windows App Store version of the localization service included in the sample project. The validation rules will use this service any time that it needs to fetch a localized string.

Another feature of the localization support are fallback messages. While you are developing the application, you often don't have all of the localization done. You can assign a value to both FailureMessage and LocalizationKey. The validation rule will try to fetch a localized string and if none exist, continue to use the specified FailureMessage. If it finds a localized string, it replaces the fallback value with the localized string for you.

The following demonstrates this:

[ValidateObjectHasValue(
    FailureMessage = "Email address can not be blank.", /* fallback if localization does not exist for the key yet */
    LocalizationKey = "User-Email-Validation-Failure-Cannot-be-blank",
    ValidationMessageType = typeof(ValidationErrorMessage))]
public string Email
{
    get
    {
        return this.email;
    }

    set
    {
        this.email = value;
        this.OnPropertyChanged("Email");
    }
}

You can create a custom implementation of IValidationLocalizationService for iOS and Android apps as well. Just initialize the ValidationLocalizationFactory with the Service type for each platform. This allows you to use localized text across all platforms that your app can run on, without making any changes to your models or building custom validation rules per-platform.

Check out the latest build over on the GitHub Repository.

Building an Exception Factory

If you write a lot of code that takes dependencies in a method or constructor, you'll find that you write a lot of null checking like this:

public DefaultGame(ILoggingService loggingService, IWorldService worldService)
{
    if (loggingService == null)
    {
        throw new ArgumentNullException("loggingService", "Logging Service must not be null!");
    }
    else if (worldService == null)
    {
        throw new ArgumentNullException("worldService", "World Service must not be null!");
    }

    this.loggingService = loggingService;
    this.worldService = worldService;
}

Depending on the number of dependencies, this tends to bloat methods with more safety checks than actual code. I wanted to resolve this, and did so with a factory.

The factory needed to satisfy a few things.

  • Instance exceptions
  • Support stashing custom data in to the exceptions
  • Only throw if a given condition was met
  • Allow a callback if the conditions are not met (meaning no exception will be thrown)

Turns out this was pretty easy to implement. The end product, in its simplest form looks like this:

public DefaultGame(ILoggingService loggingService, IWorldService worldService)
{
    ExceptionFactory
        .ThrowExceptionIf< ArgumentNullException>(loggingService == null);
    ExceptionFactory
        .ThrowExceptionIf< ArgumentNullException>(worldService == null);

    this.loggingService = loggingService;
    this.worldService = worldService;
}

You can also invoke a callback, and provide a custom message.

public DefaultGame(ILoggingService loggingService, IWorldService worldService)
{
    ExceptionFactory
        .ThrowExceptionIf< ArgumentNullException>(loggingService == null, "Logging Service must not be null!")
        .ElseDo(() => this.loggingService = loggingService);

    ExceptionFactory
        .ThrowExceptionIf< ArgumentNullException>(worldService == null, "World Service must not be null!")
        .ElseDo(() => this.worldService = worldService);
}

If you need to do something more complex with your conditional check, you use a Func< bool>

ExceptionFactory
    .ThrowExceptionIf< Exception>(
        () =>
        {
            this.Worlds = worldService.GetAllWorlds();
            return this.Worlds.Any();
        },
        "An empty world can not be used!"));

It also supports providing a custom exception factory method for use. Since some exceptions have additional parameters in the constructor, this can be really useful.

ExceptionFactory
    .ThrowExceptionIf< ArgumentNullException>(
        worldService == null,
        () => new ArgumentNullException("worldService", "World Service must not be null!"));

We can also pass custom data in to the exception if we want.

ExceptionFactory
    .ThrowExceptionIf< ArgumentNullException>(
        worldService == null,
        () => new ArgumentNullException("worldService", "World Service must not be null!"),
        new KeyValuePair< string, string>("Member", "DefaultGame"),
        new KeyValuePair< string, string>("MemberType", "Constructor"));

So how does the insides work? Let's take a look

The Factory

There are a total of four factory methods.

  • ThrowExceptionIf< TException>(Func< bool> predicate, string message = null, params KeyValuePair< string, string>[] data);
  • ThrowExceptionIf< TException>(Func< bool> predicate, Func< TException> exception, params KeyValuePair< string, string>[] data)
  • ThrowExceptionIf< TException>(bool condition, string message = null, params KeyValuePair< string, string>[] data)
  • ThrowExceptionIf< TException>(bool condition, Func< TException> exception, params KeyValuePair< string, string>[] data)

and one last method for adding data to the exception

AddExceptionData(Exception exception, params KeyValuePair< string, string>[] data)

Since we can build this in an overloaded fashion, we will just build the most complex method out, then let the rest of them piggy back on top of it.

public static ExceptionFactoryResult ThrowExceptionIf< TException>(bool condition, Func< TException> exception, params KeyValuePair< string, string>[] data) where TException : Exception, new()
{
    if (condition)
    {
        return new ExceptionFactoryResult();
    }

    TException exceptionToThrow = exception();
    AddExceptionData(exceptionToThrow, data);

    throw exceptionToThrow;
}

This method is really straight forward, it requires a bool value to indicate if this exception must be instanced and thrown or not. If the condition is false, then we go ahead and invoke the Func< TException> delegate. This returns the exception that we are to throw.

Next we take the optional exception data parameter and pass it to an AddExceptionData method. That method will iterate over the param data and add it to the exception. We then finally throw the exception. Really straight forward.

The AddExceptionData method looks like this:

public static void AddExceptionData(Exception exception, params KeyValuePair< string, string>[] data)
{
    foreach (var exceptionData in data)
    {
        exception.Data.Add(exceptionData.Key, exceptionData.Value);
    }
}

Now we can build out our overloads. The second most complex one is really easy to write. We just pass a delegate in that instances a new exception using the Activator class.

public static ExceptionFactoryResult ThrowExceptionIf< TException>(bool condition, string message = null, params KeyValuePair< string, string>[] data) where TException : Exception, new()
{
    return ThrowExceptionIf< TException>(
        condition,
        () => (TException)Activator.CreateInstance(typeof(TException), message),
        data);
}

Next, we will provide support for delegate predicates.

public static ExceptionFactoryResult ThrowExceptionIf< TException>(Func< bool> predicate, Func< TException> exception, params KeyValuePair< string, string>[] data) where TException : Exception, new()
{
    return ThrowExceptionIf< TException>(predicate(), exception, character, data);
}

public static ExceptionFactoryResult ThrowExceptionIf< TException>(Func< bool> predicate, string message = null, params KeyValuePair< string, string>[] data) where TException : Exception, new()
{
    return ThrowExceptionIf< TException>(predicate(), message, character, data);
}

The last thing we need to do is support the method callbacks. This is done using the ExceptionFactoryResult class that all of our factory methods return. This class has nothing more than a simple method that takes an Action and invokes it.

public class ExceptionFactoryResult
{
    public void ElseDo(Action callback)
    {
        callback();
    }
}

That's all there is to it!

ValidatableBase 2.0

A couple of weeks ago I released verison 2.0 of ValidatableBase on GitHub. The newest build provides Data Annotation styled attribute validation for Universal Windows applications, iOS, Android and any other platform that .NET Portable Class Libraries can run on.

Release features

  • New Data Annotation styled attribute validation system has been added. The attributes implement IValidationRule and provide a simple means of writing custom validation rules if needed.
  • Registration of properties is no longer needed.
  • The library is now in a Portable Class Library, usable across all platforms that PCL's can run on.
    • As a side-effect of this, the validation messages collection is no longer directly exposed. The underlying implementation is Dictionary< string, IEnumerable< IValidationMessage>> which requires conversion if you need to bind to the validation results. The example app includes an extension method that can be used to perform the conversion, for view models that want to expose validation to the view.
  • ValidatableBase now has a ValidationChanged event that you can subscribe to. Any time validation is changed, either by adding/removing messages or running validation, your view models can be notified.

Validation Rule examples

ValidatableBase supports a very flexible attribute based validation system that can be used on any of your models, regardless of platform.

To demonstrate, let's create a very basic model with zero validation.

namespace SampleUniversalApp.Models
{
    using System.ComponentModel;
    using System.Linq;
    using System.Reflection;
    using Sullinger.ValidatableBase.Models;
    using Sullinger.ValidatableBase.Models.ValidationRules;

    public class User : INotifyPropertyChanged
    {
        private string email = string.Empty;

        private string password = string.Empty;

        public event PropertyChangedEventHandler PropertyChanged;

        public string Email
        {
            get
            {
                return this.email;
            }

            set
            {
                this.email = value;
                this.OnPropertyChanged("Email");
            }
        }

        public string Password
        {
            get
            {
                return this.password;
            }

            set
            {
                this.password = value;
                this.OnPropertyChanged("Password");
            }
        }

        protected virtual void OnPropertyChanged(string propertyName = "")
        {
            var handler = this.PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

This model is really straight-forward and shouldn't need any explaining. It implements INotifyPropertyChanged and has two properties.

Validation

Now that we have a model, we want to validate it. We will perform validation on both the Email and the Password properties. We want to make sure neither of them are blank.

The first thing we have to do is inherit from a concrete Type implementing IValidatable. The ValidatableBase API ships with a base class called ValidatableBase (surprise, surprise) that you can use.

Update your class definition to look like the following:

public class User : ValidatableBase, INotifyPropertyChanged

Since the ValidatableBase API is cross-platform and lives within a Portable Class Library, the ValidatableBase class does not implement INotifyPropertyChanged. If you are targeting a platform that supports this (such as WPF) then you can build a secondary base-class on top of ValidatableBase, or build your own implementation of IValidatable.

The ValidatableBase API performs validation mostly through attributes. To make sure neither property is left blank, we will decorate each property with a ValidateObjectHasValue attribute.

    [ValidateObjectHasValue(
        FailureMessage = "E-Mail can not be left blank.", 
        ValidationMessageType = typeof(ValidationErrorMessage))]
    public string Email
    {
        get
        {
            return this.email;
        }

        set
        {
            this.email = value;
            this.OnPropertyChanged("Email");
        }
    }

    [ValidateObjectHasValue(
        FailureMessage = "Password can not be left blank.",
        ValidationMessageType = typeof(ValidationErrorMessage))]
    public string Password
    {
        get
        {
            return this.password;
        }

        set
        {
            this.password = value;
            this.OnPropertyChanged("Password");
        }
    }

Performing the validation

The next step is to obviously perform the validation. To do that, we will create a view model that implements ICommand, exposing an Execute method.

public class MainPageViewModel : ICommand, INotifyPropertyChanged
{
    public MainPageViewModel()
    {
        this.AppUser = new User();
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public event EventHandler CanExecuteChanged;
    public User AppUser { get; private set; }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        // Perform validation on the user's built in validation.
        this.AppUser.ValidateAll();

        // Do stuff.
        return;
    }

We have now validated our model. You can check if the model has any validation messages (indicating failure) by using the HasValidationMessages< IValidationMessage>() method.

if (this.AppUser.HasValidationMessages< ValidationErrorMessage>())
{
    // Failed, so abort.
    return;
}

Adding additional validation rules.

Since the models validation is performed by the attributes, we can come back at a later date and add or replace validation on the properties without having to re-write a large amount of logic code. To demonstrate, we will replace the Password properties validation. Instead of checking if the property is empty, we will check if the password entered meets a required password range of 6 to 20 characters.

    [ValidateStringIsGreaterThan(GreaterThanValue = 6, 
        FailureMessage = "Password must be greater than 6 characters.", 
        ValidationMessageType = typeof(ValidationErrorMessage))]
    [ValidateStringIsLessThan(LessThanValue = 20, 
        FailureMessage = "Password must be less than 20 characters.", 
        ValidationMessageType = typeof(ValidationErrorMessage))]
    public string Password
    {
        get
        {
            return this.password;
        }

        set
        {
            this.password = value;
            this.OnPropertyChanged("Password");
        }
    }

Now when we run the this.AppUser.ValidateAll() method, the property will ensure it has a password between the range of 6 characters and 20. If not, a ValidationErrorMessage is created.

Mud Designer's first real sprint

Now that I have Mud Designer moved over to Visual Studio Online (VSO) I can start really making use of the Agile platform that VSO is built on top of. Going forward, the project will adopt the Agile methodology, with developing taking place during sprints.

The first sprint started today with three User Stories being brought in to the sprint.

  • Text File Data Storage Service
  • Character Security Permissions
  • Command Security

Text File Data Storage Service

Once this is completed, I will have the first of many data storage services created. The intent is to create a simple data store to use while I build the rest of the engine. I have other Stories for creating SQL and Cloud based services, but those are more complex. While the engine is being developed, I want to just use a simple local data store.

Character Security Permissions

This story contains the meat of the engines security framework. When completed, Permissions can be created and assigned to Roles. There will be a framework constructed for objects that need specific security permissions, to register for them.

Command Security

Pigging backing on top of the Character Security Permissions story, this will allow Commands to register for permissions they require. The engine will ensure that users have the required permissions within their roles before letting them execute the command.

Wrapping up

Since I am the only developer, each sprint is scheduled for 4 weeks, so it will be a bit before this sprint is completed. If the user stories are finished early, I will pull more stories in and continue working until the end of the sprint.

As each story is completed, I plan on writing a post on what work was done and what the features are. I would also like to do a code analysis and report the quality of the code for each story.