Category: WSS 3.0

Side effect of setting content databases to Offline

By , February 18, 2011

It is pretty common for people to want a large department, such as Marketing, to have their own content database, or they may want to limit a content database to just a few site collections.  I have seen two different ways of accomplishing this and no real description of the negative side effects of one of the methods, so here is what I have experienced.

First the recommended method is setting the limit in the content database’s properties, and the second method is disabling the content database when it has reached the desired threshold (marking it as Offline).  At first both solutions will work equally well, as when a content database is Offline, no new site collections will be added to the database, but new content and sub sites created in a site collection that is already stored in the database will continue to be stored in the disabled database.

A seemingly unrelated issue that is very hard to troubleshoot however will eventually occur if you use the Offline/Disable method, so don’t use it!

Each site collection has it’s cache of user information for it’s members called the User Information List.  It primarily saves the person’s account name, name and job title and it is supposed to be updated to match the information in the User Profiles via a timer job.  When a Content Database is marked as being Offline however this timer job will skip over the site collections in the Offline databases and not update the User Information List.  So, eventually you will have a listing of users with the incorrect job titles (due to promotions) or names if they are ever changed.  We all know that Executives and Managers want others to know their lofty job titles, so this is a potentially big issue.

To correct this, turn the Content Databases back online, and run the following:

List the databases that are out of date:
stsadm -o sync -listolddatabases <n>

Delete the Synchronization information for the out of date databases (IT WILL NOT DELETE THE DATABASES)
stsadm -o sync -deleteolddatabases

Then Force the Databases to Re-Sync
stsadm -o sync

The Databases should now re-sync and update the information.  The timer job will then forever keep up with it.  Alternatively you could skip the stsadm commands and the timer job would eventually run and pickup the online content databases, but I like a little instant gratification now and again.

Free Flash Chart Web Part for SharePoint

By , July 14, 2010

The first time I made a web part was a couple of years ago when I wanted to chart SharePoint data inside of SharePoint.  I found some free flash charts that seemed pretty functional and away I went.

The web part turned out pretty well and I may have gone overboard adding additional features to it, but it definitely served its purpose (been in production for 2 years without issues).  Since it was the first web part I ever made, the code isn’t the most efficient and it was my plan to always go back and rewrite it.  But, with the coming of SharePoint 2010 and it’s own Chart web part, I don’t think I will spend the time to improve this, so here it is.

It does Pie, Bar, Column, line, area, doughnut and funnel charts.  A couple of the charts support three series of data, 2 as columns and one line which is pretty impressive.  Here are a few images of it running in a production environment.  It works in MOSS 2007 and WSS 3.0.  I haven’t tested it in anything other than that.  Enjoy!

Download the Project Here

Download the WSP Here

 

Birmingham Users Group on March 9th

By , March 1, 2010

It looks like I will be presenting about the options of Business Intelligence in SharePoint at the Birmingham SharePoint User’s Group on March 9th.

This will be a similar presentation to what I gave a couple of months back here in Huntsville but I will go into more detail and add a couple of demos.  It will be 2007 focused but I will highlight some of the changes coming in 2010 and maybe throw a demo or two in there on 2010 if I have the time to get a suitable environment set up.

Below is information on the session and hopefully someone will feel like showing up to listen to me :)

Microsoft SharePoint Business Intelligence Solutions for Everyone

Steve McDonnell
Senior SharePoint Consultant, IE-Dynetics

With Microsoft SharePoint, Business Intelligence does not have to be reserved for executives or companies with multimillion dollar IT budgets.  Whether you have Windows SharePoint Services or Enterprise MOSS, there are several options for Business Intelligence that we will dive into.  If you want to see a detailed overview of what Microsoft BI has to offer filled with real time examples of dashboards and reports, then this is a presentation for you.

Specific topics to be covered in this presentation:

  • WSS 3.0 BI Capabilities
  • The Data View Web Part
  • Custom Web Parts and Dashboard Solutions
  • SQL Server Capabilities (SSIS, SSAS, SSRS)
  • Enterprise MOSS 2007 Capabilities (Excel Services, KPIs, BDC)
  • PerformancePoint Server 2007
  • The Future of BI With SharePoint Server 2010

