Skip to main content
 
Go Search
Home
Categories
Bloggers
By: Michael Becker | Posted: April 5, 2010 at 11:15 AM

As part of a FAST search proof of concept, I got the opportunity recently to create slightly more advanced navigator than the typical text-based set of hyperlinks. I used the FAST ESP WebParts (available on CodePlex) as a starting point and the Dundas Chart control as the basis for the Ajax user interface. Getting the data from FAST into the chart was surprisingly easy.

Here’s what it ended up looking like:

image

Here’s what was involved:

FAST configuration:

You’ll need to update your index profile if it doesn’t already contain what you need. In my case, I have a field called “pubyear” that I’ve added that gets an integer placed in it during document processing:

<field name="pubyear" type="int32"/>

And here’s the numeric navigator for that field:

<numeric-navigator name="pubyearnavigator" display="Publication Year" unit="year" algorithm="equalwidth" resolution="1">
    <field-ref name="pubyear" />
</numeric-navigator>

WebPart stuff:

  • Create a webpart class and derive from ESPSearchWebPart – this gets you wired up to search context, etc.
  • Provide overrides for ConfigureSearch and the other ESPSearchWebPart stuff as necessary. You can basically just steal the implementation from one of the “out-of-the-box” navigators and change what you need to.
  • Override CreateChildControls and load the usercontrol that will do the interesting parts.

ASP.NET and other stuff:

  • Create a usercontrol
  • In the Page_Load event, go get the relevant IESPNavigator from search context. This is where having the FAST ESP WebParts comes in handy:

foreach (IESPNavigator espNavigator in this.SearchResult.Navigators)
{
    if (espNavigator.Name == "pubyearnavigator")
    { 
        Dictionary<string, int> years = getYears(espNavigator.NavigatorItems.GetEnumerator());
        Chart1.Series["Series1"].Points.DataBindXY(years.Keys, years.Values); 
        Chart1.ChartAreas["Default"].CursorX.UserEnabled = true; 
        Chart1.AxisViewChanged += new ViewEventHandler(Chart1_AxisViewChanged); 
    } 
}

A couple of helper methods to convert the data that FAST returns to something that the chart can easily consume:

private Dictionary<string, int> getYears(IEnumerator<IESPNavigatorItem> navigatorItems)
{
  Dictionary<string, int> years = new Dictionary<string, int>();

  while (navigatorItems.MoveNext())
  {
      IESPNavigatorItem navigatorItem = (IESPNavigatorItem)navigatorItems.Current;
      years.Add(parseDate(navigatorItem.FilterValue), navigatorItem.ResultCount);
  }

  return years;
}

private string parseDate(string modifierValue)
{
  //Looks like 3 different variants -- the first value, all the inner values, and then the last value...
  //[;2008-04-01T00:00:00Z]
  //[2008-04-02T00:00:00Z;2008-04-02T00:00:00Z]
  //[2009-12-01T00:00:00Z;]

  //get rid of [ and ] brackets
  //take the second value after the ; (unless there is no second, then take the first)
  //drop the time (T and everything after it)
  string[] dates = modifierValue.Trim(new char[] { '[', ']' }).Split(';');

  return dates[string.IsNullOrEmpty(dates[1]) ? 0 : 1].Split('T')[0];
}

The whole thing really didn’t end up being much work. The ESP WebParts take care of all of the details of the query server semantics, and the Chart control took care of most of the UI. There were a few details to be worked out in the UI obviously. I also had to tweak some of the code in the SearchRequest, ESPSearchManager and FQLQueryBuilder objects in order to support the slightly more complicated data that has to flow back up to the query server to support “ranges” in the navigator rather than a single value.

Here are the links to the items I used:

FAST ESP WebParts for SharePoint 2007

Dundas Chart for .NET

By: Michael Becker | Posted: August 14, 2008 at 10:30 AM

I'm posting this here because I always have trouble finding it when I need it.

