Thursday, September 20, 2007

Secured section inside web.config

Finally a chance to work on some code - six months of writing documentation just isn't good for the health!!

So in my web app I want to store an ID and password, and I want to encrypt these keys to secure them. But there are other settings I want to leave in clear text to make for easier editing for my admins, and less web page work for me. The answer - custom sections inside my ASP.Net 2.0 web.config.

Good stuff! But as usual, it ain't straight forward. Here's what I ended up implementing. The web.config contains the following entry to create the new secured section:

















Then inside my project I created the following class. It sets up the key/value pairs I needed inside my secure settings section of web.config.

Then, to use the class, easy peasy - just need to iterate through the pairs to find the key I need:

private string GetSecureConfigKey(string key)
{
String retval = "";
SecureConfiguration configInfo = (SecureConfiguration)ConfigurationManager.GetSection("secureSettings");
if (configInfo != null)
{
foreach (SecureConfigurationADKey secureKey in SecureConfiguration.GetConfig().ADKeys)
{
if (secureKey.Key.ToLower().Equals(key.ToLower()))
retval = secureKey.Value;
}
}
return retval;
}

To set the value, I needed to do something similar - but kept getting an error message that the configuration was read only. Huh?? Problem was I tried setting the value inside my foreach loop - tsk tsk. So instead I use the following:

Configuration objConfig = WebConfigurationManager.OpenWebConfiguration("~");
SecureConfiguration configInfo = objConfig.GetSection("secureSettings") as SecureConfiguration;
foreach (SecureConfigurationADKey secureKey in SecureConfiguration.GetConfig().ADKeys)
{
if (secureKey.Key.ToLower().Equals(Key.ToLower()))
{
index = i;
}
i++;
}

then

configInfo.ADKeys[index].Value = Value;

And all's well that ends well. Note too that I check the .IsProtected property of my section when I save the value:

if (!configInfo.SectionInformation.IsProtected)
{
configInfo.SectionInformation.ProtectSection("RsaProtectedConfigurationProvider");
configInfo.SectionInformation.ForceSave = true;
}

and it works!

The class I use for the new web.config section follows:
public class SecureConfiguration : ConfigurationSection
{
public static SecureConfiguration GetConfig()
{
return ConfigurationManager.GetSection("secureSettings") as SecureConfiguration;
}

[ConfigurationProperty("adInfo")]
public SecureConfigurationADKeysCollection ADKeys
{
get
{
return this["adInfo"] as SecureConfigurationADKeysCollection;
}
set
{
this["adInfo"] = value;
}
}
}

public class SecureConfigurationADKey : ConfigurationElement
{
[ConfigurationProperty("key", IsRequired = true)]
public string Key
{
get
{
return this["key"] as string;
}
set
{
this["key"] = value;
}
}

[ConfigurationProperty("value", IsRequired = true)]
public string Value
{
get
{
return this["value"] as string;
}

set
{
this["value"] = value;
}
}
}

public class SecureConfigurationADKeysCollection : ConfigurationElementCollection
{
public SecureConfigurationADKey this[int index]
{
get
{
return base.BaseGet(index) as SecureConfigurationADKey;
}
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}

protected override ConfigurationElement CreateNewElement()
{
return new SecureConfigurationADKey();
}

protected override object GetElementKey(ConfigurationElement element)
{
return ((SecureConfigurationADKey)element).Key;
}
}

Friday, August 17, 2007

ASP.Net 2.0 and web part properties

So I take it back - one can store web part properties with web parts. I was missing the WebBrowsable property - that seems to do the trick. From Ted Pattison's book, Inside WSS v3 - good reading indeed!

private string xmlUrl;
[ Personalizable(PersonalizationScope.Shared),
WebBrowsable(true),
WebDisplayName("Feed Url"),
WebDescription("Set your RSS feed's XML URL here!")
]
public string XmlUrl {
get { return xmlUrl; }
set { xmlUrl = value; }
}

Tuesday, May 08, 2007

Web Parts catalog

Had a question come up last week - where's a good place for finding a catalog of SharePoint web parts? Well, here's a list - some pretty decent stuff. I like the Charting web part, nice way to add charts to WSS v2. Note that these are all for V2/2003.

http://www.sharepointcustomization.com/resources/wpsamples.htm

Wednesday, May 02, 2007

Outlook 2007 - Deliver Mail to Offline Folder