Two different database servers for SharePoint?

By , January 20, 2010

So, I ran across something strange that was new to me and I couldn’t find anything online that led me to believe that someone else had seen this either, so I am blogging about it.

I was brought in to troubleshoot a couple of random things at a customer site and once I got poking around, I saw that they were using two separate database servers for their single WSS 3.0 environment.  Their configuration database was in the embedded version of SQL Express (I will refer to this as SQL Express below) that installs automatically when you select a simple install and most of their content databases were on a full version of SQL Server 2005.

This was a 1 server farm and both SQL Express and SQL Server 2005 were on the same server that hosts WSS 3.0.  Well, to my knowledge it was impossible to have more than one database server or instance per farm and that seems to be correct according to the documentation I have found online.  Also, I was not sure how to even go about trying to replicate it… so how did this happen?

Well, they had upgraded about a year prior from WSS 2.0 to 3.0, and it is my best guess that when doing that upgrade they somehow (and for some strange reason) decided to go ahead and install a full version of SQL Server, and they were in some way able to start using it for new content databases, but keep the config and existing content databases on the SQL Express Install.

This is just a guess, and I hope that if someone else has seen this and stumbles across this blog that they can confirm or correct what I stated above.

The customer had no idea they had a full version of SQL installed (and it wasn’t licensed) so I basically rebuilt a new SharePoint server and used the database migration technique, moving databases from both instances of SQL into a fresh SQL Express install and everything is now running fine and dandy on one Database instance.  Strange eh?

Filtering Data View Web Parts By The Query String With Out of the box WebParts

By , November 9, 2009

So, recently I have been building a Project Management SharePoint site to better manage our customer projects, deploying it on our Extranet server that runs WSS.  There is quite a bit involved as I have been working on it for the better part of two weeks, pulling data from our LOB systems and surfacing it into our SharePoint Extranet, but what I want to highlight here is how I am filtering a set of dataview web parts on one page in particular.  There were a lot of articles out there on how to do cool things with Data View web parts that i referenced in building my solution, but there wasn’t anything that I could find that did the filtering the way I wanted to.

Basically, from the home page of my Projects website, there are a few charts and a Data View web part listing every project – you can click on each one to take you to a project details page.  I wanted this Data View to pass the ID of the project in the querystring to the project details page.  Well, this is straight forward as I just did a little formatting within SharePoint Designer, formatting each Project Item as a URL.  The challenge was on the Project Details page itself.

You can easily create a parameter in a Data View Web Part as shown here, and it worked quite well.  Once someone clicked on a project, the Project Details opened up as shown below.  If you want to see how to make a Data View Web Part look like the chart below, see this blog.



Well, this is kind of cool, but what if someone wanted to see the details of another project without having to go back to the front page and selecting it? I needed a way for the user to input another project ID number and see its details. Well, a DropDown box would be ideal, but there are way too many projects for that to work correctly, so I decided to use the Form Web Part (Remember I am using WSS 3.0 for this application) which gives me a Text box I can connect to other web parts. So, I created the web part and connected them all up, passing the value of the parameter I created earlier for each DataView. This worked great when there was nothing being passed in the query string, but if something was in the querystring it would override the connections made in the web parts. So, I decided to see if I could make it edit the QueryString when someone clicked the Form Web Part. This was actually pretty simple, it just took a little time to figure out.

Turns out, there is a source control button on the Form Web Part where you can directly edit the JavaScript – Pretty Handy. Well, all I had to do was insert the following snippit of JavaScript, and I was done.

<script type="text/javascript">
function moveMe(n){
    window.location = "projectdetails.aspx?ProjectID=" + n
}
</script>

<div onkeydown="if (event.keyCode == 13) moveMe(document.getElementById('myText').value)">
<input type="text" name="T1" id="myText"/>
<input type="button" value="Go" onclick="moveMe(document.getElementById('myText').value)"/>
</div>

All this code does, is define one small function that will redirect the page to a new URL (Modifying the Query String) with the input from the textbox. The two events are for someone clicking the Go button and pressing enter.

So, as you can see (sort of) in the image below, I now have a textbox that lets you modify the querystring, which is then read by three data view web parts to filter their data.



Pretty neat for not needing to put any code directly on the server.

