Using event receivers you can hook into several predefined SharePoint site/web/list etc. events, get access to event information and run custom code when one occurs.
There’s a basic walkthrough with lots of useful information on MSDN, be sure to check it out.
I wouldn’t list all possible events and sources of events here, the URL above does a nice job in that. Basically you can create an event receiver by subclassing the appropriate class and overriding the self-explanatory virtual methods it provides. There are five classes you can use:
- SPWebEventReceiver: provides events for SPSites and SPWebs.
- SPListEventReceiver: events for SPLists and SPFields, basically to the schema.
- SPItemEventReceiver: events for SPListItems, to the data.
- SPEmailEventReceiver: one event for e-mail enabled lists and webs..
- SPWorkflowEventReceiver: events for workflows.
Most implementations have two types of events: before and after events. This resembles Windows Forms or WPF programming, where you had Clicking and Clicked events. Before events occurs before the event takes place (before SharePoint updates the content database), run on the same thread as the event itself, and provides cancellation and access to the event data. You can access almost every piece of information that will be stored in the After property of the SPItemEventProperties class, except the new item’s (if any) id, which hasn’t been generated yet at this point.
After events lets you run your code after a specific event occurred. By default they run on a background thread, and there aren’t any guarantees that they’ll be runned by the time your page posts back to you. You can control this behavior by setting the synchronization state of your event receiver to synchronous. Then after events will run on the same thread as the event itself.
It’s easy to trigger a stack overflow with event receivers. Imagine that you’ve subscribed to the updating of an SPListItem, and set a property on the same list item from your event receiver. This’ll cause to recursively call the receiver again, until you run out of stack space. To prevent this, there’s a protected property called EventFiringEnabled. Just set it to false, and to true after you’re done. Note that this works only in your own event receiver subclass, not from the outside.
The before events are appropriate candidates for validation. And, if validation fails, you’ll definitely like to cancel further processing of the event. Event receivers provide a way for that. First, you’d like to set the Status of the SPItemEventProperties class to one of the values that indicates cancellation. This by itself won’t cancel the event, after you’ve done this you’ll need to set the Cancel property of this class to true. You can also provide an error message or redirect to another URL using the same class.
SharePoint Timer Jobs are processes that run periodically in the background, managed by the SharePoint ecosystem. Long-running operations you normally would like to run nightly (or any time of the day which is considered off-peak) are ideal candidates to be implemented in timer jobs.
To create and run timer jobs you need two things:
- A class that inherits from the SPJobDefinition base class.
- A feature to deploy your job.
All timer jobs have to inherit from SPJobDefinition. Depending on what you develop there are two constructors you can call. If you’re developing a service application then you must call the constructor which has an SPService parameter, otherwise you’d call the one with an SPWebApplication parameter. You’ll also have to write a parameterless constructor, for serializing purposes. Here’s a list of SPJobDefinition constructor parameters:
- Name: the name of your timer job, must be unique per web application / service.
- SPWebApplication/SPService: the web or service application to associate the timer job.
- SPServer: optionally you can associate your timer job with a SharePoint server, but it’s not a requirement.
- SPJobLockType: the lock type of the job.
There are three lock types for a timer job:
- None: runs the job on all servers on the farm, where the parent web or service application is provisioned.
- ContentDatabase: runs the job once per content database. If you specify this lock level you’ll get the identifier of the current content database in the job’s Execute method.
- Job: the job will run only one time.
After identifying and calling the appropriate constructor, you need to override one method to actually do something. This is the Execute method. If you specified the lock type ContentDatabase, then the guid parameter of this method will contain the current content database’s id. Otherwise you can disregard this parameter. In the execute method you can do anything that needs to be done in your job.
To deploy your timer job you’ll need a feature with an event receiver. In the event receiver’s FeatureActivated method you’ll call your concrete SPJobDefinition’s constructor, assign an SPSchedule object specifying when to run your job and assign it to the job’s Schedule property. Then you’ll have to call the Update method on the job definition, and you’re good to go.
To debug a timer job you’ll have to attach to the OWSTIMER process from Visual Studio. Then if you don’t want to wait the scheduled start time of your job, you can run the job from Central Administration.
There’s a very thorough guide for writing timer jobs here if you need more details.
SharePoint has a somewhat clumsy object model when it comes to permission management but it’s easy to get used to it.
There are two main classes here, one for entities who have permissions, and one for objects on which they have them. SPPrincipal as its name suggests represents a principal who can be a user or a group. The two respective classes are SPUser and SPGroup. As you might figured it out, a user can be member of zero or more groups. The interesting fact is that groups cannot be nested, so it’s a pretty flat hierarchy.
There’s a base class for all securable objects, which is called SPSecurableObject. It’s a nice entry point for all permission operations, since you can deal with a single class. Now to make things easier securable objects can inherit permissions from their parent. You can check if this is the case using the HasUniqueRoleAssignments property. There are two methods to enable or disable permission inheritance, called BreakRoleInheritance and ResetRoleInheritance. SPSecurableObject has a bunch of useful other methods, for example DoesUserHasPermission which checks for a given permission for the current user.
The more interesting thing is that each securable object has a property called RoleAssignments. This is a collection typed SPRoleAssignmentCollection (fortunately using the magic of the Cast<T>() LINQ method you can easily convert it to IEnumerable<SPRoleAssignment>).
An SPRoleAssigment has two noteworthy properties. The first is called Member, and it refers to the principal (SPPrincipal) who has some permissions on the given object. The second is called RoleDefinitionBindings (another strongly typed collection) which contains role definition information, as members of the class SPRoleDefinitionBinding. To make things more complicated you can access the actual permissions on the given object by reading the SPRoleDefinitionBinding class’s BasePermissions property.
Things gone quite complicated, so let’s review some SharePoint terms. Some systems (like ASP.NET) uses the term role to refer to a collection of users. SharePoint uses the term group for this. The term Role is deprecated in the SharePoint object model. It uses the term RoleDefinition instead. A RoleDefinition is a collection of permissions. You can assign role definitions to individual users and groups. So the last term to deal with is permission. A permission is some kind of right on a securable object, like reading, writing, etc. I hope I made the mess a little bit clearer here.
Visual Studio has some nice built-in support for developing custom SharePoint solutions. I personally tend to use the SharePoint Module project template, so this post will describe its workings as well.
First the most straightforward: how to make a solution sandboxed/farm level? In the project properties there’s one called Sandboxed solution. Simply set it to true to make your solution sandboxed, otherwise false.
To customize the features which will be added to your package simply open the Package folder. It has a nice editor in which you can add and remove features in your solution.
To deal with assemblies just select the Advanced tab on the package editor surface. There you can add assemblies from your solution output, or from other locations as well. You can also specify if you want individual assemblies to get deployed to the GAC or just to the web application.
You can also map folders in your project to their counterparts in the SharePoint 14 folder (this is only allowed in farm level solutions). Just right click on your SharePoint project and select “SharePoint mapped folder” from the Add context menu. To deal with resources add a mapped folder to the SharePoint Resources folder, and put your .resx files in there. Make sure to set their Build Action to Content, otherwise you won’t be able to use them.
All of the settings I specified above will be merged into a single file called Manifest.xml. If you feel brave, you can edit this xml file by hand. You just need to enter the package editor, select the Manifest tab, expand Edit options, and click on the link starting Overwrite generated XML. Visual Studio will try to persuade you not to do so, but this change is reversible any time. If you clicked through these items, the package editor will be disabled, and you can edit the XML file by hand. The schema is pretty self-explanatory, so I wouldn’t describe it here.
A delegate control is a SharePoint control which allows you to show different content in a predefined master page area on different sites, webs, etc.
SharePoint comes with a set of predefined delegate controls, but of course you can add your own ones to the master page if the existing ones don’t satisfy your needs.
If you’d like to override a delegate control then you’ll need a feature to do so. In your feature you need to add a Control element to your XML, like the following:
<Control Id=”IdOfTheDelegateControl” Sequence=”100” ControlSrc=”/_ControlTemplates/Mycontrol.ascx” />
Or you can specify a control from an assembly, like so:
<Control Id=”IdOfTheDelegateControl” Sequence=”100” ControlClass=”MyControl” ControlAssembly=”Assembly.MyControls” />
You can also set properties on your controls in the XML definition:
There’s one property I haven’t mentioned yet, the AllowMultipleControls. As its name suggests it renders all the controls specified in different levels in the delegate control, ordering by the Sequence property. If you don’t set it (the default is false) then the override with the lowest Sequence number will be rendered on the page.
You can read more about delegate controls at MSDN.
When working from code you eventually run into situations where the current user’s rights are not sufficient. To overcome these situations SharePoint allows some methods to run your code in the context of different users and different privilege levels.
To impersonate a user, you can pass the user’s token when you create an SPSite. This looks like this:
SPUser someUser; //acquire the user somehow
using (SPSite site = new SPSite(“my site’s url”, someUser.UserToken))
That’s fairly easy, now the code will have the same privileges as the user being impersonated. Running code that uses the system account user works exactly the same way. The only difference is that you need to acquire the user token of the system account user. This can be gathered from the SPContext class, the full path is SPContext.Current.Site.SystemAccount.UserToken.
Finally you can run code with full control a little bit differently. You need to use the SPSecurity.RunWithElevatedPrivileges method. This method expects a parameterless void delegate, and it’ll execute the code given in a full control context. If you’d do any write operations you should call SPUtility.ValidateFromDigest (especially if you invoke the delegate from an HTTP GET). The other way is to play around with AllowUnsafeUpdates on web and site objects, but validating the form digest in the entry point is much easier and more convenient.
When you invoke the RunWithElevatedPrivileges method it’ll run in a different context and you can only access the objects created in the delegate method with elevated permissions. Previously created objects are accessed with the original permissions. Also the SPContext site and webs won’t magically become elevated, you should recreate them as your own objects if you’d like to access them, by using their identifiers.
You should note that the RunWithElevatedPrivileges method does not work in sandboxed solutions.
Since the SharePoint object model uses a lot of unmanaged objects you should dispose them after you’re done working with them. The standard way in the .NET Framework to do so is using the IDisposable interface – so is in SharePoint.
As a standard rule of thumb: whenever you create a new instance of an SPSite or an SPWeb, you should dispose it. The same holds true if you acquire your SPSite or SPWeb through an indexer, a property or whatever EXCEPT when you get the instance via the SPContext class. SharePoint manages the current SPContext, and you should never release it. There’s one other exception: the disposal of SPSite.RootWeb is managed internally, you should not release it.
Now the preferred way is to use a using statement, like this:
using(SPSite site = new SPSite(“mysiteurl”))
using(SPWeb web = site.OpenWeb())
//do stuff here
The using blocks will be converted to appropriate try and finally blocks, so your unmanaged resources will be disposed. Of course you can use the try/catch/finally or try/finally pattern explicitly. In this case you should dispose your objects in the finally block, but be sure to check for null references first.
One last thing to consider: since Response.Redirect by default does not play well with finally blocks (e. g. finally won’t be called) you should dispose your objects explicitly before calling Response.Redirect.
Check out this series of blog posts about best practices with SharePoint and IDisposable.