Thoughts, Tips and Tricks on what I'm currently do for a living. Currently most of my spare time is spent on contributing to Akka.NET.

Thursday, December 18, 2008

How to deploy a VS Database Project GDR using vsdbcmd

With the release of Visual Studio Team System 2008 Database Edition GDR we now have the possibility to use a standalone command for deploying a database project. Unfortunately the documentation for the new vsdbcmd is so full of errors that you cannot use it, see for example “Command-line Reference for VSDBCMD (Deployment and Schema Import)” and “How to: Prepare a Database for Deployment From a Command Prompt by Using VSDBCMD ”. Seems to be written for a previous version.

Properties

The documentation states that you should specify properties using this syntax:

/p:PropertyName:PropertyValue    INCORRECT!

The correct syntax is:

/p:PropertyName=PropertyValue 

Verbose and quiet

It also states that there exists a /verbose or /v option. This option do not exist. It has been removed. It’s verbose by default and you can use the undocumented /quiet or /q to turn of verbosity [source].

Invalid property names

The common deployment properties list isn’t right either. For example the TargetDatabaseName is in fact called TargetDatabase. Have a look in the .sqldeployment and .deploymanifest files for proper naming. These files exists in the directory created when building the project, for example MyDbProject/sql/debug/.

Deploy-example

Below is an example of how to deploy a database project that has been built by Visual Studio or Team Server Foundation (TFS).

Start a command prompt and change directory to the directory that was created when the project was built (for example MyDbProject/sql/debug/) and execute the command below (on a single line).

"%ProgramFiles%\Microsoft Visual Studio 9.0\VSTSDB\deploy\vsdbcmd"
/a:Deploy
/ConnectionString:"Data Source=MyServer;Integrated Security=True;"
/dsp:SQL
/manifest:MyDbProject.deploymanifest
/p:TargetDatabase=MyDb
/dd

This will create a .sql file and deploy it, i.e. execute it on the server. If you remove the /dd option the .sql will be created but not deployed.

Sunday, July 27, 2008

How to include scripts after asp.net ajax framework's

When registering script includes using ScriptManager.RegisterClientScriptInclude("myFile.js") the files are always included before the frameworks. This causes errors if your code depends on the ajax framework's. Errors like "Type is not defined" are common since the first line in the included script file often tries to register a namespace: Type.registerNamespace('My.Namespace');

On the other hand, files registered as ScriptReferences on the ScriptManagerProxy will be included AFTER the framework's includes.

<asp:ScriptManagerProxy runat="server" id="ScriptManagerProxy1">
    <Scripts><asp:ScriptReference Path="~/anotherFile.js" /></Scripts>
</asp:ScriptManagerProxy>

Not exactly the expected behavior and certainly not what we want.

When calling ScriptManager.RegisterClientScriptInclude() it delegates to the class ClientScriptManager. This class holds, internally, a list of registered scripts, and new entries ends up at the end. So the call ScriptManager.RegisterClientScriptInclude("myFile.js") will put myFile.js at the end of that list. Like this:

ClientScriptManagerClientScripts = { ..., "~/myFile.js" }

The references in the ScriptManagerProxies are handled a bit differently. The ScriptManager will, on the event PagePreRenderComplete, collect all references from the proxies in a list, and first in that list put the ajax framework's files.

List in ScriptManager = { ajax framework files, "~/anotherFile.js" }

After being collected the files are registered with the ClientScriptManager and are appended at the end of it's list. Since we registered myFile.js before PagePreRenderComplete (which occurs late in the asp.net page's life cycle), myFile.js is before the framework's files:

ClientScriptManagerClientScripts = { ..., "~/myFile.js", ..., ajax framework files, "~/anotherFile.js"}

These are rendered to the page in this order and it explains why anotherFile.js may use the framework directly and myFile.js not.

Solution 1. Use a proxy

The simplest solution is to put a ScriptManagerProxy on the page (or user control, or MasterPage) and either, as shown above declare a ScriptReference, or add one by code:

ScriptManagerProxy1.Scripts.Add(new ScriptReference("~/myFile.js"));

Solution 2. Register the file after PagePreRenderComplete

If you somehow manage to register myFile.js after PagePreRenderComplete (i.e. after ScriptControl has added all files) it will end up after the framework's files. But you'll need to do it before the rendering takes place. Between PreRender and Render phases is the SaveViewState phase. If we override SaveViewState on a control (Page is also a control) and registers myFile.js there it will be added to the list after the framework's files. This is sort of a hack and it's not guaranteed to work when new versions of the framework are released, but: It works.

protected override object SaveViewState()
{
    ScriptManager.RegisterClientScriptInclude(this,GetType(),"myFile","~/myFile.js");
    return base.SaveViewState();
}

Solution 3. Your own ScriptManager

This is, at least to me, the most elegant solution. Basically: you create the class MyScriptManager, which derives from ScriptManager; you replace the ScriptManager on the page (or user control, or MasterPage) with MyScriptManager; add the method RegisterClientScriptInclude(string url) to MyScriptManager and by some magic inside MyScriptManager makes sure that files registered thru the new method will be rendered after the ajax framework's includes.

When the ScriptManager collects all the ScriptReferences from the proxies (as explained above) it also collects ScriptReferences from all controls that have been registered with the ScriptManager as ScriptControls, by calling the method GetScriptReferences, that ScriptControls must implement. These files will be added after the proxies' files.

This is what we'll do: The new RegisterClientScriptInclude method will only add the url to a list, and not register it anywhere else. We let MyScriptManager be a ScriptControl (i.e. implement IScriptControl) and register itself as such. In the method GetScriptReferences we will, when requested, supply the list of registered files, and they will end up after the framework's.

Not much code is needed.

public class MyScriptManager : ScriptManager, IScriptControl
{
    private List<string> _registeredScripts = new List<string>();

    public virtual void RegisterClientScriptInclude(string url)
    {
        _registeredScripts.Add(url);
    }

    protected override void OnPreRender(EventArgs e)
    {
        //Register this instance as a ScriptControl.
        RegisterScriptControl(this);
        base.OnPreRender(e);
    }

    public new static MyScriptManager GetCurrent(Page page)
    {
        return (MyScriptManager) ScriptManager.GetCurrent(page);
    }

    #region IScriptControl Members
    IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
    {
        return null;
    }

    IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
    {
        //For each element in _registeredScripts create a
        //ScriptReference and return the IEnumerable
        return _registeredScripts.ConvertAll(s => new ScriptReference(s));
    }
    #endregion
}

Please note that I've simplified the code in order to show the concept. In production code you'll want to encapsulate the list in a public property that creates the list if needed. And you want two protected virtual versions of GetScriptDescriptors and GetScriptReferences that the existing ones will call.

To register a file to be included after the framework's files you use:

MyScriptManager.GetCurrent(Page).RegisterClientScriptInclude("~/myFile.js");

If you're using AjaxControlToolkit change the inheritance to ToolkitScriptManager.

Note the order the files are included:

  1. ScriptManager.RegisterClientScriptInclude registered files
  2. Ajax framework's files
  3. All ScriptManagerProxy ScriptReferences
  4. MyScriptManager.RegisterClientScriptInclude registered files

If you want your files to be included between 2 and three you're in deep water. It's probably doable but far from trivial, since ScriptManager is pretty closed for extension. It would be nice if Microsoft adhered the Open/Closed-principle a bit more. :)