Microsoft Business Intelligence Demo

By , September 29, 2009

We have a monthly users group at IE-Dynetics, and this week I will be the lucky one presenting. The topic is Microsoft Business Intelligence, and in about an hour I will demonstrate how the various Microsoft BI Products work together. If anyone is local to the Huntsville AL area and would like to attend (or just wants a free lunch), it will take place at 10am, Friday October 2nd, at 4900 Bradford Dr. in Huntsville. Lunch will be provided and served around 12.

I plan to demonstrate the following technologies and explain where each one fits into the big picture:

  • SQL Server (Including SSAS, SSIS and SSRS)
  • SharePoint Server 2007
  • SQL Server Reporting Services Integration with SharePoint
  • Excel Services and Key Performance Indicators (KPIs)
  • PerformancePoint Server 2007

Here is a link to a description of the event.

I’m looking forward to it and hope that they order in Pizza this time… I’m not a sandwich person.

Getting the SPUser object out of a person or group list field

By , September 3, 2009

So, as it turns out it is kind of tricky to get at the SPUser object stored in a person / group field.

I am building a workflow that needs to grab the email of a user, based on an SPUser object stored in a list field, and it gave me a little trouble so I decided to share. Nothing that I found online gave me the exact answer I was looking for, although this article got me headed in the right direction. Below is the code to grab the email stored in a SPUser object from a person field within a SharePoint Workflow.

SPFieldUser userField = (SPFieldUser)workflowProperties.Item.Fields.GetField("Customer");
SPFieldUserValue fieldValue = (SPFieldUserValue)userField.GetFieldValue(workflowProperties.Item[userField.Id].ToString());
string customerEmail = fieldValue.User.Email;

Kind of ugly if you ask me, but hey it works.

Running SharePoint Code with Elevated Privileges – A Real Example

By , August 31, 2009

In case you didn’t know, there is a construct built into SharePoint that allows developers to create code that runs as the System Account instead of the logged in user, essentially giving that user Administrator Level Permissions in a confined space. I will go over the code required to do this below, but essentially you have a block of code within your larger project that needs the logged in user to have certain permissions that they may not have, and this small block of code will then be run as the System Account, giving it the permissions needed for the task at hand.

Well, at first thought everyone I have explained this to has the same questions and concerns, “Why would you ever want to do that?” or “That is a huge security risk!”.
I could never come up with a very good example to explain it and my attempts to reassure customers that the security risk is very minimal as the user will only have permission to do exactly what the developer lets them sometimes was not well understood.

Well I finally had a project that required me to do this, so I figured I would share it to give a fairly simple real world example.

Project Objective: Create an extranet to serve as a customer support site where users can log Service Requests, manage their account, order new products, etc. via the web or by phone.

This is fairly simple and something I have done several times for different customers, but this project had one difference. Service / Support Requests could be made by phone, where all of my other projects were strictly web oriented. Does anyone see the problem this raises yet? Well I eventually incorporated the Cisco phone system into the site to generate Service Requests, but that is not what I am talking about. With any system like this, it is vital that customers only have access to their own Service Requests (as in they cant view other user’s requests).

Typically, when using a SharePoint list one can turn on the settings in the Advanced Settings section of a List (seen below) so that users can only view and edit their own List Items. This is very handy and works very well when users are always the creator of their own items, but in this case when someone calls in, someone else will be creating the service request for them… see the problem? Well, I thought about trying to change the Created By field to the user that called in, but decided it would be a neat place to use the elevated permissions approach.

Advanced List Settings

The Solution: So, this is the summary of what I did. I didn’t give any customers permissions to the Service Request List at all from within SharePoint. All access that customers had to that List was done through custom forms and web parts that made use of the elevated permission construct. This ended up being 3 forms and 3 web parts that used Elevated Permissions. Customers would use one form to create a Service Request, another to view it, another to edit it and then three web parts to display the contents of the list on various pages.

I added a custom text field to the list called Customer ID which would store each customer’s unique ID, detailing who each Service Request belonged to. The custom forms that users would use would populate this field upon creation by looking to see who the current user was, and looking up their ID in another List. Likewise when a member of the support staff was creating a service request received over the phone, they would fill in the Customer’s ID before creating it (The phone system was later used to automate this). This way, we are not relying on the Created Field to limit what each user is allowed to see.

