Breaking changes between Content Studio version 5.1 and 5.2
Starting with version 5.2 the
for more information see Breaking interface changes in version 5.2.html
Synchronous event handlers in Content Studio
Synchronous event handlers is a powerful functionality that executes when a user performs a certain action in Content Studio. They behave very much the same way as a regular event in Windows program or as a trigger in SQL Server. The following synchronous events are supported in Content Studio 5.3.
- OnBeforeDocumentSave
- This event gets excuted before a document has been saved or before any processing has been made. Use this event to check a document content before it has reached any of the Content Studio save or create methods.
-
New in version 5.2.
Supplies the content to be saved. This event can prevent a document from beeing saved or created but cannot roll back any change made in Content Studio. -
New in version 5.3.
In this version a new interface for event handlers is supported if implemented by the handler. This makes it possible for a handler to change the content of the document that is about to be saved. The event handler can then supply the changed content back to Content Studio before it goes on with the save operation. This is only supported in the OnBeforeDocumentSave event. - OnDocumentPreview
-
Added in version 5.3.
The event handler receives limited data (similar to the OnBeforeDocumentSave save event) about the document and occurs when a document is being previewed from the Content Studio editor. When the event occurs all of the temporary files has been generated and this type of an event handler cannot prevent preview from accuring. The event handler can return a custom identifier and a custom url that can be used when previewing data in an external content store. - OnDocumentPreviewDispose
-
Added in version 5.3.
The event handler receives limited data (similar to the OnBeforeDocumentSave save event) about the document and occurs the Content Studio editor removes the temporary files after a preview operation. This event can be used to clean up temporary data in an external content store. - OnBeforeDocumentSynchronize
- New in version 5.2
- No content is supplied. This event can prevent a document from beeing synchronized but will not roll back any already processed document.
- OnDocumentApprove
- Starting with version 5.2, this event now supplies the just approved content of the document. In earlier versions the content was empty.
- OnDocumentCheckIn
- New in version 5.2
- The draft content is supplied.
- This event is fired when a document is checked in and is atomic.
- OnDocumentCheckOut
- New in version 5.2
- The draft content is supplied.
- This event is fired when a document is checked out and is atomic.
- OnDocumentCreate
- No content is supplied since this event is raised when the meta data is created before the actual content is processed. To access the content you must use the OnDocumentSave event that is raised just after this event. This event is is atomic.
- OnDocumentDelete
- This event is fired when a document is thrown into the recycling bin
- If existing, the approved content is supplied; otherwise the draft content is passed. For corrupt documents where no content exists content is empty. This event is atomic.
- OnDocumentDestroy
- This event is fired when a document in the recycling bin is deleted.
- No content is supplied since the document has already been deleted. This event is atomic.
- OnDocumentMoveInHierarchy
- New in version 5.2
- Content is not supplied since this event can occur on a document not checked out and for this reason Content Studio cannot determine which type of content to read.
- This event is fired when a document is moved within a document hierarchy (i.e its parent document is changed or its position is changed). This event is atomic.
- OnDocumentReject
- New in version 5.2
- The draft content is supplied.
- This event is fired when a document that are on versioning is rejected by an editor. This event is atomic.
- OnDocumentRestore
- New in version 5.2
- The draft content is supplied.
- This event is fired when a document is restored from the recycling bin. This event is atomic.
- OnDocumentRevision
- New in version 5.2
- The draft content is supplied.
- This event is fired when a document is sent on versioning so it can be approved by an editor. This event is atomic.
- OnDocumentRevisionRestore
- New in version 5.3
- The draft content is supplied.
- This event is fired when an older version of a document is restored and its content is copied into the draft content of the document. This event is atomic.
- OnDocumentSave
- The draft content is supplied. This event is atomic.
- OnXmlIndexSave
- New in version 5.2
-
Important
For more information on the content supplied in this event see the OnXmlIndexSave event data.html knowledgebase article.
This event is not atomic to its nature which means that it will NOT rollback the document save operation nor will it rollback the previous xml indexing change operation. - OnXmlIndexFieldDelete
- New in version 5.2
-
Important
For more information on the content supplied in this event see the OnXmlIndexFieldDelete event data.html knowledgebase article.
This event is not atomic to its nature which means that it will NOT rollback the previous xml indexing delete operation.
A synchronous event in Content Studio is raised whenever any of the actions above occurs on a category were an event handler has been registered. The main advantage with these events is that they are atomic - i.e. they can roll back any change that the action made to Content Studio. If an event handler triggers when any document in the Test1 category is approved and the event handler is set up to synchronize data with an external system in an atomic way, it important that the event handler can undo the preceding approve procedure made in Content Studio. Since synchronous event handler has the capability to roll back events in Content Studio this type of event handler has the character of being OnBefore event handlers.
Important information to implementers!
Versions prior to 5.2 RC1
Never try perform any read or update operation on the document that triggered the
event in a Content Studio synchronous event handler. This is true for all events
that calls their event handlers from within an Sql transaction. This can cause serious
problems and Content Studio ends up in a dead lock situation since the document
affected is locked by Sql Server until the transaction is committed. When your event
handler tries to read the locked document it will wait until the document is released
by the lock holder, which will not happend since the caller is waiting for the event
handler to finish.
The affected events are:
- OnDocumentApprove
- OnDocumentSave
- OnDocumentDelete
- OnDocumentDestroy
- OnDocumentCreate
Versions 5.2 RC1 and later
Read operations on the triggering document are supported, write operations are not. When you read properties for the affected document you will get the values as they will appear after that the main transaction has commited the changes. Deleted documents will be deleted and approved documents will appear as beeing approved even though that the changes might be rolled back by your event handler implementation.
OnDocumentDestroy is a special case. Since the document appears as beeing deleted in the database and its underlying file was deleted by a preceeding delete operation there is no way of getting information of the document deleted other that the properties passed in to the event handler.
Recursive events
Remember that that it is possible to create recursive events that can effectively lock up Content Studio entirely until a StackOverflowException occurs. This will occur if, for example, a OnDocumentSave event handler creates a new or updates an existing document in the same category as itself. The first document's OnDocumentSave will trigger the same event handler on the second document which in its turn will trigger OnDocumentSave on a third document and so forth. In these cases you must handle these issues in you implementation to avoid uncontrolled recursions.
In your handler you can easily check for the number of recursions by using the fact that each recursive call to your handler will be executed on the same thread. You can use a static counter field in your handler marked with the ThreadStatic attribute. This will ensure that only instances running on the same thread will share the value.
public class OnDocumentSave : DocumentSaveSyncHandler
{
private readonly int MaxRecursions = 1;
[ThreadStatic]
private static int Recursions;
protected override void Init()
{
base.Init();
Recursions++;
}
protected override void DoWork()
{
//stop execution if the number of recursions has
//exceeded the limit.
if (Recursions > MaxRecursions)
return;
//Working code follows
}
}
Public Class OnDocumentSave
Inherits DocumentSaveSyncHandler
Private Const MaxRecursions As Integer = 1
<ThreadStatic>
Private Shared Recursions As Integer
Protected Overloads Overrides Sub Init()
MyBase.Init()
Recursions += 1
End Sub
Protected Overloads Overrides Sub DoWork()
'stop execution if the number of recursions has
'exceeded the limit.
If Recursions > MaxRecursions Then
Return;
'Working code follows
End Sub
End Class
Creating an event handler in Content Studio version 5.2 and later
In earlier versions of Content Studio you had to create synchronous event handlers
from scratch by implementing the
If you write your event handler in Visual Studio 2005 or later you must create a new class library project and set a reference to both the SyncEvtHand.dll and CS5Interfaces.dll assemblies. These libraries are not directly related to Content Studio and is therefor not subject to versioning problem that can occure between Content Studio product updates. In your class library, you can have as many event handlers as you like but each handler must be implemented in a public class of their own. Each class should extend (inherit from) one of the following abstract base classes.
Base class | Usage |
---|---|
|
Represents an event handler that handles the OnDocumentPreview synchronous event in Content Studio 5.3 and later. |
|
Represents an event handler that handles the OnDocumentPreviewDispose synchronous event in Content Studio 5.3 and later. |
|
Represents an event handler that handles the OnBeforeDocumentSave synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnBeforeDocumentSynchronize synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentApprove synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentCheckIn synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentCheckOut synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentCreate synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentDelete synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentDestroy synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentMoveInHierarchy synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentSave synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentReject synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentRestore synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentRevision synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnDocumentRevisionRestore synchronous event in Content Studio 5.3 and later. |
|
Represents an event handler that handles the OnXmlIndexFieldDelete synchronous event in Content Studio 5.2 and later. |
|
Represents an event handler that handles the OnXmlIndexSave synchronous event in Content Studio 5.2 and later. |
Each class supplies a set of properties representing the document and the calling user. There are also a number of methods you should or can override and the actual work for the developer is to supply his or her programming logic in these methods.
-
ParseInputXml -
Parses the xml passed in to base class implementation of the
EventHandler method and loads the properties. - Normally, there is no need to override the base class implementation.
-
ValidateEvent - Validates the passed in event and determines whether this implementation can handle this event. This method returns false if the wrong type of event invokes the handler which results in an InvalidOperationException.
- If you choose to inherit from one of the classes above there is no need override this method since these classes already have taken care of the
- validation for you.
-
ParseCustomData - Called during initialization and provides a possibility for the implementer to analyze and perhaps parse the custom data passed in. Custom data can be supplied by the person that subscribed to the event handler in Content Studio.
- Override this method if you need to analyze or parse the custom data passed in.
-
Init - This method gets called when the base class has initialized all of its data, just before that the actual work is to be done.
- Override this method when there is a need for your implementation to do some private initialization such as creating a logging utility or read data from the registry or a configuration file.
-
DoWork - This is the method in which the actual work is done.
- Each event handler must override the method and supply the actual implementation of the event handler.
-
Finish - This method is called by the base class when the actual work has been done.
-
Override this method if there is a need to perform some code after that all work
has been done. For example you might like to set your own value to the
Status ,StatusText andCancel properties. -
Note that if an exception is generated in the
DoWork method this method is not called. For this reason you should not rely on this method for releasing any resources that needs to be freed, such as file handles. Instead you should override theDispose method and release these resources there.
Improvements in version 5.3
Version 5.3 introduces a number of improvements to event actions and to the base classes that your handlers inherit from.
A number of properties have been added and document content is now avaliable in a more structured way. When dealing
with EPT-documents the content now comes as an
The possibility of changing document content that is about to be saved in the OnBeforeDocumentSave event opens up a number of possibilities for the programmer. For example you could combine two or more Ept-fields into another field to create a new view of the data. Before Content Studio 5.3 you had to do these kinds of operations on the client using Javascript code.
The following code shows an handler that combine data from two Ept fields, FirstName and LastName, into a third field, DisplayName. The field DisplayName must exist in the schema or it will be ignored by Content Studio
//Only works in an handler that handles the OnBeforeDocumentSave event
public class MyBeforeDocumentSaveHandler : BeforeDocumentSaveSyncHandler
{
protected override void DoWork()
{
if (DocumentType == DocumentTypes.EPT_Document)
{
EptContent["DisplayName"] =
String.Format("{0} {1}", EptContent["FirstName"], EptContent["LastName"]);
}
}
}
'Only works in an handler that handles the OnBeforeDocumentSave event
Public class MyBeforeDocumentSaveHandler : BeforeDocumentSaveSyncHandler
Protected Overrides Sub DoWork()
If DocumentType = DocumentTypes.EPT_Document Then
EptContent("DisplayName") = _
String.Format("{0} {1}", EptContent("FirstName"), EptContent("LastName"))
End If
End Sub
End Class
There also has been a great number of properties added to the event handler base classes.
Name | Description |
---|---|
|
Gets an |
|
Gets the archive date of the document that triggered the event. This value can be null to indicate no limit. |
|
Gets an |
|
Gets an |
|
Important
|
|
Gets an |
|
Gets an |
|
Gets a For more information about this property see the section below. |
|
Gets a |
|
Gets the content of the ept document as an object as Content Studio's implementation of
the |
|
Gets an |
|
Gets the publish date of the document that triggered the event. |
|
Gets an |
|
Gets an |
|
Gets an |
The
Property name | Description |
---|---|
|
Gets a value indicating whether the document that triggered the event currently has the flag set that allows it to be published. |
|
Gets a value indicating whether the document that triggered the event has been reviewed in a workflow controlled by Content Studio Workflow Server. |
|
Gets a value indicating whether the document has a draft content. |
|
Gets a value indicating whether the document that triggered event the adds meta data content. |
|
Gets a value indicating whether the document that triggered the event has been authored in Content Studio Workflow Server. |
|
Gets a value indicating whether the document that triggered the event has been approved by Content Studio Workflow Server. |
|
Gets a value indicating whether the document that triggered the event is for authoring in Content Studio Workflow Server. |
|
Gets a value indicating whether the document that triggered the event is beeing reviewed in a workflow controlled by Content Studio Workflow Server. |
|
Gets a value indicating whether the document that triggered the event is in the recycling bin. |
|
Gets a value indicating whether the document is on workflow. |
|
Gets a value indicating whether the document that triggered the event is protected. |
|
Gets a value indicating whether the document that triggered the event is rejected in workflow. |
|
Gets a value indicating whether the document that triggered the event is rejected by the Content Studio Workflow Server. |
The new property
Name | Description |
---|---|
|
Gets or sets the archive date of the document that triggered the event.
This value can be null to indicate that no archive date should be applied.
The value of this property indicates the value that is about to be saved, not necessarily the current value of the document. |
|
Set or gets a value that specifies the content of the BODY tag of the document that triggered the event.
The value of this property indicates the value that is about to be saved, not necessarily the current value of the document. |
|
Set or gets a value that specifies the introduction content the document that triggered the event.
The value of this property indicates the value that is about to be saved, not necessarily the current value of the document. |
|
Gets or sets the keywords of the document that triggered the event.
The value of this property indicates the value that is about to be saved, not necessarily the current value of the document. |
|
Set or gets a value that specifies a short description of the document that triggered the event.
The value of this property indicates the value that is about to be saved, not necessarily the current value of the document. |
|
Set or gets a value that specifies the MenuData for the document that triggered the event.
MenuData contains user defined data for menu items and is only applicable if the document acts as a meny node content.
The value of this property indicates the value that is about to be saved, not necessarily the current value of the document. |
|
Gets or sets a value that represents the MenuTarget of the document that triggered the event.
MenuTarget contains a user defined target data for menu items and is only applicable if the document acts as a meny node content.
The value of this property indicates the value that is about to be saved, not necessarily the current value of the document. |
|
Set or gets a value that specifies the MenuUrl for the document that triggered the event.
MenuUrl contains a user defined Url for menu items and is only applicable if the document acts as a meny node content.
The value of this property indicates the value that is about to be saved, not necessarily the current value of the document. |
|
Gets or sets the publish date of the document as passed in by the caller that triggered the event.
The value of this property indicates the value that is about to be saved, not necessarily the current value of the document. |
|
Gets or sets a value indicating whether the document that triggered the event can be published.
The value of this property indicates the value that is about to be saved, not necessarily the current value of the document. |
The following sample handler shows how to change the PublishDate and the Keywords of a document that is about to be
saved.
This code only works in the OnBeforeDocumentSave synchronous event.
//Only works in an handler that handles the OnBeforeDocumentSave event
public class MyBeforeDocumentSaveHandler : BeforeDocumentSaveSyncHandler
{
protected override void DoWork()
{
//Do not forget to check for null!
if (SaveOperationArguments != null)
{
SaveOperationArguments.PublishDate = DateTime.Today.AddDays(1);
SaveOperationArguments.Keywords = "Content Studio CMS";
}
}
}
'Only works in an handler that handles the OnBeforeDocumentSave event
Public Class MyBeforeDocumentSaveHandler : BeforeDocumentSaveSyncHandler
Protected Overrides Sub DoWork()
'Do not forget to check for Nothing!
If Not SaveOperationArguments Is Nothing Then
SaveOperationArguments.PublishDate = DateTime.Today.AddDays(1)
SaveOperationArguments.Keywords = "Content Studio CMS"
End If
End Sub
End Class
Implementing IDisposable
If you need to release some resources in your handler, you should override the
The code sample is taken from the official MSDN documentation provided by Microsoft.
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Release managed resources.
}
// Release unmanaged resources.
// Set large fields to null.
// Call Dispose on your base class.
base.Dispose(disposing);
}
Protected Overloads Overrides Sub Dispose(disposing As Boolean)
If disposed = False Then
If disposing Then
'Release managed resources.
End If
End If
'Release unmanaged resources.
'Set large fields to null.
'Call Dispose on your base class.
Mybase.Dispose(disposing)
End Sub
Handling exceptions in atomic events
All exceptions generated in your handler will bubble up to the caller and when the
event is the current database transaction in Content Studio is rolled back to avoid
data corruption. You can also cancel the event and rollback any previous uncommitted
database changes by setting the
Of course, you also have the possibility to throw other exceptions if you like and sometimes this is the best thing to do when an unexpected problem occurs in your event handler.
Exceptions in non-atomic events
Non-atomic events are those that is not executed in the transaction context of the corresponding Content Studio
action. In these type of events the
- OnBeforeDocumentSave
- OnDocumentPreview
- OnDocumentPreviewDispose
- OnBeforeDocumentSynchronize
- OnXmlIndexSave
- OnXmlIndexFieldDelete
A sample event handler
The following code sample shows a simple implementation of a handler that implements auditing functionality when a document is successfully approved. It writes information about the approver and the document to a simple text file.
public class OnDocumentApprove : DocumentApproveSyncHandler
{
private System.IO.StreamWriter writer;
protected override void Init()
{
base.Init();
//create a StreamWriter that writes to a simple text file
try
{
writer = new System.IO.StreamWriter(@"C:\temp\logging\CSApprove.log", true);
writer.WriteLine()
writer.WriteLine("The job has been initialized")
}
catch(System.IO.IOException){}
}
protected override void DoWork()
{
//write the auditing message
if(writer != null)
{
writer.WriteLine(@"{0}\t{1} (id {2}) has been approved by {3} ({4})",
DateTime.Now,
DocumentName,
DocumentId,
CallerName,
CallerLogOnName);
}
}
protected override void Finish()
{
base.Finish();
Status = 0;
StatusText = "Success";
Cancel = false;
if(writer != null)
writer.WriteLine("The job has been completed.");
}
//Handle disposing of the writer
private bool disposed;
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Release managed resources.
if (!disposed && writer != null)
{
writer.WriteLine("The writer is closing.");
writer.Dispose();
}
disposed = true;
}
base.Dispose(disposing);
}
}
Public Class OnDocumentApprove
Inherits DocumentApproveSyncHandler
Private writer As System.IO.StreamWriter
Protected Overrides Sub Init()
Mybase.Init()
'create a StreamWriter that writes to a simple text file
Try
writer = New System.IO.StreamWriter("C:\temp\logging\CSApprove.log", True)
writer.WriteLine()
writer.WriteLine("The job has been initialized")
Catch System.IO.IOException
End Try
End Sub
Protected Overrides Sub DoWork()
'write the auditing message
If Not writer Is Nothing Then
writer.WriteLine("{0}" & vbTab & "{1} (id {2}) has been approved by {3} ({4})", _
DateTime.Now, _
DocumentName, _
DocumentId, _
CallerName, _
CallerLogOnName)
End If
End Sub
Protected Overrides Sub Finish()
Mybase.Finish()
Status = 0
StatusText = "Success"
Cancel = False
If Not writer Is Nothing Then
writer.WriteLine("The job has been completed.")
End If
End Sub
'Handle disposing of the writer
Private disposed As Boolean
Protected Overloads Overrides Sub Dispose(disposing As Boolean)
If disposing Then
'Release managed resources.
If Not disposed And Not writer Is Nothing
writer.WriteLine("The writer is closing.")
writer.Dispose()
disposed = True
End If
End If
Mybase.Dispose(disposing)
End Sub
End Class
This should output something like this to the log file.
The job has been initialized 2008-11-27 13:54:27 Test/Doc/Doc1 (id 2261) has been approved by John Doe (COORP\johdoe) The job has been completed. The writer is closing.
When you have built and tested your handler you are ready to install it in Content Studio. How this is done is described later in this article.
Validating, policy based event handlers
Policy based event handlers are available in version 5.2 SP1 and later.
In Content Studio 5.2 SP1 you can create validating event handlers that gets its validating rules through a set of policies. These policies are defined in an XML based format and supplied to the handler from the Command property in the Event Actions dialog.
The format of this XML is strictly controlled by an XML Schema and the following sample code gives an example of a policy set with one policy that limits the size of an uploaded file.
A sample policy set
<customData>
<policies>
<policy Enabled="true"
Name="MaxUploadedFileSize"
Value="23"
ValueClass="Mb"
Compare="FileSize"
InterpretValueAs="Maximum"
Description="Policy: Limits the size of an uploaded file."
ViolationMessage="A rule on this web site limits the size of an uploaded file, Please use a smaller file instead. Maximum file size is {0}" />
</policies>
</customData>
XPath | Description | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
customData/policies | Defines the start of the policy set. Only one policies element is allowed. | ||||||||||||||||
customData/policies/policy | Defines a single policy in the set. There can be unlimited number of policies in the set. | ||||||||||||||||
customData/policies/policy/@Enabled | Defines whether the policy is enabled or not. When the document is evaluated against the policy, only enabled policies are used. this attribute must be supplied and must have one of the acceptable values; true or false. | ||||||||||||||||
customData/policies/policy/@Name | Defines the name of the policy. All policy declarations must have a name of at least one character and each policy must have a name that is unique in the set of policies. | ||||||||||||||||
customData/policies/policy/@Value | Defines the value of the policy. This is the value that your event handler will use to compare against some property of the document. This can for example be the maximum allowed length of a file to upload. The attribute must exist but can be empty. | ||||||||||||||||
customData/policies/policy/@ValueClass | Defines a value that tells your event handler how the policy value should be interpreted. This can be the unit used when determine the size of the file uploaded. In this case @ValueClass might be "" or "Kb", "Mb" or "Gb" to indicate size in Bytes, KiloBytes, MegaBytes or GigaBytes respective. The attribute is optional. | ||||||||||||||||
customData/policies/policy/@Compare | This attribute provides a way for the caller to inform the handler which check operation to make. Compare is useful if you have more that one policy check to do and you need to inform the handler on what this policy should be checked against. The attribute optional. | ||||||||||||||||
customData/policies/policy/@InterpretValueAs |
This value tells your event handler how the comparison should be made. This attribute
is required and values accepted are defined in the
|
||||||||||||||||
customData/policies/policy/@Description | A description of the policy. The attribute is required but can be empty. | ||||||||||||||||
customData/policies/policy/@ViolationMessage | The message to use as message in the InvalidOperationException that is thrown when a policy is violated. If this message contains the format placeholder {0} this place holder will be replaced by the value that was violated. The attribute optional. |
When you have written you policy definition you simply into the Command field in an Event Action definition.
The Event actions property dialog with a policy definition.
When you implement your own policy based event handler you typically derive from one of the bases classes provided.
Base class | Usage |
---|---|
|
Represents a policy based event handler that handles the OnBeforeDocumentSave synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnBeforeDocumentSynchronize synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentApprove synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentCheckIn synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentCheckOut synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentCreate synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentDelete synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentDestroy synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentMoveInHierarchy synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentSave synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentReject synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentRestore synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentRevision synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnDocumentRevisionRestore synchronous event in Content Studio 5.3 and later. |
|
Represents a policy based event handler that handles the OnXmlIndexFieldDelete synchronous event in Content Studio 5.2 SP1 and later. |
|
Represents a policy based event handler that handles the OnXmlIndexSave synchronous event in Content Studio 5.2 SP1 and later. |
There are at least two methods you must override in your event handler;
The actual part of the validation work takes place in your implementation of the
A sample validating event handler
The follwing sample shows a complete implementation of a validating event handler
that handles the OnBeforeDocumentSave event and uses a policy that was demonstrated
earlier in this article. This class extends the
using System;
using ContentStudio.EventActions.SynchronousEventHandlers;
using System.IO;
namespace SyncTestHandlers
{
public class UploadFileSizeCheckingSyncHandler : BeforeDocumentSavePolicySyncHandler
{
protected override void Init()
{
//get the document type?
var catReader = new ContentStudio.Document.CategoryReader();
if (catReader.GetDocumentType(ConnectionId, CallerSessionId, CategoryId) != ContentStudio.Document.DocumentTypes.File)
throw new InvalidOperationException("This event handler implementation can only be used on categories that contains uploaded files");
//make sure that the base class init method gets executed properly
base.Init();
}
protected override bool ValidatePolicy(Policy policy)
{
if (policy.Enabled == false)
return true;
//Validate the file size against the rule that checks the file size
if (String.Equals("FileSize", policy.Compare, StringComparison.OrdinalIgnoreCase))
{
//determine whether the temporary, uploaded file exists on disc
if (!File.Exists(ContentSourceFile))
throw new FileNotFoundException("Uploaded temporary file \"{0}\" could not be found.", ContentSourceFile);
//get the size of the file stored on disc
var fileSize = new FileInfo(ContentSourceFile).Length;
//get the limiting value
var limitFileSize = GetActualFileSize(policy.GetValue<long>(), policy.ValueClass);
//This policy checks the size of the uploaded
switch(policy.InterpretValueAs)
{
//The file may not be larger than the limiting value
case Policy.ValueInterpretation.Maximum:
if (fileSize > limitFileSize )
return false;
return true;
//The file may not be smaller than the limiting value
case Policy.ValueInterpretation.Minimum:
if (fileSize < limitFileSize)
return false;
return true;
//The file must be equal to the limiting value
case Policy.ValueInterpretation.Equal:
if (fileSize != limitFileSize)
return false;
return true;
//The file cannot be equal to the limiting value
case Policy.ValueInterpretation.NotEqual:
if (fileSize == limitFileSize)
return false;
return true;
//there is no comparison for other types in this handler!
default:
throw new ArgumentException
("policy",
String.Format("The value {0} of the InterpretValueAs attribute is not valid for this handler!",
policy.InterpretValueAs)
);
}
}
return true;
}
protected override string InterpretPolicyValue(Policy policy)
{
if(policy == null)
return String.Empty;
var format = "{0} {1}";
if (String.Equals("FileSize", policy.Compare, StringComparison.OrdinalIgnoreCase))
{
if(String.IsNullOrEmpty(policy.ValueClass))
return String.Format(format, policy.Value, "Bytes");
if (policy.ValueClass.Equals("B", StringComparison.OrdinalIgnoreCase))
return String.Format(format, policy.Value, "Bytes");
if (policy.ValueClass.Equals("KB", StringComparison.OrdinalIgnoreCase))
return String.Format(format, policy.Value, "Kb");
if (policy.ValueClass.Equals("MB", StringComparison.OrdinalIgnoreCase))
return String.Format(format, policy.Value, "Mb");
if (policy.ValueClass.Equals("GB", StringComparison.OrdinalIgnoreCase))
return String.Format(format, policy.Value, "Gb");
}
return base.InterpretPolicyValue(policy);
}
private long GetActualFileSize(long size, string unit)
{
if(String.IsNullOrEmpty(unit))
return size;
if (unit.Equals("B", StringComparison.OrdinalIgnoreCase))
return size;
if (unit.Equals("KB", StringComparison.OrdinalIgnoreCase))
return size * 1024;
if (unit.Equals("MB", StringComparison.OrdinalIgnoreCase))
return size * 1024 * 1024;
if (unit.Equals("GB", StringComparison.OrdinalIgnoreCase))
return size * 1024 * 1024 * 1024;
return -1;
}
protected override void DoWork()
{
//nothing to do here this time
}
}
}
When the policy is violated a user that tries to upload a too large file will experience the following message stating that the file is to large to be uploaded.
Validation error message
Creating the Event handler in earlier version or from scratch
Often there is very little use to implement the eventh handler interfaces directly, it is much preferred to inherit from any of the base classes above to radically facilitate the development task.
Any event handler of this type must implement the
Content Studio introduced the
The EventHandler method of the
void EventHandler(string EventXMLArguments,
string CustomData,
object Content,
ICSCredentialsContainer Credentials,
ref bool Cancel,
out int Status,
out string StatusText)
Sub EventHandler(EventXMLArguments As String, _
CustomData As String, _
Content As Object, _
Credentials As ICSCredentialsContainer, _
ByRef Cancel As Boolean, _
<OutAttribute> ByRef Status As Integer, _
<OutAttribute> ByRef StatusText As String)
The EventHandler method of the
void EventHandler(
string eventXmlArguments,
string customData,
ICSContentContainer content,
ICSCredentialsContainer credentials,
ref bool cancel,
out int status,
out string statusText
)
Sub EventHandler ( _
eventXmlArguments As String, _
customData As String, _
content As ICSContentContainer, _
credentials As ICSCredentialsContainer, _
ByRef cancel As Boolean, _
<OutAttribute> ByRef status As Integer, _
<OutAttribute> ByRef statusText As String _
)
- EventXMLArguments
- String
- An xml document sent to the implementer from Content Studio.
-
The exact content varies between different events. The sample is typical for a document
event (ex. OnDocumentSave or OnDocumentApprove).
Xml<event type="Integer value" event="String value" msgid="String value"> <timestamp>Date value</timestamp> <connectionid>Integer value</connectionid> <callerinfo sid="String value" logonname="String value" email="String value" fullname="String value" userkey="String value" sessionid="Integer value"/> <objectdata> <documentid>Integer value</documentid> <documentname>String value</documentname> <filename>String value</filename> <encoding>String value</encoding> <categoryid>Integer value</categoryid> <documenttitle>String value</documenttitle> <!-- In version 5.3, additional fields have been added --> </objectdata> </event>
Event Xml explained Element Attribute Description event This element acts as the root node of the document. type The numeric identifier of the event. event The name the event. msgid A unique identifier of the specific event message. timestamp The date and time of when the event was raised. connectionid An identifier of the Web site callerinfo This element has attributes that contain data about the caller. sid The caller's security identifier in the SDL format (ex. S-1-5-16). logonname The caller's logon name in the DOMAIN\USERNAME format. email The caller's email address, if registered in CS. fullname The caller's fullname, if registered in CS. userkey The caller's Content Studio userkey (ex. ABCD1). sessionid The caller's session identifier. objectdata This element act as root node for elements that contains data about the affected document. documentid The identifier of the document documentname The logical file name of the affected document (ex. Unit1/Category1/pic1.gif). filename The file name of the affected document as it appears on disc. encoding The encoding (ex. utf-8), if existing, of the affected document. categoryid An identifier of the category where the affected document is placed. documenttitle The name of the affected document. - CustomData
- String
- User defined static data that has been specified for the Event action definition that triggered this event. The data is supplied via the Command text field in the Event actions property window.
-
Content (in
ICSEventHandler ) only. - System.Object
- The document content as it exists in Content Studio when the event was raised. Not all events supply content.
-
Content (in
ICSEventHandler2 ) only. -
ContentStudio.EventActions.ICSContentContainer - This interface is used to supply the affected document content from Content Studio to the handler and back from the handler to Content Studio. In addition to this you can pass a collection of property names and values back to Content Studio from the event handler. Currently Content Studio only uses returned content and property values in the OnBeforeDocumentSave event.
- Credentials
-
ContentStudio.EventActions.ICSCredentialsContainer -
A reference to Content Studio's implementation of the
ICSCredentialsContainer interface. This is used to pass system defined credentials to the custom implementation. The event handler can use these credentials when ex. communicating with an external system such as a database or a mail server. This parameter is null (Nothing in Visual Basic) if no credentials has been specified. - Cancel
- Boolean
- A reference to a Boolean parameter. Event handlers sets this parameter to true to indicate that all changes in the Content Studio event that triggered the event should be rolled back. If the value remains false after the call, the event in Content Studio will not be rolled back, regardless of the outcome of the operation performed by the event handler.
- Status
- Int32
- Implementations should set this parameter to zero to indicate success. All other values will throw an error in the Content Studio event that triggered the event causing data to be rolled back. This parameter is passed uninitialized.
- StatusText
- String
- Implementation sets this parameter to the textual representation of the error indicated in the Status parameter. This parameter is passed uninitialized.
All your logic will be implemented in this method and depending on what you like to do you might not need all of the parameters. However, since both the Status and the StatusText parameters are output parameters, you must provide a value for them. They will inform Content Studio about the outcome of the call - Status = 0 indicates success and any other value is an error. The Cancel parameter is important since it controls whether Content Studio should roll back every change been made in Content Studio. By setting Cancel to true the event handler informs Content Studio that a rollback should be performed.
A small sample is shown below - it is intended to run OnDocumentDelete and effectively prevents any document in the category where it is applied, to be deleted. For demostration purpose the code uses the CustomData parameter to provide a user defined message to the caller. In this particular sample we do not use any of the Xml data passed in via the EventXMLArguments parameter but with this data you can get some information about the document and the user that triggered the event.
Finally the event handler is ready to be compiled and tested.
A sample event handler
using System;
using ContentStudio.EventActions;
namespace SyncEventHandler
{
public class OnDocumentDeleteHandler : ContentStudio.EventActions.ICSEventHandler2
{
void ICSEventHandler.EventHandler(string EventXMLArguments,
string CustomData,
ICSContentContainer Content,
ICSCredentialsContainer Credentials,
ref bool Cancel,
out int Status,
out string StatusText)
{
Status = -1;
//Any custom message passed in?
if((CustomData != null) || (CustomData.Length == 0))
StatusText = CustomData;
else
StatusText = "Delete is forbidden in this category";
Cancel = true;
}
}
}
Imports System
Imports ContentStudio.EventActions
Namespace SyncEventHandler
Public Class OnDocumentDeleteHandler
Implements ContentStudio.EventActions.ICSEventHandler2
Private Sub EventHandler(EventXMLArguments As String, _
CustomData As String, _
Content As ICSContentContainer, _
Credentials As ICSCredentialsContainer, _
ByRef Cancel As Boolean, _
<OutAttribute> ByRef Status As Integer, _
<OutAttribute> ByRef StatusText As String) _
Implements ICSEventHandler.EventHandler
Status = -1
'Handle the case when CustomData is Nothing
If CustomData Is Nothing Then
CustomData = String.Empty
End If
'Any custom message passed in?
If CustomData.Length = 0 Then
StatusText = "Delete is forbidden in this category"
Else
StatusText = CustomData
End If
Cancel = True
End Sub
End Class
End Namespace
Installing the event handler
Compile the code as a class library (.dll file). In Visual Studio this is done automatically
for you and outside Visual Studio you can use one of the compilers that is a part
of the freely .NET Framework SDK. For more information about the command line compilers
see the .NET Framework SDK documentation. You can name your .dll SyncEventHandler.dll,
the name of the file is normally also the Assembly name and the assembly name is
important when you use the event handler from Content Studio later.
NOTE
You must use at least version 2.0 of the .NET Framework SDK or Visual Studio 2005
or later to be able to build the event handler.
Now, locate the installation directory of the Content Studio Server service (CS5ServiceHost.exe) that normally gets installed in the C:\Program files\Teknikhuset\Content Studio 5\CSServer folder. Copy the dll into this directory and you are ready to go!
Use the event handler
When you have properly installed your event handler in Content Studio it is time
to test it.
Select a category where you would like the event handler to be used. Be default
only members of the administrators group has permissions to manage event handlers
in Content Studio. This permission can be delegated on the site root level or on
a single category but event actions permissions are not inherited to child categories.
On the category of choise, create a new Event handler. This will bring up the "Properties
for Event actions" dialog.
- Name your event handler Prohibit document delete
- Select the OnDocumentDelete event
- Set the type to Synchronous Event handler object
The User data button opens up the User Credentials dialog that makes it possible to provide credentials that the event handler receives through the ICSCredentials interface but in this case there is no need for this functionality. Credentials, however, can be useful if you need to communicate with an external system and could contain login credentials for that system.
The ProgID field specifies the programmatic name of your event handler and can be written in serveral ways where the simplest form is
Namespace.Class, Assembly name
The complete format looks like:
Namespace.Class, Assembly name, Version=value, Culture=value, PublicKeyToken=value
- Examples
- The first example is as simple as it gets where only the full name and the assembly name are given. The assembly name is normally the same as the name of the dll file minus the .dll extension.
-
SyncEventHandler.OnDocumentDelete, SyncEventHandler
- The second example is more specific an specifies that the event handler has been given a strong name and we also needs a specific version of the assembly to be loaded with a specific culture. This advanced type info is needed when your event handler assembly has been registered in the global assembly cache (GAC).
-
SyncEventHandler.OnDocumentDelete, SyncEventHandler, Culture=neutral, Version=1.0.0.0, PublicKeyToken=feea6d462bd740da
Finally, if everything is OK so far Content Studio will throw an error message when you delete any document located in the category in question.
Debugging the event handler
In order to be able to debug your event handler from Visual Studio you must have Content Studio installed locally on your machine and the services Content Studio server and Content Studio Service Manager must be running.
- Change the output directory of your event handler's assembly to the same directory where the Content Studio services are installed. ex. C:\Program Files\Teknikhuset\Content Studio 5\CSServer, look for the file CS5ServiceHost.exe
- Rebuild your project, with debugging information, at the new location.
- Start the debugging session from Visual Studio by clicking the Debug - Attach to Process... menu alternative. A new dialog shows up and from that dialog you select the CS5ServiceHost.exe process and press the Attach button.
- You now can set breakpoints in your code and the breakpoints should be hit whenever Content Studio raises a event that is set up to be handled by your event handler.