Orchard ComputedField not working

I have a part which contains a Password field which I want to be encrypted in the database. I have used Orchard.Email as an inspiration to do this, but I'm running into a problem using the ComputedField.

When editing a part, the ComputedField should take care of the encryption. I wrote a handler which should set the Getter and Setter delegates for the ComputedField, but when get to the driver to update the model, these delegates are NULL, so I get a NullReferenceException. However, when I retrieve the part, the delegates are set properly.

My code:

Part
public class TaskServerPart : ContentPart<TaskServerPartRecord>
{
    private readonly ComputedField<string> _password = new ComputedField<string>();

    public ComputedField<string> PasswordField
    {
        get { return _password; }
    }

    public string Name
    {
        get { return Record.Name; }
        set { Record.Name = value; }
    }
    public string Domain
    {
        get { return Record.Domain; }
        set { Record.Domain = value; }
    }
    public string Username
    {
        get { return Record.Username; }
        set { Record.Username = value; }
    }

    public string Password
    {
        get { return _password.Value; }
        set { _password.Value = value; }
    }
} 
PartRecord
public class TaskServerPartRecord : ContentPartRecord
{
    public virtual string Name { get; set; }
    public virtual string Domain { get; set; }
    public virtual string Username { get; set; }
    public virtual string Password { get; set; }
}
Handler
public class TaskServerPartHandler : ContentHandler
{
    private readonly IEncryptionService _encryptionService;

    public TaskServerPartHandler(IRepository<TaskServerPartRecord> repository, IEncryptionService encryptionService)
    {
        _encryptionService = encryptionService;
        Logger = NullLogger.Instance;
        Filters.Add(new ActivatingFilter<TaskServerPart>("Site"));
        Filters.Add(StorageFilter.For(repository));
        OnLoaded<TaskServerPart>(LoadHandlers);
    }
    void LoadHandlers(LoadContentContext context, TaskServerPart part)
    {
        part.PasswordField.Getter(() =>
        {
            try
            {
                return String.IsNullOrWhiteSpace(part.Record.Password) ? String.Empty : Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(part.Record.Password)));
            }
            catch
            {
                Logger.Error("The task server password could not be decrypted. It might be corrupted, try to reset it.");
                return null;
            }
        });

        part.PasswordField.Setter(value => part.Record.Password = String.IsNullOrWhiteSpace(value) ? String.Empty : Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(value))));
    }
}
Driver
public class TaskServerDriver : ContentPartDriver<TaskServerPart>
{
    protected override string Prefix { get { return "TaskServer"; } }

    //GET
    protected override DriverResult Editor(
        TaskServerPart part, dynamic shapeHelper)
    {

        return ContentShape("Parts_TaskServer_Edit",
                            () => shapeHelper.EditorTemplate(
                                TemplateName: "Parts/TaskServer",
                                Model: part,
                                Prefix: Prefix))
            ;
    }
    //POST
    protected override DriverResult Editor(
        TaskServerPart part, IUpdateModel updater, dynamic shapeHelper) {
        return ContentShape("Parts_TaskServer_Edit", () => {
            var previousPassword = part.Password; // This fails, because the ComputedField has no Getter or Setter delegates
            updater.TryUpdateModel(part, Prefix, null, null);

            // restore password if the input is empty, meaning it has not been changed
            if (String.IsNullOrEmpty(part.Password)) {
                part.Password = previousPassword;
            }
            return shapeHelper.EditorTemplate(TemplateName: "Parts/TaskServer", Model: part, Prefix: Prefix);
        });
    }
}

Update

I put a breakpoint on the Handler to see when it get's called.

  1. The first time it gets called, the properties have the values of the record in the database, which makes sense. I modified one of the properties to see what would happen and track the object. (Edit: I have since found to "Make Object ID" option in the Watch window, which tells me the same thing.)
  2. At this point, the driver gets called on the item which has the property value I set in step 1, but the ComputedField's Getter and Setter are NULL, so the exception occurs and the update fails
  3. After this, the Handler get called again on yet another instance of my part, I assume as a result of a redirect after the the database should have been updated.

I added an extra property to my part (but not to the partrecord). If I set the value of this property when the breakpoint hits the Handler, the value is not there when I get to the driver, so I'm dealing with a different part object than the one for which I set the ComputedField's delegates, though the underlying record object is the same. Using "Make Object ID", I found basically the same thing: I'm dealing with three instances of TaskServerPart. The second instance, which is the one my Driver gets to deal with, is the only one for which the Handler isn't executed.

The Initialized and Activated events are being fired for the object that ends up in the driver, should I be using one of those instead of the Loaded event?

Answers


The Loaded event was not fired for the object that was sent to the driver. The Initialized event was, so I used that one to set the Getter and Setter delegates of the ComputedField.


Need Your Help

Instantiate list of objects with CDI

java-8 cdi weld

I am currently refactoring some legacy code and came across a snippet as below.