OK, it's not a post about SharePoint (got one of those coming - workflow - cool stuff) but I was looking & looking for this when it was right in front of me all the time. I like having my Outlook mail pulled off the server & delivered locally. Cached Exchange Mode, yes? In Outlook 2007 to get mail delivered to the Offline folder and downloaded from the server, so that I don't get the dreaded "Mailbox is getting pretty big there dude" message, do the following:

1) Create an offline mail folder
2) Tools / Options / Mail Setup / Data Files
3) Locate your offline folder, click once, then click "Set as Default"
4) Wait whilst your mail downloads....
5) Send you and all your colleages a 25MB attachment!!

Tuesday, April 10, 2007

Day of Dot Net 2007 Speaker

I'll be speaking at Day of Dot Net 2007 in Ann Arbor 5/5 - see you there!! I'll be talking about SharePoint 2007 - WSS & MOSS & all that good stuff!



Day of .Net May 5, 2007 - I'll be there!

Wednesday, April 04, 2007

SPListItem.Update isn't updating!

Here's a good one. Why doesn't this work:

SPList myList = myWeb.Lists["MyList"];

for (int i == 0; i < myList.Count; i++)
{
myList.Items[i]["Value"] = "42";
myList.Items[i].Update();
}

This *should* set the Value column to 42 for every item in the list. But for some reason, no exception, no Event Log entry, running with impersonation, etc etc - this just doesn't work.

The reason: laziness. Here's what does work:

SPList myList = myWeb.Lists["MyList"];

for (int i == 0; i < myList.Count; i++)
{
SPListItem myItem = myList.Items[i];
myItem["Value"] = "42";
myItem.Update();
}

myItem isn't disposable, so that should do the trick.

Monday, March 26, 2007

SharePoint 2003 & SQL Server 2005

The answer is Yes...as long as you're using Service Pack 2. Note that the installation process is a bit different. Instead of going through the complete installs (ie, creating the config & content databases) you stop midstream, install SP2, then carry on.

http://support.microsoft.com/kb/917446

and

http://office.microsoft.com/en-us/techcenter/HA100806971033.aspx

Thursday, March 15, 2007

Where are my Custom Properties?

So you've been doing web part development on SPS2003, you've built web parts with custom properties, which are found when you switch to edit page, edit web part properties. All well and good. Now along comes WSS v3 which use ASP.Net 2.0 web parts as well as SharePoint web parts.

You install the VS.Net 2005 extensions for WSS, you build the sample web part project as described in MSDN, you see that indeed custom properties are supported, cool.

So now you go and copy over some code from your SPS 2003 web parts, you've got the code to add in your custom property:

[Browsable(true), Category("Miscellaneous"), DefaultValue(defaultText), WebPartStorage(Storage.Personal), FriendlyName("Text"), Description("Text Property")]
public string Text
{
get { return text; }
set { text = value; }
}

But when you go to edit your web part properties - this custom property isn't there!!

Here's the catch. From the example in the SDK, the custom property isn't really visible to the ASP.Net web part - instead, the HTMLTextbox control is added to the web part, with an HTMLButton used to call the SaveProperties method.

All well and good, but to me this means my end users can see all of those ugly properties! If the properties are only visible on the Web Part Properties page, using the new security trimming the end user wouldn't be able to see the properties. I'm not so concerned about securing the values of the properties, I just don't want to clutter up my web pages with all these text boxes!

I suppose I could add in code to check the user's security and use CSS to hide the properties, but then that's a performance hit.

The answer: go back to good ol Microsoft.SharePoint.WebPartPages.WebPart instead of System.Web.UI.WebControls.WebParts.WebPart. That is - instead of using the ASP.Net web part base class, you'll need to use the SharePoint web part base class. You can see this yourself if you switch the base class of that SDK sample - now, a Miscellaneous section will appear on the web part properties because the web part is a SharePoint web part, not an ASP.Net web part.

You might also get an error if you try to add a web part that was originally an ASP.Net web part that you re-based the class to become a SharePoint web part...looks like the answer, unfortunately, is to create a brand new project using these instructions:

Creating a SharePoint Web Part


I've only tried this on WSS, but that's sure what seems to be going on...

Wednesday, March 14, 2007

So you're ready to deploy your webpart....

