Patrick's SharePoint Blog

SharePoint's Booming world

Archive for the ‘Visual Studio 2010’ Category

Custom Email activity for SharePoint Designer 2010

Posted by Patrick Boom on August 9, 2011

You have probably run into it if you created SharePoint 2010 workflows using SharePoint Designer. The cool part of SharePoint 2010 is that it actually allows you to modify or copy the out-of-the-box workflows. The not so cool part is that the activities in SharePoint Designer are quite limited in their functionality.

For example, in the email activity that comes out of the box it is not possible to add attachments, nor does it allow you specify the From field.

Fortunately, using Visual Studio, we can upgrade the toolbox for SharePoint Designer activities to add more specialized activities that can then be used in the designer workflows.

Sure, it is possible to just create the workflow in Visual Studio, but to me, the hassle of creating a complete Visual Studio workflow, just because you want to change the From field does not make sense. It is not cost effective. SharePoint Designer workflows are powerful and should be part of the evaluation for any workflow solution. It has its limits, but also a lot of strengths.

In this post, I will show how to create a custom workflow activity for this purpose. To keep things simple, if possible using this subject, I will only allow for a single attachment. I will leave it up to you to update the activity with multiple attachments if you want to. Before I begin though, if you would like to just buy a lot of additional custom activities for SharePoint 2010, please take a look at the offerings from Nintex.

To build this feature, we will follow the following steps:

  1. Set up a standard SharePoint solution in Visual Studio 2010
  2. Define our ACTIONS file, which tell SharePoint and SharePoint Designer which activities I would like to include
  3. Define our SendMail class, which will implement the actions specified in the ACTIONS file
  4. Register our new custom action in the web.config using an eventreceiver (I will post a cool feature to do this easily in a later post)
  5. Package, deploy and test

Set up the solution structure

First, create a new Visual Studio solution using the SharePoint empty project template . Ensure that you have a key, so the resulting assembly is signed and deployable to the GAC. Then add a SharePoint Mapped folder, pointing to the following location:

Template\<yourlanguageLCID>\Workflow

Once done, add a file in this folder with the extension ACTIONS. You can call the file any way you want, but I used BoomCustomActivities.ACTIONS. Your solution should look like this now:


Define the ACTIONS file for our activity

We will now define which actions we will add to the toolbox. These will become available in the Actions tab on the Ribbon in SharePoint Designer. Populate the ACTIONS file with the following XML definition:

<?xml version=1.0encoding=utf-8?>
  <WorkflowInfo Language=en-us>
    <Actions Sequential=thenParallel=and>
<Action
Name=Send email with attachment
ClassName=$SharePoint.Project.FileNameWithoutExtension$.SendEmailActivity
Assembly=$SharePoint.Project.AssemblyFullName$
AppliesTo=all
Category=Email actions>
<RuleDesigner
Sentence=Send email with attachment %1 to %2. Use %3 as the sender>
<FieldBind Field=AttachmentFileNameText=this file (url)DesignerType=TextAreaId=1/>
<FieldBind Field=To,CC,Subject,BodyText=these user(s)DesignerType=EmailId=2/>
<FieldBind Field=FromText=this userDesignerType=TextAreaId=3/>
</RuleDesigner>
<Parameters>
<Parameter Name=__ContextType=Microsoft.SharePoint.WorkflowActions.WorkflowContextDirection=In/>
<Parameter Name=__ListIdType=System.String, mscorlibDirection=In/>
<Parameter Name=__ListItemType=System.Int32, mscorlibDirection=In/>
<Parameter Name=__ActivationPropertiesType=Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties, Microsoft.SharePointDirection=Out/>
<Parameter Name=AttachmentFileNameType=System.String, mscorlibDirection=In/>
<Parameter Name=ToType=System.Collections.ArrayList, mscorlibDirection=In/>
<Parameter Name=CCType=System.Collections.ArrayList, mscorlibDirection=Optional/>
<Parameter Name=SubjectType=System.String, mscorlibDirection=In/>
<Parameter Name=Body” Type=System.String, mscorlibDirection=Optional/>
<Parameter Name=FromType=System.String, mscorlibDirection=In/>
</Parameters>
</Action>
</Actions>
</WorkflowInfo>