Below I will go through the basics of how Elevated Permissions work and then show in more detail how I used it in my solution. Then in the next few days I hope to stick out a web part that incorporates it as well.

This is the basic code snippet you need to run with elevated permissions:

private void yourFunction()
{
      // Non-Elevated Permission Code Goes Here

      SPSecurity.RunWithElevatedPrivileges(delegate()
      {
            // Elevated Permission Code Goes Here
      });      

      // Non-Elevated Permission Code Goes Here
}

Pretty much everything you find online about this has code that is poorly explained, so hopefully I can do a decent job and make it easy. The code above is simple enough right? Well here is one place people routinely run into problems. If you are using an instance of SPWeb or SPSite before your Elevated Permission Code block, you won’t be able to use it. You need to get a new instance of the SPWeb or SPSite object to use once you enter the elevated block. Below is the code that I use to do this.

private void yourFunction()
{
      SPSite site = SPContext.Current.Site;
      SPWeb web = SPContext.Current.Web;

      SPSecurity.RunWithElevatedPrivileges(delegate()
      {
            using (SPSite ElevatedSite = new SPSite(site.ID))
            {
                  using (SPWeb ElevatedWeb = ElevatedSite.OpenWeb(web.ID))
                  {
                        // Code Using the SPWeb Object Goes Here
                  }
            }
       });
}

I am pretty sure that little tidbit cost me about a day the first time I tried to play with Elevated Permissions, so hopefully that will help some of you out. After that, it is really simple as you just put your code in the middle of it just like you would if you weren’t using the elevated permissions.

As further example, in my NewSR.aspx form that users go to when creating a new Service Request, I use a runWithElevatedPrivileges block in two functions. The first is a private function I call within the OnPreLoad() function called LoadData(). Inside this function I am populating drop down menus from data in a SharePoint list for the Priority of the service request and the products that are affected.

The second function with a runWithElevatedPrivileges block is my onSubmit() function that is called when a user clicks the submit button.

New SR Form

So it is actually pretty simple. The only other thing I am going to demonstrate is how I made the view and Edit Forms Secure. As you probably know, every List Item has an integer ID (.ID) and a unique guid ID (.UniqueID). Well, the custom forms I made for view and edit look in the querystring for a guid which it then uses to look up the Service Request. (If you can guess the guid of another service request, well you broke my security but it seems quite unlikely) There isn’t anything fancy about accessing the querystring to get the guid and look up the Service Request, but I thought it would be handy to store a link to the item within the item itself as an additional field. This makes it easy to email the user a link to view their service request among other things.

So I created a text field called Link and populated it as shown below.

private void yourFunction()
{
      SPSite site = SPContext.Current.Site;
      SPWeb web = SPContext.Current.Web;

      SPSecurity.RunWithElevatedPrivileges(delegate()
      {
            using (SPSite ElevatedSite = new SPSite(site.ID))
            {
                  using (SPWeb ElevatedWeb = ElevatedSite.OpenWeb(web.ID))
                  {
                        srList = ElevatedWeb.Lists["Service Requests"];
                        SPListItem newItem = srList.Items.Add();
                       
                        // Do stuff to create the list item
 
                        ElevatedWeb.AllowUnsafeUpdates = true;
                        newItem.Update();
                        Guid temp = newItem.UniqueId;
                        newItem["Link"] = "<DIV><a href=\"https://YourSite.com/_layouts/custom/ViewSR.aspx?ID=" + temp.ToString("d") + "\">View SR</a></DIV>";
                        newItem.Update();
                        ElevatedWeb.AllowUnsafeUpdates = false;
                  }
            }
       });
}

The tricky part here is having to call item.update() twice. You have to call it twice because the first time you call it is when the list item is actually created. Before then, it does not have a Unique ID to reference. So once it is created, you can grab the Unique ID, and populate the Link field with a HTML formatted URL that references the item and can be easily inserted into a HTML based email or a page that has a content editor web part CEWP with the JavaScript in it found here. You then call update the second time to save your link field.

Support Screen with Link

The use of the link field and the JavaScript found above allows me to render links in a web part as shown above.

