views:

42

answers:

2

I am doing this in SharePoint 2010, but wouldn't be surprised if the problem exists in SharePoint 2007 and has the same solution.

I have a runtime security trimmer on my BDC data. I was expecting the security trimmer to give me URLs based off of the "default" profile URL defined in the model. Unfortunately, that is not the case. It gives me a URL like: bdc3://amms_amms/default/00000000%252d0000%252d0000%252d0000%252d000000000000/1971/amms/1973?s_id=ibqaaaaaaaaa=&s_ce=07nk0004080g10003o03vvf

I need to get the properties of this object (really just the primary key value). Any idea how I do this with the BDC object model? The following link seems to provide some help, but I haven't seen anything that consumes the URL above.

http://msdn.microsoft.com/en-us/library/ee556400.aspx


Update: I see that SharePoint 2007 has an AccessChecker (http://msdn.microsoft.com/en-us/library/aa981124.aspx) and 2010 likely has this as well (can't find good documentation for 2010 on this). We can't easily have security descriptors in the database, but the AccessChecker method might suffice.

Digging a little further I see that Microsoft.Office.Server.Search.Connector.BDC.BdcSecurityTrimmer is what is likely what is used by the AccessChecker in SharePoint 2010. It appears that this does a query to the DB per URL. Seems inefficient even if it does do it on multiple threads (which the 2007 documentation claims to do). I think I would prefer to batch up the information into a single web service call, but am on the fence...

A: 

I'm not sure it is the best approach, but I got this to work by using Reflector to reverse engineer Microsoft.Office.Server.Search.Connector.BDC.BdcSecurityTrimmer. I only needed the identity value so that simplified this a little.

Below is my code that takes an array of documentCrawlUrls provided to the security trimmer and translates them into an array of primary keys as defined in my BDC model file. Once I have those I can determine the security trimming using more custom .NET code.

In CheckAccess() of my security trimmer (ISecurityTrimmer2) I have:

String[] ids = GetIds(documentCrawlUrls);

Then I have the following private method:

private string[] GetIds(IList<string> documentCrawlUrls)
{
    string[] ids = new String[documentCrawlUrls.Count];
    for (int i = 0; i < documentCrawlUrls.Count; i++)
    {
        try
        {
            string url = documentCrawlUrls[i];

            Identity identity = null;
            IEntity entity = null;
            ILobSystemInstance lsi = null;
            ParseUri(url, out entity, out identity, out lsi);
            if (identity != null)
            {
                object[] values = identity.GetIdentifierValues();
                if (values.Length > 0)
                {
                    ids[i] = values[0].ToString();
                }
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Trace.WriteLine("Error: " + ex.Message);
        }
    }

    return ids;
}

I didn't want to rewrite the SPBdcUri class and it is internal, so I cheat with reflection. I currently only use one of the out parameters so I can improve on efficiency. I may re-write the portions of SPBdcUri that I need instead of resorting to reflection.

private void ParseUri(string crawlUri, out IEntity entity, out Identity identity, out ILobSystemInstance lsi)
{
    //SPBdcUri uri = new SPBdcUri(new Uri(crawlUri));
    AssemblyName assemblyName = new AssemblyName("Microsoft.Office.Server.Search.Connector, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
    Assembly assembly = Assembly.Load(assemblyName);
    Type spBdcUriType = assembly.GetType("Microsoft.Office.Server.Search.Connector.BDC.SPBDC.SPBdcUri");
    object uri = Activator.CreateInstance(spBdcUriType,
        BindingFlags.NonPublic | BindingFlags.Instance,
        null, new object[] { new Uri(crawlUri) }, System.Globalization.CultureInfo.CurrentCulture);

    //uri.DoOverrideBDCThrottlingLimits = false;
    spBdcUriType.InvokeMember("DoOverrideBDCThrottlingLimits",
        BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
        null, uri, new object[] { false });

    //entity = uri.Entity;
    object entityObj = spBdcUriType.InvokeMember("Entity",
        BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty,
        null, uri, null);
    entity = (IEntity)entityObj;

    //identity = uri.Identity;
    object identityObj = spBdcUriType.InvokeMember("Identity",
        BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty,
        null, uri, null);
    identity = (Identity)identityObj;

    //lsi = uri.LobSystemInstance;
    object lsiObj = spBdcUriType.InvokeMember("LobSystemInstance",
        BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty,
        null, uri, null);
    lsi = (ILobSystemInstance)lsiObj;
}

Oh, here's my "using" statements:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

using Microsoft.BusinessData.MetadataModel.Collections;
using Microsoft.BusinessData.MetadataModel;
using Microsoft.BusinessData.Runtime;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.BusinessData.SharedService;
using Microsoft.Office.Server.Search.Query;
Kirk Liemohn
A: 

OK, here's a simplification of my previous answer. It appears that you can totally avoid reflection:

using Microsoft.BusinessData.Runtime;
using Microsoft.Office.Server.Search.Connector;
using Microsoft.Office.Server.Search.Query;    

private string[] GetIds(IList<string> documentCrawlUrls)
{
    string[] ids = new String[documentCrawlUrls.Count];
    for (int i = 0; i < documentCrawlUrls.Count; i++)
    {
        try
        {
            string url = documentCrawlUrls[i];
            string id = new Microsoft.Office.Server.Search.Connector.UriParser(new Uri(url)).QueryStringParameters["s_id"];
            ids[i] = Identity.Deserialize(id).GetIdentifierValues()[0].ToString();
        }
        catch (Exception ex)
        {
            System.Diagnostics.Trace.WriteLine("Error: " + ex.Message);
        }
    }

    return ids;
}

Note that I tried avoiding using the UriParser from Microsoft.Office.Server.Search.Connector using code like:

string id = HttpUtility.ParseQueryString(new Uri(url).Query)["s_id"];
ids[i] = Identity.Deserialize(id.ToUpper()).GetIdentifierValues()[0].ToString();

Unfortunately, this worked for some Id's and not others. I decided not to investigate any further and just use the special UriParser. In one example the ids I was looking for were "5,20,21,7,8,6,14,19,17,18,4" but this second approach gave me "5,20,21,24581,8,24580,24588,24593,17,24592,4". That messed me up for a few minutes since the first 3 were correct.

Kirk Liemohn