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:
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):
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.

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