Well, hopefully this will be useful to someone – I think it would have saved me a day or so once upon a time when I first tried to do this.

Getting Started: The Basics of Web Part Development

By , August 11, 2009

Introduction

I taught a week long SharePoint boot camp about a month ago for Microsoft Enterprise Customers that pretty much covered everything SharePoint.  The first day dealt with Planning and Governance, days 2 and 3 went over Administration and Point&Click design, and Days 4 and 5 involved custom development.

Over the next month or so I plan to put up quite a few of the examples I used during the class, but today I want to go over how to get started with Web Parts and then build onto the topic in later postings.  It is one of my favorite things to code in SharePoint and a good foundation will definitely make your life easier in the long run.  You have to have some understanding of the Page Life Cycle to follow what is happening when, and hopefully after reading this you will be better equipped to go roll your own.

I will cover this topic  in five small sections:

  1. The Page Lice Cycle
  2. The Web Part Life Cycle
  3. Tools to help make Web Parts
  4. The Hello World Web Part
  5. Adding functionality to your Web Part

Before I get started, I just want to point out is that Web Parts are not a SharePoint construct – they are simply an ASP web control.  ASP web parts can be used in SharePoint and Web parts you make for your SharePoint site may be used in an ASP based web page (If you do everything correctly).  We however will focus on SharePoint.

The Page Life Cycle

When a request is received for an ASP page, the .NET framework runs the page through a series of steps to initialize objects, analyze the request, view any state and post back information, handle any raised events and finally render the output.  A solid understanding of the page life cycle will let you better understand what and when everything is happening.  More importantly perhaps, it will let you understand where you need to put your code that it fits in correctly.  Below are the 6 stages of the Page Life Cycle.  Note that they are not official stages, but are being grouped logically here.

  1. Page Request
    1. Determines if the requested page is cached
    2. There are no methods to override at this stage
  2. Start up
    1. The Request and Response properties are set
    2. Determines if there is a new request or post back
    3. There are no methods to override at this stage
  3. Initialization
    1. Themes and Master pages are applied
    2. Controls are created in the Web Part’s OnInit() Method
    3. Client side scripts and connection strings are registered
    4. The OnInit() method for the page is invoked after each child control’s OnInit()
  4. Loading
    1. Properties that utilize the post back or view state are set
    2. Initial or default values are set in the OnLoad() Function
    3. The Page’s OnLoad() is invoked before each child control’s OnLoad()
  5. Rendering – 3 Sub Phases
    1. Pre-Render
      1. EnsureChildControls() and CreateChildControls() are called
        1. Note: All Controls should be added through CreateChildControls()
      2. OnPreRender() is called which is the last chance to edit the page
        1. OnPreRender() for the page is called which recursively calls each control’s
    2. Save State
      1. The View and Control States are created and saved
    3. Render
      1. All controls process their Render() method and generate HTML
  6. Unloading
    1. This is the final stage where open files and database connections are closed
    2. There are no methods to override at this stage

The Web Part Life Cycle

It is kind of hard to separate the Web Part life cycle from the Page’s as the Web Part runs within the context of the Page, but it helped me understand it when I tried to separate it out.  You could also look at this little part as a summary of what is really important from the above section.

  1. Initialization Stage

    1. Web Part’s OnInit() is called BEFORE the Page’s OnInit()
    2. The SPWebPartManager loads and applies any personalization settings
  2. Loading Stage
    1. Web Part’s OnLoad() is called AFTER the Page’s OnLoad()
  3. Render Stage
    1. CreateChildControls() is called – Add your controls here
    2. OnPreRender() is called AFTER the page’s OnPreRender()
    3. RenderContents() is called

Tools for Web Part Development

So, now that we have a basic outline of when events occur in the life cycle of a web part, we need to start developing.  There are a couple of tools that will definitely make your life easier that I highly recommend.  The first one is Visual Studio Extensions for SharePoint which is a plug in for Visual Studio that has a web part template.  The newest version (1.3) made some very good improvements and even lets you deploy a web part to the local bin folder instead of the GAC.  This is the tool I will be using later in this segment and what I generally use for all of my web part projects.

