Luuk Krijnen
Luuk Krijnen

Reputation: 1192

using Entity framework code first with an IOC container for creating entities without a default constructor

I want to build an entity framework code first datalayer that encrypts some of its properties. For that an ICryptographer should be injected when creating an instance with data loaded from the database.

Is it possible to inject ICryptographer using a IOC Container such like unity?

DAL example:

public class context : DbContext
{
    public DbSet<Credentials> Credentials {get;set;}
}

public CredentialsConfiguration()
{
    ToTable("Profiles");
    HasKey(p => p.ProfileName);
    Ignore(p => p.SecretKey);
    Ignore(p => p.AccessKey);
    Property(p => p._accessKey)
        .HasColumnName("AccessKey");
    Property(p => p._secretKey)
        .HasColumnName("SecretKey");
}

Common Entities Assembly: (with internals visible to DAL)

public class Credentials
{
    private readonly ICryptographer _cryptographer;

    public Credentials(ICryptographer cryptographer)
    {
        _cryptographer = cryptographer;
    }


    internal string _accessKey { get; set; }
    public string AccessKey
    {
        get { return _cryptographer.Decrypt(_accessKey); }
        set { _accessKey = _cryptographer.Encrypt(value); }
    }

}

Upvotes: 3

Views: 1159

Answers (1)

3dd
3dd

Reputation: 2530

This is a very contrived example but might push you into the right direction

We need a way to know when to inject a property into the entity class, as such they are marked using

public interface ICryptographerUser {
    ICryptographer Cryptographer { get; set; }
}

Where ICryptographer is defined as

public interface ICryptographer {
    string Decrypt(string value);
    string Encrypt(string value);
}

The instance that will be injected is defined as

public class Cryptographer : ICryptographer {
    public string Decrypt(string value) {
        return "Decrypted";
    }

    public string Encrypt(string value) {
        return "Encrypted";
    }
}

When adding an entity we need to make use of a Factory that is cognizant of the fact that we need to inject properties (You can make use of IOC to do this)

public static class EntityFactory {
    public static T CreateInstance<T> () {
        var entity = Activator.CreateInstance<T>();
        if (entity is ICryptographerUser) {
            //INJECT INSTANCE HERE
            (entity as ICryptographerUser).Cryptographer = new Cryptographer();
        }

        return entity;
    }      
}

Now to add and entity we can just use

var entity = EntityFactory.CreateInstance<Credentials>();
        entity.SetAccessKey("123");
        entity.SecretKey = "456";
        entity.ProfileName = "a";

        contect.Set<Credentials>().Add(entity);

When objects are queried from Context the following code will inject the objects, but this is done after their respective properties has already been set

public MyContext() {
        IObjectContextAdapter objectContextAdapter = (this as IObjectContextAdapter);
        objectContextAdapter.ObjectContext.ObjectStateManager.ObjectStateManagerChanged += ObjectStateManager_ObjectStateManagerChanged;
    }

    private void ObjectStateManager_ObjectStateManagerChanged(object sender, System.ComponentModel.CollectionChangeEventArgs e) {
        // we are only interested in entities that
        // have been added to the state manager
        if (e.Action != CollectionChangeAction.Add)
            return;

        IObjectContextAdapter objectContextAdapter = (this as IObjectContextAdapter);

        var state = objectContextAdapter.ObjectContext.ObjectStateManager.GetObjectStateEntry(e.Element).State;

        // we are only interested in entities that
        // are unchanged (that is; loaded from DB)
        if (state != EntityState.Unchanged)
            return;

        OnObjectMaterialized(e.Element);
    }

    private void OnObjectMaterialized(object e) {
        if (e is ICryptographerUser) {
            //INJECT INSTANCE HERE
            (e as ICryptographerUser).Cryptographer = new Cryptographer();
        }      
    }

As the instance will only be injected after the Entity has been materialized I needed to modify your Entity definition as follows

public class Credentials : ICryptographerUser {
    public string ProfileName { get; set; }

    internal string _secretKey { get; set; }
    internal string _accessKey { get; set; }


    public string SecretKey { get; set; }

    public string AccessKey {
        get { return _accessKey; }
        private set { _accessKey = value; }
    }

    public string AccessKeyDecrypted {
        get { return Cryptographer.Decrypt(_accessKey); }
    }

    public void SetAccessKey(string value) {
        _accessKey = Cryptographer.Encrypt(value);
    }

    public ICryptographer Cryptographer { get; set; }
}

Note that AccessKey has private set { _accessKey = value; } which allows EF to set the property when the object materializes but when you set the property value you must call public void SetAccessKey(string value) which makes use of Cryptographer to encrypt the field.

Upvotes: 2

Related Questions