Continuing on from Part 2, we get into the bulk of the functionality, defining the structure and dealing with IIS.
Step 5: Structure
The default structure we have defined for us is not really ideal for our web application:
Default Structure
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION" Name="!(loc.ProductName)">
</Directory>
</Directory>
</Directory>
Although you can install a web application under Program Files, I would have thought this was rare (we never do!). Somewhat more appropriate might be:
Web Friendly Structure
<!-- Define the 'structure' for the installation -->
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id="IISMain" Name='inetpub'>
<Directory Id="WWWMain" Name='wwwroot'
ComponentGuidGenerationSeed='CA19CA4A-C69B-4CDB-BCBD-6C3C5E9A3EDC'>
<Directory Id='INSTALLLOCATION' Name='!(loc.ProductName)'>
</Directory>
</Directory>
</Directory>
</Directory>
(Check out the documentation for an explanation of ComponentGuidGenerationSeed.)
This structure is far more familiar, so we end up with something like c:\inetpub\wwwroot\MyWebApp. We obviously do not want to hardcode such path settings, so we’ll look at how we manipulate this a bit latter when discussing the user interface.
Step 6: IIS
Aside from the physical folder structure, we will need to define various other settings for our web application such as virtual directory, web application pool, web application, mime types, mappings, etc.. If you haven’t already, add the IIS namespace to the Product.wxs:
Add IIS Namespace
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:iis="http://schemas.microsoft.com/wix/IIsExtension">
Then add in the following within our inner directory:
IIS Definitions
<Directory Id='INSTALLLOCATION' Name='!(loc.ProductName)'>
<!-- The component to define the Virtual Directory.-->
<Component Id="WebVirtualDirComponent" Guid="A720C1C9-1D8D-4941-976D-FB1C5C9EF8BB">
<!-- Define App Pool - identity if not set defaults to: ApplicationPoolIdentity -->
<iis:WebAppPool Id="AppPool" Name="[VD][WEBSITE_ID]" ManagedRuntimeVersion="v4.0"
IdleTimeout="0" RecycleMinutes="0" ManagedPipelineMode="integrated">
</iis:WebAppPool>
<iis:WebVirtualDir Id="VDir" Alias="[VD]"
Directory="INSTALLLOCATION"
WebSite="SelectedWebSite">
<iis:MimeMap Id="SilverlightMimeType" Extension=".xap"
Type="application/x-silverlight-app" />
<iis:WebApplication Id="MyWebAppApplication" WebAppPool="AppPool"
Name="[VD][WEBSITE_ID]" />
<iis:WebDirProperties Id="MyWebSite_Properties" AnonymousAccess="yes"
WindowsAuthentication="no" DefaultDocuments="Default.aspx" />
</iis:WebVirtualDir>
<CreateFolder/>
<!-- Need to have to ensure created -->
</Component>
<Component Id="EnableASPNet4Extension" Permanent="yes"
Guid="C8CDAB96-5DDC-4B4C-AD7E-CD09B59F7813">
<CreateFolder/>
<!-- Need to have to ensure created -->
<iis:WebServiceExtension Id="ASPNet4Extension" Group="ASP.NET v4.0.30319" Allow="yes"
File="[ASPNETISAPIDLL]" Description="ASP.NET v4.0.30319"
UIDeletable="no"/>
</Component>
</Directory>
So here we are defining a component that contains the creation of an application pool and a virtual directory. When we define the virtual directory, we also define any appropriate mime type mappings (the above shows Silverlight), setup of the web application and link to the appropriate application pool, and authentication and default document details.
Clearly some of these details will vary depending on your authentication requirements, etc. but you get the general idea (I hope!).
The second component ensures that we enable the ASP .NET ISAPI extension which we need for our application to run! The reason we have this as a separate component is because when we uninstall our web application we do not want this entry to be removed as it can/will affect other applications on the same server. Hence the Permanent=”yes”.
You will notice we have a number of variables in the above such as [VD] and [WEBSITE_ID]. This is because we don’t want to hardcode such settings…we want to allow the user to pick/define these…something we’ll see a bit later when discussing the user interface.
However, a few that we will look at now are defined in another separate include file I’ve called WebSites.wxi.
Product.wxs Modification
<?include Settings.wxi ?> <!-- Apply our settings -->
<?include Conditions.wxi ?> <!-- Perform conditional checks -->
<?include WebSites.wxi ?> <!-- Get our website properties defined/initialised -->
WebSites.wxi
<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Get the ASP.NET DLL path (used for registering script maps below) -->
<PropertyRef Id="NETFRAMEWORK40FULLINSTALLROOTDIR"/>
<SetProperty Id="ASPNETISAPIDLL" Sequence="execute" Before="ConfigureIIs"
Value="[NETFRAMEWORK40FULLINSTALLROOTDIR]aspnet_isapi.dll" />
<SetProperty Id="ASPNETREGIIS" Sequence="execute" Before="ConfigureIIs"
Value="[NETFRAMEWORK40FULLINSTALLROOTDIR]aspnet_regiis.exe" />
</Include>
Again thanks to the IIS extension, we can get the appropriate pathnames for aspnet_isapi.dll and aspnet_regiis.exe. You can see that we used [ASPNETISAPIDLL] above in the WebServiceExtension definition.
One thing we have not yet defined is the web site itself. We keep this element definition outside of our component, because we only want to use an existing web site…if we put it inside, we’d be creating…and we don’t want to do that; and further we don’t want the web site impacted when we uninstall our application (see here). So we can define as follows in the Product.wxs:
WebSite Element in Product.wxs
</Directory>
<iis:WebSite Id='SelectedWebSite' Description='[WEBSITE_DESCRIPTION]'
Directory='INSTALLLOCATION' SiteId='[WEBSITE_ID]'>
<!-- This element has to be here or WiX does not compile. -->
<iis:WebAddress Id="AllUnassigned" Port="80"/>
</iis:WebSite>
<Feature Id="ProductFeature" Title="!(loc.ProductName)" Level="1">
You can see the tie to the INSTALLLOCATION, and then some variables. A good explanation of how the web site element can be defined to minimise what we need to set can be found here (you’ll need to scroll down it a bit).
One thing we have not yet done, is to apply mappings. Now there are two ways I have seen this done:-
- Define multiple WebApplicationExtension elements inside your WebApplication element
- Call aspnet_regiis.exe and get that to do the work for us
Given that there are LOTS of mappings, I took the second option here. In the Product.wxs, add the following:
Apply Mappings
<CustomAction Id="UpdateWebAppMapping" Directory="INSTALLLOCATION" Return="asyncNoWait"
ExeCommand='[ASPNETREGIIS] -norestart -s "W3SVC/[WEBSITE_ID]/ROOT/[VD]"' />
<InstallExecuteSequence>
<Custom Action="UpdateWebAppMapping" After="InstallFinalize">ASPNETREGIIS AND NOT Installed</Custom>
</InstallExecuteSequence>
This defines a custom action that will make a call to aspnet_regiis.exe and apply the mappings for us to our application.
Finally, we need to adjust our Feature entry to include our two components:
Adjust Feature in Product.wxs
<Feature Id="ProductFeature" Title="!(loc.ProductName)" Level="1">
<ComponentRef Id='WebVirtualDirComponent' />
<ComponentRef Id='EnableASPNet4Extension'/>
<ComponentGroupRef Id="MyWebApp_Project" />
<ComponentGroupRef Id="Product.Generated" />
</Feature>
Next Time
In the next post we’ll fill in some of the ‘gaps’ here as we discuss providing a user interface to get the appropriate information from the administrator, and applying those to our variables.
Print | posted on Friday, 25 February 2011 1:07 PM