The second tool you should look at is the SharePoint Dispose Checker Tool which scans your code for SharePoint Memory leaks related to improper Dispose calls.  Run this before ever putting your SharePoint code into production – memory leaks are bad.

The Hello World Web Part

Note: You can download the project I go through below Here if you don’t want to follow along.

So, now that you have installed the Visual Studio Extensions for SharePoint, create a new Project and select SharePoint –> Web Part as the project template.  This will give you a blank web part that has a webpart.xml file that defines properties on the web part, a .webpart file that further defines the web part and a c# file with the code for the web part.

The first thing we want to do is rename the webpart.xml file to the name you want your web part to have.  It will ask you if you want it to change the name throughout the project, select yes.  Next, open up the Webpart.xml file and modify the <file> tag as shown below.  By adding the property tag to it, it allows us to group our web parts in the “Add New Web Part” dialog box sharepoint users will see when adding a web part to their page.  An Image of how this will work is also shown below, showing one of my test servers and several web parts I have made listed under my custom group header.

<File Path=“HelloWorld.webpart" Url=“HelloWorld.webpart" Type="GhostableInLibrary">
    <Property Name="Group" Value="Steve's Custom Web parts" />
</File>


Add new web part dialog

Allright, now that we have a couple of things set up, lets add a little bit of code.  Inside of the CreateChildControls method that was created for you, put the following line of code.

this.Controls.Add(new LiteralControl("CreateChildControls"));

Then, paste in the following function.

protected override void RenderContents(HtmlTextWriter writer)
{
     writer.Write("Render Contents");
}

So we now are adding a control to the CreateChildControls function which will be rendered in the web part, displaying “CreateChildControls”.  But we also added a write statement to the RenderContents method… so what will happen?  Will both print?  Which one will print first?  Deploy it and find out (or scroll down just a little bit).  To deploy it, open the project settings and type in the url of your test server in the debug window.  Then clicking deploy in the top tool bar and deploy solution, it will be installed on your server.  Now go add it to a web part page like any other web part, easy as that.

So, that should have printed out “Render Contents”.  Now, stick the following line of code at the very beginning of the Render Contents Method.

base.RenderContents(writer);

So now it should print out “CreateChildControlsRender Contents”.  This is because before we did not include the base functions of the default RenderContents() method, which renders everything that was added in CreateChildControls().  This may seem useless, but it is important to understand what is being done where, and if nothing else, remember to call the base method when you override one.

Now, something that is very useful when developing web parts is to catch and display your error messages in the web part (Yes, you will make errors).  To do so, we need to catch the error and then add it as a literal to the web part.  Replace the contents of the CreateChildControls method with the following code to try it out.

try
{
     SPSite site = new SPSite("http:\\badurl");
     this.Controls.Add(new LiteralControl("CreateChildControls"));
}
catch (Exception Ex)
{
     this.Controls.Add(new LiteralControl(Ex.Message));
}

This should produce the following error message, unless your web application happens to be named badurl.
Web Part Error Catching

That is actually very useful and I recommend to always do it, and then maybe remove it once putting your code into production.  You could also put some friendly error message telling people to call IT if you see this message.

There is one other semi-cool thing I want to show you that can help when creating web parts.  We are going to enable Tracing for the page on which the web part was added.  This will show when each function was called and really helps track down bugs.  To turn on tracing, paste the code below into the try of the constructor of the web part.  Note that you will need to add trace statements like the one below to each of your methods for this technique to truly be helpful.

Context.Trace.IsEnabled = true;
Context.Trace.Write("WebPart", "Begining WebPartConstructor()");
Context.Trace.Write("WebPart", "EndingWebPartConstructor()");

Now redeploy the solution and scroll down in the page, pretty neat eh?  Below is a screen shot of what mine looks like in case you don’t feel like trying it.

Well, at least I think page level tracing is cool.  You can also turn it on for the entire site by modifying the web.config file, but that is another story.\

All good web parts have a custom editor in which you can specify any parameters or values you want.  After all, web parts are supposed to be generic and reusable right?  Below is the code you need to add an editor class to your project that inherits from the EditorPart class.  The first thing to do is add a blank c# class file to the project.  Add the first block of code below to it, erasing anything that may already be in the file.  Then, add the second block of code to the main c# file.