Let’s discuss the contents. The WorkflowInfo node just indications this definition applies to Workflows. The Actions node defines how the text is build up in the workflow designer. For example, if used in a sequential action, the action will read <actiondescription> then. Nothing really interesting there. The real magic starts with the Action node. In there, the attributes Name, ClassName, Assembly, AppliesTo and Category define the name of the action in the actions menu, the class and assembly for the code, if it relates to list items, documents only or all, and the Category in which it is listed in the actions menu.

The RuleDesigner node specifies the text shown in the editor and related input parameters. The attribute Sentence specifies the sentence shown in the designer. You specify each parameter by a % followed by a number. %1 will point to the first parameter and so on. The parameters are specified using FieldBind attributes. The Text attribute substitutes the ‘%’ parameter indicator in the sentence in the designer. The Id attribute should match the order of the parameter.

The Parameters node and subsections define the parameters that will be passed into your custom activity class. Parameters obtained from the FieldBinding should exactly match the parameter names in the parameters section for them to be transferred to your code. You also specify whether your parameter is in or out and the type of the property, which is a .NET type. For more information on Workflow Action files, see MSDN.

So now we have our ACTIONS definition file. Let’s move on to the code itself.

Create the class for our activity

Add a new class to your project. Call the class SendEmailActivity as stated in the ACTIONS file. Have the class inherit from the System.Workflow.ComponentModel.Activity class. That is the easy part. Now for each of the parameters we stated in the ACTIONS file, we need to create properties and ensure that they map. First do the Workflow Context properties. They use a DepencyProperty to map the property in the class to the parameter stated in the ACTIONS file. See below code snippet.

#region [ Workflow Context Properties ]

public static DependencyProperty __ContextProperty = DependencyProperty.Register(“__Context”, typeof(WorkflowContext), typeof(SendEmailActivity));
[ValidationOption(ValidationOption.Required)]
public WorkflowContext __Context
{
get
{
return
((WorkflowContext)(base.GetValue(__ContextProperty)));
}
set
{
    base.SetValue(__ContextProperty, value);
}
}

public
static DependencyProperty __ListIdProperty = DependencyProperty.Register(“__ListId”, typeof(string), typeof(SendEmailActivity));
[ValidationOption(ValidationOption.Required)]
public string __ListId
{
  get
{
      return ((string)(base.GetValue(__ListIdProperty)));
}

set
{
      base.SetValue(__ListIdProperty, value);
}
}

public static DependencyProperty __ListItemProperty = DependencyProperty.Register(“__ListItem”, typeof(int), typeof(SendEmailActivity));
[ValidationOption(ValidationOption.Required)]
public int __ListItem
{
  get
  {
    return ((int)(base.GetValue(__ListItemProperty)));
}

set
{
base.SetValue(__ListItemProperty, value);
}
}

public
static DependencyProperty __ActivationPropertiesProperty = DependencyProperty.Register(“__ActivationProperties”, typeof(SPWorkflowActivationProperties), typeof(SendEmailActivity));
[ValidationOption(ValidationOption.Required)]
public SPWorkflowActivationProperties __ActivationProperties
{
  get
{
return (SPWorkflowActivationProperties)base.GetValue(__ActivationPropertiesProperty);
}

set
{
base.SetValue(__ActivationPropertiesProperty, value);
}
}

#endregion
[ Workflow Context Properties ]

What is important here is that the name of the DependencyProperty is equal to the name of the property in the class, appended with Property. Also, the name of the property should be equal to the name of the parameter in the ACTIONS file. Now that we have these properties, we also map the rest of the properties that are more relevant to our solution.

#region [ Custom Workflow Properties ]

public static DependencyProperty ToProperty = DependencyProperty.Register(“To”, typeof(ArrayList), typeof(SendEmailActivity));
[ValidationOption(ValidationOption.Required)]
public ArrayList To
{
  get
{
return ((ArrayList)(base.GetValue(SendEmailActivity.ToProperty)));
}

set
{
base.SetValue(SendEmailActivity.ToProperty, value);
}
}

public
static DependencyProperty CCProperty = DependencyProperty.Register(“CC”, typeof(ArrayList), typeof(SendEmailActivity));
[ValidationOption(ValidationOption.Optional)]
public ArrayList CC
{
  get
  {
return ((ArrayList)(base.GetValue(SendEmailActivity.CCProperty)));
}

set
{
base.SetValue(SendEmailActivity.CCProperty, value);
}
}

