I am using a custom class to expose some custom schema in Active Directory. I am storing a binary blob, per the project requirements this data must be stored in the AD, I can not use a external store (I would if I could).
When I create the user it stores the blob fine. I also can retrieve the blob back out fine too and get all my data. The issue is if I need to update the value and I am getting errors
Small example program:
using System;
using System.DirectoryServices.AccountManagement;
namespace SandboxConsole40
{
class Program
{
static void Main(string[] args)
{
using(var context = new PrincipalContext(ContextType.Domain))
{
using (var clear = ExamplePrincipal.FindByIdentity(context, "example"))
{
if (clear != null)
clear.Delete();
}
using (var create = new ExamplePrincipal(context, "example", "Password1", false))
{
create.Save();
}
using (var set = ExamplePrincipal.FindByIdentity(context, "example"))
{
set.BlobData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; //This fails with method 2.
set.Save();
}
using (var lookup = ExamplePrincipal.FindByIdentity(context, "example"))
{
Console.WriteLine(BitConverter.ToString(lookup.BlobData));
}
using (var update = ExamplePrincipal.FindByIdentity(context, "example"))
{
update.BlobData = new byte[] { 0x12, 0x34, 0x56, 0x67 };
update.Save(); //This save fails with method 1.
}
}
Console.WriteLine("Done");
Console.ReadLine();
}
[DirectoryObjectClass("user")]
[DirectoryRdnPrefix("CN")]
class ExamplePrincipal : UserPrincipal
{
public ExamplePrincipal(PrincipalContext context) : base(context) { }
public ExamplePrincipal(PrincipalContext context, string samAccountName, string password, bool enabled)
: base(context, samAccountName, password, enabled) { }
public static new ExamplePrincipal FindByIdentity(PrincipalContext context, string identityValue)
{
return (ExamplePrincipal)FindByIdentityWithType(context, typeof(ExamplePrincipal), identityValue);
}
[DirectoryProperty("vwBlobData")]
public byte[] BlobData
{
get
{
if (ExtensionGet("vwBlobData").Length != 1)
return null;
return (byte[])ExtensionGet("vwBlobData")[0];
}
set
{
//method 1
this.ExtensionSet("vwBlobData", value );
//method 2
//this.ExtensionSet("vwBlobData", new object[] { value});
}
}
}
}
}
If I use method 1 I get the following exception on the update.Save()
operation
System.DirectoryServices.AccountManagement.PrincipalOperationException was unhandled HResult=-2146233087 Message=The specified directory service attribute or value already exists. Source=System.DirectoryServices.AccountManagement ErrorCode=-2147016691 StackTrace: //Snip InnerException: System.DirectoryServices.DirectoryServicesCOMException HResult=-2147016691 Message=The specified directory service attribute or value already exists. Source=System.DirectoryServices ErrorCode=-2147016691 ExtendedError=8321 ExtendedErrorMessage=00002081: AtrErr: DSID-030F154F, #1: 0: 00002081: DSID-030F154F, problem 1006 (ATT_OR_VALUE_EXISTS), data 0, Att 82818fec (vwBlobData) StackTrace: //Snip InnerException:
If I use method 2 I get a exception on from the this.ExtensionSet
call from the set.BlobData
call.
System.ArgumentException was unhandled HResult=-2147024809 Message=Collections whose elements are another collection cannot be set by ExtensionClasses. Source=System.DirectoryServices.AccountManagement StackTrace: //Snip InnerException:
In summary: I can set the value if it is currently not set, but if I want to overwrite a existing value I am getting a error.
I found a work around, by setting the value to null first it no-longer throws the exception.
using (var update = ExamplePrincipal.FindByIdentity(context, "example"))
{
update.BlobData = null;
update.Save();
update.BlobData = new byte[] { 0x12, 0x34, 0x56, 0x67 };
update.Save(); //No longer fails with method 1.
}
I am leaving the question open for a bit to see if anyone else can answer if there is a "proper" way to do this.
Found a 2nd work around that does not require forcing a save.
[DirectoryProperty("vwBlobData")]
public byte[] BlobData
{
get
{
if (ExtensionGet("vwBlobData").Length != 1)
return null;
return (byte[])ExtensionGet("vwBlobData")[0];
}
set
{
((DirectoryEntry)this.GetUnderlyingObject())
.Properties["vwBlobData"].Value = value;
}
}
By casting directly to the underlying object you can set the value directly. I checked using ILSpy and disposing the AccountManagement wrapper disposes the underlying object, so no Dispose()
is required for the GetUnderlyingObject()
call.
BEST SOLUTION
I found out the 2nd work around required the object to be persisted to work, so I made a best of both worlds approach. This works when you have not yet persisted the object, when the object is null, and when the object has a value already.
[DirectoryProperty("vwBlobData")]
public byte[] BlobData
{
get
{
if (ExtensionGet("vwBlobData").Length != 1)
return null;
return (byte[])ExtensionGet("vwBlobData")[0];
}
set
{
if(ExtensionGet("vwBlobData").Length == 0)
this.ExtensionSet("vwBlobData", data);
else
((DirectoryEntry)this.GetUnderlyingObject())
.Properties["vwBlobData"].Value = data;
}
}
来源:https://stackoverflow.com/questions/14566887/how-to-set-a-binary-attribute-when-using-a-accountmanagement-extension-class