Create an instance of DirectoryEntry for use in te

2020-06-17 14:27发布

问题:

I'm attempting to create an instance of a DirectoryEntry so that I can use this to test some code that will be passed a DirectoryEntry. However, despite a lot of trying I can't find a way to instantiate a DE and initialize it's PropertyCollection.

I have the following code which was taken and modified from another answer on SO that was doing the same process but for the SearchResult object. It seems that the Add method has been completely disabled and I can't find a way to call a constructor on PropertyCollection to pass in some properties.

using System.Collections;
using System.DirectoryServices;
using System.Globalization;
using System.Reflection;
using System.Runtime.Serialization;

public static class DirectoryEntryFactory
{
    const BindingFlags nonPublicInstance = BindingFlags.NonPublic | BindingFlags.Instance;
    const BindingFlags publicInstance = BindingFlags.Public | BindingFlags.Instance;

    public static DirectoryEntry Construct<T>(T anonInstance)
    {
        var e = GetUninitializedObject<DirectoryEntry>();

        SetPropertiesField(e);

        var dictionary = (IDictionary)e.Properties;
        var type = typeof(T);
        var propertyInfos = type.GetProperties(publicInstance);

        foreach (var propertyInfo in propertyInfos)
        {
            var value = propertyInfo.GetValue(anonInstance, null);
            var valueCollection = GetUninitializedObject<PropertyValueCollection>();
            var innerList = GetInnerList(valueCollection);
            innerList.Add(value);

            var lowerKey = propertyInfo.Name.ToLower(CultureInfo.InvariantCulture);

            // These both throw exceptions saying you can't add to a PropertyCollection
            //(typeof(PropertyCollection)).InvokeMember("System.Collections.IDictionary.Add", nonPublicInstance | BindingFlags.InvokeMethod, null, dictionary, new object[] { propertyInfo.Name, value });
            //dictionary.Add(lowerKey, propertyCollection);
        }

        return e;
    }

    private static ArrayList GetInnerList(object propertyCollection)
    {
        var propertyInfo = typeof(PropertyValueCollection).GetProperty("InnerList", nonPublicInstance);
        return (ArrayList)propertyInfo.GetValue(propertyCollection, null);
    }

    private static void SetPropertiesField(DirectoryEntry e)
    {
        var propertiesField = typeof(DirectoryEntry).GetField("propertyCollection", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        propertiesField.SetValue(e, GetUninitializedObject<PropertyCollection>());
    }

    private static T GetUninitializedObject<T>()
    {
        return (T)FormatterServices.GetUninitializedObject(typeof(T));
    }
}

usage is intended to be

DirectoryEntry e = DirectoryEntryFactory.Construct(new { attr1 = "Hello", attr2 = "World"});

I'm hoping I've missed something as I'm pretty new to using reflection in anger.

回答1:

I'm not familiar with DirectoryEntry itself, but I'm a big fan of the Adapter pattern for testing. It is an annoyance that you would have to do such a thing, but the code itself is trivial and the classes can be placed in a project folder to hide them away from your main project.

For example, I have a FileInfoAdapter and DirectoryInfoAdapter that wrap those classes that touch the filesystem.

FileInfoAdapter:

public class FileInfoAdapter : IFileInfo
{
    private readonly FileSystemInfo _fi;

    public FileInfoAdapter(string fileName)
        : this(new FileInfo(fileName))
    { }

    public FileInfoAdapter(FileSystemInfo fi)
    {
        _fi = fi;
    }

    public string Name { get { return _fi.Name; } }
    public string FullName { get { return _fi.FullName; } }
    public bool Exists { get { return _fi.Exists; } }
}