如何在使用帐户管理扩展类时设置二进制属性

本文关键字:扩展 设置 属性 二进制 管理 | 更新日期: 2023-09-27 17:56:16

我正在使用自定义类在Active Directory中公开一些自定义架构。我正在存储一个二进制 blob,根据项目要求,这些数据必须存储在 AD 中,我不能使用外部存储(如果可以的话,我会)。

当我创建用户时,它会很好地存储 blob。我也可以很好地检索斑点并获取我的所有数据。问题是如果我需要更新值并且收到错误

小示例程序:

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});
                }
            }
        }
    }
}

如果我使用方法 1,我会在update.Save()操作中出现以下异常

System.DirectoryServices.AccountManagement.PrincipalOperationException is unhandle  HResult=-2146233087  消息 = 指定的目录服务属性或值已存在。  源=系统.目录服务.帐户管理  错误代码=-2147016691  堆栈跟踪:       剪 断  InnerException: System.DirectoryServices.DirectoryServicesCOMException       HResult=-2147016691       消息 = 指定的目录服务属性或值已存在。       源=系统目录服务       错误代码=-2147016691       扩展错误 = 8321       扩展错误消息=00002081: 错误: DSID-030F154F, #1:    0:00002081:DSID-030F154F,问题 1006 (ATT_OR_VALUE_EXISTS),数据 0,Att 82818fec (vwBlobData)       堆栈跟踪:            剪 断       内部异常:

如果我使用方法 2,我会从set.BlobData调用的this.ExtensionSet调用中得到异常。

System.ArgumentException 未处理  HResult=-2147024809  Message=其元素是另一个集合的集合不能由扩展类设置。  源=系统.目录服务.帐户管理  堆栈跟踪:      剪 断  内部异常:

总之:如果当前未设置值,我可以设置该值,但是如果我想覆盖现有值,则会出现错误。

如何在使用帐户管理扩展类时设置二进制属性

我找到了一个解决方法,通过将值设置为null,它不再抛出异常。

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.
}

我将这个问题留置一会儿,看看是否有其他人可以回答是否有"适当"的方法可以做到这一点。


找到了不需要强制保存的第二个解决方法。

[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;
    }
}

通过直接强制转换为基础对象,可以直接设置值。我使用 ILSpy 进行了检查,并释放了帐户管理包装器会释放基础对象,因此GetUnderlyingObject()调用不需要Dispose()


最佳解决方案

我发现第二个解决方法需要持久化对象才能工作,所以我做了一个两全其美的方法。当您尚未保留对象、对象为 null 以及对象已具有值时,这有效。

[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;
    }
}