Click here to Skip to main content
Click here to Skip to main content

WSP to WAP and vice versa in under 5 minutes

, 25 May 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Convert your ASP.NET WSP projects to the WAP project model or vice versa.

Introduction

ASP.NET 2.0 brought with it a new compilation model for Web Applications (together with a new Project Model for them, the Web Site Project (WSP) model).

The WSP project model for Web Applications is extremely productive for program development due to its "Edit & Continue" features. However, the older ASP.NET 1.x WAP project model had deployment advantages, including better modularity and defined dependencies in your code, and the delivery of only the created assembly to the server, thereby protecting your intellectual property rights.

This article discusses the conversion of WAP projects to WSP and vice versa so that you can get the best of both worlds during development and deployment. The article would also discuss this approach specifically for DotNetNuke modules as that is where I invented (or discovered) this easy conversion (assisted by Visual Studio), and would discuss specific tricks as regards to DNN modules.

Background

Compilation and debugging in ASP.NET 1.x was always a tricky task. You had to create a Web Application (WAP) project inside your website directory, ensuring that your bin directory is updated with the new DLL file as soon as you did a re-build. This WAP project contained both your business logic as well as the UI layer (the .aspx, .ascx files etc.). The major problem with this approach was debugging. This blog post by Mitchel Sellers describes in detail the steps involved in debugging a WAP project. As you would notice, the steps including Manual Attach are a pain in the neck to be performed over-and-over again. Not to mention that all the WAP project files were locked for editing by Visual Studio during debugging. That meant that you needed to detach, make your changes, compile the whole solution again, and reattach for every small change you made to your code. Also notice, as Mitchel mentions in the above blog, this process is pretty slow (taking up to 45 seconds on DotNetNuke sites, which is a long time from a developer's perspective).

Enter ASP.NET 2.0

ASP.NET 2.0 introduced a new Dynamic Compilation model. Here, you place your business logic that needs to be available across the Web Application in the App_Code directory, with all the rest of the UI and other things going into any directory as named by you (you, however, cannot choose 7-8 directory names reserved as special directories with specific purposes by ASP.NET 2.0, including App_Code, App_Data, App_Themes etc.)

The major benefit of this approach from a development perspective is dynamic compilation of all code outside any ASP.NET reserved directories. What this means is that you can change the code in a running Web Application, and when you request the page containing the code again, that page alone is recompiled without any other recompilations. This offers an extremely RAD opportunity. However, remember that changes made to any file in any of ASP.NET's reserved directories, global.asax, and the web.config lead to the entire App Domain being recycled (effectively meaning that the entire Web Application is recompiled and rebuilt, in simpler terms).

(One more point worth noting is that this "Edit & Continue" like feature for web pages is a bit different than its Desktop counterpart. In Desktop apps, you can modify the code while the control is in the debugger. However, in Web apps, you need to make changes to your code and request the page from the browser again. You cannot make changes while in the debugger, and if you do, those would be visible only on the next request to that page. Also, don't forget to save your page after making the changes. Saving is not required in desktop apps, but required in Web apps, i.e., in WSP projects.)

The Deployment Perspective

ASP.NET 1.x WAP projects, however, definitely made deployment simpler than ASP.NET 2.0 WSP projects. In particular, you provided compiled assemblies for deployment (together with just the .aspx or .ascx files, without any code-behind). The code-behind was compiled into the project assembly.

I would be biased if I don't mention that you have similar functionality for WSP projects. Here also, you can pre-compile your project and deliver only the compiled code for deployment. But this compilation produces tens, hundreds, or even thousands of assemblies (one each for each project folder) depending upon your project size and structure. The naming and number of these assemblies was again something that made it difficult to manage. Michael Washington explains in this blog post about WSP compilation (remember, his explanation is targeted specifically for DNN modules, but you can easily pick the higher level concepts from it).

Also, the WSP model makes it hard to manage inter-dependencies in your site modules. If you are designing a modular and extensible approach for your site, where you have clearly defined dependencies of one module on another, you might face situations where Module1 code uses Module2 code, and vice versa. There is nothing preventing these circular dependencies in WSP projects as technically they are part of the same WSP project. The modules are only there in your design. In implementation, they are part of the same project.

Refactoring the above modules as two WAP projects would lead you to prevent such circular dependencies as only one assembly can refer to another without creating a circular reference (which obviously is disallowed).

WSP to WAP

That should be enough of a background. Let's start converting code from a WSP project to a WAP one. Much of this process is automated using Visual Studio 2005 or greater.

Well, Microsoft relied so heavily on the pros of the WSP project model while releasing ASP.NET 2.0 and VS2005 that no support was provided for WAP projects in the initial release of VS2005.

So, if you are using VS2005 (without the SP1, the support for WAP projects have been integrated from VS2005 SP1 onwards), you need to download the WAP add-in for VS2005 from Microsoft, from here. But before doing so, you also need to install an update to VS2005 for supporting WAP projects. The update is available here. Download and install the update first before installing the WAP add-in. Please note that you don't need to perform any of these two installs if you are using VS2005 SP1 or VS2008, as they come integrated with WAP project support. More information on WAP projects is available here.

Now, create a new "Web Application Project" (this should be an installed template when you select "New Project" after you have your VS ready for WAP projects).

Create a new folder named "App_Code" in your project. VS will ask you to rename it to Old_App_Code. Agree to it. Now, copy all files of this project from your WSP project's App_Code folder to this folder in your WAP project. 

Next, copy all other files and folders that belong to this project from your WSP project folder to this WAP project folder. Now, select "Show All Files" from Solution Explorer in your WAP project, and right-click and include all files you copied to your WAP project into your project. (You have an "Include In Project" option for each item you right-click, if that is not included in your project but is inside the project folder. You don't need to do this for each individual file. You can select folders and include them and their files all at once.)

Finally, right-click the project node in "Solution Explorer", the name of your project. There should be an option (probably fourth one, but depends upon your settings) that says "Convert to Web Application". Select it. That's it. VS would go over all your files and make the necessary changes (including generating new .designer files) to convert the code for WAP.

There might be errors in this process (mostly due to missing Assembly References in your WAP project). If so, add references to the desired assemblies, right-click the project node, and select "Convert to Web Application" again. Other common reasons for errors include missing namespace imports. Also note that you can reference one WAP project from another. WAP projects behave like class libraries.

The deployment of WAP projects includes publishing the project. Right-click the project node again and select "Publish Project". On the window that appears, select the Publish Path and some other mostly self-explanatory options. Click Publish, and VS would copy all the necessary files required for deployment of that project to the folder you specified.

I have noticed some differences between VS2005 and VS2008 in the Publish process. Specifically, VS2005 also copied .sql files to the Publish directory, whereas VS2008 does not. So, you might need to copy some files manually to the Publish folder. You can also create a build script to automate this task. I will present some part of the build script below.

You can zip up and provide the Publish folder you specified for deployment. After a couple of conversions of WSP projects to WAP, it now takes me less than 2-3 minutes to perform this conversion (discounting the occasions when I have to perform some refactoring due to circular references).

WAP to WSP

All software products undergo revision. So, you have published your WAP project, it is successful, and the client is happy. Now, he/she needs enhancements. And, you want to go back to the WSP model for development for reasons that should be obvious now.

Unfortunately, VS would not help you go back to WSP in any way. Not to worry. I came up with my own solution. I analyzed the project files after conversion to WAP and compared them with the original WSP files (using KDiff - I cannot explain how useful I have found this free utility in many situations, while comparing different sets of files for differences).

And, I noticed that VS performs a comparatively simpler process for WAP conversion. (It basically generates a designer file for each of your markup files, and converts the CodeFile attribute in the each markup file to Codebehind, that's it. It leaves all other files untouched.)

And, I simulated the reverse process to go back to WSP. I have created myself helper projects for this task, that do such things for me on a click. However, those projects are extremely dependent upon my development environment and the directory structures which I use to organize my projects. I am reproducing the core code of a couple of these helper projects here so you can use them for yourself as you desire.

The code below accepts a DirectoryInfo object for a directory path, and converts all the files in that directory suitable for use in a WSP project. It would preserve the sub-directories of the DirectoryInfo object path.

Private Shared sourceDir As String = "C:\Source"
Private Shared destinationDir As String = "C:\Destination" 

Public Shared Sub Main()
     processDirectory(New DirectoryInfo(sourceDir))
End Sub 

Private Shared Sub processDirectory(ByVal dirInfo As DirectoryInfo)
	'Create all directories in the destination.
	createSubDirectories(dirInfo)

	For Each file As FileInfo In dirInfo.GetFiles("*", SearchOption.AllDirectories)
		processFile(file)
	Next
End Sub

Private Shared Sub createSubDirectories(ByVal dirInfo As DirectoryInfo)
	For Each dir As DirectoryInfo In dirInfo.GetDirectories
		createSubDirectories(dir)
	Next

	Dim destination As String = dirInfo.FullName.Replace(sourceDir, destinationDir)
	If (Not Directory.Exists(destination)) Then
		Directory.CreateDirectory(destination)
	End If
End Sub

Private Shared Sub processFile(ByVal file As FileInfo)
	Dim destination As String = file.FullName.Replace(sourceDir, destinationDir)

	If (file.Name.EndsWith("designer.vb", StringComparison.CurrentCultureIgnoreCase) _
	        Or file.Name.EndsWith("designer.cs", _
	        StringComparison.CurrentCultureIgnoreCase)) Then
		'Ignore & do not copy to destination directory.
		Exit Sub

	ElseIf (file.Name.EndsWith("ascx", _
	        StringComparison.CurrentCultureIgnoreCase)) Then
		'Replace CodeBehind with CodeFile
		Dim text As String
		text = IO.File.ReadAllText(file.FullName)
		text = text.Replace("Codebehind", "CodeFile")

		'Write the file to destination in proper directory.
		IO.File.WriteAllText(destination, text)
	Else
		'Copy the file as it is.
		file.CopyTo(destination, True)
	End If

End Sub

The DNN perspective

As I said above, I have tailored this process for DNN module development because that is where I use it most of the time.

Specifically, I am presenting below some code and parts of a successful build script that would allow you to package your Module for installation into DNN on successful build.

  1. The first step is to create the appropriate DataProvider files. Below is some code to convert your .sql files to .SqlDataProvider files fit for installation into DNN (the code replaces dbo for {databaseowner} etc.
  2. Private Sub parseCompleteDefinitions()
        Dim files As String() = IO.Directory.GetFiles(basePath & _
                                "Scripts\(1) Original", "*.sql")
    
        For Each fileName As String In files
            If (Not fileName.StartsWith("Old", _
                  StringComparison.CurrentCultureIgnoreCase)) Then
                'DO NOT generate SqlDataProvider for Sql files starting woth Old
                Dim file As New IO.FileInfo(fileName)
                Dim objDef As String = IO.File.ReadAllText(fileName)
    
                createScript(file.Name.Remove(file.Name.LastIndexOf("."c)), _
                             objDef, basePath & "Scripts\", False)
            End If
        Next
    End Sub
    
    Private Sub createScript(ByVal objName As String, ByVal objDef As String, _
            ByVal basePath As String, ByVal writeOriginal As Boolean)
        Dim modified As String
    
        modified = objDef.Replace("[dbo].", "{databaseOwner}{objectQualifier}")
    
        'Adjust for GO
        Dim ownerVar As String = "DECLARE @owner nvarchar(MAX);" & vbCrLf & _
                        "set @owner = SUBSTRING(N'{databaseOwner}', 1, _
                        LEN(N'{databaseOwner}')-1);" & vbCrLf & _
                        "EXEC sys.sp_addextendedproperty"
        modified = modified.Replace("EXEC sys.sp_addextendedproperty", ownerVar)
        modified = modified.Replace("EXECUTE sp_addextendedproperty", ownerVar)
    
        modified = modified.Replace("N'dbo'", "@owner")
        'IMPORTANT: Perform, this step only AFTER the above 2 steps.
        modified = modified.Replace("dbo.", "{databaseOwner}")
    
        If (modified.Contains("dbo")) Then
            Throw New Exception("Still containes dbo:- " & objName)
        End If
    
        'Remember, DNN needs DataProvider files in UTF-8 encoding.
        Dim stream As New IO.StreamWriter(basePath & "(2) DNN\" & _
            objName & ".SqlDataProvider", False, Text.Encoding.UTF8)
        Using stream
            stream.Write(modified)
        End Using
    
        If (writeOriginal) Then
            stream = New IO.StreamWriter(basePath & "(1) Original\" & objName & ".sql")
            Using stream
                stream.Write(objDef)
            End Using
        End If
    End Sub

    You would notice from the code it depends upon my project directory structure. You can easily tweak it for yours.

  3. Now, the build script part. Here are some parts of the build script:
  4. First, set this in your project's Post-build:

    set solDir=$(SolutionDir)
    set proDir=$(ProjectDir)
    set targetAssemblyName=$(TargetName)
    if $(ConfigurationName)==Release "$(ProjectDir)build.bat"

    Now, create a build.bat in your project's root directory. Here are some parts I have put in it:

    set winRar="D:\WinRAR\winrar"
    cd "%solDir%"
    cd..
    
    echo "Preparing Sql Script"
    ../../SqlScriptGenerator "%proDir%"
    
    set source=%proDir%DesktopModules\
    set publishDir=%solDir%Publish\%targetAssemblyName%\DesktopModules\
    
    echo "Copying DNN Manifest to Publish Directory"
    md "%publishDir%"
    cd "%source%"
    copy "%baseDir%%targetAssemblyName%.dnn" "%publishDir%%baseDir%"
    
    echo "Copying Sql & SqlDataProvider files to Publish Directory"
    md "%publishDir%%scriptDir%"
    copy "%scriptDir%*.Sql" "%publishDir%%scriptDir%"
    
    echo "Copying Common Dependencies"
    set source=%proDir%bin
    set target=%publishDir%%baseDir%Dependencies
    md "%target%"
    cd "%source%"
    copy "%source%\AjaxControlToolkit.dll" "%target%\"
    
    copy "%source%\%targetAssemblyName%.dll" "%target%\"
    
    
    echo "Creating zip package"
    cd "%publishDir%%baseDir%"
    del %targetAssemblyName%.zip
    %winRar% a -r -ibck -afzip %targetAssemblyName%
    • The above code for SqlDataProvider generation has been compiled into a project. The build script calls the project's executable to prepare DataProviders for the WAP project module.
    • It then copies the DNN manifest file (*.dnn) to the Publish directory, which would not have been copied by VS on Publish.
    • It then copies the SqlDataProvider files to the Publish directory, again not copied by VS.
    • Any .dll files including the Project's DLL is copied into a particular sub-directory in the Publish folder (VS copies it to the bin folder on Publish). I needed them in some other directory.
    • Finally, WinRar is used to prepare the DNN Module installation zip file.

DNN Traps

You most probably are referencing DNN's Label, SectionHead controls etc., from ~/controls. Remember to also copy the controls folder from your WSP solution to your WAP project, if your WAP project is outside the WSP solution. Else, it would generate errors when you select "Convert to Web Application" in VS.

Conclusion

There is no doubt to me about WSP's RAD and debugging advantages and WAP's deployment ones. I have found the WSP approach better for development and the WAP approach better for deployment. With WSP, I can modify the code-behinds without having the entire installation recompile (of course, changes to App_Code classes need recompilation).

I always develop my modules as WSP. Then, when they are complete, I create a new WAP project, and add the existing files using the 'Add Existing Item' option from the VS context-menu appearing on right-clicking a project node in Solution Explorer. This further avoids copying effort. Conversion to WAP is a breeze after that, with VS supporting it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Rahul Singla

India India
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141220.1 | Last Updated 25 May 2009
Article Copyright 2009 by Rahul Singla
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid