Monday, January 12, 2009
Search related errors...
Same thing if I tried opening up any of the Search config pages in SSP Config. The logs had all sorts of stuff - missing "S2LeftNav_Administration", missing features, and the SSP Search DB had some broken stored procedures, Proc_MSS_Recompile was missing, proc_MSS_FlushTemp0 was missing some arguements, the Microsoft.SharePoint.Portal.WebControls.SearchBoxEx control was missing, all kinds of stuff.
Found a post where someone had applied the July Infrastructure Update and had seen some of these errors, and had a fix of updating certain specific files. But, I didn't apply that fix as one of my colleagues said there was an issue with the Alternate Access Mapping (subsequently fixed in the October update).
Well, looking back into the Event Viewer, I saw that 'someone did something' - Dec 19th a security patch for SharePoint was applied to close up a hole with Search. With the odd state of things, I guessed that whomever applied the update probably freaked out when the SharePoint Configuration Wizard started & bailed out - bad idea!! Sure enough, re-running the Wizard (with screen caps which are going into an admin guide thank you very much indeed) cleared up the problems.
Next weirdness was a 10 hour incremental crawl, and the indexer showing "Computing ranking" for a long spell. Well, stopped the crawl, reset the index, then did a full crawl, then about 10 minutes later (not a heap of content) started getting search results again.
But of course, Search not working for three weeks ought to have raised at least one trouble ticket!! Looks like it's time for some end user education...
Wednesday, October 22, 2008
FIPS and SharePoint
Well, one monkey off the back at long last. Way back in February 2008 we ran into a problem where SharePoint would not work when the "System Crptography: Use FIPS compliant algorithms for encryption, hashing and signing" AD security policy was set to Enabled. SharePoint would come up with "An unexpected error occurred", with some 6482 messages in the Event Logs.
Well, along came the August Cumulative Updates, which apparently resolved the issue. So I installed the CU's per the instructions here, enabled FIPS, and what do you know, it still didn't work, still got "An unexpected error occurred"!!!
Well, turns out that the fix for SharePoint indeed just disables logging of those 6482 error codes. In addition to those CU's, you still need to update the web.config for the SharePoint site, so that it tells .Net to use the encryption that FIPS requires. Which oddly enough doesn't seem to be as strong as the non-FIPS encryption, but then I'm no expert! Please see this article for the details on how to modify the machinekey setting to use the 3DES algorith. Good luck!!
Tuesday, August 19, 2008
Custom ServerURL field in a SharePoint list
Trying to use an SPWeb object that has been closed or disposed and is no longer valid.
Turns out ServerURL is an internal field - when I reference ServerURL0 then I get my own field. Reckon it's time to re-create that field with a different name!
Tuesday, April 29, 2008
Day of Dot Net - West Michigan
Showing icons in a list view
I started looking in to the KPI Dashboard sample to see if I could somehow customize this, but after an hour and a cup of Starbuck's it was still none too clear. Then the coffee cleared away the neurons & I recalled seeing discussion on using the spankin' new XSLT Data Viewer. I had done used this with SPS 2003 and it was pretty good then...and it's better now with MOSS.
The XSLT Data Viewer is a MOSS goodie, sorry WSS'ers. To use it, I fired up SharePoint Designer, opened up my list, changed to the 'Split' view, then with a right click I chose the Convert to XSLT Data View option.
In my list I have a SLA column, a Target column, and a Status column. I wanted the indicator in the Status column.
After a few seconds (slow on the VPC for some reason) I was able to view the list. Looking at the XSLT, for the Status column, I changed what was there to:
<!--Status-->
<TD Class="{$IDAVVHVE}"><xsl:choose>
<xsl:when test="@SLA amp-gt; @Target">
<img src="/_layouts/images/KPIDefault-Blue.gif" alt="Exceeded SLA Target"/>
</xsl:when>
<xsl:when test="@SLA= @Target">
<img src="/_layouts/images/KPIDefault-2.gif" alt="Met SLA"/>
</xsl:when>
<xsl:when test="@SLA amp-lt; @Target">
<img src="/_layouts/images/KPIDefault-0.gif" alt="Missed SLA"/>
</xsl:when>
</xsl:choose>
</TD>
Change the 'amp-gt;' to an ampersandgt; (I should find a better blog page) and 'amp-lt;' to the ampersandlt; and you're good to go!
Now I need to come up with a nice shape for the Blue icon. Blue Moon? mmmm, tasty, good idea!
Tuesday, April 08, 2008
EditControlBlock registrationtypes
So...I could either add code that points out to the dear user that one ought not use this with folders, or see if there was a way to hide the new menu item from folders - that is, the new menu item appears only for documents.
RegistrationType and RegistrationID to the rescue. These items are part of the custom action XML defined by the Feature.XML. Here's what I've been able to determine. RegistrationType allows for "ContentType" and "FileType" values, as well as List and ProgID.
Specifying "ContentType" is what I'm after. This will allow me to associate the new action with a specific content type. The RegistrationID element is used to specify the ID of the content type. To get this ID, open your document library settings, in the ContentType section click on the content type of interest, then grab the hex string in the URL after the "ctype=" parameter. Paste this value in to the RegistrationID value.
Alternatively, if I only want this feature to appear for ".xls" files, then I'd use the FileType RegistationType, and the value "xls" for the registationID. Repeat the CustomAction block for other file types.
The completed Feature.XML looks like:
Feature.xml:
<?xml version="1.0" encoding="utf-8" ?>
<Feature Id="1a8e5fae-577f-4cbd-addb-a31d875300a6"
Title="Copy Document"
Description="My custom document copy feature"
Version="1.0.0.0"
Scope="Web"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="CopyDocument.xml" />
</ElementManifests>
</Feature>
CopyDocument.xml:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- Per Item Dropdown (ECB)-->
<CustomAction
Id="Custom.PromoteDocument"
RegistrationType="ContentType"
RegistrationId="ctype=0x01010038C4691B76BEB34CB53E65CADDF82AD3"
Location="EditControlBlock"
Rights="EditListItems"
Title="Promote Document">
<UrlAction Url="~site/_layouts/MyCustomCopyDocument.aspx?List={ListId}&ID={ItemId}"/>
</CustomAction>
</Elements>
Note also the use of {ListId} and {ItemID} - I use this with my code behind file to get the SPList and SPListItem from the content SPWeb. With these I can then get to the attachment object of the SPListItem, so I can copy that document around.
Monday, January 28, 2008
Change AD password with code
First, for background, take a look at this excellent article: AccountManagement Namespace
Joe & Ethan give a great overview of using the new features in 3.5.
In my web part, then, I have a three text boxes and a "Change" button - the bulk of the web part is based on the work done by Robin Meuré did here, with a few cosmetic changes but the main change done to the code in the btn_Click, posted here:
SPWeb webContext = SPControl.GetContextWeb(Context);
string strLoginName = webContext.CurrentUser.LoginName;
PrincipalContext domainContext = new PrincipalContext(ContextType.Domain);
UserPrincipal user = UserPrincipal.FindByIdentity(domainContext, strLoginName);
try
{
string oldPasswordValue = oldpassword.Text;
string newPasswordValue = newpassword.Text;
user.ChangePassword(oldPasswordValue, newPasswordValue);
output.Text = "Password changed. Please close this browser window and log back on with your new password.";
}
catch (Exception ex)
{
output.Text += String.Format("Password couldn't be changed due to restrictions: {0}", ex.Message);
}
finally
{
user.Dispose();
domainContext.Dispose();
}
That's it! Easy-peasy.
Note that when I was getting the user object, I first tried just getting a UserPrincipal object by context:
UserPrincipal user = new UserPrincipal(domainContext);
Problem here was, when i invoked the ChangePassword method, I got the very friendly message back that: ChangePassword method can not be called on an unpersisted Principal object. So instead I get the user account info from the SPContext object, pass that in to the Directory services methods, and we're good to go.
Thursday, January 17, 2008
To copy a file from one lib to another...
So the challenge has been adding a menu item to the Edit Control Block to copy items from one library to another. There are some good examples out there, Andrew Connell's blog has a great example, so I won't repeat it - but I will show all of the code I have. Might help you out.
In this scenario, I have three "document center" on my portal: Working, Staging, and Production ("Docs"). I want to let my users choose when to promote a document, but I didn't want this to be workflow dependent: ie, not based on setting a document property, or based on a check in event, etc - let the user decide.
The drop down for the Edit Control Block was the answer. "Promote Document" will now appear for users with the right access. This feature passes the list ID and item ID to an ASPX page, which then does the work of copying the document over.
Some helpful hints:
In your feature's XML file, you can assign the "Rights" value to the minimum rights a user needs in order to see your new ECB item. That way it won't show up for everyone - nice!
Inside the code, after moving the file, the field items won't copy also. So you'll need to copy them with code. But then when you perform a ListItem.Update the ModifiedBy turns in to the System Account (assuming impersonation) - ick! So intead use the ever cool SPListItem.SystemUpdate() which does *not* change the Modified or ModifiedBy properties.
Here we are:
Feature.xml
<?xml version="1.0" encoding="utf-8" ?>
<Feature Id="1a8e5f42-5742-4c42-ad42-a31d87530042" // enter your own guid
Title="Promote Document"
Description="Promote a document from work to staging, or from staging to production" Version="1.0.0.0"
Scope="Web"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="PromoteDocument.xml" /> <
/ElementManifests>
</Feature>
The CopytoWorking.XML file, the manifest listed in feature.xml:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- Per Item Dropdown (ECB)-->
<CustomAction
Id="CopytoWorking"
RegistrationType="ContentType"
RegistrationId="0x01"
Location="EditControlBlock"
Rights="EditListItems"
Title="Promote Document">
<UrlAction Url="~site/_layouts/PromoteDocument.aspx?List={ListId}&ID={ItemId}"/> </CustomAction>
</Elements>
and finally the ASPX page. This uses the Application.Master page layout so the new page looks sort of like a SharePoint page. Worked for me!
<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Page language="C#" MasterPageFile="~/_layouts/application.master" %>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" src="~/_controltemplates/ButtonSection.ascx" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="System.Net" %>
<asp:Content ID="Content1" ContentPlaceHolderId="PlaceHolderPageTitle" runat="server">
<asp:Literal id="PageTitleLabel1" runat="server" ></asp:Literal>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderId="PlaceHolderPageTitleInTitleArea" runat="server">
<asp:Label id="PageTitleLabel" runat="server" ></asp:Label>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderId="PlaceHolderAdditionalPageHead" runat="server">
<script runat="server">
public void CancelClick(Object obj, EventArgs e)
{
SPWeb oWeb = SPContext.Current.Web;
SPList oList = oWeb.Lists[new Guid(Context.Request["List"])];
Response.Redirect(oList.DefaultViewUrl);
}
public void ConfirmClick(Object obj, EventArgs e)
{
SPWeb oWeb = SPContext.Current.Web;
SPUser currentUser = oWeb.CurrentUser;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
oWeb.AllowUnsafeUpdates = true;
SPList oList = oWeb.Lists[new Guid(Context.Request["List"])];
SPListItem oItem = oList.GetItemById(int.Parse(Context.Request["ID"]));
byte[] fileContent = oItem.File.OpenBinary();
String currentUrl = oItem.File.ServerRelativeUrl;
string[] currentUrlSplit = currentUrl.Split('/');
if (currentUrlSplit[1].CompareTo("Staging") == 0)
{ currentUrlSplit[1] = "Docs"; }
if (currentUrlSplit[1].CompareTo("Working") == 0)
{ currentUrlSplit[1] = "Staging"; }
StringBuilder sb = new StringBuilder();
String siteUrl = oWeb.ParentWeb.Url;
string[] siteUrlSplit = siteUrl.Split('/');
sb.Append(siteUrlSplit[0]); // this is the protocol
sb.Append("//");
sb.Append(siteUrlSplit[2]); // this is the server
string server = sb.ToString();
// now add the rest of the URL - site & folders
for (int i = 1; i < currentUrlSplit.Length; i++)
{
sb.Append("/");
sb.Append(currentUrlSplit[i]);
}
String newUrl = sb.ToString();
SPWeb newWeb = new SPSite(newUrl).OpenWeb();
newWeb.AllowUnsafeUpdates = true;
EnsureParentFolder(newWeb, newUrl);
// copy the file over
SPFile newFile = newWeb.Files.Add(newUrl, fileContent, currentUser, currentUser, oItem.File.TimeCreated, oItem.File.TimeLastModified);
// add the properties
foreach (SPField oField in oItem.Fields)
{
if (!oField.ReadOnlyField &&
oField.InternalName != "ContentType" &&
oField.Type != SPFieldType.Invalid &&
oField.Type != SPFieldType.WorkflowStatus &&
oField.Type != SPFieldType.File &&
oField.Type != SPFieldType.Computed &&
oField.Type != SPFieldType.User &&
oField.Type != SPFieldType.Lookup)
{
newFile.Item[oField.Title] = oItem[oField.Title];
}
}
newFile.Item.SystemUpdate(true);
newWeb.Dispose();
Response.Redirect(oList.DefaultViewUrl);
});
}
public static string EnsureParentFolder(SPWeb parentSite, string destinUrl)
{
destinUrl = parentSite.GetFile(destinUrl).Url;
int index = destinUrl.LastIndexOf("/");
string parentFolderUrl = string.Empty;
if (index > -1)
{
parentFolderUrl = destinUrl.Substring(0, index);
SPFolder parentFolder = parentSite.GetFolder(parentFolderUrl);
if (!parentFolder.Exists)
{
SPFolder currentFolder = parentSite.RootFolder;
foreach (string folder in parentFolderUrl.Split('/'))
{
currentFolder = currentFolder.SubFolders.Add(folder);
}
}
}
return parentFolderUrl;
}
</script>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderId="PlaceHolderPageImage" runat="server"> <img src="/_layouts/images/blank.gif" width=1 height=1 alt="" /></asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderId="PlaceHolderPageDescription" runat="server">
<asp:Label id="PageDescriptionLabel" runat="server" ></asp:Label>
<asp:PlaceHolder id="PublishDescription" Visible="true" runat="server">
<tr> <TD height="8px"><img src="/_layouts/images/blank.gif" width="1" height="8" alt=""></td></tr>
<tr><td colspan="2">
<table cellpadding="2" cellspacing="1" width="100%" class="ms-informationbar" style="margin-bottom: 5px;" border=0>
<tr><td width="10" valign="middle" style="padding: 4px">
<img id="Img1" src="/_layouts/images/exclaim.gif" alt="<%$Resources:wss,exclaim_icon%>" runat="server"/></td>
<td class="ms-descriptiontext"><SharePoint:EncodedLiteral ID="EncodedLiteral1" runat="server" text="Click Confirm to continue or Cancel to return. Promoting a document will remove it from this library and move it from Work to Staging, or Staging to Production." EncodeMethod='HtmlEncode'/>
</td></tr></table>
</td></tr>
</asp:PlaceHolder></asp:Content>
<asp:Content ID="Content6" ContentPlaceholderID="PlaceHolderMain" runat="server">
<asp:Button CssClass="ms-ButtonHeightWidth" AccessKey="o" ID="Confirm" runat="server" Text="Confirm" OnClick="ConfirmClick"/>
<asp:Button CssClass="ms-ButtonHeightWidth" AccessKey="a" ID="Cancel" runat="server" Text = "Cancel" OnClick = "CancelClick" />
</asp:Content>
Thursday, September 20, 2007
Secured section inside web.config
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
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
http://www.sharepointcustomization.com/resources/wpsamples.htm
Wednesday, May 02, 2007
Outlook 2007 - Deliver Mail to Offline Folder
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
Wednesday, April 04, 2007
SPListItem.Update isn't updating!
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
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?
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....
- In Visual Studio, switch to Build configuration
- Rebuild and deploy once again to your local VPC
- 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
- Zip up the entire Release directory, then copy it to your prod server
- 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
- Unzip your project, then run Setup.exe
- 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.
- 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
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
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
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.