SQL Server 2005 ships by default with a utility for exporting objects to T-SQL scripts. Unfortunately, it doesn't allow you to export the data in your tables.

There is, however, a freely available add-on that does this for you – it works just like the wizard that's included out of the box, but the last step asks you if you want schema and data, data only or schema only.

You can get the "Microsoft SQL Server Database Publishing Wizard 1.1" here:

http://www.microsoft.com/downloads/details.aspx?familyid=56E5B1C5-BF17-42E0-A410-371A838E570A&displaylang=en

In order for it to install properly, you will probably also need the "Feature Pack for Microsoft SQL Server 2005 - April 2006" which you can get here:

http://www.microsoft.com/downloads/details.aspx?FamilyID=df0ba5aa-b4bd-4705-aa0a-b477ba72a9cb&displaylang=en

By: Michael Becker | Posted: March 21, 2008 at 3:05 PM

One option for securing ASP.NET pages that – IMHO – gets neglected quite often is the use of CodeAccessSecurity "Permission Attributes." You can read more about the general approach here.

Basically, they provide a very nice mechanism for encapsulating all of the rules you want to apply that will allow or disallow a particular user from viewing a page. You then drop the attribute onto the Page_Load event of the page you want to protect, and you're done.

I thought I was going to need this approach to protect some page that were stored in the layouts section of a SharePoint site I was working with a while ago, but the need never materialized. I basically wanted to only allow access to these pages if the user was a member of a particular SharePoint group.

Here's the approach I came up with.

First, I needed an IPermission implementation that would actually make the decision and enforce it. Here is that implementation (you'll notice I left quite a lot unimplemented since I didn't need it…):

    public class SPUserPermission : IPermission

    {

        private string _role;

 

        public SPUserPermission()

        {

        }

 

        public SPUserPermission(string role)

        {

            _role = role;

        }

 

        #region IPermission Members

 

        public void Demand()

        {

            bool success = false;

 

            SPUtility.EnsureAuthentication();

 

            SPUser spUser = SPContext.Current.Web.CurrentUser;

 

            if (spUser != null)

            {

                foreach (SPGroup spGroup in spUser.Groups)

                {

                    if (spGroup.Name.ToLower() == _role.ToLower())

                    {

                        success = true;

                        break;

                    }

                }

            }

 

            if (!success)

            {

                SPUtility.HandleAccessDenied(new UnauthorizedAccessException());

            }

        }

 

        #region un-implemented members

 

        public IPermission Copy()

        {

            throw new NotImplementedException();

        }

 

        public IPermission Intersect(IPermission target)

        {

            throw new NotImplementedException();

        }

 

        public bool IsSubsetOf(IPermission target)

        {

            throw new NotImplementedException();

        }

 

        public IPermission Union(IPermission target)

        {

            throw new NotImplementedException();

        }

 

        #endregion

 

        #region ISecurityEncodable Members

 

        public void FromXml(SecurityElement e)

        {

            throw new NotImplementedException();

        }

 

        public SecurityElement ToXml()

        {

            throw new NotImplementedException();

        }

 

        #endregion

 

        #endregion

    }


Next I need an attribute that uses this implementation:

    public class SPUserPermissionAttribute : CodeAccessSecurityAttribute

    {

        private string _role;

 

        public SPUserPermissionAttribute(SecurityAction action)

            : base(action)

        {

        }

 

        public override IPermission CreatePermission()

        {

            return new SPUserPermission(this.Role);

        }

 

        public string Role

        {

            get

            {

                return _role;

            }

            set

            {

                _role = value;

            }

        }

 

    }

You'll notice that all this attribute does is create an instance of my IPermission implementation and pass it the Role property that's passed into the attribute. What happens next is determined by how I use this attribute on my pages:

    [SPUserPermission(SecurityAction.Demand, Role = "Foo")]

    protected void Page_Load(object sender, EventArgs e)

    {

    }

 

