- You must have the appropriate SSDT components installed for the version of SSIS you are targeting.
- You must have the Client SDK components for SSIS installed (part of SQL Server).
- You must run Visual Studio as an adminstrator.
- The Gac Powershell module must be installed.
- Open Powershell as an administrator.
- Ensure execution of local scripts is allowed (Set-ExecutionPolicy -remoteSigned)
- Ensure the module is installed (Get-Module -ListAvailable -Name Gac)
- Install it if it is not already there (Install-Module Gac -Force)
For purposes of this document SQL 2016 will be used (13.0). Older or newer versions will have a different version number. Where it says {SsisVersion} use the correct version number for the targeted SSIS version.
- Create a new project for the task (P3Net.IntegrationServices.Tasks.DoSomething). a. Project Type = Class Library b. Framework Version = 4.5.2 (do not go higher than this version). c. Sign the assembly using the SNK file in the solution. d. Copy the Build Events settings from P3Net.IntegrationServices into the project. e. Update the AssemblyVersion.cs file to contain appropriate information about the assembly. Remove the following attributes. 1. AssemblyVersion 2. AssemblyFileVersion f. Add a shared file link to the Shared\AssemblyVersion.cs file.
- Add references to the required assemblies. a. P3Net.IntegrationServices (Project) b. Microsoft.SqlServer.ManagedDTS {SsisVersion} (Framework\Extensions) c. System.Drawing (Framework) d. System.Runtime.Serialization (Framework)
- Add a new class to represent the task (i.e. DoSomethingTask).
a. Derive from
BaseTask
. b. Make the task public. - Create an icon for the task.
a. Add to the project (ensure Build Action is Embedded Resource).
b. Go to Resources editor and add a resource for the icon. c. Ensure the Resources generated file is Public. - In the task type add the standard properties that will be needed.
public const string Description = "Some description seen by the designer"; public const string TaskName = "Display name of the task"; public static Icon Icon = Resources.MyTaskIcon; public const string UITypeName = "P3Net.IntegrationServices.Tasks.MyTaskUI, P3Net.IntegrationServices.Tasks.MyTask.UI, Version=" + AssemblyMetadata.ProductVersion + ", Culture=Neutral, PublicKeyToken={Public key}";
- Apply the
DtsTask
attribute[DtsTask(DisplayName = MyTask.TaskName, RequiredProductLevel = DTSProductLevel.None, Description = MyTask.Description, IconResource = "{path to icon resource}", UITypeName = MyTask.UITypeName)]
- Implement the
ExecuteCore
method a. This method is called at runtime to perform the work of the task. b. Use the context parameter to access information needed by the task.
c. ReturnDTSExecResult.Success
if the task completes or throw an exception otherwise.
d. Consider raising informational events to help diagnose issues at runtime. - (Optional) Implement
ValidateCore
a. If the task needs to do any validation including verifying connections and that all properties have been set then implement this method. b. If any validation fails then use Events.LogError to log the error and returnDTSExecResult.Failure
. c. Return the results of Base.ValidateCore if no validation errors occur.
For each property that will be configurable in SSIS do the following.
- Create a public property with a getter and setter.
- Optionally set a default value for the property
- If the property will be using a connection from ConnectionManager then do the following.
a. Create a private field to store the connection ID (i.e. _connectionId).
b. Create the getter/setter to use the private field
public string MyConnection { get { return TryGetConnectionName(_connectionId) ?? ""; } set { _connectionId = TryGetConnectionId(value); } }
To use a connection at runtime do the following.
- Use
Connections.GetConnection
to get the ConnectionManager. - Use
Acquireconnection
to get the underlying connection. - Pass the connection to a new connection type (i.e. HttpClientConnection) to get a usable instance.
- If any changes will be made to the connection, including URL or credentials then use
Clone()
on the object returned by AcquireConnection.var cm = context.Connections.GetConnection(ServerConnection); //Create a copy of the connection because we're going to change the URL var conn = new HttpClientConnection(cm.AcquireConnection(context.Transaction)).Clone(); if (conn == null) throw new Exception("Unable to acquire connection.");
Variables are multi-threaded resources in SSIS. Accessing a variable requires that it be locked and unlocked. Extension methods are provided to hide the details and can be used in most cases.
- Use
Variables.TryGetInfo
to get information about a variable. - Use
Variables.GetVar<T>
orTryGetVar<T>
to safely read a variable's value. Note that the value may change after the get returns. - Use
Variables.SetVar<T>
to safely set a variable's value. - If a lock needs to be held longer than a get/set call then use the standard locking API.
Every property of a task has to be persisted to XML. The default implementation will handle this for public properties of primitive or string types. For all other types including arrays and lists persistence has to be manually implemented.
-
Add the
IDTSComponentPersist
interface to the task. -
Implement
LoadXML
a. Use the extension methods and XML API to get each attribute and element from the XML. b. Remember that new properties may not have been persisted so handle this case gracefully. ``` public void LoadFromXML ( XmlElement node, IDTSInfoEvents infoEvents ) { Content = node.GetAttributeValue("Content"); ReportFormat = node.GetAttributeValue("ReportFormat"); ReportPath = node.GetAttributeValue("ReportPath"); m_connectionId = node.GetAttributeValue("ServerConnection");var elements = node.SelectNodes("Arguments/Argument").OfType<XmlElement>(); foreach (var element in elements) { var arg = new ReportParameter() { Name = element.GetAttributeValue("name"), Type = (ReportParameterType)Enum.Parse(typeof(ReportParameterType), element.GetAttributeValue("type")), IsNullable = Boolean.Parse(element.GetAttributeValue("isNullable")), DefaultValue = element.GetAttributeValue("defaultValue"), Value = element.GetAttributeValue("value") }; Arguments.Add(arg); }; } ```
-
Implement
SaveXML
a. Use the extension methods and XML API to store each property in an attribute or element. Use attributes for simple types and elements for complex types. b. The properties must be stored in a child element. ``` public void SaveToXML ( XmlDocument doc, IDTSInfoEvents infoEvents ) { var root = doc.CreateAndAddElement(GetType().Name);root.SetAttributeValue("Content", Content); root.SetAttributeValue("ReportFormat", ReportFormat); root.SetAttributeValue("ReportPath", ReportPath); root.SetAttributeValue("ServerConnection", m_connectionId); var element = root.CreateAndAddElement("Arguments"); foreach (var arg in Arguments) { var argumentElement = element.CreateAndAddElement("Argument"); argumentElement.SetAttributeValue("name", arg.Name); argumentElement.SetAttributeValue("type", arg.Type); argumentElement.SetAttributeValue("isNullable", arg.IsNullable); if (!String.IsNullOrWhiteSpace(arg.DefaultValue)) argumentElement.SetAttributeValue("defaultValue", arg.DefaultValue); argumentElement.SetAttributeValue("value", arg.Value); }; } ```
- Create a new project for the task (P3Net.IntegrationServices.Tasks.DoSomething.UI). a. Project Type = Class Library b. Framework Version = 4.5.2 (do not go higher than this version). c. Sign the assembly using the SNK file in the solution. d. Copy the Build Events settings from P3Net.IntegrationServices.UI into the project. e. Update the AssemblyVersion.cs file to contain appropriate information about the assembly. e. Update the AssemblyVersion.cs file to contain appropriate information about the assembly. Remove the following attributes. 1. AssemblyVersion 2. AssemblyFileVersion f. Add a shared file link to the Shared\AssemblyVersion.cs file.
- Add references to the required assemblies. a. P3Net.IntegrationServices (Project) b. P3Net.IntegrationServices.UI (Project) c. The runtime assembly for the task (Project) d. Microsoft.SqlServer.Dts.Design {SsisVersion} (Framework\Extensions) e. Microsoft.SqlServer.ManagedDTS {SsisVersion} (Framework\Extensions) e. System.Drawing (Framework) f. System.Runtime.Serialization (Framework) g. System.Windows.Forms (Framework) h. Microsoft.DataTransformationServices.Controls (Browse -> %Windows%\Microsoft.NET\assembly\GAC_MSIL\Microsoft.DataTransformationServices.Controls\v4.0_11.0)
- Create a new, internal WinForms form class (eg. MyTaskForm).
- Set the following properties. a. Text = My Task Editor
- Change the form to derive from
DTSBaseTaskUI
. - Modify the constructor to accept at least the
TaskHost
host andObject
representing the connections. - Modify the constructor to pass the task name, icon, description, host and connection objects to the base class (these values can come from the constant properties created for the runtime task).
- (Recommended) Pass true as the last parameter so the user has access to the Expressions tab.
- In the constructor add each task view instance to the host.
var startView = new GeneralView(); DTSTaskUIHost.FastLoad = false; DTSTaskUIHost.AddView("General", startView, null); DTSTaskUIHost.AddView("Settings", new SettingsView(), null); DTSTaskUIHost.FastLoad = true; DTSTaskUIHost.SelectView(startView);
Each tab in the designer is a separate view on the form. Many views can be implemented by using a PropertyGrid
. For these types of views do the following.
-
Create a new type, internal to represent the view data (eg. GeneralViewNode).
-
Add the
SortProperties
attribute to configure the order in which properties are shown. -
In the constructor initialize any needed fields.
-
For each property that is to be exposed. a. Add a public property with appropriate default value. b. Add the
Category
attribute to group the properties in the UI. c. Add theDescription
attribute to provide help for the property. -
Create a new, internal type to represent the view (i.e. GeneralView).
-
Derive the view from
DtsTaskUIPropertyView<TNode>
whereTNode
is the view data type. -
Override
CreateNode
to create the view data. -
(Optional) Implement
OnInitializeCore
to do any view-specific initialization. a. Ensure that you callBase.OnInitializeCore
before accessing any Node data. b. This method is called only once so only do one-time initialization in it. -
(Optional) Implement
OnPropertyChanged
if the view needs to do something when a property value is changed. -
Implement
Save
to save the changes back to the task.protected override void Save ( ) { var task = GetTask<MyTask>(); //General properties Host.Name = Node.Name; Host.Description = Node.Description; //Task properties task.Property1 = Node.Property1; ... }
-
(Optional) Implement
OnSelectionCore
to handle any per-selection initialization. a. Updates to the UI based upon changes in other views should be done here. b. UseGetView<T>
to get access to data in other views that impact what is displayed in the view. UseGetTask
only for initialization.
For views that will not use a PropertyGrid
additional work is needed.
- Create a new, internal type to represent the view (i.e. SettingsView).
- Derive the type from
DtsTaskUIView
(note that the designer will no longer render the form after this change). - Implement the type as you would any form.
- Override
OnInitializeCore
to initialize the view controls based upon the associated task. - Override
Save
to save the view contents back to the associated task. - (Optional) Implement
OnSelectionCore
to do any per-selection initialization. a. Updates to the UI based upon changes in other views should be done here. b. UseGetView<T>
to get access to data in other views that impact what is displayed in the view. UseGetTask
only for initialization.
-
Create a new, public class that derives from
DtsTaskUI
and represents the UI for the task (eg. MyTaskUI). -
Implement the
GetViewCore
method to return an instance of the task form created earlier.protected override ContainerControl GetViewCore () { return new GenerateSsrsTaskForm(Host, ServiceProvider.GetService<IDtsConnectionService>()); }
-
On the
DtsAttribute
for the runtime task add aUITypeName
property and set it to the type just created.
If properties shown in the UI should be variables then additional work is needed to make the integration seamless.
- Apply a type converter attribute to the property in the view data type and use the
VariablesStringConverter
type. - (Optional) Apply the
DefaultVariable
attribute to the property to specify the default name and type of the new variable. - (Alternative) If the defaults are more complex than can be specified in the attribute then implement the
INewVariableProvider
interface. - The base type will handle the creation and selection of the new variable.
If properties shown in the UI should be connections then additional work is needed to make the integration seamless.
- Apply a type converter attribute to the property in the view data type and use a converter that is specific to the type of connection(s) that are supported (see Converters for existing types).
- The base type will handle the creation and selection of the new connection.
To debug the runtime side of the task do the following.
- Build the code and ensure it was properly registered.
- Start SSDT and load an SSIS project.
- Drag and drop the task to the designer and set any properties.
- Save the project.
- Set a breakpoint on the first task in the designer.
- Start the SSIS package.
- When the breakpoint is hit switch back to VS and Attach to Process -> DtsDebugHost.exe (Managed x64 version).
- Set breakpoints in the task code accordingly.
- Continue running the SSIS package.
- Build the code and ensure it was properly registered.
- Restart SSDT if it is already running.
- Load an SSIS project.
- Drag and drop the task to the designer and save the project.
- Switch back to VS and Attach to Process -> DevEnv.exe.
- Set any breakpoints desired.
- Use SSDT to open the task UI for debugging.