In my previous blog I introduced the new Extension Installer from a developer's perspective. In this Blog I will continue that discussion by showing how the various classes interact.
Reading the Manifest.
The Installer class has a number of Constructors, but the Install Wizard invokes the following Contructor.
63 Public Sub New(ByVal tempFolder As String, ByVal manifest As String, ByVal physicalSitePath As String, ByVal loadManifest As Boolean)
64 _InstallerInfo = New InstallerInfo(tempFolder, manifest, physicalSitePath)
65 If loadManifest Then
66 ReadManifest()
67 End If
68 End Sub
This Constructor instantiates an InstallerInfo instance, and then calls the ReadManifest method.
358 Public Sub ReadManifest()
359 InstallerInfo.Log.StartJob(Util.DNN_Reading)
360
361 If InstallerInfo.ManifestFile IsNot Nothing Then
362 ReadManifest(New FileStream(InstallerInfo.ManifestFile.TempFileName, FileMode.Open, FileAccess.Read))
363 End If
364
365 If InstallerInfo.Log.Valid Then
366 InstallerInfo.Log.EndJob(Util.DNN_Success)
367 Else
368 'Delete Temp Folder
369 If Not String.IsNullOrEmpty(TempInstallFolder) Then
370 Directory.Delete(TempInstallFolder, True)
371 End If
372 End If
373 End Sub
This method in turn calls a private ReadManifest method that actually does the work, by creating a FileStream from the manifest file..
254 Private Sub ReadManifest(ByVal stream As Stream)
255 Dim doc As New XPathDocument(stream)
256
257 'Read the root node to determine what version the manifest is
258 Dim rootNav As XPathNavigator = doc.CreateNavigator()
259 rootNav.MoveToFirstChild()
260 Dim packageType As String = Null.NullString
261 If rootNav.Name = "dotnetnuke" Then
262 packageType = Util.ReadAttribute(rootNav, "type")
263 ElseIf rootNav.Name.ToLower = "languagepack" Then
264 packageType = "LanguagePack"
265 Else
266 InstallerInfo.Log.AddFailure(Util.PACKAGE_UnRecognizable)
267 End If
268
269 Select Case packageType
270 Case "Package"
271 'Parse the package nodes
272 ProcessPackages(rootNav)
273 Case "Module"
274 'Legacy Module
275 Dim modulewriter As New ModulePackageWriter(rootNav, InstallerInfo)
276 Dim legacyManifest As String = modulewriter.WriteManifest()
277 Dim legacyDoc As New XPathDocument(New StringReader(legacyManifest))
278
279 'Parse the package nodes
280 ProcessPackages(legacyDoc.CreateNavigator().SelectSingleNode("dotnetnuke"))
281 Case "LanguagePack"
282 'Legacy Language Pack
283 Dim languageWriter As New LanguagePackWriter(rootNav, InstallerInfo)
284 Dim legacyManifest As String = languageWriter.WriteManifest()
285 Dim legacyDoc As New XPathDocument(New StringReader(legacyManifest))
286
287 'Parse the package nodes
288 ProcessPackages(legacyDoc.CreateNavigator().SelectSingleNode("dotnetnuke"))
289 End Select
290 End Sub
In this method an XPathDocument is created from the FileStream. Note, that we are using an XPathDocument rather than an XmlDocument. This is because in a read-only scenario these classes perform better.
The first section of this method determines whether the manifest represents a Legacy LanguagePack, a Legacy Module or a new Package. In the Select Case statement, if the manifest represents a Legacy Module or a Legacy LanguagePack, the manifests are passed to a "PackageWriter" which reads thje legacy manifest and returns a Package manifest. In all three cases the resulting manifests are passed to the ProcessPackages method.
246 Private Sub ProcessPackages(ByVal rootNav As XPathNavigator)
247 'Parse the package nodes
248 For Each nav As XPathNavigator In rootNav.Select("packages")
249 Dim name As String = XmlUtils.GetNodeValue(nav, "package/name")
250 Packages.Add(name, New PackageInstaller(nav.InnerXml, InstallerInfo))
251 Next
252 End Sub
In theory a v5.0 manifest can support multiple packages and so this method loops through the packages/package elements to create a PackageInstaller class for each package and add it to the Packages collection. I said in theory we can have mutiple packages in a manifest (and as you can see this is supported in the Installer Framework), but in practice the Install Wizard currently only supports a single package in a manifest. The manifest fragment for the package is passed to the constructor of the PackageInstaller. Note also that the InstallerInfo instance is also passed to the PackageInstaller.
97 Public Sub New(ByVal packageManifest As String, ByVal info As InstallerInfo)
98 Package = New PackageInfo(info)
99 Package.Manifest = packageManifest
100
101 If Not String.IsNullOrEmpty(packageManifest) Then
102 'Create an XPathDocument from the Xml
103 Dim doc As New XPathDocument(New StringReader(packageManifest))
104 Dim nav As XPathNavigator = doc.CreateNavigator().SelectSingleNode("package")
105 ReadManifest(nav)
106 End If
107 End Sub
The constructor checks that the manifest is not empty and calls its ReadManifest method.
326 Public Overrides Sub ReadManifest(ByVal manifestNav As XPathNavigator)
327
328 .....
416
417 'Read Components
418 ReadComponents(manifestNav)
419 End Sub
The ReadManifest method is quite long as it processes the manifest elements for the package, but the last statement of the method calls the ReadComponents method.
186 Private Sub ReadComponents(ByVal manifestNav As XPathNavigator)
187 'Parse the component nodes
188 For Each componentNav As XPathNavigator In manifestNav.CreateNavigator().Select("components/component")
189 'Set default order to next value (ie the same as the size of the collection)
190 Dim order As Integer = ComponentInstallers.Count
191
192 Dim type As String = componentNav.GetAttribute("type", "")
193
194 If InstallMode = InstallMode.Install Then
195 Dim installOrder As String = componentNav.GetAttribute("installOrder", "")
196 If Not String.IsNullOrEmpty(installOrder) Then
197 order = Integer.Parse(installOrder)
198 End If
199 Else
200 Dim unInstallOrder As String = componentNav.GetAttribute("unInstallOrder", "")
201 If Not String.IsNullOrEmpty(unInstallOrder) Then
202 order = Integer.Parse(unInstallOrder)
203 End If
204 End If
205
206 If Package.InstallerInfo IsNot Nothing Then
207 Log.AddInfo(Util.DNN_ReadingComponent + " - " + type)
208 End If
209
210 Dim installer As ComponentInstallerBase = InstallerFactory.GetInstaller(componentNav, Package)
211 If installer Is Nothing Then
212 Log.AddFailure(Util.EXCEPTION_InstallerCreate)
213 End If
214
215 ComponentInstallers.Add(order, installer)
216 Me.Package.InstallerInfo.AllowableFiles += ", " + installer.AllowableFiles
217 Next
218 End Sub
This method demonstrates one of the most important features of the new Installer. It reads the individual component sections of the manifest and uses a Factory Class to create the appropriate ComponentInstaller (line 210). The factory class inspects the manifest passed to it and returns a ComponentInstaller which is added to the ComponentInstallers collection. This collection is a SortedList. In most situations the components are added to the SortedList in the order in which they are processed in the manifest, but the manifest supports the use of an "installOrder" and "uninstallOrder" attribute which can be used to control the order of installation of the components.
Lets look at the Factory Class.
107 Public Shared Function GetInstaller(ByVal manifestNav As XPathNavigator, ByVal package As PackageInfo) As ComponentInstallerBase
108 Dim installerType As String = Util.ReadAttribute(manifestNav, "type")
109 Dim componentVersion As String = Util.ReadAttribute(manifestNav, "version")
110
111 Dim installer As ComponentInstallerBase = GetInstaller(installerType)
112
113 If installer IsNot Nothing Then
114 'Set package
115 installer.Package = package
116
117 'Set type
118 installer.Type = installerType
119
120 If Not String.IsNullOrEmpty(componentVersion) Then
121 installer.Version = New Version(componentVersion)
122 Else
123 installer.Version = package.Version
124 End If
125
126 'Read Manifest
127 If package.InstallerInfo.InstallMode <> InstallMode.ManifestOnly OrElse installer.SupportsManifestOnlyInstall Then
128 installer.ReadManifest(manifestNav)
129 End If
130 End If
131
132 Return installer
133
134 End Function
The public GetInstaller method first calls a private method that determines which installer to instantiate.
54 Public Shared Function GetInstaller(ByVal installerType As String) As ComponentInstallerBase
55 Dim installer As ComponentInstallerBase = Nothing
56
57 Select Case installerType
58 Case "File"
59 installer = New FileInstaller()
60 Case "Assembly"
61 installer = New AssemblyInstaller()
62 Case "ResourceFile"
63 installer = New ResourceFileInstaller()
64 Case "AuthenticationSystem", "Auth_System"
65 installer = New AuthenticationInstaller()
66 Case "Script"
67 installer = New ScriptInstaller()
68 Case "Config"
69 installer = New ConfigInstaller()
70 Case "Cleanup"
71 installer = New CleanupInstaller()
72 Case "Skin"
73 installer = New SkinInstaller()
74 Case "Container"
75 installer = New ContainerInstaller()
76 Case "Module"
77 installer = New ModuleInstaller()
78 Case "Language", "LanguagePack"
79 installer = New LanguageInstaller()
80 Case "SkinObject"
81 installer = New SkinControlInstaller()
82 Case Else
83 'Installer type is defined in the List
84 Dim listController As New Lists.ListController()
85 Dim entry As ListEntryInfo = listController.GetListEntryInfo("Installer", installerType)
86
87 If entry IsNot Nothing AndAlso Not String.IsNullOrEmpty(entry.Text) Then
88 'The class for the Installer is specified in the Text property
89 installer = CType(Reflection.CreateObject(entry.Text, "Installer_" + entry.Value), ComponentInstallerBase)
90 End If
91 End Select
92
93 Return installer
94
95 End Function
If we look at this method we can see the list of Installers which are provided. In the "Case Else" block however we can see that this ComponentInstaller system is extensible. This allows, developers to add their own ComponentInstallers to a DotNetNuke installation by registering them in the Lists Module. Thus a developer could create a module that uses templates for example. The developer would include code in the module for a TemplateComponentInstaller, and would register the installer into the Lists module. They could then provide their own "Install Template" functionality to add new templates.
Each component has its own ReadManifest which is called by public GetInstaller method. For example the ConfigInstallers ReadManifest is shown below.
172 Public Overrides Sub ReadManifest(ByVal manifestNav As XPathNavigator)
173 Dim nav As XPathNavigator = manifestNav.SelectSingleNode("config")
174
175 'Get the name of the target config file to update
176 Dim nodeNav As XPathNavigator = nav.SelectSingleNode("configFile")
177 Dim targetFileName As String = nodeNav.Value
178 If Not String.IsNullOrEmpty(targetFileName) Then
179 _TargetFile = New InstallFile(targetFileName, "", Me.Package.InstallerInfo)
180 End If
181
182 'Get the Install config changes
183 nodeNav = nav.SelectSingleNode("install")
184 _InstallConfig = nodeNav.InnerXml
185
186 'Get the UnInstall config changes
187 nodeNav = nav.SelectSingleNode("uninstall")
188 _UnInstallConfig = nodeNav.InnerXml
189 End Sub
This blog has stepped though the code for reading the manifest in the new Extension Installer. Once this process is complete, the Installer class contains a Dictionaryof PackageInstallers, and each PackageInstaller contains a SortedList of ComponentInstallers. In my next blog in this series I will show how a similar approach is used to "Install" and "UnInstall" a Package.