When Page_Load fires, this attribute must fire first. It will initialize my SPUserPermissionAttribute and pass through the action that I want – Demand, and the Role that I want – Foo. This will cause my IPermission implementation's Role property to be set and its Demand() method to fire. That will grab the CurrentUser from SPContext and iterate its Groups looking for my Role propery value. If it can't find it, SPUtility.HandleAccessDenied will be called and you'll find yourself on SharePoint's Access Denied page.

I'm relatively sure that this approach would work out nicely. However, some caveats apply since this never ended up making it into a production system. Hopefully someone will find this useful.

By: Michael Becker | Posted: September 13, 2007 at 12:45 AM

Forms Based Authentication in ASP.NET 2.0 has a nice feature that allows you to control whether or not your authentication ticket is stored in an HTTP cookie, or on the URL. In order to force the ticket to be transported on the URL, you simply add the attribute cookieless="UseUri" to the configuration\system.web\authentication\forms node in your web.config. Now all of your URLs will automatically get the authentication ticket appended to them before being written out to the client browser. Inbound URLs will automatically have the ticket deserialized and the appropriate identity will be placed on the new thread for the request. Basically, it all just works, as long as you play by the rules.

One of the important rules is that all URLs must be relative, otherwise the framework is not going to write the authentication ticket into them on the way out. If that ticket isn't written into the URL, and a user clicks it, the result will be that the new request comes in unauthenticated. This is a subtly different than the case for authentication tickets stored in HTTP cookies. Since modern browsers (unless specifically configured not to) will automatically send up HTTP cookies with new requests, it doesn't matter whether you use absolute or relative links on your site, as long as you don't end up changing the domain. If the domain changes, the cookie is not coming back up.

So… MOSS and WSS 3.0 are both now solidly based on the ASP.NET 2.0 platform, so this should all work in a SharePoint site, right? Well, sort of. So basically, no – not at all. Once you update your web.config to use "cookieless mode" everything appears to be working at first. You authenticate, and the ticket magically appears in your URL. The problems start to appear once you begin to navigate around your site. Some URLs have the ticket in them, and some don't. It basically comes down to this – if a URL is written using a standard ASP.NET control, or if you write a relative URL yourself, the ticket is preserved and cookieless FBA works. However, if a SharePoint control writes the URL, it's absolute, the ticket is not written, and FBA breaks.

We ran this past our PSS contacts, and apparently this is a known "issue", and it's not slated to be addressed in the 3.0 product. Basically, the feature is not supported. It no doubt has something to do with SharePoint writing URLs to support Alternate Access Mappings. There's really no reason why it couldn't write them relatively, but it obviously doesn't.

At any rate, I couldn't find any evidence of anyone else out there trying this – successfully or not. And it does not seem to be documented anywhere that I could find. So, if you're doing research to see if you can do this – everything I've seen and heard says you can't.

By: Michael Becker | Posted: September 12, 2007 at 2:16 PM

After a conversation with Travis recently about the home lab environment he built for hosting virtual machines, I got a serious itch to build a new machine myself. With RAM prices as low as they are, it's actually not too expensive to put together a machine that can easily host eight VMs simultaneously with perfectly adequate performance. This makes SharePoint development a lot easier since you can set up a full blown MOSS environment that you have complete control over without having to run everything on your laptop.

Here are the specs for anyone who's interested:

That's pretty much it. Throw in a CD-ROM so that you can install your host OS (or just borrow a CD-ROM from another machine temporarily.) The grand total was just over $1000. For me, running 8 VMs at once and seeing the below makes it all worthwhile.

 

 About Michael Becker

ArchitectMichael Becker is an architect for PointBridge. He has over 10 years of experience in the IT industry ranging from “Big 5” consulting to corporate IT for the insurance and investment industries. He is... [more]

 Tag Cloud

 External Links

 ‭(Hidden)‬ Admin Links