I am trying to extend UserPrincipal to easily read directory properties. When I debug, the user returns null, but I can find the expected information in Current information.
Extended class
public class UserPrincipalEx: UserPrincipal
{
public UserPrincipalEx(PrincipalContext ctx) : base(ctx) { }
public new static UserPrincipalEx FindByIdentity(PrincipalContext ctx, string un)
{
return UserPrincipal.FindByIdentity(ctx, un) as UserPrincipalEx;
}
//example of property to retrieve
[DirectoryProperty("MI")]
public string MI
{
get
{
if (ExtensionGet("Initials").Length < 1) return null;
return ExtensionGet("Initials").ToString();
}
set { ExtensionSet("Initials", value); }
}
}
Code that exists elsewhere to retrieve the current user's information.
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, Environment.UserDomainName);
UserPrincipalEx user = UserPrincipalEx.FindByIdentity(ctx, un);
//more code to work with the data
Relevant debug info:
I need user to not return null.
I have searched through the "similar questions" and tried to follow their examples, but I am unable to get this to work.
Update 20230105 I tried marc_s's solution below in a new project, no joy.
Update 20230106 I tried using Convert to change the class of a UserPrincipal, also no joy. Object must implement iConvertible. UserPrincipal does not.
UserPrincipal up = UserPrincipal.FindByIdentity(ctx, un);
user = (UserPrincipalEx)Convert.ChangeType(up, typeof(UserPrincipalEx));
I've done this before - years ago - and I used to use this code:
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("Person")]
public class UserPrincipalEx: UserPrincipal
{
public UserPrincipalEx(PrincipalContext ctx) : base(ctx)
{ }
// Implement the constructor with initialization parameters.
public UserPrincipalEx (PrincipalContext context,
string samAccountName,
string password,
bool enabled) : base(context, samAccountName, password, enabled)
{ }
// Create the "Department" property.
[DirectoryProperty("department")]
public string Department
{
get
{
if (ExtensionGet("department").Length != 1)
return string.Empty;
return (string)ExtensionGet("department")[0];
}
set { ExtensionSet("department", value); }
}
// Create the "Manager" property.
[DirectoryProperty("manager")]
public string Manager
{
get
{
if (ExtensionGet("manager").Length != 1)
return string.Empty;
return (string)ExtensionGet("manager")[0];
}
set { ExtensionSet("manager", value); }
}
}
So just basically do NOT create a static FindByIdentity method, but implement a second constructor. Also, accessing the ExtensionGet methods seems to a tad different from your version.
To search for a user, use the FindByIdentity defined on the base class - like this:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, Environment.UserDomainName))
{
UserPrincipalEx user = UserPrincipalEx.FindByIdentity(ctx, un);
// here, "user" should be just fine,
// and the "Department" and "Manager" properties should be readable
}
This is not what I had intended, but it will suffice for this project and may be helpful to someone else in the future.
Taken from How to implement and call a custom extension method.
public static class UserEx
{
public static string MI (this UserPrincipal up)
{
return ((DirectoryEntry)up.GetUnderlyingObject()).Properties["Initials"][0].ToString();
}
}
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, Environment.UserDomainName))
{
UserPrincipal up = UserPrincipal.FindByIdentity(ctx, un);
Console.WriteLine($"{up.DisplayName}'s middle initial is {up.MI()}");
//output: John Q. Public's middle initial is Q.
}
Of course, this will only GET data, but another method could be added to SET data fairly easily.
I am writing a binary PowerShell module that, much like the ActiveDirectory module, will have a number of cmdlets and types that can potentially return more than the default set of properties - and those properties (100+) are dependent on what the user requests. Just like the AD module, I would like to return the type with the properties that were requested so that it is transparent to the user i.e. not one huge class with lots of empty properties that they haven't requested.
I was looking at the documentation for the ActiveDirectory module and I noticed that things like ADUser and ADComputer ultimately are inherited from ADPropertyCollection which I assume has the properties required in its InnerDictionary after whatever searches it does.
This did not really explain how PowerShell presents the AD types flexibly i.e.
(Get-ADUser -Identity some.user).GetType()
(Get-ADUser -Identity some.user -Properties *).GetType()
# Both return a type of ADUser, despite have drastically different amounts of properties.
This was until I looked at the Types ps1xml file for the AD module, which presents something like this:
<Types>
<!-- other types -->
<Type>
<Name>Microsoft.ActiveDirectory.Management.ADUser</Name>
<TypeAdapter>
<TypeName>Microsoft.ActiveDirectory.Management.ADEntityAdapter</TypeName>
</TypeAdapter>
</Type>
</Types>
So I guess the source of the "magic" properties is ADEntityAdapter which is inherited from the abstract class PSPropertyAdapter.
The issue I have is now I am not sure how to implement it and there are not any easily searchable examples of it being implemented. I appreciate its a real edge case. I have had a small attempt at a very rough implementation below, please ignore any code faux pas - I will of course not actually write the code like below. I just wanted to at least try and show I have thought about this.
using System.Collections.Generic;
namespace Sample
{
public class PropertyCollection
{
public string Id { get; set; }
public Dictionary<string, object> Attributes { get; set; }
public PropertyCollection()
{
this.Id = "SampleID";
this.Attributes = new Dictionary<string, object>();
for (var i = 0; i < 10; i++)
{
this.Attributes.Add("KeyAttribute" + i, i);
}
for (var i = 10; i < 100; i++)
{
this.Attributes.Add("OtherAttribute" + i, i);
}
}
}
}
namespace Sample
{
public class ReturnedClass : PropertyCollection
{
public string SomeName { get; set; }
public ReturnedClass() : base() { }
}
}
using System;
using System.Collections.ObjectModel;
using System.Management.Automation;
namespace Sample
{
public class PropertyEntityAdapter : PSPropertyAdapter
{
public override Collection<PSAdaptedProperty> GetProperties(object baseObject)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("Some Exception");
}
Collection<PSAdaptedProperty> collection = new Collection<PSAdaptedProperty>();
foreach (string name in pc.Attributes.Keys)
{
collection.Add(new PSAdaptedProperty(name, null));
}
return collection;
}
public override PSAdaptedProperty GetProperty(object baseObject, string propertyName)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc.Attributes.TryGetValue(propertyName, out object pcValue))
{
return new PSAdaptedProperty(propertyName, pcValue);
}
throw new Exception("Prop not found");
}
public override string GetPropertyTypeName(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.TryGetValue(adaptedProperty.Name, out object pcValue))
{
return pcValue.GetType().FullName;
}
return pc.GetType().FullName;
}
public override object GetPropertyValue(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty?.BaseObject == null || adaptedProperty?.Name == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.TryGetValue(adaptedProperty.Name, out object pcValue))
{
return pcValue;
}
return null;
}
public override bool IsGettable(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.ContainsKey(adaptedProperty.Name))
{
return true;
}
return false;
}
public override bool IsSettable(PSAdaptedProperty adaptedProperty)
{
return false;
}
public override void SetPropertyValue(PSAdaptedProperty adaptedProperty, object value)
{
throw new System.NotImplementedException();
}
}
}
** I have purposely not added any code for the Set* methods to reduce the code required here. If the above seems right, then I am sure I know what to do.
Has anyone done this before? Are there any very basic examples out there? Am I on the right path, or am I misunderstanding? Thanks in advance.
If anyone comes comes looking for this and was interested in doing something similar, then my question is actually more or less how it is done. I hope it is of use or interest to others working on binary modules.
One thing I did find were there were some additional properties that were passed through the GetProperty method that seemed to be from the PowerShell engine itself i.e. PSComputerName, so there was no need to throw an error here, just return a new PSAdaptedProperty with a null object.
public override PSAdaptedProperty GetProperty(object baseObject, string propertyName)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc.Attributes.TryGetValue(propertyName, out object pcValue))
{
return new PSAdaptedProperty(propertyName, pcValue);
}
return new PSAdaptedProperty(propertyName, null);
}
You can then either load the Types data via your module manifest or in the console using Update-Typedata -PrependPath path_to_file\file_name.
All the properties in the dictionary are returned to give the appearance that they are full properties of the class.
I would like to implement my own IRuntimePolicy. I am following the given example, but I need to access our database or the best would be to have our UserSession object injected.
When is the security object created on runtime? Is this possible? I have not found any examples.
We use Ninject 3.2.3 I believe (or the latest available for MVC 5).
I imagine something like
public class GlimpseSecurityPolicy : IRuntimePolicy
{
private readonly IAclManager aclManager;
private readonly IUserSession userSession;
public GlimpseSecurityPolicy(IUserSession userSession, IAclManager aclManager)
{
this.userSession = userSession;
this.aclManager = aclManager;
}
public RuntimeEvent ExecuteOn
{
// check policy when request ends and when executing a resource (like glimpse.axd)
get { return RuntimeEvent.EndRequest | RuntimeEvent.ExecuteResource; }
}
public RuntimePolicy Execute(IRuntimePolicyContext policyContext)
{
if (!this.aclManager.IsUserAllowed(UserAction.AccessGlimpse, this.userSession.GetUser()))
{
return RuntimePolicy.Off;
}
return RuntimePolicy.On;
}
}
Ultimately, we came up with only one option: to use DependencyResolver.Current.GetService<IThing>().
The code result is then straightforward and ugly:
public class GlimpseSecurityPolicy : IRuntimePolicy
{
public RuntimeEvent ExecuteOn => RuntimeEvent.EndRequest | RuntimeEvent.ExecuteResource;
public RuntimePolicy Execute(IRuntimePolicyContext policyContext)
{
var aclManager = DependencyResolver.Current.GetService<IAclManager>();
var userSession = DependencyResolver.Current.GetService<IUserSession>();
if (!aclManager.IsUserAllowed(UserAction.AccessGlimpse, userSession.GetUser()))
{
return RuntimePolicy.Off;
}
return RuntimePolicy.On;
}
}
I have read lots of information about page caching and partial page caching in a MVC application. However, I would like to know how you would cache data.
In my scenario I will be using LINQ to Entities (entity framework). On the first call to GetNames (or whatever the method is) I want to grab the data from the database. I want to save the results in cache and on the second call to use the cached version if it exists.
Can anyone show an example of how this would work, where this should be implemented (model?) and if it would work.
I have seen this done in traditional ASP.NET apps , typically for very static data.
Here's a nice and simple cache helper class/service I use:
using System.Runtime.Caching;
public class InMemoryCache: ICacheService
{
public T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class
{
T item = MemoryCache.Default.Get(cacheKey) as T;
if (item == null)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(10));
}
return item;
}
}
interface ICacheService
{
T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class;
}
Usage:
cacheProvider.GetOrSet("cache key", (delegate method if cache is empty));
Cache provider will check if there's anything by the name of "cache id" in the cache, and if there's not, it will call a delegate method to fetch data and store it in cache.
Example:
var products=cacheService.GetOrSet("catalog.products", ()=>productRepository.GetAll())
Reference the System.Web dll in your model and use System.Web.Caching.Cache
public string[] GetNames()
{
string[] names = Cache["names"] as string[];
if(names == null) //not in cache
{
names = DB.GetNames();
Cache["names"] = names;
}
return names;
}
A bit simplified but I guess that would work. This is not MVC specific and I have always used this method for caching data.
I'm referring to TT's post and suggest the following approach:
Reference the System.Web dll in your model and use System.Web.Caching.Cache
public string[] GetNames()
{
var noms = Cache["names"];
if(noms == null)
{
noms = DB.GetNames();
Cache["names"] = noms;
}
return ((string[])noms);
}
You should not return a value re-read from the cache, since you'll never know if at that specific moment it is still in the cache. Even if you inserted it in the statement before, it might already be gone or has never been added to the cache - you just don't know.
So you add the data read from the database and return it directly, not re-reading from the cache.
For .NET 4.5+ framework
add reference: System.Runtime.Caching
add using statement:
using System.Runtime.Caching;
public string[] GetNames()
{
var noms = System.Runtime.Caching.MemoryCache.Default["names"];
if(noms == null)
{
noms = DB.GetNames();
System.Runtime.Caching.MemoryCache.Default["names"] = noms;
}
return ((string[])noms);
}
In the .NET Framework 3.5 and earlier versions, ASP.NET provided an in-memory cache implementation in the System.Web.Caching namespace. In previous versions of the .NET Framework, caching was available only in the System.Web namespace and therefore required a dependency on ASP.NET classes. In the .NET Framework 4, the System.Runtime.Caching namespace contains APIs that are designed for both Web and non-Web applications.
More info:
https://msdn.microsoft.com/en-us/library/dd997357(v=vs.110).aspx
https://learn.microsoft.com/en-us/dotnet/framework/performance/caching-in-net-framework-applications
Steve Smith did two great blog posts which demonstrate how to use his CachedRepository pattern in ASP.NET MVC. It uses the repository pattern effectively and allows you to get caching without having to change your existing code.
http://ardalis.com/Introducing-the-CachedRepository-Pattern
http://ardalis.com/building-a-cachedrepository-via-strategy-pattern
In these two posts he shows you how to set up this pattern and also explains why it is useful. By using this pattern you get caching without your existing code seeing any of the caching logic. Essentially you use the cached repository as if it were any other repository.
I have used it in this way and it works for me.
https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx
parameters info for system.web.caching.cache.add.
public string GetInfo()
{
string name = string.Empty;
if(System.Web.HttpContext.Current.Cache["KeyName"] == null)
{
name = GetNameMethod();
System.Web.HttpContext.Current.Cache.Add("KeyName", name, null, DateTime.Noew.AddMinutes(5), Cache.NoSlidingExpiration, CacheitemPriority.AboveNormal, null);
}
else
{
name = System.Web.HttpContext.Current.Cache["KeyName"] as string;
}
return name;
}
AppFabric Caching is distributed and an in-memory caching technic that stores data in key-value pairs using physical memory across multiple servers. AppFabric provides performance and scalability improvements for .NET Framework applications. Concepts and Architecture
Extending #Hrvoje Hudo's answer...
Code:
using System;
using System.Runtime.Caching;
public class InMemoryCache : ICacheService
{
public TValue Get<TValue>(string cacheKey, int durationInMinutes, Func<TValue> getItemCallback) where TValue : class
{
TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
if (item == null)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
}
return item;
}
public TValue Get<TValue, TId>(string cacheKeyFormat, TId id, int durationInMinutes, Func<TId, TValue> getItemCallback) where TValue : class
{
string cacheKey = string.Format(cacheKeyFormat, id);
TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
if (item == null)
{
item = getItemCallback(id);
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
}
return item;
}
}
interface ICacheService
{
TValue Get<TValue>(string cacheKey, Func<TValue> getItemCallback) where TValue : class;
TValue Get<TValue, TId>(string cacheKeyFormat, TId id, Func<TId, TValue> getItemCallback) where TValue : class;
}
Examples
Single item caching (when each item is cached based on its ID because caching the entire catalog for the item type would be too intensive).
Product product = cache.Get("product_{0}", productId, 10, productData.getProductById);
Caching all of something
IEnumerable<Categories> categories = cache.Get("categories", 20, categoryData.getCategories);
Why TId
The second helper is especially nice because most data keys are not composite. Additional methods could be added if you use composite keys often. In this way you avoid doing all sorts of string concatenation or string.Formats to get the key to pass to the cache helper. It also makes passing the data access method easier because you don't have to pass the ID into the wrapper method... the whole thing becomes very terse and consistant for the majority of use cases.
Here's an improvement to Hrvoje Hudo's answer. This implementation has a couple of key improvements:
Cache keys are created automatically based on the function to update data and the object passed in that specifies dependencies
Pass in time span for any cache duration
Uses a lock for thread safety
Note that this has a dependency on Newtonsoft.Json to serialize the dependsOn object, but that can be easily swapped out for any other serialization method.
ICache.cs
public interface ICache
{
T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class;
}
InMemoryCache.cs
using System;
using System.Reflection;
using System.Runtime.Caching;
using Newtonsoft.Json;
public class InMemoryCache : ICache
{
private static readonly object CacheLockObject = new object();
public T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class
{
string cacheKey = GetCacheKey(getItemCallback, dependsOn);
T item = MemoryCache.Default.Get(cacheKey) as T;
if (item == null)
{
lock (CacheLockObject)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.Add(duration));
}
}
return item;
}
private string GetCacheKey<T>(Func<T> itemCallback, object dependsOn) where T: class
{
var serializedDependants = JsonConvert.SerializeObject(dependsOn);
var methodType = itemCallback.GetType();
return methodType.FullName + serializedDependants;
}
}
Usage:
var order = _cache.GetOrSet(
() => _session.Set<Order>().SingleOrDefault(o => o.Id == orderId)
, new { id = orderId }
, new TimeSpan(0, 10, 0)
);
public sealed class CacheManager
{
private static volatile CacheManager instance;
private static object syncRoot = new Object();
private ObjectCache cache = null;
private CacheItemPolicy defaultCacheItemPolicy = null;
private CacheEntryRemovedCallback callback = null;
private bool allowCache = true;
private CacheManager()
{
cache = MemoryCache.Default;
callback = new CacheEntryRemovedCallback(this.CachedItemRemovedCallback);
defaultCacheItemPolicy = new CacheItemPolicy();
defaultCacheItemPolicy.AbsoluteExpiration = DateTime.Now.AddHours(1.0);
defaultCacheItemPolicy.RemovedCallback = callback;
allowCache = StringUtils.Str2Bool(ConfigurationManager.AppSettings["AllowCache"]); ;
}
public static CacheManager Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new CacheManager();
}
}
}
return instance;
}
}
public IEnumerable GetCache(String Key)
{
if (Key == null || !allowCache)
{
return null;
}
try
{
String Key_ = Key;
if (cache.Contains(Key_))
{
return (IEnumerable)cache.Get(Key_);
}
else
{
return null;
}
}
catch (Exception)
{
return null;
}
}
public void ClearCache(string key)
{
AddCache(key, null);
}
public bool AddCache(String Key, IEnumerable data, CacheItemPolicy cacheItemPolicy = null)
{
if (!allowCache) return true;
try
{
if (Key == null)
{
return false;
}
if (cacheItemPolicy == null)
{
cacheItemPolicy = defaultCacheItemPolicy;
}
String Key_ = Key;
lock (Key_)
{
return cache.Add(Key_, data, cacheItemPolicy);
}
}
catch (Exception)
{
return false;
}
}
private void CachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
{
String strLog = String.Concat("Reason: ", arguments.RemovedReason.ToString(), " | Key-Name: ", arguments.CacheItem.Key, " | Value-Object: ", arguments.CacheItem.Value.ToString());
LogManager.Instance.Info(strLog);
}
}
I use two classes. First one the cache core object:
public class Cacher<TValue>
where TValue : class
{
#region Properties
private Func<TValue> _init;
public string Key { get; private set; }
public TValue Value
{
get
{
var item = HttpRuntime.Cache.Get(Key) as TValue;
if (item == null)
{
item = _init();
HttpContext.Current.Cache.Insert(Key, item);
}
return item;
}
}
#endregion
#region Constructor
public Cacher(string key, Func<TValue> init)
{
Key = key;
_init = init;
}
#endregion
#region Methods
public void Refresh()
{
HttpRuntime.Cache.Remove(Key);
}
#endregion
}
Second one is list of cache objects:
public static class Caches
{
static Caches()
{
Languages = new Cacher<IEnumerable<Language>>("Languages", () =>
{
using (var context = new WordsContext())
{
return context.Languages.ToList();
}
});
}
public static Cacher<IEnumerable<Language>> Languages { get; private set; }
}
I will say implementing Singleton on this persisting data issue can be a solution for this matter in case you find previous solutions much complicated
public class GPDataDictionary
{
private Dictionary<string, object> configDictionary = new Dictionary<string, object>();
/// <summary>
/// Configuration values dictionary
/// </summary>
public Dictionary<string, object> ConfigDictionary
{
get { return configDictionary; }
}
private static GPDataDictionary instance;
public static GPDataDictionary Instance
{
get
{
if (instance == null)
{
instance = new GPDataDictionary();
}
return instance;
}
}
// private constructor
private GPDataDictionary() { }
} // singleton
HttpContext.Current.Cache.Insert("subjectlist", subjectlist);
You can also try and use the caching built into ASP MVC:
Add the following attribute to the controller method you'd like to cache:
[OutputCache(Duration=10)]
In this case the ActionResult of this will be cached for 10 seconds.
More on this here
Is this possible to set a condition for a "query-by-example" principal to NOT LIKE rather than LIKE ?
Like Method:
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.Name= "Mario";
This will return all users with the name "Mario".
Is this possible to create a condition to get all the users who are not named "Mario" ?
Something like this :
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.Name != "Mario";
All users with an other name than "Mario".
No, however you can get all users. Then use linq to filter them.
UserPrincipal qbeUser = new UserPrincipal(ctx);
PrincipalSearcher pSearch = new PrincipalSearcher(qbeUser);
PrincipalSearchResult<Principal> pResult = pSearch.FindAll();
var notMario = (from u in pResult
where u.Name != "Mario"
select u);
Then depending on what you want to do
foreach (Principal p in notMario) {
// Do Somthing
}
This can be achieved by extending the UserPrincipal class :
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("Person")]
public class UserPrincipalEXT : UserPrincipal
{
// Inplement the constructor using the base class constructor.
public UserPrincipalEXT(PrincipalContext context)
: base(context)
{ }
// Implement the constructor with initialization parameters.
public UserPrincipalEXT(PrincipalContext context,
string samAccountName,
string password,
bool enabled)
: base(context, samAccountName, password, enabled)
{ }
// Create the "employeeType" property with the "!" for NOT LIKE.
[DirectoryProperty("!employeeType")]
public string NotLikeEmployeeType
{
get
{
if (ExtensionGet("!employeeType").Length != 1)
return string.Empty;
return (string)ExtensionGet("!employeeType")[0];
}
set { ExtensionSet("!employeeType", value); }
}
// Implement the overloaded search method FindByIdentity.
public static new UserPrincipalEXT FindByIdentity(PrincipalContext context, string identityValue)
{
return (UserPrincipalEXT)FindByIdentityWithType(context, typeof(UserPrincipalEXT), identityValue);
}
// Implement the overloaded search method FindByIdentity.
public static new UserPrincipalEXT FindByIdentity(PrincipalContext context, IdentityType identityType, string identityValue)
{
return (UserPrincipalEXT)FindByIdentityWithType(context, typeof(UserPrincipalEXT), identityType, identityValue);
}
}
The important thing to understand is here :
// Create the "employeeType" property.
[DirectoryProperty("!employeeType")]
public string NotLikeEmployeeType
{
get
{
if (ExtensionGet("!employeeType").Length != 1)
return string.Empty;
return (string)ExtensionGet("!employeeType")[0];
}
set { ExtensionSet("!employeeType", value); }
}
Given that the "DirectoryProperty" with ExtensionGet AND ExtensionSet create the condition when the property (NotLikeEmployeeType in this case) is not empty, you can add an "!" before the AD property (employeeType in this case).
This is how we can use the extension :
UserPrincipalEXT qbeUser = new UserPrincipalEXT(ctx);
qbeUser.NotLikeEmployeeType = "exclude";
Now the condition returned will be :
!employeeType = exclude
And this is exactly what we want ! Not Like ! :)
And, the great thing about extension, is you can add AD properties to the class that they are normally not exposed (like employeeType)