public
static DependencyProperty SubjectProperty = DependencyProperty.Register(“Subject”, typeof(string), typeof(SendEmailActivity));
[ValidationOption(ValidationOption.Required)]
public string Subject
{
  get
{
return ((string)(base.GetValue(SendEmailActivity.SubjectProperty)));
  }

set
  {
    base.SetValue(SendEmailActivity.SubjectProperty, value);
  }
}

public
static DependencyProperty BodyProperty = DependencyProperty.Register(“Body”, typeof(string), typeof(SendEmailActivity));
[ValidationOption(ValidationOption.Optional)]

public
string Body
{
  get
  {
return ((string)(base.GetValue(SendEmailActivity.BodyProperty)));
  }

set

  {
base.SetValue(SendEmailActivity.BodyProperty, value);
  }
}

public static DependencyProperty AttachmentFileNameProperty = DependencyProperty.Register(“AttachmentFileName”, typeof(string), typeof(SendEmailActivity));
[ValidationOption(ValidationOption.Required)]
public string AttachmentFileName
{
  get
  {
    return ((string)(base.GetValue(AttachmentFileNameProperty)));
  }

set
  {
base.SetValue(AttachmentFileNameProperty, value);
  }
}

public static DependencyProperty FromProperty = DependencyProperty.Register(“From”, typeof(string), typeof(SendEmailActivity));
[ValidationOption(ValidationOption.Required)]
public string From
{
  get
{
return ((string)(base.GetValue(SendEmailActivity.FromProperty)));
  }

set
  {
base.SetValue(SendEmailActivity.FromProperty, value);
}
}

#endregion [ Custom Workflow Properties ]

So basically it is more of the same. It connects the parameters from the Action file to our code behind class. So now that we have our properties and our class is fed with the parameters entered by the workflow designer in SharePoint Designer, we can further construct our class to do something useful. The base class System.Workflow.ComponentModel.Activity contains a method called Execute, which is the entry point for our custom activity. To implement, override the method in your custom activity. See the implementation of the method below:

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
  using (var web = __Context.Web)
{
  try
{
       // Get all of the information we currently have about 
       // the item that this workflow is running on
var message = BuildMailMessage(web);
// get the attachment if specified
SPFile attachment = null;

try
{
using (SPSite site = new SPSite(AttachmentFileName))
{
using (SPWeb fileWeb = site.OpenWeb())
{
attachment = fileWeb.GetFile(AttachmentFileName);
string name = attachment.Name;
Stream ms = attachment.OpenBinaryStream();
message.Attachments.Add(new Attachment(ms, name));
}
}
}
catch (Exception ex)
      {
// log could not find file
Common.WriteFailToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, “Unable to add attachment. Could not find or load file ‘{0}’.”, AttachmentFileName));
}

      if (!string.IsNullOrEmpty(From))
{
message.From = GetMailAddress(__Context.Web, From);
}

if (message.To.Count > 0)
      {
        // Send email w/ attachments
SmtpClient smtpClient = LoadSmtpInformation();
smtpClient.Send(message);

// Log entry to Workflow History Log
Common.WriteSuccessToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, “Email successfully sent to the following recipients: {0}”, message.To));
      }
      else
      {
// Log entry to Workflow History Log
StringBuilder emailAddressesTo = new StringBuilder();

for (int i = 0; i < To.Count; i++)
        {
          emailAddressesTo.AppendFormat(CultureInfo.InvariantCulture, “{0}, “, To[i]);
}

        // Trim off last comma
emailAddressesTo = emailAddressesTo.Remove(emailAddressesTo.Length – 1, 2);
Common.WriteFailToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, “Unable to send email out. No valid user email addresses for the following users ({0}) were found.”, emailAddressesTo));
}
   }
   catch (Exception ex)
   {
// Log entry to Workflow History Log
Common.WriteFailToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, “Failed to send email. Error message: {0}”, ex.Message));
   }
   finally
   {
      // Cleanup – Dispose of these private properties before exiting
if (__ActivationProperties != null)
{
__ActivationProperties.Dispose();
}
if (__Context != null)
{
__Context.Dispose();
}
}
  }

return ActivityExecutionStatus.Closed;
}

