I've supported sub type mapping since version 1.0, but it was more of a way of reusing code for your directory objects than supporting polymorphism.
So you could map your classes like so:
But if you were to query for PersonInheritanceTest, every type that came back would be a PersonInheritanceTest object and then you'd have to do your own inspection on the object to load the real type.
LINQ to LDAP 4.0 will now let you map your sub types (remember to specify a unique set of object classes for your types!) and have the correct instances created. The only drawback is you have to map your objects up front now since I have to figure out the inheritance tree for mappings. Ad-hoc mapping will still work, but you may see different results depending on when the mappings get added via querying.
Friday, September 11, 2015
Sunday, October 20, 2013
LINQ to LDAP Examples
I've been working on a "Getting Started" guide and an official examples project. A friend of mine created LINQ to LDAP Demonstrations to help me out since I was slacking and not keeping the samples project up to date. The new examples point to two live OpenLDAP servers (thanks to Stuart Lewis and the University of Texas) so you can actually play around with the framework.
Some of the examples highlight the differences between plain old DirectoryServices and LINQ to LDAP. Not everything works correctly using DirectoryServices (i.e. paging) so I've made the queries pretty basic so everything works across the board. If you know of a publicly accessible AD/AD-LDS server then let me know. You can find the examples here.
Some of the examples highlight the differences between plain old DirectoryServices and LINQ to LDAP. Not everything works correctly using DirectoryServices (i.e. paging) so I've made the queries pretty basic so everything works across the board. If you know of a publicly accessible AD/AD-LDS server then let me know. You can find the examples here.
Monday, October 8, 2012
LINQ to LDAP: Event Listeners
I'm adding support for listening to Add, Update, and Delete operations in the latest version of LINQ to LDAP. This will allow you to perform validation or logging operations before anything is executed. So let's try this out:
There are 3 interfaces that you can implement to subscribe to events: IPreAddEventListener, IPreDeleteEventListener, IPreUpdateEventListener. Each interface has a Notify method that will give you access to the entry being modified (the mapped object, the DirectoryAttributes, or the Distinguished Name in the case of Delete), the LdapConnection used for the modification, and the DirectoryRequest that will be sent. The DirectoryRequest won't have any of the attribute values added since you may want to do validation beforehand. You can also add / modify anything you want about the request.
Using your event listener is pretty straightforward:
When using the LdapConfiguration you can call RegisterListener and it will be used across all DirectoryContexts. When using the LdapConnection extension methods you can pass it in as a parameter. And that's about it.
There are 3 interfaces that you can implement to subscribe to events: IPreAddEventListener, IPreDeleteEventListener, IPreUpdateEventListener. Each interface has a Notify method that will give you access to the entry being modified (the mapped object, the DirectoryAttributes, or the Distinguished Name in the case of Delete), the LdapConnection used for the modification, and the DirectoryRequest that will be sent. The DirectoryRequest won't have any of the attribute values added since you may want to do validation beforehand. You can also add / modify anything you want about the request.
Using your event listener is pretty straightforward:
When using the LdapConfiguration you can call RegisterListener and it will be used across all DirectoryContexts. When using the LdapConnection extension methods you can pass it in as a parameter. And that's about it.
Sunday, October 7, 2012
LINQ to LDAP: Attribute Scoped Queries
Active Directory has supported this feature since Windows Server 2003, however I only learned about it recently from Simon Garratt. An attribute scoped query allows you to search within any multivalued attribute of an entry. This becomes really useful when you want to search for users within a group without performing multiple queries.
Say you want to find all users in the sales department for a group:
So what's going on here? I'm looking for User objects stored in "CN=Group,DC=server,DC=com" and I'm using a base search scope since I'm looking within a specific distinguished name.
On the second line I call ScopeToAttribute and I give it the name of the attribute on the group (member in this case). On a side note, ScopeToAtribute is just some syntactic sugar for calling WithControls and passing in a AsqRequestControl.
On the third line I'm just creating a standard filter. Since I'm querying for users my filter will be in that context. I can search on any User property and create any projection from a User.
I think this is pretty cool since it goes for any multivalued attribute that links to other directory entries (members, groups, employees, etc.).
Say you want to find all users in the sales department for a group:
So what's going on here? I'm looking for User objects stored in "CN=Group,DC=server,DC=com" and I'm using a base search scope since I'm looking within a specific distinguished name.
On the second line I call ScopeToAttribute and I give it the name of the attribute on the group (member in this case). On a side note, ScopeToAtribute is just some syntactic sugar for calling WithControls and passing in a AsqRequestControl.
On the third line I'm just creating a standard filter. Since I'm querying for users my filter will be in that context. I can search on any User property and create any projection from a User.
I think this is pretty cool since it goes for any multivalued attribute that links to other directory entries (members, groups, employees, etc.).
Sunday, July 8, 2012
LINQ to LDAP: The Other Lync
Lync is Microsoft's unified communication platform. It ships with a pretty awesome SDK that makes adding presence information to your WPF and Silverlght apps pretty easy. It's also possible to add presence information to your ASP.Net apps, but it's a little trickier.
The first thing you'll need for WPF or Silverlight is the SDK. The installed assemblies should be located in Program Files (or the x86 variant if you're running 64bit) under Microsoft Lync\SDK\Assemblies.
You'll need Microsoft.Lync.Controls.Framework.dll, Microsoft.Lync.Controls.dll, Microsoft.Lync.Model.dll, and Microsoft.Lync.Utilities.dll. Reference those assemblies and you're ready to drop in a control:
The PresenceIndicator control gives you the familiar jelly bean and the image of the person. The Source property should be bound to the user's SIP Uri. What the heck is a SIP Uri? The short version is that it is the address for initiating a communication session with a user via Session Initiation Protocol. You can read the long version here and here.
You may be asking yourself what any of this has to do with LINQ to LDAP. Odds are if you are tracking anything about your users in your applications it's either the sAMAccountName or the Guid. The SIP Uri for those users, however, is stored in the msRTCSIP-PrimaryUserAddress attribute. You can perform a simple lookup to get this attribute. Query by example will not work in this case since the property name is invalid, but mapping or dynamic querying will work.
ASP.Net isn't quite as straightforward. Here's a great post about piggybacking on the NameCtrl in SharePoint 2010. Make sure you do not prefix the uri with "sip:" in the ASP.Net example or it won't work.
You'll need Microsoft.Lync.Controls.Framework.dll, Microsoft.Lync.Controls.dll, Microsoft.Lync.Model.dll, and Microsoft.Lync.Utilities.dll. Reference those assemblies and you're ready to drop in a control:
The PresenceIndicator control gives you the familiar jelly bean and the image of the person. The Source property should be bound to the user's SIP Uri. What the heck is a SIP Uri? The short version is that it is the address for initiating a communication session with a user via Session Initiation Protocol. You can read the long version here and here.
You may be asking yourself what any of this has to do with LINQ to LDAP. Odds are if you are tracking anything about your users in your applications it's either the sAMAccountName or the Guid. The SIP Uri for those users, however, is stored in the msRTCSIP-PrimaryUserAddress attribute. You can perform a simple lookup to get this attribute. Query by example will not work in this case since the property name is invalid, but mapping or dynamic querying will work.
ASP.Net isn't quite as straightforward. Here's a great post about piggybacking on the NameCtrl in SharePoint 2010. Make sure you do not prefix the uri with "sip:" in the ASP.Net example or it won't work.
Friday, June 22, 2012
LINQ to LDAP: Closer to Home
During my experience with passwords I was working directly with an LdapConnection. After a while I wondered how easy it would be to add extension methods directly to the LdapConnection. It was actually pretty easy so all of the methods that don't rely on object mappings are now available from an LdapConnection object now.
Thursday, June 21, 2012
LINQ to LDAP: How do passwords work?
A couple hours of trial and error I have finally figured out setting passwords in Active Directory and Lightweight Directory Services. As a result of this I ported all of the dynamic functionality to some extension methods for LdapConnection.
Modifying passwords has been in the back of my head ever since the question was asked here. If I had looked closer at the answer on StackOverflow I would have seen that the solution was for Sun-One. If there's one thing I've learned working on LINQ to LDAP it's that every server has been implemented a little differently.
So let's get down to it. The first thing I learned is userPassword in AD LDS is not the "real" password attribute. That would be unicodePwd. I then received a very helpful "An operation error occurred." A few searches later and I found out that means you'll have to use port 636 instead of 389. Turns out Microsoft does not allow changing the password over a non SSL connection. However, you don't have to explicitly set SSL to true if you're post Windows 2000 (I hope you are). Okie dokie let's fire this up.
"The LDAP server is unavailable."
You'll get this error if SSL is not on for your server. This lead me to generating a self signed certificate and installing it for my local instance. You can read about the how and why here. Alright, now my server is responding to requests on 636, but I'm getting "A value in the request is invalid." That's when I found this post explaining how to do this the right way. I wish I had found that first. So here's the resulting code:
For those using AD LDS and you don't have SSL on, let me save you some trouble. I found this post later allowing you to enable modifying the password over port 389. Well I hope this was helpful.
update
I should probably add that you should not enable setting passwords over an unencrypted connection. I am working in a test environment so it's no big deal for me.
Modifying passwords has been in the back of my head ever since the question was asked here. If I had looked closer at the answer on StackOverflow I would have seen that the solution was for Sun-One. If there's one thing I've learned working on LINQ to LDAP it's that every server has been implemented a little differently.
So let's get down to it. The first thing I learned is userPassword in AD LDS is not the "real" password attribute. That would be unicodePwd. I then received a very helpful "An operation error occurred." A few searches later and I found out that means you'll have to use port 636 instead of 389. Turns out Microsoft does not allow changing the password over a non SSL connection. However, you don't have to explicitly set SSL to true if you're post Windows 2000 (I hope you are). Okie dokie let's fire this up.
"The LDAP server is unavailable."
You'll get this error if SSL is not on for your server. This lead me to generating a self signed certificate and installing it for my local instance. You can read about the how and why here. Alright, now my server is responding to requests on 636, but I'm getting "A value in the request is invalid." That's when I found this post explaining how to do this the right way. I wish I had found that first. So here's the resulting code:
For those using AD LDS and you don't have SSL on, let me save you some trouble. I found this post later allowing you to enable modifying the password over port 389. Well I hope this was helpful.
update
I should probably add that you should not enable setting passwords over an unencrypted connection. I am working in a test environment so it's no big deal for me.
Monday, June 11, 2012
LINQ to LDAP: Documentation
Just a minor update. 3.0 is coming. I'm currently reviewing and updating the documentation over at CodePlex. I realized that I haven't touched it since sometime around 1.5 / 2.0 so it's in dire need of a refresh. There are a few things from 3.0 in there so don't download 2.5 and scream "WTF, this is all wrong!" :).
Saturday, June 2, 2012
Stop Checking CanExecute!
I stumbled upon an interesting problem in WPF. I've only seen a few rumblings about it on the internet so it may just be me doing something stupid. I can reproduce it so that's enough for me to look into it.
It's a common setup. There's a view model that manages workspaces (tabs, windows, whatever) similar to Josh Smith's example. However, when removing a view model from the ObservableCollection my view continues to poll the CanExecute of the commands it's bound too. No big deal, just set them to null and raise property changed and problem solved. Well what happens if you just want to unbind your current view and rebind to a new view? The view model has all the state in MVVM after all.
You could have some sort of unbind implementation on your workspaces that will null your commands and reinitialize them (bleh). The simplest option I've found is to set DataContext to null on your view when it unloads.
You can then just inherit from this control and call it a day.
Here's a project demonstrating the problem.
It's a common setup. There's a view model that manages workspaces (tabs, windows, whatever) similar to Josh Smith's example. However, when removing a view model from the ObservableCollection my view continues to poll the CanExecute of the commands it's bound too. No big deal, just set them to null and raise property changed and problem solved. Well what happens if you just want to unbind your current view and rebind to a new view? The view model has all the state in MVVM after all.
You could have some sort of unbind implementation on your workspaces that will null your commands and reinitialize them (bleh). The simplest option I've found is to set DataContext to null on your view when it unloads.
You can then just inherit from this control and call it a day.
Here's a project demonstrating the problem.
Saturday, March 17, 2012
LINQ To LDAP: 2.5
Been a while since my last post. There are quite a few changes in 2.5, but I want to focus on the biggest ones.
Multiple Naming Contexts and Configurations
First big change is now you can query for mapped objects in any naming context. This makes it easier to reuse a mapping for different parts of the directory or even with a completely different directory. The naming context you specify here will always override whatever you have mapped.
This leads me to the next change. I've had a lot of questions about having dynamic configurations and multiple directory support. I decided to give you more control over the lifespan of LdapConfiguration. I've also exposed DirectoryMapper so you can share your mappings among multiple configurations. Each configuration has it's own connection factory so this should handle connecting to multiple directories.
You can store your configurations with your favorite IoC container, in a static class, or whatever your preference. You can still use a single global one by calling UseStaticStorage on the configuration.
Custom Mapping
I've also added support for mapping properties to custom values. There's a new method on ClassMap called MapCustom. Here's an example of mapping ObjectGuid as a custom property:
I've also added support for forwarding values in place of others. Right now it's only available when mapping DateTimes (due to the 9223372036854775807 value meaning "not set"). I plan to expand this to other property types if necessary. Here's how you use it:
Querying
I just want to add a small note about the Filter class. Previously I was escaping any special characters you passed in. I think now that I should give you more control over the values. You can now use Filter.Equal("attribute", "*"). However, if you want to escape any special characters you can use Filter.Equal("attribute", "some value".CleanFilterValue()).
You can read more about what's in this release here.
Multiple Naming Contexts and Configurations
First big change is now you can query for mapped objects in any naming context. This makes it easier to reuse a mapping for different parts of the directory or even with a completely different directory. The naming context you specify here will always override whatever you have mapped.
context.Query<User>(namingContext: "naming context");
This leads me to the next change. I've had a lot of questions about having dynamic configurations and multiple directory support. I decided to give you more control over the lifespan of LdapConfiguration. I've also exposed DirectoryMapper so you can share your mappings among multiple configurations. Each configuration has it's own connection factory so this should handle connecting to multiple directories.
You can store your configurations with your favorite IoC container, in a static class, or whatever your preference. You can still use a single global one by calling UseStaticStorage on the configuration.
Custom Mapping
I've also added support for mapping properties to custom values. There's a new method on ClassMap called MapCustom. Here's an example of mapping ObjectGuid as a custom property:
MapCustom(s => s.ObjectGuid).Named("objectGuid")
.ConvertFromDirectoryUsing(d =>
{
var bytes = d.GetValues(typeof(byte[]))[0] as byte[];
return new Guid(bytes);
})
.ConvertToFilterUsing(v => v.ToStringOctet())
.ConvertToDirectoryUsing(v => v.ToByteArray())
.CompareChangesUsing((guid1, guid2) => guid1.Equals(guid2));
At a minimum you have to implement ConvertFromDirectoryUsing. If you want to filter by it you have to implement ConvertToFilterUsing. And if you want to update the property then you have to implement ConvertToDirectoryUsing. CompareChangesUsing is optional. If you've implemented IDrectoryObject or subclassed DirectoryObjectBase and your custom mapping is for something that can't really use the default Equals implementation then you'll want to implement it.I've also added support for forwarding values in place of others. Right now it's only available when mapping DateTimes (due to the 9223372036854775807 value meaning "not set"). I plan to expand this to other property types if necessary. Here's how you use it:
Map(c => c.AccountExpires)
.Named("accountExpires")
.DateTimeFormat(null)
.DirectoryValue("9223372036854775807").Returns(null)
.InstanceValue(DateTime.MinValue).Sends("9223372036854775807")
.InstanceValueNullOrDefault().Sends("9223372036854775807");
Querying
I just want to add a small note about the Filter class. Previously I was escaping any special characters you passed in. I think now that I should give you more control over the values. You can now use Filter.Equal("attribute", "*"). However, if you want to escape any special characters you can use Filter.Equal("attribute", "some value".CleanFilterValue()).
You can read more about what's in this release here.
Wednesday, January 11, 2012
LINQ To LDAP: More CRUD
I just realized that I never actually finished writing about CRUD operations with LINQ to LDAP. I guess I don't have to worry too much because someone else did it for me! He plans to have a full series on using LINQ to LDAP with Visual Studio LightSwitch.
Tuesday, December 27, 2011
LINQ To LDAP: Visiting the Locals
I recently ran into a problem with how LINQ to LDAP's expression visitor was working. This expression:
My hackish solution was simply to ignore constants that don't have a corresponding property. Well that worked, but I saw two problems. First it created unnecessarily complex filters (& and | where one wasn't necessary). The second issue was the InvalidOperationException caused when .Value was called for a nullable without a value.
So fast forward several days of on and off coding (Christmas is for family and coding!) and I think I have a solution. I already borrowed the SubtreeEvaluator so I just needed to create a visitor that would rewrite only boolean constants. Well, I ended up creating two. One to rewrite and then another to remove redundant and unnecessary values. When I finish refactoring, you'll be able to find the BooleanRewriter and BooleanReducer in the Visitors folder.
source.Where(u=> objectGuid.HasValue && u.objectGuid == objectGuid.Value)was throwing a NullReferenceException. The problem was related to that first condition. The visit constant for QueryTranslator was making an assumption that for each constant it found there would be a corresponding member access.
My hackish solution was simply to ignore constants that don't have a corresponding property. Well that worked, but I saw two problems. First it created unnecessarily complex filters (& and | where one wasn't necessary). The second issue was the InvalidOperationException caused when .Value was called for a nullable without a value.
So fast forward several days of on and off coding (Christmas is for family and coding!) and I think I have a solution. I already borrowed the SubtreeEvaluator so I just needed to create a visitor that would rewrite only boolean constants. Well, I ended up creating two. One to rewrite and then another to remove redundant and unnecessary values. When I finish refactoring, you'll be able to find the BooleanRewriter and BooleanReducer in the Visitors folder.
//if objectGuid doesn't have a value the expression will reduce to: source.Where(u=> false) //if objectGuid has a value then the expression will reduce to: source.Where(u=> u.objectGuid == objectGuid.Value)
Tuesday, December 13, 2011
SharePoint as a document database
No, not the No-SQL kind. I'm nearing completion of a project at work where our internal customer wanted a central repository for their documents. The catch? SharePoint tends to be a bad word among most users. Thankfully SharePoint 2010 ships with a nice RESTful service exposed via ListData.svc so we can leverage SharePoint without anyone really knowing about it.
Following Corey Roth's blog post and this MSDN article, I was able to easily query for documents using LINQ. You even get an index back, but I had to parse it from the server response when adding the document since it gives you the full location as "http://<sharepoint-server>/<site>/_vti_bin/ListData.svc/<ListName>(<Index>)". A lot of this is off the top of my head (sorry, can't really post code samples from my work code):
So I can save and find the document, but how do I edit it? That was a little trickier. My initial idea was just to use Process.Start on the url for the document (this is a WPF app by the way), but for some reason SharePoint defaults to read only when you do that. Nick Grattan has a great solution that I modified to use dynamics since it works so well when you have to bust out some COM code.
And voilĂ ! Now I can store meta data about the document in my app database, retrieve it from SharePoint and get the power of locking and versioning! I wish I could take credit for the idea, but my boss recommended it. I just thought I'd share the adventure of trying to make it work.
Following Corey Roth's blog post and this MSDN article, I was able to easily query for documents using LINQ. You even get an index back, but I had to parse it from the server response when adding the document since it gives you the full location as "http://<sharepoint-server>/<site>/_vti_bin/ListData.svc/<ListName>(<Index>)". A lot of this is off the top of my head (sorry, can't really post code samples from my work code):
var service = new MyListDataService(new Uri("http://<sharepoint-server>/<site>/_vti_bin/ListData.svc"))
{
Credentials = CredentialCache.DefaultCredentials
};
string path = "/<site>/<ListName>/doc.txt";
string contentType = "plain/text";
DocumentItem item = new DocumentItem()
{
ContentType = contentType,
Name = "doc.txt",
Path = path,
Title = "doc"
};
service.AddToDocuments(item);
byte[] data = //wherever the raw file data comes from
service.SetSaveStream(item, data, false, contentType, path);
var response = service.SaveChanges();
int id = //code to parse the index from the Location header of the response
var document = service.Documents.FirstOrDefault(d => d.Id == id);
So I can save and find the document, but how do I edit it? That was a little trickier. My initial idea was just to use Process.Start on the url for the document (this is a WPF app by the way), but for some reason SharePoint defaults to read only when you do that. Nick Grattan has a great solution that I modified to use dynamics since it works so well when you have to bust out some COM code.
var docUrl = "http://<sharepoint-server>/<site>/<ListName>/doc.txt";
Type t = Type.GetTypeFromProgID("SharePoint.OpenDocuments.3");
dynamic sharePointControl = Activator.CreateInstance(t);
sharePointControl.EditDocument(docUrl, string.Empty);
And voilĂ ! Now I can store meta data about the document in my app database, retrieve it from SharePoint and get the power of locking and versioning! I wish I could take credit for the idea, but my boss recommended it. I just thought I'd share the adventure of trying to make it work.
Labels:
ADO.NET Data Services,
REST,
SharePoint,
SharePoint 2010
Sunday, December 4, 2011
LINQ To LDAP: Configuration
There have been more than a few questions about the use of static properties within LdapConfiguration. This was intended for simplicity, but it's always nice to have an option for more complex applications.
With the recent changes you now can control the lifetime of your configuration as well as have multiple configurations within a project. Configurations are self contained with their own DirectoryMapper and ConnectionFactory. You can still choose to have a global configuration by calling UseStaticStorage on LdapConfiguration.
With the recent changes you now can control the lifetime of your configuration as well as have multiple configurations within a project. Configurations are self contained with their own DirectoryMapper and ConnectionFactory. You can still choose to have a global configuration by calling UseStaticStorage on LdapConfiguration.
var config = new LdapConfiguration()
.AddMapping(new AttributeClassMap<User>())
.MaxPageSizeIs(500)
.LogTo(new SimpleTextLogger(Console.Out))
.UseStaticStorage();
config.ConfigurePooledFactory("localhost")
.MaxPoolSizeIs(50)
.MinPoolSizeIs(0)
.ProtocolVersion(3);
//will look for the static Configuration property on LdapConfiguration
using (var context = new DirectoryContext())
{
}
//the next two statements construct a DirectoryContext in the same manner
using (var context = config.CreateContext())
{
}
using (var context = new DirectoryContext(config))
{
}
//will create a LdapConfiguration local to this instance.
//As a result, no mappings can be cached beyond the lifetime
//for this DirectoryContext.
using (var context = new DirectoryContext(new LdapConnection("localhost"), true))
{
}
Monday, November 28, 2011
LINQ To LDAP: Logging
I've been working on logging in LINQ to LDAP recently. I added logging support for all requests as well as any errors that occur. I created an interface so that you can integrate your own logging providers.
Out of the box I included a basic implementation:
public interface ILinqToLdapLogger
{
bool TraceEnabled { get; }
void Trace(string message);
void Error(Exception ex, string message = null);
}
Trace is meant for logging query information. I check TraceEnabled before writing information via Trace. Error is always called when exception is encountered. I wasn't sure if I should add logging hooks for error since most projects will handle errors within their own applications, but I figured you could simply ignore calls to Error in your own implementations.Out of the box I included a basic implementation:
public class SimpleTextLogger : ILinqToLdapLogger
{
private readonly TextWriter _textWriter;
public SimpleTextLogger(TextWriter textWriter)
{
_textWriter = textWriter;
TraceEnabled = true;
}
public bool TraceEnabled { get; set; }
public void Trace(string message)
{
_textWriter.WriteLine(message);
}
public void Error(Exception ex, string message = null)
{
if (message != null) _textWriter.WriteLine(message);
ObjectDumper.Write(ex, 0, _textWriter);
}
}
This is mainly used for debugging support, but it also serves as an example implementation. Here's another example using Common.Logging .Net which offers a lot more support for different logging frameworks:public class CommonLogger : ILinqToLdapLogger
{
private ILog _errorLog;
private ILog _traceLog;
public CommonLogger()
{
_traceLog = LogManager.GetLogger("trace");
_errorLog = LogManager.GetLogger("error");
}
public void Trace(string message)
{
_traceLog.Trace(message);
}
public void Error(Exception ex, string message = null)
{
_errorLog.Error(message, ex);
}
public bool TraceEnabled
{
get { return _traceLog.IsTraceEnabled; }
}
}
You can attach a log directly to a DirectoryContext or through the LdapConfiguration so it can be used with the ConncectionFactory as well. I'll go into more detail in my next post about the LdapConfiguration changes.var context = new DirectoryContext()
{
Logger = new SimpleTextLogger(Console.Out)
};
var config = new LdapConfiguration()
.LogTo(new SimpleTextLogger(Console.Out));
//will inject the logger for you
context = config.CreateContext();
Friday, November 11, 2011
LINQ To LDAP: NuGet
LINQ to LDAP 2.0.2 has been released over at codeplex. I've also finally made the jump to NuGet. Happy day!
Thursday, November 10, 2011
LINQ To LDAP: More Control
Currently paging and sorting controls will automatically be created for the by the framework. That's nice for most situations, but sometimes you need a little more control.
I added a WithControls extension method that allows you to specify as many additional DirectoryControls as necessary.
If you try to perform an OrderBy or ToPage operation while specifying your own via WithControls then an InvalidOperationException will be thrown.
One cool thing here is behind the scenes I still check if you're requesting a page so you can cast the resulting list as a LdapPage and get the cookie for the next page. And that's about it.
I added a WithControls extension method that allows you to specify as many additional DirectoryControls as necessary.
List<User> list = context.Query<User>()
.WithControls(new DirectoryControl[]
{
new PageResultRequestControl(100)
{
IsCritical = true
},
new SortRequestControl(new[]{new SortKey("cn", null, true)})
{
IsCritical = true
},
new ShowDeletedControl
{
IsCritical = true
}
})
.ToList();
If you try to perform an OrderBy or ToPage operation while specifying your own via WithControls then an InvalidOperationException will be thrown.
One cool thing here is behind the scenes I still check if you're requesting a page so you can cast the resulting list as a LdapPage and get the cookie for the next page. And that's about it.
Wednesday, November 9, 2011
LINQ To LDAP: Closing in on 2.0
It's been a while since I've posted. I moved across town, took a vacation, and have just been generally busy. But I'm back and with a few updates about 2.0.
Since the last time I posted, I've made a few API changes and added quite a bit more mapping support. One thing I want to talk about is some tweaks to paging. I've covered ToPage and PageAll in a previous post. PageAll, however, is being deprecated in version 2.0. In it's place you can do this:
In cases when the take size is smaller than the page size, take size will be used.
This is part of the trunk, but is not in the 2.0 beta 2 release. I've updated most of the documentation so you can see a lot of the new stuff over at the codeplex page. I'll also be adding this project to Nuget soon.
Since the last time I posted, I've made a few API changes and added quite a bit more mapping support. One thing I want to talk about is some tweaks to paging. I've covered ToPage and PageAll in a previous post. PageAll, however, is being deprecated in version 2.0. In it's place you can do this:
//will page all results when enumerated based on the server max page size from LdapConfiguration
context.Query<User>();
//will create a page request for 10 results and stop there
context.Query<User>()
.Take(10);
//will page all the results in groups of 50
context.Query<User>()
.InPagesOf(50);
//will create a page request for 2 entries at a time up to a maximum of 10 results
context.Query<User>()
.Take(10)
.InPagesOf(2);
In cases when the take size is smaller than the page size, take size will be used.
This is part of the trunk, but is not in the 2.0 beta 2 release. I've updated most of the documentation so you can see a lot of the new stuff over at the codeplex page. I'll also be adding this project to Nuget soon.
Thursday, August 25, 2011
LINQ To LDAP: Hello DirectoryAttributes!
I had a very pleasant surprise when testing my DirectoryAttributes implementation. The biggest driving force for creating LINQ TO LDAP was simplifying the querying process. With dynamics, you had to create your filters manually which is nice for simple scenarios but can get messy pretty quickly.
Since I've gone back to a static type, I'm pleased to say this query works perfectly:
The Filter class still only supports Equal and Approximately so I'll need to fill it out with the other operations. Happy coding!
Since I've gone back to a static type, I'm pleased to say this query works perfectly:
var query = context.Query(namingContext, objectClass: "Person")
.Where(da => (Filter.Equal(da, "givenname", "Andrew") && Filter.Equal(da, "sn", "Fuller")) ||
Filter.Equal(da, "cn", "Andrew Fuller"))
.Select(da => new
{
Guid = da.GetGuid("objectguid"),
EmployeeId = da.GetInt("employeeid")
});
var user = query.FirstOrDefault();
//Produces this filter:
(&(objectClass=Person)(|(&(givenname=Andrew)(sn=Fuller))(cn=Andrew Fuller)))
Attributes: objectguid, employeeid
Projections are supported as well. This will look at the parameters for the Get methods so your anonymous object properties can be called anything.The Filter class still only supports Equal and Approximately so I'll need to fill it out with the other operations. Happy coding!
Wednesday, August 24, 2011
LINQ To LDAP: Bye Bye Dynamics
Well, it was a fun ride while it lasted. Given the performance penalty for blindly accessing byte arrays and the guessing game that I have to handle to avoid it, it only makes sense to peel away the abstraction and give control back to the user.
So in place of dynamics I created IDirectoryAttributes and DirectoryAttributes. If you've ever worked with IDataReader, it will feel very familiar. Here are the get methods for the interface:
Pretty standard. Attribute is case insensitive. If it's not found then null will be returned. Since everything is either a string or byte array from the directory, if it fails to convert then a FormatException will be thrown. GetValue will try to guess the type like before and is only here to support the implementation of IEnumerable<string, object>. Of course if there's a datatype here that's missing, just use Get and you'll get direct access to the DirectoryAttribute.
So what about change tracking? Since DirectoryAttributes is just a wrapper for SearchResultEntry, setting values is tracked in an internal dictionary. Just call SetValue(attributeName, value) and it will take care of the rest.
That's it. I hope this helps with performance and satisfies anyone who can't / won't map classes.
So in place of dynamics I created IDirectoryAttributes and DirectoryAttributes. If you've ever worked with IDataReader, it will feel very familiar. Here are the get methods for the interface:
string DistinguishedName { get; }
object GetValue(string attribute);
DirectoryAttribute Get(string attribute);
byte[] GetBytes(string attribute);
string[] GetStrings(string attribute);
string GetString(string attribute);
byte? GetByte(string attribute);
int? GetInt(string attribute);
long? GetLong(string attribute);
double? GetDouble(string attribute);
decimal? GetDecimal(string attribute);
short? GetShort(string attribute);
float? GetFloat(string attribute);
bool? GetBoolean(string attribute);
DateTime? GetDateTime(string attribute, string format = "yyyyMMddHHmmss.0Z");
Guid? GetGuid(string attribute);
SecurityIdentifier GetSecurityIdentifier(string attribute);
IEnumerable<byte[]> GetByteArrays(string attribute);
Pretty standard. Attribute is case insensitive. If it's not found then null will be returned. Since everything is either a string or byte array from the directory, if it fails to convert then a FormatException will be thrown. GetValue will try to guess the type like before and is only here to support the implementation of IEnumerable<string, object>. Of course if there's a datatype here that's missing, just use Get and you'll get direct access to the DirectoryAttribute.
So what about change tracking? Since DirectoryAttributes is just a wrapper for SearchResultEntry, setting values is tracked in an internal dictionary. Just call SetValue(attributeName, value) and it will take care of the rest.
var factory = new LdapConnectionFactory("localhost");
string namingContext = "CN=Users,CN=Employees,DC=Northwind,DC=local";
using (IDirectoryContext context = new DirectoryContext(factory.GetConnection(), true))
{
var query = context.Query(namingContext, objectClass: "Person")
.Where("cn=Andrew Fuller")
.Select("cn");
var user = query.FirstOrDefault();
user.SetValue("employeeid", 1);
var updated = context.Update(user);
Console.WriteLine(updated.GetInt("employeeid"));
Console.WriteLine(updated.GetGuid("objectguid"));
}
That's it. I hope this helps with performance and satisfies anyone who can't / won't map classes.
Subscribe to:
Posts (Atom)