using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;

using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;

namespace WebPart2
{
    class myEditor : System.Web.UI.WebControls.WebParts.EditorPart
    {
        private TextBox _message;

        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            _message = new TextBox();
            Controls.Add(_message);
        }

        public override bool ApplyChanges()
        {
            EnsureChildControls();
            HelloWorld webPart = WebPartToEdit as HelloWorld;

            if (webPart != null)
            {
                webPart.myMessage = _message.Text;
            }
            return true;
        }

        public override void SyncChanges()
        {
            EnsureChildControls();
            HelloWorld webPart = WebPartToEdit as HelloWorld;

            if (webPart != null)
            {
                _message.Text = webPart.myMessage;
            }
        }

    }
}
using System.Collections.Generic;
protected override void CreateChildControls()
{
    try
    {
        this.Controls.Add(new LiteralControl(myMessage));
    }
    catch (Exception Ex)
    {
        this.Controls.Add(new LiteralControl(Ex.Message));
    }
}

[WebBrowsable(false)]
public string myMessage { get; set; }

public override EditorPartCollection CreateEditorParts()
{
    List<EditorPart> editorParts = new List<EditorPart>();

    myEditor editor = new myEditor()
    {
        ID = this.ID + "_editorPart1",
        Title = "Test Editor"
    };

    editorParts.Add(editor);

    return new EditorPartCollection(editorParts);
}

This may seem like a lot, but it really isn’t too difficult.  Make sure that your namespaces match what your using in your project. Below is a screenshot of the working editor class, passing the value entered in the editor to the web part.

Now, if you look at the code in the Editor class, there are three main functions that you need.  The apply changes is called when you click apply or ok and it does what it says, it applies the changes.  The Sync changes kind of works the other way, pulling information from the web part into the editor class.  You can also override the same render stage methods in the editor class that you can in the web part to make your editor look more custom if you like. Also take note of the one method we added to the web part class. All this does is add your custom editor to the web part. You can set a few fields here like the title and description, but that’s pretty much it. Also important to notice is the attributes we set on the myMessage string in the web part class. This is required for the editor class to be able to retrieve its value. If you remove the line directly above it and declare it like a normal string, every time the editor window is opened any value previously entered into it will be lost.

Well, I am tired… and I the code is pretty self explanatory. Feel free to ask any questions if I failed to explain something. I think I will go over verbs in another posting and then get into some cooler, more usable examples. Web Parts in SharePoint 2007 and WSS 3.0 are much easier to get started than people think, and I encourage you to give them a shot.

You can download the web part project I went over above here.

Getting a list item’s attachments through code

By , August 10, 2009

So, I came across the requirement to create a list of links to all of the attachments of a list item in my custom aspx page. Seems easy right? I should be able to grab the list item and do something like item.Attachments to get a collection of some kind of SPAttachment Object… right? Well as it turns out, it’s not quite so simple.

SPListItem.Attachments returns a SPAttachmentCollection object which is fairly limited in what it allows you to do.  It does provide a simple way of adding and deleting attachments, but thats about it.  You can however treat the SPAttachmentCollection as if it was a StringCollection and enumerate through the collection as shown below to get the names of the files.

1
2
3
4
5
// item is a SPListItem
foreach (string attachmentName in item.Attachments)
{
     // attachmentName is the name of the attachment
}

Well that is better than nothing, but I needed links to the documents, not just a list of them. So the next thing we need to do is prefix the attachmentName string with the rest of the URL. It turns out that it is fairly simple to use a SPFolder object as seen below to grab the rest of the URL.

1
2
3
4
5
6
// item is a SPListItem, _Files is the Literal in my custom page
foreach (string attachmentName in item.Attachments)
{
     SPFolder attachments = item.ParentList.RootFolder.SubFolders["Attachments"].SubFolders[item.ID.ToString()];                                    
     _Files.Text += "<a href=\"https://SharePoint.com/" + attachments.Url + "/" + attachmentName + "\">" + attachmentName + "</a><br>";                              
}

So, while I don’t really like it, I think this is the easiest way of getting a link to the attachments for a given list item. If anyone has a better method, please feel free to share. Below is a little image of what resulting page may look like.

ListItemAttachments