To walk through the method, we first get the current web through the Workflow Context. We then use a couple of helper methods to build the mail message (BuildMailMessage), get the SMTP information from SharePoint (LoadSmtpInformation) and get the mail address of a user if it is passed as an account name (GetMailAddress). Please review those methods yourself if needed. Please note the LoadSmtpInformation method. It requests the outbound server address from the WebApplication. Be aware that multiple SMTP settings exist in SharePoint 2010 and that you can specify the outbound server both on server level in Central Admin and for each web application separately. In our case, we use the latter.


private static SmtpClient LoadSmtpInformation()
{
  string smtpServer = SPAdministrationWebApplication.Local.OutboundMailServiceInstance.Server.Address;
return new SmtpClient(smtpServer);
}

After we created the message, we try to load the attachment specified by the property. To do that, we use the AttachmentFileName property to open the SPSite where the file resides. We then open the SPWeb and use the GetFile method to load the file into a SPFile object. Finally, we use a MemoryStream to add the attachment to the mail message.

The rest of the method is to assign the From and To mail addresses. You can further expand the activity to also include CC and BCC addresses if you wish.

Register your custom action

Before you can use your custom action, you need to register it with SharePoint in the web.config. We therefore use a feature receiver to do this when activated. Add a feature to your solution and attach a feature receiver. Implement the receiver as follows:


public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
  SPWebApplication webapp = (SPWebApplication)properties.Feature.Parent;
  UpdateWebConfig(webapp, true);
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
  SPWebApplication webapp = (SPWebApplication)properties.Feature.Parent;
UpdateWebConfig(webapp, false);
}

private void UpdateWebConfig(SPWebApplication webApp, bool featureActivated)
{
  SPWebConfigModification modification = new SPWebConfigModification(“authorizedType[@Assembly=\”Boom.WorkflowActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f97cfd14d08de08\”][@Namespace=\”Boom.WorkflowActivities\”][@TypeName=\”*\”][@Authorized=\”True\”]”, “configuration/System.Workflow.ComponentModel.WorkflowCompiler/authorizedTypes”);

  modification.Owner = “Boom.WorkflowActivities”;
  modification.Sequence = 0;
  modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
  modification.Value = string.Format(CultureInfo.InvariantCulture, “<authorizedType Assembly=\”{0}\” Namespace=\”{1}\” TypeName=\”{2}\” Authorized=\”{3}\”/>”, new object[] { “Boom.WorkflowActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f97cfd14d08de08”, “Boom.WorkflowActivities”, “*”, “True” });

  if
(featureActivated)
webApp.WebConfigModifications.Add(modification);
  else
    webApp.WebConfigModifications.Remove(modification);

SPFarm
.Local.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
}

This will add or remove an authorizedType node to your web.config file, enabling SharePoint to use the action in SharePoint Designer workflows.

Add safecontrol entries to the package manifest

To ensure that SharePoint will safely load our custom activity, we add a SafeControl entry to the web.config. We do that by overriding the Package.Template.xml to include our SafeControl entry. Also note we use the VS ability to inject the assembly details on build.

<Assemblies>
  <Assembly Location=Boom.WorkflowActivities.dll DeploymentTarget=GlobalAssemblyCache>
<SafeControls>
<SafeControl Assembly=$SharePoint.Project.AssemblyFullName$ Namespace=$SharePoint.Project.FileNameWithoutExtension$ TypeName=* />
</SafeControls>
</Assembly>
</Assemblies>

Build and Test

So, the hard work is now done. The last thing to do is have VS build the package and deploy to your environment. Once an IIS reset (or app pool recycle) has been done, fire up SharePoint Designer and connect to your site. Create a new SharePoint Reusable Workflow and see if your custom action is there:

Figure 1: Custom action available

Figure 2: Complete the action parameters

Conclusion

You can do a lot with the reusable workflow abilities in SharePoint Designer 2010. However, the best part is that you can extend the capabilities that the OOB version has, which makes the SharePoint workflows far more likely to be used as they can be tweaked. A definite improvement over SharePoint 2007 where they were as they came.

Have fun building your own!

Posted in SharePoint 2010, Visual Studio 2010 | Tagged: , , , , , | 55 Comments »

Using a Timer Job to ensure diagnostic areas of SPGuidance

Posted by Patrick Boom on November 23, 2010

