Friday, August 13, 2010
Monday, October 12, 2009
Useful Function for Type Casting Your Query String Values in ASP.NET
Whenever you want to get the query string value in ASP.NET, you usually use Request.QueryString. However, that always gives you a string value that is not type cast to the variable you want. So instead of an integer or a Guid, you always get a string. Here is little function that addresses that and gives you the proper type based on the variable type passed in. It’s used as follows:
' Declare a variable of Guid type
Dim assetID As Guid
' Get the value from the query string
getValue("id", assetID)
' Declare a variable of Integer type
Dim imageWidth As Integer
' Get the value from the query string
getValue("imageWidth", imageWidth)
And for the function itself:
'''''' Gets the value of the query string key specified ''' '''The data type of the value to be returned ''' The query string key ''' The variable to store the value in '''Public Shared Sub getValue(Of T)(ByVal key As String, ByRef value As T) If Not String.IsNullOrEmpty(Request.QueryString.Item(key)) Then ' If the passed in type is Guid If GetType(T) Is GetType(Guid) Then ' Check the format of the query string for a valid Guid If isValidGuid(Convert.ToString(Request.QueryString.Item(key))) Then ' Type cast the value from a string to a Guid value = DirectCast(CType(New Guid(Convert.ToString(Request.QueryString.Item(key))), Object), T) Else ' Type cast the an empty guid for the value value = DirectCast(CType(Guid.Empty, Object), T) End If Else ' Type cast query string value to the requested type value = CType(Convert.ChangeType(Request.QueryString.Item(key).Trim(), GetType(T)), T) End If End If End Sub
Plus the "isValidGuid" helper function:
Public Shared Function isValidGuid(ByVal guidCandidate As String) As Boolean
Dim isValidGuidRegEx As New Regex("^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled)
Dim isValid As Boolean = False
If Not String.IsNullOrEmpty(guidCandidate) Then
If isValidGuidRegEx.IsMatch(guidCandidate) Then
isValid = True
End If
End If
Return isValid
End Function
Wednesday, November 05, 2008
SQL Server Scheduled Backups with NAnt
When installing and using production web applications, backup is always a must. I have written about SQL Server Backup before and this post would expend on that. The main goal here is to create a solution that periodically backs up your database. To do this, we’ll use the backup script from the previous post, employ NAnt to execute the script and zip the created backup. Last, we’ll schedule the execution of the NAnt build script through the Windows Task Scheduler or the “at” command. This procedure can backup a local or a remote SQL Server (as long as the remote server is on the same network).
Prerequisites
- Installed and configured version of NAnt (see Getting Started with NAnt - .NET Build Tool)
- Installed NAntContrib tasks (for the SQL task, see NAntContrib on SourceForge)
- Task Scheduler Service enabled in Control Panel/Admin Tools/Services
- SQL login with “dbo” rights to the database (to install the backupDatabase procedure)
- SQL login that has “public” rights to the database (so it can execute the backup)
Backup SQL Procedure
The script below with create the SQL server stored procedure to create a backup file of a given database. It takes two parameters:
- databaseName – The SQL server database to be backed up
- backupDirectory – The directory where the backup file will be created
You need to execute it against the database that you will be backing up and give the “public” role execution permissions to the procedure.
Code
exec dbo.sp_executesql @statement = N'
/*
Created:
07.18.2008 by Boyan Kostadinov (boyank@gmail.com)
Dependencies:
None
Usage:
exec dbo.backupDatabase ''ensembleVideo'', ''C:\Temp''
Parameters:
@databaseName - varchar
- The database to backup
@backupDirectory - varchar
- The path to where the database should be
backed up. This should be an existing directory on
the SQL Server where the database is located
Description:
Backsup a given database to the specified directory
*/
create procedure dbo.backupDatabase
@databaseName varchar(100),
@backupDirectory varchar(1000)
as
declare @backupFileName varchar(100),
@databaseDataFilename varchar(100), @databaseLogFilename varchar(100),
@databaseDataFile varchar(100), @databaseLogFile varchar(100),
@execSql varchar(1000)
-- If the backup directory does not end with ''\'', append one
if charindex(''\'', reverse(@backupDirectory)) > 1
set @backupDirectory = @backupDirectory + ''\''
-- Create the backup file name based on the backup directory, the database name and today''s date
set @backupFileName = @backupDirectory + @databaseName + ''-'' + replace(convert(varchar, getdate(), 110), ''-'', ''.'') + ''.bak''
set @execSql = ''
backup database ['' + @databaseName + '']
to disk = '''''' + @backupFileName + ''''''
with
noformat,
noinit,
name = '''''' + @databaseName + '' backup'''',
norewind,
nounload,
skip''
exec(@execSql)'
go
NAnt Backup Script
The script is somewhat complex. Here are the list of features:
- Backup a local SQL Server instance or a SQL Server instance on the same network. The key is that the file system of the networked instance must be available through UNC shares to the machine executing the backup script.
- Use a specified connection string or read the connection string from a .NET (or other type of XML)configuration file.
I will not dive into the script itself since it's properties and flow is documented fairly well.
Code
<project name="backupDatabase" default="run" xmlns="http://nant.sf.net/release/0.86-beta1/nant.xsd">
<!-- Set the name of the backup file that will be created -->
<property name="zipFileName" value="databaseBackup" overwrite="false" />
<!-- Set the path where the backup file will be finally stored -->
<property name="localBackupDirectory" value="C:\Temp" overwrite="false" />
<!-- Set the local path (relative to the SQL Server instance) where SQL Server will write the backup file -->
<!-- This has to always be a local path since SQL server can't write to network paths -->
<property name="sqlServerLocalBackupDirectory" value="C:\Temp" overwrite="false" />
<!-- Set the UNC path to the above "sqlServerLocalBackupDirectory" local path -->
<!-- This is only needed if you are backing up SQL server on the network -->
<property name="sqlServerUNCBackupDirectory" value="\\beehive\windows$\Temp" overwrite="false" />
<!-- For backing up a local SQL Server instance, commented the first "sqlServerUNCBackupDirectory" property -->
<!-- and uncommented the one below this line, or set the "sqlServerUNCBackupDirectory" property to "" -->
<!--<property name="sqlServerUNCBackupDirectory" value="" overwrite="false" />-->
<!-- Set the .NET connection string for connecting to the database -->
<!-- This setting always takes precedence over the config file below -->
<!-- To use a config file instead, set this property to "" (like so value="") -->
<property name="connectionString" value="Data Source=beehive\sql2005;Initial Catalog=ensembleVideo;User ID=test;Password=test;" overwrite="false" />
<!-- Alternatively set the path to where the build script should get the connection string from -->
<!-- This is usually a app.config or connectionString.config file -->
<property name="connectionStringConfigFilePath" value="C:\Inetpub\wwwroot\myApp\config\connectionStrings.config" overwrite="false" />
<!-- Set the XPath expression that will be used to grab the connection string from the config file -->
<property name="connectionStringXPath" value="/connectionStrings/add[@name = 'sqlServerConnection']/@connectionString" overwrite="false" />
<!-- Set the regular expression that's needed to get the database name from the connection string -->
<property name="getDatabaseNameFromConnecionStringRegEx" value="Initial Catalog=(?'databaseName'.*?);" overwrite="false" />
<property name="todaysDate" value="${string::substring(string::replace(datetime::to-string(datetime::now()), '/', '.'), 0, 10)}" />
<property name="todaysLocalBackupDirectory" value="${path::combine(localBackupDirectory, todaysDate)}" />
<target name="run">
<!-- If the SQL Server UNC directory was not specified and the local backup directory exists -->
<if test="${string::get-length(sqlServerUNCBackupDirectory) == 0 and directory::exists(localBackupDirectory)}">
<!-- This is a back of a local SQL Server instance -->
<mkdir dir="${todaysLocalBackupDirectory}" />
<property name="todaysSqlServerLocalBackupDirectory" value="${todaysLocalBackupDirectory}" />
<property name="todaysSqlServerUNCBackupDirectory" value="${todaysLocalBackupDirectory}" />
<property name="localSqlServer" value="true" />
</if>
<if test="${string::get-length(sqlServerUNCBackupDirectory) > 0 and directory::exists(sqlServerUNCBackupDirectory) }" >
<property name="todaysSqlServerLocalBackupDirectory" value="${path::combine(sqlServerLocalBackupDirectory, todaysDate)}" />
<property name="todaysSqlServerUNCBackupDirectory" value="${path::combine(sqlServerUNCBackupDirectory, todaysDate)}" />
<!-- This is a back of a networked SQL Server instance -->
<mkdir dir="${todaysSqlServerUNCBackupDirectory}" />
<property name="localSqlServer" value="false" />
</if>
<!-- If the connection string is empty,
the connection string file exists and the XPath to find the connection string is not empty -->
<if test="${string::get-length(connectionString) == 0 and file::exists(connectionStringConfigFilePath)
and string::get-length(connectionStringXPath) > 0}" >
<!-- Get the connection string to the database from the connection string config file -->
<xmlpeek
file="${connectionStringConfigFilePath}"
xpath="${connectionStringXPath}"
property="connectionString">
</xmlpeek>
</if>
<!-- If the connectionString property is not empty and the regular expression to get the database name is not empty -->
<if test="${string::get-length(connectionString) > 0 and string::get-length(getDatabaseNameFromConnecionStringRegEx) > 0}">
<!-- Get the database name from the connection string -->
<regex pattern="${getDatabaseNameFromConnecionStringRegEx}" input="${connectionString}" />
<!-- Execute the stored procedure to bckup the database -->
<sql connstring="Provider=SQLOLEDB;${connectionString}" transaction="false" delimiter=";" delimstyle="Normal">
exec dbo.backupDatabase '${databaseName}', '${todaysSqlServerLocalBackupDirectory}';
</sql>
<!-- Zip up the created databse backup file -->
<zip zipfile="${path::combine(todaysSqlServerUNCBackupDirectory, zipFileName + '-' + todaysDate + '.zip')}" ziplevel="9">
<fileset basedir="${todaysSqlServerUNCBackupDirectory}">
<exclude name="**/*.zip" />
<include name="*.*" />
</fileset>
</zip>
<!-- Delete all other files in the today's backup directory except for the created zip files -->
<delete>
<fileset basedir="${todaysSqlServerUNCBackupDirectory}">
<exclude name="**/*.zip" />
<include name="*.*" />
</fileset>
</delete>
<!-- Move the contents of today's backup directory to the local backup directory -->
<move todir="${localBackupDirectory}">
<fileset basedir="${todaysSqlServerUNCBackupDirectory}">
<include name="*.*" />
</fileset>
</move>
<!-- Delete the "todays" directories -->
<if test="${localSqlServer}">
<delete dir="${todaysLocalBackupDirectory}" />
</if>
<if test="${not localSqlServer}">
<delete dir="${todaysSqlServerUNCBackupDirectory}" />
</if>
</if>
</target>
</project>
Setting Up Backup Script and Scheduling
Review the build script and set following properties to match your setup:- zipFileName – The name of the zip file that will be created for the database backup
- localBackupDirectory – The local directory where the backup will be stored
- sqlServerLocalBackupDirectory - The local path (relative to the SQL Server instance) where SQL Server will write the backup file. This has to always be a local path since SQL server can't write to network paths.
- sqlServerUNCBackupDirectory – The UNC path to the above "sqlServerLocalBackupDirectory" local path. This is only needed if you are backing up SQL server on the network. For backing up a local SQL Server instance, set this "”.
- connectionString - The .NET connection string for connecting to the database. This setting always takes precedence over the “connectionStringConfigFilePath“ setting. To use a configuration file instead, set this property to "".
- connectionStringConfigFilePath - Alternatively set the path to where the build script should get the connection string from. This is usually a app.config or connectionString.config file.
- connectionStringXPath - The XPath expression that will be used to grab the connection string from the configuration file.
- getDatabaseNameFromConnecionStringRegEx - The regular expression that's needed to get the database name from the connection string.
The next step is to create the schedule with either the Task Scheduler or with “at” command.
To use the Task Scheduler:
- Create a .bat file with the following: “driveLetter:\path\to\nant.exe /f:pathToNAntBackupScript.build” and of course replace that with the actual path to nant.exe and to the NAnt build script you got here.
- Go to Control Panel / Scheduled Tasks / Add Scheduled Task
- Browse for the .bat file you created in #1
- Configure the schedule run as often as you would like
To use the “at” command:
- Do the same as #1 above.
- Open a command prompt and execute the “at” command:
”at 23:00 /every:M,T,W,Th,F pathToBatFileFromStep1.bat”
That will schedule the task to execute every day of the week at 11:00pm. You can get more info on the “at” command from How To Use the AT Command to Schedule Tasks.
Bonus
You don’t have to hard code the values in the NAnt build script. You can pass them from the command line. In that manner you can reuse the same script for different database. You simply need to call the script with –D:propertyName=”value" like so:
path\to\nant.exe /f:pathToNAntBackupScript.build –D:zipFileName="myDatabase" –D:localBackupDirectory="D:\Temp"
Downloads
http://tech-cats.net/blog/downloads/sql/procedure-dbo.backupDatabase.txt
http://tech-cats.net/blog/nantScripts/backupDatabase.build
http://tech-cats.net/blog/nantScripts/backupDatabase.bat
Posted by
Boyan Kostadinov
at
23:54
2
Comments
| Share this post: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Categories: NAnt, SQL Server, Utilities
Monday, November 03, 2008
Using NAnt to Traverse Directories and Execute SQL
In the latest version of Ensemble Video I had a need to migrate the old mechanism of showing content to new way that involved a single page and some URL rewriting (more on that another time). The basics of the old mechanism were that for each publishing point used by the application, a separate directory with specific “index.aspx” was created. That was always a major pain when updates had to be applied since I always had to preserve the old files but still update existing content. Long story short, I got away from that but I still needed to figure out what publishing points were in use so I can set the database flag for the new mechanism. So, the NAnt fanatic that I am, I build a NAnt script to do that. In this example, you will learn:
- How to parse XML for a certain value
- How to traverse directories with NAnt
- How to execute an “if” statement in NAnt
- How to get file and directory names using the NAnt built-in functions
- How to execute SQL script with SQL task from NAntContrib
The prerequisites are that 1). You have NAnt installed and 2). You have NAntContrib installed.
So let’s go one by one from the list above and you will see the final script at the end.
- How to parse XML for certain value
<xmlpeek
file="${path::combine(path::get-full-path(webApplicationDirectory), 'config\connectionStrings.config')}" xpath="/connectionStrings/add[@name = 'ensembleVideoConnection']/@connectionString" property="connectionString" />
This parses the XML file under webApplicationDirectory\config\connectionStrings.config and uses an XPath expression to get the property of the connectionString with the name ensembleVideoConnection. The value is stored in the NAnt property "connectionString" - How to traverse directories with NAnt
<foreach item="Folder" in="${sitesDirectory}" property="orgDirectoryName"> <echo message="${orgDirectoryName}" /> </foreach>
This task traverses through the directories inside the directory specified by "sitesDirectory", stores the current directory in orgDirectoryName and then echoes the current directory. - How to execute an “if” statement in NAnt
<if test="${string::to-lower(path::get-file-name(orgDirectoryName)) != '_svn'}"> <echo message="${path::get-file-name(orgDirectoryName)}" /> </if>
Here we test if the current directory name is equal to "_svn". Here is another example where we test if the current directory contains a desired file:
<if test="${file::exists(path::combine(webSiteDirectoryName, 'index.aspx'))}"> <property name="enableQuickPublish" value="1" /> </if> - How to get file and directory names using the NAnt built-in functions
<-- Get only the directory name --> path::get-file-name(orgDirectoryName) <-- Get the directory name from the path --> path::get-directory-name(orgDirectoryName) <-- Create a full path based on the directory name and a file name --> path::combine(webSiteDirectoryName, 'index.aspx')
You can find out more about NAnt built-in functions at NAnt Functions - How to execute SQL script with SQL task from NAntContrib
<sql connstring="Provider=SQLOLEDB;${connectionString}" transaction="true" delimiter=";" delimstyle="Normal"> update dbo.webSites set isQuickPublished = ${enableQuickPublish} where webSiteID = ( select webSiteID from dbo.listWebSites where webSafeOrganizationName = '${path::get-file-name(orgDirectoryName)}' and webSafeDepartmentName = '${path::get-file-name(departmentDirectoryName)}' and webSafeWebSiteName = '${path::get-file-name(webSiteDirectoryName)}' ); </sql>
There is not much to the SQL statement execution. As you can see you can use NAnt properties and function inside the SQL script. You can check out all of NAntContrib's tasks at NAntContrib Tasks
As promised here is the full contents of the build script:
<project name="renameSqlToText" default="run" xmlns="http://nant.sf.net/release/0.86-beta1/nant.xsd">
<!-- Set the path to the web application -->
<property name="webApplicationDirectory" value="D:\_Boyan's Documents\_Projects\_ensembleVideo\trunk\webApplication\app" />
<!-- Set the name of the sites directory (under the applicaiton directory) -->
<property name="sitesDirectoryName" value="sites" />
<property name="sitesDirectory" value="${path::combine(path::get-full-path(webApplicationDirectory), sitesDirectoryName)}" overwrite="true" />
<target name="run">
<!-- Get the database connection string from the web application -->
<xmlpeek
file="${path::combine(path::get-full-path(webApplicationDirectory), 'config\connectionStrings.config')}"
xpath="/connectionStrings/add[@name = 'ensembleVideoConnection']/@connectionString"
property="connectionString" />
<!-- For each directory in the sitesDirectory -->
<foreach item="Folder" in="${sitesDirectory}" property="orgDirectoryName">
<if test="${string::to-lower(path::get-file-name(orgDirectoryName)) != '_svn'}">
<echo message="${path::get-file-name(orgDirectoryName)}" />
<foreach item="Folder" in="${orgDirectoryName}" property="departmentDirectoryName">
<echo message=" ${path::get-file-name(departmentDirectoryName)}" />
<foreach item="Folder" in="${departmentDirectoryName}" property="webSiteDirectoryName">
<echo message=" ${path::get-file-name(webSiteDirectoryName)}" />
<!-- Set the 'enableQuickPublish' property to false -->
<property name="enableQuickPublish" value="0" />
<!-- If a index.aspx file exists, set the 'enableQuickPublish' property to true -->
<if test="${file::exists(path::combine(webSiteDirectoryName, 'index.aspx'))}">
<property name="enableQuickPublish" value="1" />
</if>
<!-- Run a database query to update the flag in the database -->
<sql
connstring="Provider=SQLOLEDB;${connectionString}"
transaction="true"
delimiter=";"
delimstyle="Normal">
update dbo.webSites
set isQuickPublished = ${enableQuickPublish}
where webSiteID = (
select webSiteID
from dbo.listWebSites
where webSafeOrganizationName = '${path::get-file-name(orgDirectoryName)}'
and
webSafeDepartmentName = '${path::get-file-name(departmentDirectoryName)}'
and
webSafeWebSiteName = '${path::get-file-name(webSiteDirectoryName)}'
);
</sql>
</foreach>
</foreach>
</if>
</foreach>
</target>
</project>
Downloads
http://tech-cats.net/blog/nantScripts/traverseDirectoriesAndExecuteSQL.build
Friday, October 31, 2008
Export UML to XMI from Visio 2003
A friend wanted to export his UML diagrams created with Visio to XMI files so then we could run some kind of ColdFusion tool to generate code based on the created diagrams. It appeared however that Visio did not come out of the box a way to do that. Some googling reveled that there is a add-on Microsoft offers but you have to get it, compile it (in Visual C++ non the less) and install it. What a major hassle. So being the problem solver I tend to be, I installed Visual C++ and went at it. About almost an hour later, the procedure below did the trick.
Something to Note: The supported UML diagrams are: Static Structure, Component and Deployment diagrams, and State chart (directly from Microsoft’s documentation about the add-on).
- Get the zip file below
- Copy “XMIEXPRT.DLL” to “C:\Program Files\Microsoft Office\Visio11\DLL\”
- Copy “VisioUmlAddon.vsl” to “C:\Program Files\Microsoft Office\Visio11\1033\”
- Open Visio 2003 and:
- Go to Tools/Options/Security/Macro Security
- Set the Macro Security to “Medium” or “Low”
- Go back to Options and go to Advanced
- Click on “File Paths” and set the “Add-ons” path to
“C:\Program Files\Microsoft Office\Visio11\1033” - Restart Visio 2003
- Now you should be able to create a UML diagram and go to Tools/Add-Ons and see “XMI Export” all the way on the bottom.
Downloads
http://tech-cats.net/blog/downloads/UML-to-XMI-for-Visio2003.zip