You've built your web part, deployed it on your VPC, now it's time to deploy it on the production WSS box...here's what I did.

  1. In Visual Studio, switch to Build configuration
  2. Rebuild and deploy once again to your local VPC
  3. Go to the project\bin\release directory, look for Setup.bat, and edit the file, looking in line 22 to change the target URL from your http://myserver VPC server to http://prodserver
  4. Zip up the entire Release directory, then copy it to your prod server
  5. RDC to the prod server - you'll need to use an account that's in the SharePoint admin group. Otherwise when you run the stsadm.exe command you'll get some Object Not Found error messages, or messages about being unable to bind to SQL Server
  6. Unzip your project, then run Setup.exe
  7. Once done, the web part will be activated on the root site of your portal/WSS site. You can open it up, then add in the web part. You could also go to any other site and activate the web part and do the same.
  8. When you add in your web part, if you get the error message: "Unable to add selected web part(s). Incompatible Web Part markup detected. Use *.dwp Web Part XML instead of *.webpart Web Part XML", well, the catch is you're using the wrong project base class. That is - with WSS/MOSS, you can use the ASP.Net 2.0 web part framework, or the good ol SharePoint web part framework. The SDK provides a good explanation of this. If you built your web part from the VS.Net template, then you'll need to make sure you're using the ASP.Net base class: System.Web.UI.WebControls.WebParts.WebPart instead of Microsoft.SharePoint.WebPartPages.WebParts. How to fix this? That's for another post.

Must have's for VS2005 SharePoint coding

You gotta have the following:

Code snippets - you can add snippets to create the code structure for your entire web part or a web part property:

http://weblogs.asp.net/jan/archive/2006/02/01/437037.aspx

Windows Workflow Foundation. Doing workflow development? You'll need this installed first, otherwise you'll get a goofy error message when you try to instantiate the workflow (forgot to copy it):

VS.Net 2005 extensions for WF 3.0
http://www.microsoft.com/downloads/details.aspx?FamilyId=5D61409E-1FA3-48CF-8023-E8F38E709BA6&displaylang=en


VS.Net 2005 extensions for WSS v3:
http://www.microsoft.com/downloads/details.aspx?familyid=19f21e5e-b715-4f0c-b959-8c6dcbdc1057&displaylang=en

Details about Site Enumeration

There are a few different ways to enumerate sites through the SharePoint Object Model. One thing to keep in mind: a 'site' in OM lingo refers to a top level site. Everything else - subsites - are referred to as 'webs'.

When you create a new SharePoint site, unless you're creating a new Site Collection through SharePoint Central Administrator, you're creating a subsite on an existing site collection. This is true from the root WSS site - http://myserver - any sites created through the Site Actions menu are subsites of the root site.

So - here are some properties I use to crawl through the set of site collections & subsites.

SPSite.AllWebs - this property of the SPSite object returns the top level site as well as all of the subsites of the top level site. This is helpful if you need to get a list of all of a WSS web application's sites and subsites, say if you were doing a site specific backup or report. But since the data listed isn't hierarchical, if you want to build a site tree view not all that helpful.

SPSite.RootWeb - this returns the SPWeb object for a site. Most useful when you get a top level site and want to look at the contents of the site, not just site properties.

SPSite.RootWeb.Webs - this returns just the site collections underneath the SPSite object.

SPWeb.Webs - returns the sites immediately underneath the SPWeb site

Now, let's say you want to open up the URL http://server/sites/sc1/subsite1 using the following line of code:

SPSite parentSite = new SPSite("http://myserver/sites/SC1/SubSite1")

If you look at parentSite.Url, you'll see the URL to the site collection: http://myserver/sites/SC1, and not the Subsite you wanted. There's probably a more elegant way to do this, but what I do is use the AllWebs property then get the site relative URL ('SubSite1') as the AllWebs indexer:

String targetUrl = "http://server/sites/sc1/subsite1";
String relativeUrl = targetUrl.Replace(parentSite.Url, ""); // parentSiteUrl = http://myserver/sites/sc1
SPWeb subsiteWeb = parentSite.AllWebs[relativeUrl]; // relativeURL = 'SubSite1'

SharePoint web parts vs ASP.Net 2.0 web parts

When I was setting up my second WSS v3 web part (got HelloWorld to work!) I needed to add in a web part property. I copied in the correct code, but that property just would not appear when I edited the web part settings. The culprit: When developing a web part for WSS v3 or MOSS, make sure to extend Microsoft.SharePoint.WebPartPages.WebPart instead of System.Web.UI.WebControls.WebParts.WebPart. That is:

public class MyWebPart: Microsoft.SharePoint.WebPartPages.WebPart

instead of

public class MyWebPart: System.Web.UI.WebControls.WebParts.WebPart

You might also want to remove the reference to System.Web.UI.WebControls.WebParts from your Using/Imports statements.