In one of my previous posts, I described a PowerShell script that could be run on each front-end to ensure that for each diagnostic area defined in the logging framework, a corresponding event source was created. That script should be run with sufficient privileges on the target box, but does the job.

I then promised to post a method to do that from a timer job next, but work and private life has kept me busy lately 😉 My apologies. However, here it is.

In this post, I would like to expand on that a little, but instead of using the PowerShell route, I use a SharePoint Timer Job to accomplish the same thing.

You can download the example Visual Studio 2010 project from this location. What we will do in this post:

  1. Create a timer job that should be run on each front-end that ensures that for each diagnostic area, an event source is registered.
  2. Create two features:
    1. Diagnostics areas and categories
    2. Timer Job

In a real-world situation, I would publish the SPGuidance assemblies in a separate WSP, however, for simplicity; they are packaged with this solution.

I will create one diagnostic area called Boom.CustomLogging and two categories, called Connections and Events. From within the web part, I will log to both categories. In my next post, I will also show how to create a custom logging component. For this post, we use the default one (which logs to the event log and ULS logs).

First, open Visual Studio and create an empty SharePoint project. We will create three features:

  1. DiagnosticAreas à Sets up the diagnostic areas and categories
  2. JobInstaller à Will install our job to ensure the event sources
  3. WebParts à Will contain our test web part

Also create a Web Part element called LoggingTester and add it to the solution. Add Event receivers to the DiagnosticAreas and JobInstaller features. Finally, add a class to your project called EnsureEventSourcesJob.cs that will hold your timer job to ensure event sources. Your solution explorer should look something like this:

Configure Custom Diagnostic Areas

We will start with the event receiver for the Diagnostic areas. This feature receiver will set up our areas and categories that we will use in our sample. I want to configure one custom diagnostic area that is specific to my application and two categories beneath it.

  • Diagnostic Area: Boom.CustomLogging
    • Category: Connections (default event severity Error, default trace severity Medium)
    • Category: Events (default event severity Information, default trace severity Medium)

On the side, it is also possible to add categories to the default SharePoint 2010 diagnostic areas, such as Access Services, Excel Services Application and SharePoint Foundation Search, however, this is considered bad practice. In short, do not do it. Whenever you wish to introduce custom categories, do so in your own diagnostic area.

The following piece of code was taken from the SPGuidance chm. It defines a property in our event receiver class that returns a DiagnosticsAreaCollection with my custom areas and categories.

// This helper property builds a collection of areas and categories.
DiagnosticsAreaCollection _myAreas = null;
DiagnosticsAreaCollection MyAreas
{
   get {
      if (_myAreas == null)
     
{
         _myAreas = new DiagnosticsAreaCollection();
        
DiagnosticsArea boomArea = new DiagnosticsArea(“Boom.CustomLogging”);
         boomArea.DiagnosticsCategories.Add(new DiagnosticsCategory(“Events”, EventSeverity.Information, TraceSeverity.Medium));
         boomArea.DiagnosticsCategories.Add(new DiagnosticsCategory(“Connections”, EventSeverity.Error, TraceSeverity.Medium));
      
         _
myAreas.Add(boomArea);
     }

     return
_myAreas;
   }
}

In this code snippet, we define a property that creates a new Microsoft.Practices.SharePoint.Common.Logging.DiagnosticsAreaCollection, creates a new Microsoft.Practices.SharePoint.Common.Logging.DiagnosticsArea called Boom.CustomLogging and finally adds two Microsoft.Practices.SharePoint.Common.Logging.DiagnosticsCategory objects that defines my two categories and adds it to the custom area. Finally, I add the area to the collection and return it.

No real magic here. We will use this property in both the feature activated and feature deactivating events. So, let us go in the feature activated event.

public override void FeatureActivated(SPFeatureReceiverProperties properties) {

   IConfigManager
configMgr = SharePointServiceLocator.GetCurrent().GetInstance<IConfigManager>();
   DiagnosticsAreaCollection configuredAreas = new DiagnosticsAreaCollection(configMgr);

   foreach
(DiagnosticsArea newArea in MyAreas)
{
     var existingArea = configuredAreas[newArea.Name];
     if (existingArea == null) {
        configuredAreas.Add(newArea);
    
else {
        throw new SPException(“Diagnostic area already exists”);
     }
   }

   configuredAreas.SaveConfiguration();
}

In this method, we use the SharePointServiceLocator, which is also part of the Guidance framework, to return an instance of IConfigManager. The IConfigManager can be used to store configuration data on any level in SharePoint, so at Web, Site, WebApp and Farm level. Again, this is part of the Guidance framework and provides a single consistent framework to store configuration data that is not to be located in the web.config. Using the config manager, we request the current custom configured diagnostic areas. Then, for each area from our property (our new set), we check whether or not it already exists. If not, we add it. Finally, save the configuration again.

The feature deactivating method basically does the reverse, shown below.

public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {
   // Then remove the areas
   IConfigManager configMgr = SharePointServiceLocator.GetCurrent().GetInstance<IConfigManager>();
   DiagnosticsAreaCollection configuredAreas = new DiagnosticsAreaCollection(configMgr);
  
  
foreach (DiagnosticsArea area in MyAreas) {
      DiagnosticsArea areaToRemove = configuredAreas[area.Name];
      if (areaToRemove != null) {
         foreach (DiagnosticsCategory c in area.DiagnosticsCategories) {
            var existingCat = areaToRemove.DiagnosticsCategories[c.Name];
            if (existingCat != null) {
               areaToRemove.DiagnosticsCategories.Remove(existingCat);
            }
         }
     
        
if (areaToRemove.DiagnosticsCategories.Count == 0) {
            configuredAreas.Remove(areaToRemove);
         }
      }
   }

   configuredAreas.SaveConfiguration();
}

The only difference here is that we take a safe approach as suggested in the guidance, because other applications (depending on company guidelines) could have added categories to the diagnostic area. The safe way therefore is to remove the own configured categories and if none remain, remove the area.

Create the timer job to ensure event sources

In this section we will create a simple timer job that needs to be executed on every server in the farm. The job will call a method from the Guidance framework that will ensure that for each custom diagnostic area, a corresponding event source is created. This will have the same function as my PowerShell script in my previous post, just implemented through a timer job. The advantage here however, is that I can configure the timer job to run on specific intervals on all servers within the farm. The PowerShell script can also be scheduled and run remotely, but needs more management.

We define the timer job in the EnsureEventSourcesJob class.

public class EnsureSourcesJob : SPJobDefinition {
   public EnsureSourcesJob() : base() {}
  
   public
EnsureSourcesJob(string name, SPWebApplication webApplication) : base(name, webApplication, null, SPJobLockType.None) {
      this.Title = name;
   }

   public o
verride void Execute(Guid targetInstanceId)
{
      ILogger logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>();
      logger.TraceToDeveloper(“Ensure Configured Diagnostic Areas registered timer job executing.”, 103, Microsoft.SharePoint.Administration.TraceSeverity.Monitorable, “Events”);
      // Important! The JobLockType should be set to None to run on each server!
      if (this.LockType == SPJobLockType.None) {
         // our job has only one very simple task.
         // call the ensure configured areas registered method of the DiagnosticsAreaEventSource class.
         DiagnosticsAreaEventSource.EnsureConfiguredAreasRegistered();
      }
   }
}

Two things are important in this job, which is quite simple. First, you need to provide a public parameter less constructor for the serialization. Second, the type of job specified should be of type SPJobLockType.None. This will ensure that my timer job will be run on each server within the farm.

The call to the DiagnosticsAreaEventSource.EnsureConfiguredAreasRegistered method will make sure that an event source for each of the configured diagnostic areas exists on the specific server.


Once the timer job has run, you can go to Central Admin à Diagnostic logging to see your own categories and events. They will be treated the same as any out of the box configured sources and categories.

The Visual Studio solution can be downloaded from here.

CU.

Posted in SharePoint 2010, Visual Studio 2010 | Tagged: , , , | 5 Comments »

Visual Studio 2010 RTM available for MSDN subscribers

Posted by Patrick Boom on April 16, 2010

Much anticipated and by many rated as the best Visual Studio version yet. It is now available at MSDN for MSDN subscibers.

Visit http://msdn.microsoft.com/subscriptions to pick up your copy.

Visual Studio 2010 will be officially launched at April 20th. By that time, SharePoint 2010 RTM will also be released to MSDN subscribers.

Great times ahead. Have fun playing around with this best version yet! 😉

Posted in Visual Studio 2010 | Tagged: , , | Leave a Comment »

 
%d bloggers like this: