SQL Code Generation Tool

The SQL Code Generation Tool is an open source project that I created to help automate the creation of stored procedures, model objects, and the data layer classes in the majority of the projects I'm involved in.

These scripts started in late 2010 when I simply got tired of manually coding VB.Net model classes. Over time, I added additional scripts to code gen the basic stored procedures, model classes, and data layer classes that I generally needed for each entity in my project. Initially the scripts only generated VB.Net code for the model/data layers; however, they now support both VB.Net and C#.

The scripts have went through 3 major rewrites/refactoring to get to their current version (v3.0) and now work with SQLCMD, which makes generating your files (procs, model, data) very simple.

Please keep in mind these scripts were written for a specific style of n-tier architecture, so they may or may not fit your needs out of the box. For most of the applications I work on there is a Web layer, Model layer, Data layer, and a backend SQL Server database (business/data).

These scripts more or less automated 90% of the routine tasks I would encounter after designing a database/table. I almost exclusively use these scripts to create the initial set of stored procedures, model classes (C#, VB.Net), and data layer classes (C#, VB.Net). Once generated I customize them as-needed to fit the project's needs (e.g. adding advanced filtering to stored procedures, adding derived properties to the model class, etc).

Head over to the GitHub repository for more information.

https://github.com/mpavey/sql-code-gen

Using SQL Server SQLCMD and WinZip wzzip to backup and zip/encrypt a database

Here's an example of how to create a BAT file to backup a SQL Server database using SQLCMD and to zip the backup file using WinZip wzzip with AES256 encryption.
 @ECHO OFF  
   
 set server=(local)  
 set database=Sandbox  
 set bakfile=C:\Users\matt\Desktop\temp\backup.bak  
 set zipfile=C:\Users\matt\Desktop\temp\backup.zip  
 set targetfile=C:\Users\matt\Desktop\temp\backup\backup.zip  
 set password=Password123  
   
 cd C:\Program Files\Microsoft SQL Server\110\Tools\Binn  
 SQLCMD -E -S %server% -Q "BACKUP DATABASE %database% TO DISK = N'%bakfile%' WITH COPY_ONLY, NOFORMAT, INIT"  
   
 IF EXIST "%zipfile%" del /F "%zipfile%"  
   
 cd C:\Program Files\WinZip  
 wzzip "%zipfile%" "%bakfile%" -s%password% -ycAES256  
   
 IF EXIST "%bakfile%" del /F "%bakfile%"  
   
 move "%zipfile%" "%targetfile%"  

You could then use the BAT file to create a scheduled job to automate your backup process.

Shredding XML Data in SQL Server: Elements and Attributes

SQL Server can pull values out of an XML document and hand them back as ordinary rows and columns. The syntax depends on whether your data lives in elements or in attributes, and the difference is small enough that it is easy to get wrong.

Here is the same set of data both ways, starting with elements:

DECLARE @xml XML = '
<dinosaurs>
  <d>
    <name>Aachenosaurus</name>
    <url>https://en.wikipedia.org/wiki/Aachenosaurus</url>
  </d>
  <d>
    <name>Aardonyx</name>
    <url>https://en.wikipedia.org/wiki/Aardonyx</url>
  </d>
</dinosaurs>';

;WITH ShreddedData (name, url) AS
(
    SELECT x.node.value('name[1]', 'VARCHAR(255)'),
           x.node.value('url[1]',  'VARCHAR(255)')
    FROM   @xml.nodes('dinosaurs/d') x(node)
)
SELECT * FROM ShreddedData;

And the same data expressed as attributes instead:

DECLARE @xml XML = '
<dinosaurs>
  <d name="Aachenosaurus" url="https://en.wikipedia.org/wiki/Aachenosaurus" />
  <d name="Aardonyx"      url="https://en.wikipedia.org/wiki/Aardonyx" />
</dinosaurs>';

;WITH ShreddedData (name, url) AS
(
    SELECT x.node.value('@name', 'VARCHAR(255)'),
           x.node.value('@url',  'VARCHAR(255)')
    FROM   @xml.nodes('dinosaurs/d') x(node)
)
SELECT * FROM ShreddedData;

Two things are worth knowing here. The nodes() method is what turns the XML into a rowset, one row per match on the path you give it, and value() is what pulls a single scalar out of each of those rows.

The difference between the two versions is entirely in the value() argument. An attribute is addressed with an @ in front of its name. An element needs the positional predicate, name[1] rather than just name, because value() requires an expression that returns exactly one node and XPath cannot assume there is only one child with that name. Leave off the [1] and SQL Server will reject the query rather than guess.

If your XML mixes both, which real-world documents usually do, you can pull elements and attributes in the same SELECT. They are just different expressions against the same node.

Using MVC Web API and SQL Server to create your own PayPal IPN Listener

Update: do not build a new integration on this. PayPal is retiring Instant Payment Notification. New IPN and Website Payments Standard credentials stopped being issued at the end of 2025, Website Payments Standard was deprecated in January 2026, and full end of life is January 2027. The replacement is PayPal REST Webhooks, which is a different mechanism rather than a renamed one: payloads are JSON instead of form-encoded, and verification uses an RSA-SHA256 signature check against PayPal's certificate rather than posting the payload back with cmd=_notify-validate. If you are still running an IPN listener, migrate. If you are starting fresh, start with Webhooks. This post stays up as a record of how the classic integration worked.

Recently I was tasked with creating a PayPal IPN Listener, so I started with getting familiar with the PayPal Instant Payment Notification (IPN) documentation:

https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNIntro/

For this implementation I'm using a MVC Web API Controller (VB.Net) for the listener, along with some helper classes/extensions/methods. I'm also using a SQL Server 2012 table and stored procedure to create a notifications log, which allows you to:

1) Listen
2) Log
3) React

This is important because it allows the listener to do it's job quickly, and lets you focus on the processing later.

Let's start with our SQL Server database Notifications table.
 SET ANSI_NULLS ON  
 GO  
   
 SET QUOTED_IDENTIFIER ON  
 GO  
   
 SET ANSI_PADDING ON  
 GO  
   
 CREATE TABLE [dbo].[Notifications](  
      [NotificationID] [INT] IDENTITY(1,1) NOT NULL,  
      [Timestamp] [DATETIME] NOT NULL CONSTRAINT [DF_Notifications_Timestamp] DEFAULT (GETDATE()),  
      [Type] [VARCHAR](50) NOT NULL CONSTRAINT [DF_Notifications_Type] DEFAULT (''),  
      [IPAddress] [VARCHAR](25) NOT NULL CONSTRAINT [DF_Notifications_IPAddress] DEFAULT (''),  
      [UrlRequest] [VARCHAR](MAX) NOT NULL CONSTRAINT [DF_Notifications_UrlRequest] DEFAULT (''),  
      [UserAgent] [VARCHAR](MAX) NOT NULL CONSTRAINT [DF_Notifications_UserAgent] DEFAULT (''),  
      [Data] [VARCHAR](MAX) NOT NULL CONSTRAINT [DF_Notifications_Message] DEFAULT (''),  
      [Status] [VARCHAR](MAX) NOT NULL CONSTRAINT [DF_Notifications_Status] DEFAULT (''),  
      [Method] [VARCHAR](10) NOT NULL CONSTRAINT [DF_Notifications_Method] DEFAULT (''),  
      [Processed] [BIT] NOT NULL CONSTRAINT [DF_Notifications_Processed] DEFAULT ((0)),  
      [ProcessedDate] [DATETIME] NULL,  
      [Notes] [VARCHAR](MAX) NOT NULL DEFAULT (''),  
      [TransactionID] [VARCHAR](50) NOT NULL DEFAULT (''),  
      [TransactionType] [VARCHAR](50) NOT NULL DEFAULT (''),  
      [ItemName] [VARCHAR](128) NOT NULL DEFAULT (''),  
      [ItemNumber] [VARCHAR](128) NOT NULL DEFAULT (''),  
      [Option] [VARCHAR](200) NOT NULL DEFAULT (''),  
      [Email] [VARCHAR](100) NOT NULL DEFAULT (''),  
      [PaymentStatus] [VARCHAR](25) NOT NULL DEFAULT (''),  
  CONSTRAINT [PK_Notifications] PRIMARY KEY CLUSTERED   
 (  
      [NotificationID] ASC  
 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)  
 )  
   
 GO  
   
 SET ANSI_PADDING OFF  
 GO  

Next is the Notifications_Save stored procedure to save data in the Notifications table.
 SET ANSI_NULLS ON  
 GO  
   
 SET QUOTED_IDENTIFIER ON  
 GO  
   
 CREATE PROCEDURE [dbo].[Notifications_Save]  
 (  
      @NotificationID      INT,  
      @Type                VARCHAR(50),  
      @IPAddress           VARCHAR(25),  
      @UrlRequest          VARCHAR(MAX),  
      @UserAgent           VARCHAR(MAX),  
      @Data                VARCHAR(MAX),  
      @Status              VARCHAR(MAX),  
      @Method              VARCHAR(10),  
      @Processed           BIT,  
      @ProcessedDate       DATETIME  
 )  
 AS  
 BEGIN  
      -- check to see if record exists  
      IF EXISTS (SELECT NotificationID FROM dbo.Notifications WHERE NotificationID = @NotificationID)  
      BEGIN  
           -- update  
           UPDATE    dbo.Notifications  
           SET       Type = @Type,  
                     IPAddress = @IPAddress,  
                     UrlRequest = @UrlRequest,  
                     UserAgent = @UserAgent,  
                     Data = @Data,  
                     Status = @Status,  
                     Method = @Method,  
                     Processed = @Processed,  
                     ProcessedDate = @ProcessedDate  
           WHERE     NotificationID = @NotificationID  
      END  
      ELSE  
      BEGIN  
           -- insert  
           INSERT INTO dbo.Notifications  
                (Type, IPAddress, UrlRequest, UserAgent, Data, Status, Method, Processed, ProcessedDate)  
           VALUES  
                (@Type, @IPAddress, @UrlRequest, @UserAgent, @Data, @Status, @Method, @Processed, @ProcessedDate)  
    
           -- get identity value  
           SET @NotificationID = SCOPE_IDENTITY()  
      END  
    
      -- return value  
      RETURN @NotificationID  
 END  

Next we'll create the Notification model object in our MVC project.
 Public Class Notification  
   Public Property NotificationID As Integer = 0  
   Public Property Timestamp As DateTime = DateTime.MinValue  
   Public Property Type As String = String.Empty  
   Public Property IPAddress As String = String.Empty  
   Public Property UrlRequest As String = String.Empty  
   Public Property UserAgent As String = String.Empty  
   Public Property Data As String = String.Empty  
   Public Property Status As String = String.Empty  
   Public Property Method As String = String.Empty  
   Public Property Processed As Boolean = False  
   Public Property ProcessedDate As DateTime?  
 End Class  
   

Then we'll create the Notifications data layer class for calling the stored procedure.
 Imports Microsoft.Practices.EnterpriseLibrary.Data  
 Imports System.Data.Common  
   
 Public Class Notifications     
   Public Function Save(ByVal Notification As Model.Notification) As Integer  
     'variables  
     Dim DB As Database = New DatabaseProviderFactory().CreateDefault()  
     Dim ReturnValue As Integer = -1  
   
     'command  
     Using cmd As DbCommand = DB.GetStoredProcCommand("dbo.Notifications_Save")  
       'parameters  
       With Notification  
         DB.AddInParameter(cmd, "@NotificationID", DbType.Int32, .NotificationID)  
         DB.AddInParameter(cmd, "@Type", DbType.String, .Type)  
         DB.AddInParameter(cmd, "@IPAddress", DbType.String, .IPAddress)  
         DB.AddInParameter(cmd, "@UrlRequest", DbType.String, .UrlRequest)  
         DB.AddInParameter(cmd, "@UserAgent", DbType.String, .UserAgent)  
         DB.AddInParameter(cmd, "@Data", DbType.String, .Data)  
         DB.AddInParameter(cmd, "@Status", DbType.String, .Status)  
         DB.AddInParameter(cmd, "@Method", DbType.String, .Method)  
         DB.AddInParameter(cmd, "@Processed", DbType.Boolean, .Processed)  
         DB.AddInParameter(cmd, "@ProcessedDate", DbType.DateTime, IIf(.ProcessedDate.HasValue AndAlso Not .ProcessedDate.Equals(DateTime.MinValue), .ProcessedDate, DBNull.Value))  
       End With  
   
       'return value parameter  
       DB.AddParameter(cmd, "@ReturnValue", DbType.Int32, 4, ParameterDirection.ReturnValue, False, 0, 0, "@ReturnValue", DataRowVersion.Default, Nothing)  
   
       'execute query  
       DB.ExecuteNonQuery(cmd)  
   
       'get return value  
       ReturnValue = DB.GetParameterValue(cmd, "@ReturnValue")  
     End Using  
   
     'return value  
     Return ReturnValue  
   End Function  
 End Class  

Next we have a few helper classes/extensions/methods that we are going to be calling from the listener controller.

1) IsBlank (extension method)
2) ContainsValue (extension method)
3) Result (model object)
4) PostContentToUrl (shared method)

The IsBlank extension method seems trivial, especially for such a small example, but this is just one of the many extension methods that I keep in my utilities library to keep my code, clean, simple, and consistent.
   <Extension()> _  
   Public Function IsBlank(Value As String) As Boolean  
     Dim ReturnValue As Boolean = True  
   
     If Value IsNot Nothing Then  
       ReturnValue = Value.Trim().Length = 0  
     End If  
   
     Return ReturnValue  
   End Function  

The ContainsValue extension method is another one I keep in my library to to simplify the code so you don't have to deal with case sensitivity all over the place.
   <Extension()> _  
   Public Function ContainsValue(Value As String, CompareValue As String) As Boolean  
     Dim ReturnValue As Boolean = False  
   
     If Value IsNot Nothing AndAlso CompareValue IsNot Nothing Then  
       ReturnValue = Value.Trim().IndexOf(CompareValue.Trim(), StringComparison.OrdinalIgnoreCase) >= 0  
     End If  
   
     Return ReturnValue  
   End Function  

The Result model object is a simple helper class we're going to use when we post our data to the PayPal verification URL, which will allow us to return back a property indicating success or failure, as well as the response body.
 Public Class Result  
   Public Property Success As Boolean = False  
   Public Property Message As String = String.Empty    
 End Class  

Then we have our PostContentToUrl method, which was written with re-usability in mind. It accepts a URL and a request body. This allows you to use it for POST'ing data generically to any service.
   Public Shared Function PostContentToUrl(ByVal Url As String, ByVal Data As String) As Model.Result  
     'variables  
     Dim Result As New Model.Result  
   
     Try  
       'create web request  
       Dim MyRequest As WebRequest = WebRequest.Create(Url)  
       Dim ByteArray As Byte() = Encoding.UTF8.GetBytes(Data)  
   
       'properties  
       MyRequest.Method = "POST"  
       MyRequest.ContentType = "application/x-www-form-urlencoded"  
       MyRequest.ContentLength = ByteArray.Length  
   
       'get the request stream  
       Using DataStream As Stream = MyRequest.GetRequestStream()  
         'write the data to the request stream  
         DataStream.Write(ByteArray, 0, ByteArray.Length)  
   
         'close the stream object  
         DataStream.Close()  
       End Using  
   
       'response  
       Using MyResponse As HttpWebResponse = DirectCast(MyRequest.GetResponse(), HttpWebResponse)  
         Using MyReader As New StreamReader(MyResponse.GetResponseStream())  
           Result.Message = MyReader.ReadToEnd()  
           Result.Success = True  
         End Using  
       End Using  
     Catch ex As Exception  
       Result.Message = ex.Message  
     End Try  
   
     'return  
     Return Result  
   End Function  

Next we'll setup a BaseController class to expose several properties for the current HTTP request.
 Public Class BaseController   
   Inherits System.Web.Http.ApiController   
   
   Public ReadOnly Property QueryString() As String   
    Get   
     Try   
      Return DirectCast(Request.Properties("MS_HttpContext"), HttpContextBase).Request.QueryString.ToString   
     Catch ex As Exception   
      Return String.Empty   
     End Try   
    End Get   
   End Property   
     
   Public ReadOnly Property IPAddress() As String   
    Get   
     Try   
      Return DirectCast(Request.Properties("MS_HttpContext"), HttpContextBase).Request.UserHostAddress   
     Catch ex As Exception   
      Return String.Empty   
     End Try   
    End Get   
   End Property   
     
   Public ReadOnly Property UrlRequest() As String   
    Get   
     Try   
      Return DirectCast(Request.Properties("MS_HttpContext"), HttpContextBase).Request.Url.AbsoluteUri   
     Catch ex As Exception   
      Return String.Empty   
     End Try   
    End Get   
   End Property   
     
   Public ReadOnly Property RawUrl() As String   
    Get   
     Try   
      Return DirectCast(Request.Properties("MS_HttpContext"), HttpContextBase).Request.RawUrl   
     Catch ex As Exception   
      Return String.Empty   
     End Try   
    End Get   
   End Property   
     
   Public ReadOnly Property UrlReferrer() As String   
    Get   
     Try   
      Return DirectCast(Request.Properties("MS_HttpContext"), HttpContextBase).Request.UrlReferrer.AbsoluteUri   
     Catch ex As Exception   
      Return String.Empty   
     End Try   
    End Get   
   End Property   
     
   Public ReadOnly Property UserAgent() As String   
    Get   
     Try   
      Return DirectCast(Request.Properties("MS_HttpContext"), HttpContextBase).Request.UserAgent   
     Catch ex As Exception   
      Return String.Empty   
     End Try   
    End Get   
   End Property   
     
   Public ReadOnly Property Crawler() As Boolean   
    Get   
     Try   
      Return DirectCast(Request.Properties("MS_HttpContext"), HttpContextBase).Request.Browser.Crawler   
     Catch ex As Exception   
      Return False   
     End Try   
    End Get   
   End Property  

   Public ReadOnly Property Method() As String  
     Get  
       Try  
         Return DirectCast(Request.Properties("MS_HttpContext"), HttpContextBase).Request.HttpMethod  
       Catch ex As Exception  
         Return False  
       End Try  
     End Get  
   End Property  
 End Class  

Now that we've got all the core pieces in place let's take a look at the ListenerController, which is the simplest part of the entire process.
 Imports System.Net  
 Imports System.Web.Http  
 Imports System.IO  
   
 Namespace Controllers  
   <RoutePrefix("listener")>  
   Public Class ListenerController  
     Inherits BaseController  
   
     <HttpPost>  
     <Route("ipn")>  
     Public Function InstantPaymentNotification() As String  
       'variables  
       Dim Data As String = String.Empty  
       Dim Notification As New Model.Notification  
   
       'get data  
       Try  
         Data = New StreamReader(HttpContext.Current.Request.InputStream).ReadToEnd()  
       Catch ex As Exception  
         Data = String.Empty  
       End Try  
   
       'check for valid data  
       If Data.IsBlank Then  
         Return String.Empty  
       End If  
   
       'PayPal HTTP POSTs an IPN message to your listener that notifies it of an event.  
       'Your listener returns an empty HTTP 200 response to PayPal.  
       'Your listener HTTP POSTs the complete, unaltered message back to PayPal; the message must contain the same fields (in the same order) as the original message and be encoded in the same way as the original message.  
       'PayPal sends a single word back - either VERIFIED (if the message matches the original) or INVALID (if the message does not match the original).  
   
       'Every IPN message you receive from PayPal includes a User-Agent HTTP request header whose value is PayPal IPN ( https://www.paypal.com/ipn ).  
       'Do not use this header to verify that an IPN really came from PayPal and has not been tampered with.  
       'Rather, to verify these things, you must use the IPN authentication protocol outlined above.  
       If Not MyBase.UserAgent.ContainsValue("PayPal") Then  
         Return String.Empty  
       End If  
   
       'Before you can trust the contents of the message, you must first verify that the message came from PayPal.  
       'To verify the message, you must send back the contents in the exact order they were received and precede it with the command _notify-validate, as follows:  
       Dim VerifyUrl As String = IIf(Data.ContainsValue("test_ipn=1"), "https://www.sandbox.paypal.com/cgi-bin/webscr", "https://www.paypal.com/cgi-bin/webscr")  
       Dim VerifyData As String = String.Format("cmd=_notify-validate&{0}", Data)  
       Dim Result As Model.Result = Functions.PostContentToUrl(Url:=VerifyUrl, Data:=VerifyData)  
   
       'properties  
       Notification.Type = "IPN"  
       Notification.IPAddress = MyBase.IPAddress  
       Notification.UrlRequest = MyBase.UrlRequest  
       Notification.UserAgent = MyBase.UserAgent  
       Notification.Data = Data  
       Notification.Status = Result.Message  
       Notification.Method = MyBase.Method  
   
       'save  
       Using DB As New Data.Notifications  
         DB.Save(Notification)  
       End Using  
   
       'Important: After you have authenticated an IPN message (received a VERIFIED response from PayPal), you must perform these important checks before you can assume that the IPN is both legitimate and has not already been processed:  
       'Check that the payment_status is Completed.  
       'If the payment_status is Completed, check the txn_id against the previous PayPal transaction that you processed to ensure the IPN message is not a duplicate.  
       'Check that the receiver_email is an email address registered in your PayPal account.  
       'Check that the price (carried in mc_gross) and the currency (carried in mc_currency) are correct for the item (carried in item_name or item_number).  
       'Once you have completed these checks, IPN authentication is complete.  
       'Now, you can update your database with the information provided and initiate any back-end processing that's appropriate.  
   
       'return  
       Return String.Empty  
     End Function  
   End Class  
 End Namespace  

Because we've used <RoutePrefix("listener")> for the controller and <Route("ipn")> for the InstantPaymentNotification function, our IPN listener service would be exposed as follows:

http://yourdomain.com/listener/ipn

At this point you have an IPN listener that can:

1) Receive IPN notifications from PayPal
2) Respond back instantly to PayPal for the verification handshake
3) Log notification activity to the [Notifications] table in your database

This is the point in the process where you could go several different directions, and ultimately how you validate and process the data is going to be dependent on what types of transaction types (txn_type) you support and what types of services you are selling (e.g. products, subscriptions, etc).

With that said, here is an example of how you might do some data processing in SQL (e.g. a scheduled job that runs every minute).

We'll start with a Split table-valued function to help with splitting the IPN data vertically.
 SET QUOTED_IDENTIFIER ON  
 GO  

 CREATE FUNCTION [dbo].[Split]  
 (  
      @List          VARCHAR(MAX),  
      @Delimiter     VARCHAR(1)  
 )  
 RETURNS @Table TABLE  
 (   
      ID        INT IDENTITY(1,1),  
      Value     VARCHAR(MAX)  
 )  
 AS  
 BEGIN  
      -- loop through the list  
      WHILE (CHARINDEX(@Delimiter, @List) > 0)  
      BEGIN  
           -- add the value to the table  
           INSERT INTO @Table  
                (Value)  
                SELECT Value = LTRIM(RTRIM(SUBSTRING(@List, 1, CHARINDEX(@Delimiter, @List)-1)))  
   
           -- remove the value from the list  
           SET @List = SUBSTRING(@List, CHARINDEX(@Delimiter, @List) + 1, LEN(@List))  
      END  
   
      -- insert remaining value from the list  
      INSERT INTO @Table  
           (Value)  
           SELECT Value = LTRIM(RTRIM(@List))  
        
      -- return  
      RETURN  
 END  
   
 GO  

Next we'll use a SQL script to process any unprocessed notifications.
 /*  
 Important: After you have authenticated an IPN message (received a VERIFIED response from PayPal), you must perform these important checks before you can assume that the IPN is both legitimate and has not already been processed:  
 Check that the payment_status is Completed.  
 If the payment_status is Completed, check the txn_id against the previous PayPal transaction that you processed to ensure the IPN message is not a duplicate.  
 Check that the receiver_email is an email address registered in your PayPal account.  
 Check that the price (carried in mc_gross) and the currency (carried in mc_currency) are correct for the item (carried in item_name or item_number).  
 Once you have completed these checks, IPN authentication is complete.  
 Now, you can update your database with the information provided and initiate any back-end processing that's appropriate.  
 */  
   
 -- set nocount on  
 SET NOCOUNT ON;  
   
 -- if status is not verified mark it as processed and move on  
 UPDATE    dbo.Notifications  
 SET       Processed = 1,  
           ProcessedDate = GETDATE(),  
           Notes = 'Invalid'  
 WHERE     Type = 'IPN'  
 AND       Processed = 0  
 AND       Status != 'VERIFIED'  
   
 -- temp table to get a list of records that are being processed in this batch
 DECLARE @Batch TABLE  
 (  
      NotificationID     INT              NOT NULL DEFAULT 0  
 )  
   
 -- temp table to split out the IPN data vertically
 DECLARE @Details TABLE  
 (  
      BatchID            VARCHAR(50)      NOT NULL DEFAULT '',  
      NotificationID     INT              NOT NULL DEFAULT 0,  
      Property           VARCHAR(50)      NOT NULL DEFAULT '',  
      Value              VARCHAR(MAX)     NOT NULL DEFAULT ''  
 )  
   
 -- get unprocessed notifications that are verified and save in temp table  
 INSERT INTO @Batch  
   (NotificationID)  
      SELECT       NotificationID  
      FROM         dbo.Notifications  
      WHERE        Type = 'IPN'  
      AND          Processed = 0  
      AND          Status = 'VERIFIED'  
      ORDER BY     NotificationID  
   
 -- declare variables for cursor  
 DECLARE @NotificationID    INT           = 0  
 DECLARE @Data              VARCHAR(MAX)  = ''  
   
 -- declare cursor  
 DECLARE MyCursor CURSOR LOCAL FAST_FORWARD  
 FOR  
      SELECT       n.NotificationID,  
                   n.Data  
      FROM         dbo.Notifications n  
      JOIN         @Batch x ON n.NotificationID = x.NotificationID  
      ORDER BY     n.NotificationID  
   
 -- open cursor  
 OPEN MyCursor  
   
 -- get the first result  
 FETCH NEXT FROM MyCursor INTO @NotificationID, @Data  
   
 -- loop through results  
 WHILE @@FETCH_STATUS = 0  
 BEGIN       
      -- variables  
      DECLARE @BatchID    VARCHAR(50) = FORMAT(GETDATE(), 'yyyyMMddHHmmssfffffff') + dbo.GetRandomString(5)  
        
      -- split IPN data vertically
      INSERT INTO @Details  
           (BatchID, NotificationID, Property, Value)  
           SELECT    BatchID = @BatchID,  
                     @NotificationID,  
                     Property = (SELECT Value FROM dbo.Split(Value, '=') WHERE ID = 1),  
                     Value = (SELECT dbo.DecodeValue(Value) FROM dbo.Split(Value, '=') WHERE ID = 2)  
           FROM      dbo.Split(@Data, '&')  
   
      -- fetch the next record  
      FETCH NEXT FROM MyCursor INTO @NotificationID, @Data  
 END  
   
 -- close cursor  
 CLOSE          MyCursor  
 DEALLOCATE     MyCursor  
   
 -- extract core information that we are interested in for our payment processing
 -- txn_id, txn_type, item_name, item_number, option_selection1, custom, payment_status
 UPDATE    n  
 SET       n.TransactionID = ISNULL(x.txn_id, ''),  
           n.TransactionType = ISNULL(x.txn_type, ''),  
           n.ItemName = ISNULL(x.item_name, ''),  
           n.ItemNumber = ISNULL(x.item_number, ''),  
           n.[Option] = ISNULL(x.option_selection1, ''),  
           n.Email = ISNULL(x.custom, ''),  
           n.PaymentStatus = ISNULL(x.payment_status, '')  
 FROM      @Details d  
 PIVOT     (  
                MAX(d.Value)  
                FOR Property IN (txn_id, txn_type, item_name, item_number, option_selection1, custom, payment_status)  
           )     AS x  
 JOIN      dbo.Notifications n ON x.NotificationID = n.NotificationID  
   
 -- process non-payments  
 UPDATE    n  
 SET       n.Processed = 1,  
           n.ProcessedDate = GETDATE(),  
           n.Notes = 'Non-payment'  
 FROM      dbo.Notifications n  
 JOIN      @Batch x ON n.NotificationID = x.NotificationID  
 WHERE     n.Processed = 0  
 AND       (LEN(n.TransactionID) = 0 OR LEN(n.PaymentStatus) = 0)  
   
 -- process duplicate transactions  
 UPDATE    n  
 SET       n.Processed = 1,  
           n.ProcessedDate = GETDATE(),  
           n.Notes = 'Duplicate'  
 FROM      dbo.Notifications n  
 JOIN      @Batch x ON n.NotificationID = x.NotificationID  
 WHERE     n.Processed = 0  
 AND       n.TransactionID IN (SELECT y.TransactionID FROM dbo.Notifications y WHERE y.Processed = 1)  
   
 -- process non-completed payments  
 UPDATE    n  
 SET       n.Processed = 1,  
           n.ProcessedDate = GETDATE(),  
           n.Notes = 'Unknown payment status'  
 FROM      dbo.Notifications n  
 JOIN      @Batch x ON n.NotificationID = x.NotificationID  
 WHERE     n.Processed = 0  
 AND       n.PaymentStatus != 'Completed'  
   
 -- process completed payments  
 UPDATE    n  
 SET       n.Processed = 1,  
           n.ProcessedDate = GETDATE(),  
           n.Notes = 'Completed'  
 FROM      dbo.Notifications n  
 JOIN      @Batch x ON n.NotificationID = x.NotificationID  
 WHERE     n.Processed = 0  
 AND       n.PaymentStatus = 'Completed'  

This is the point where if you have new "completed" payments you could trigger other workflow items in your system, such as email notifications to send a license key, or renewing a subscription, or otherwise giving access to the product/service to the payee.

Remember this is just a starting point and your specific data processing implementation will vary based on your needs.

Using JavaScript, jQuery, XML, and XSL to bind a grid and automatically calculate column/row totals

This example uses JavaScript and a simple XSL transformation to bind XML data to a grid. The XSL transformation does the initial calculations for the rows and columns. There is also jQuery code to handle the blur() event on the textboxes to update the totals automatically.

Since we're using a client-side XSL transformation there is code to handle the XSL transform for browsers that support XSLTProcessor (e.g. Mozilla browsers), and there is an else statement to handle other browsers (e.g. Internet Explorer). It's also worth noting that since this example is in JSFiddle, if you're using Internet Explorer you won't be able to actually see the results since JSFiddle does not allow an ActiveXObject to be created. With that said, if you copy the example to your local testing environment it is cross-browser compatible.





Click here to view the demo and test the functionality.

ASP.Net C# SQL Building Your Own Url Shortner

Several years ago I had to build a custom URL shortner for a website. I recently had a task where I needed to do something similar, so I converted it to C# and thought I'd share how easy this can be.

First off you'll need a database table for the url data. In this example I'm using SQL Server 2008.
 SET ANSI_NULLS ON  
 GO  
 SET QUOTED_IDENTIFIER ON  
 GO  
 SET ANSI_PADDING ON  
 GO  
 CREATE TABLE [dbo].[Urls](  
      [PK] [int] IDENTITY(1,1) NOT NULL,  
      [Key] [varchar](10) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL CONSTRAINT [DF_Urls_Key] DEFAULT (''),  
      [Url] [varchar](2000) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL CONSTRAINT [DF_Urls_Url] DEFAULT (''),  
      [CreatedBy] [varchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,  
      [CreatedDate] [smalldatetime] NOT NULL CONSTRAINT [DF_Urls_CreatedDate] DEFAULT (getdate()),  
  CONSTRAINT [PK_Urls] PRIMARY KEY CLUSTERED   
 (  
      [PK] ASC  
 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],  
  CONSTRAINT [IX_Urls] UNIQUE NONCLUSTERED   
 (  
      [Key] ASC  
 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]  
 ) ON [PRIMARY]  
   
 GO  
 SET ANSI_PADDING OFF  
 GO  

- PK column to act as the primary key
- Key column is a unique nonclustered column, which is the unique key for the shortened url
- Url column which is the fully qualified url that that the shortened url should redirect to
- CreatedBy is a simple varchar field to provide optional auditing
- CreatedDate is a simple smalldatetime field to provide optional auditing

It's worth noting that I'm using SQL_Latin1_General_CP1_CS_AS for the collation on the Key column. This isn't absolutely necessary; however, it means that the Key column is case-sensitive and makes the universe much larger for the number of unique values that be be stored in that column.

The next step is to create the view and scalar-valued functions.

GetUniqueIdentifierView
 SET ANSI_NULLS ON  
 GO  
 SET QUOTED_IDENTIFIER ON  
 GO  
 CREATE VIEW [dbo].[GetUniqueIdentifierView]  
 AS  
 SELECT 'ID' = NEWID()  
 GO  

GetUniqueIdentifier
 SET ANSI_NULLS ON  
 GO  
 SET QUOTED_IDENTIFIER ON  
 GO  
 CREATE FUNCTION [dbo].[GetUniqueIdentifier]()  
 RETURNS UNIQUEIDENTIFIER  
 AS   
 BEGIN  
      RETURN (SELECT ID FROM GetUniqueIdentifierView)  
 END  
 GO  

GetRandomString
 SET ANSI_NULLS OFF  
 GO  
 SET QUOTED_IDENTIFIER ON  
 GO  
 CREATE FUNCTION [dbo].[GetRandomString]  
 (  
      @Length     INT = 5  
 )  
 RETURNS VARCHAR(MAX)  
 AS  
 BEGIN  
      -- variables  
      DECLARE @Characters     VARCHAR(62)  
      DECLARE @Output         VARCHAR(MAX)  
      DECLARE @i              INT  
        
      -- values  
      SET @Characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'  
      SET @Output = ''  
      SET @i = 0  
        
      -- loop  
      WHILE @i < @Length  
      BEGIN  
           -- variables  
           DECLARE @Position INT  
             
           -- random position in character map  
           SET @Position = ABS(CAST(CAST(dbo.GetUniqueIdentifier() AS VARBINARY) AS INT)) % LEN(@Characters)  
             
           -- concatenate the random character map value to our string  
           SET @Output = @Output + SUBSTRING(@Characters, @Position + 1, 1)  
   
           -- increment loop counter       
           SET @i = @i + 1  
      END  
        
      -- return value  
      RETURN ISNULL(@Output, '')  
 END  
 GO  

At this point we have a simple function we can call to get a random string of a specified length, for example:
 SELECT dbo.GetRandomString(5)  

Now we'll create the stored procedures to finish off the SQL side of things.

The first stored procedure is Urls_List. This stored procedure has optional parameters to either return all urls, return a single url for the specified PK, or return a single url for the specified Key.
 SET ANSI_NULLS OFF  
 GO  
 SET QUOTED_IDENTIFIER ON  
 GO  
 CREATE PROCEDURE [dbo].[Urls_List]  
 (  
      @PK      INT             = 0,  
      @Key     VARCHAR(10)     = ''  
 )  
 AS  
 BEGIN  
      -- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.  
      SET NOCOUNT ON;  
   
      -- get data  
      SELECT        PK,  
                    [Key],  
                    Url,  
                    'UrlShort' = '{BaseUrl}url/?' + [Key],  
                    CreatedBy,  
                    CreatedDate  
      FROM          Urls  
      WHERE         PK =   
                          CASE  
                               WHEN @PK > 0 THEN @PK  
                               ELSE PK  
                          END  
      AND               [Key] =   
                          CASE  
                               WHEN LEN(@Key) > 0 THEN @Key  
                               ELSE [Key]  
                          END  
      ORDER BY      PK  
 END  
 GO  

Now you have a simple way to get the data from the Urls table, for example:
 -- get all urls  
 EXEC dbo.Urls_List  
   
 -- get url for specified PK  
 EXEC dbo.Urls_List @PK = 1  
   
 -- get url for specified Key  
 EXEC dbo.Urls_List @Key = 'gyzAq'  

You'll want to pay special attention to the {BaseUrl} reference in the Urls_List stored procedure. You could do one of two things here. You could plug your fully qualified base url directly in there (e.g. http://pavey.me/) OR you can keep that placeholder in there, and replace it generically in either the data layer, model object, or the web project, depending on your requirements.

The next stored procedure is Urls_Save. This stored procedure creates the record in the Urls table with a unique key and returns the record. If a record with the specified Url already exists it will return that record instead of creating a new one.
 SET ANSI_NULLS OFF  
 GO  
 SET QUOTED_IDENTIFIER ON  
 GO  
 CREATE PROCEDURE [dbo].[Urls_Save]  
 (  
      @Url          VARCHAR(2000),  
      @CreatedBy    VARCHAR(100)  
 )  
 AS  
 BEGIN  
      -- declare variables  
      DECLARE @PK     INT  
        
      -- see if url already exists  
      SELECT   @PK = PK  
      FROM     Urls  
      WHERE    Url = @Url  
        
      -- handle nulls  
      SET @PK = ISNULL(@PK, 0)  
        
      -- create url if necessary  
      IF @PK < 1  
      BEGIN  
           -- variables  
           DECLARE @Key            VARCHAR(5) = ''  
           DECLARE @KeyLength      INT        = 5  
           DECLARE @KeyIsUnique    BIT        = 0  
             
           -- get random key  
           SET @Key = dbo.GetRandomString(@KeyLength)  
   
           -- check to see if key already exists  
           IF NOT EXISTS(SELECT PK FROM Urls WHERE [Key] = @Key)  
           BEGIN  
                SET @KeyIsUnique = 1  
           END  
   
           -- if key is not unique keep looking  
           IF @KeyIsUnique = 0  
           BEGIN  
                WHILE @KeyIsUnique = 0  
                BEGIN  
                     -- get random key  
                     SET @Key = dbo.GetRandomString(@KeyLength)  
   
                     -- check to see if key already exists  
                     IF NOT EXISTS(SELECT PK FROM Urls WHERE [Key] = @Key)  
                     BEGIN  
                          SET @KeyIsUnique = 1  
                     END  
                END  
           END  
   
           -- insert record  
           INSERT INTO Urls  
                ([Key], Url, CreatedBy)  
           VALUES  
                (@Key, @Url, @CreatedBy)  
                  
           -- get identity  
           SET @PK = SCOPE_IDENTITY()  
      END  
   
      -- return url  
      EXEC Urls_List @PK = @PK  
 END  
 GO  

You'll notice in the Urls_Save stored procedure I'm using @KeyLength = 5. The GetRandomString function we created can handle creating a random string of any size, so you'll just want to set this based on your needs. In my case, a unique key length of 5 was sufficient.

This takes care of the SQL side of things. Now we are going to create a model class to represent our url data. I keep my model classes in a Model project, and I use a BaseModel class, which for purposes of this example is empty, but gives you a place to provide base support for your model classes.

BaseModel.cs
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Text;  
   
 namespace Sandbox.Model  
 {  
   public class BaseModel : IDisposable  
   {  
   
     #region "IDisposable Support"  
     // To detect redundant calls  
     private bool disposedValue;  
   
     // IDisposable  
     protected virtual void Dispose(bool disposing)  
     {  
       if (!this.disposedValue)  
       {  
         if (disposing)  
         {  
           // TODO: dispose managed state (managed objects).  
         }  
   
         // TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.  
         // TODO: set large fields to null.  
       }  
       this.disposedValue = true;  
     }  
   
     // TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.  
     //Protected Overrides Sub Finalize()  
     //  ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.  
     //  Dispose(False)  
     //  MyBase.Finalize()  
     //End Sub  
   
     // This code added by Visual Basic to correctly implement the disposable pattern.  
     public void Dispose()  
     {  
       // Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.  
       Dispose(true);  
       GC.SuppressFinalize(this);  
     }  
     #endregion  
   }  
 }  

UrlShort.cs
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Text;  
   
 namespace Sandbox.Model  
 {  
   public class UrlShortner : BaseModel  
   {  
     // constructor  
     public UrlShortner()  
     {  
       PK = 0;  
       Key = string.Empty;  
       Url = string.Empty;  
       UrlShort = string.Empty;  
       CreatedBy = string.Empty;  
       CreatedDate = DateTime.MinValue;  
     }  
   
     // public properties  
     public int PK { get; set; }  
     public string Key { get; set; }  
     public string Url { get; set; }  
     public string UrlShort { get; set; }  
     public string CreatedBy { get; set; }  
     public DateTime CreatedDate { get; set; }  
   }  
 }  

Now we'll create the data layer class. I keep the data layer classes in a Data project, and I use a BaseDB class, which for purposes of this example is empty, but gives you a place to provide base support for your data layer classes. In this example we're using Microsoft Enterprise Library 5.0 – May 2011 to provide the data access.

BaseDB.cs
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Text;  
   
 namespace Sandbox.Data  
 {  
   public class BaseDB : IDisposable  
   {  
   
     #region "IDisposable Support"  
     // To detect redundant calls  
     private bool disposedValue;  
   
     // IDisposable  
     protected virtual void Dispose(bool disposing)  
     {  
       if (!this.disposedValue)  
       {  
         if (disposing)  
         {  
           // TODO: dispose managed state (managed objects).  
         }  
   
         // TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.  
         // TODO: set large fields to null.  
       }  
       this.disposedValue = true;  
     }  
   
     // TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.  
     //Protected Overrides Sub Finalize()  
     //  ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.  
     //  Dispose(False)  
     //  MyBase.Finalize()  
     //End Sub  
   
     // This code added by Visual Basic to correctly implement the disposable pattern.  
     public void Dispose()  
     {  
       // Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.  
       Dispose(true);  
       GC.SuppressFinalize(this);  
     }  
     #endregion  
   }  
 }  
   

Urls.cs
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Text;  
 using System.Data;  
 using System.Data.Common;  
 using Microsoft.Practices.EnterpriseLibrary.Data;  
 using Sandbox.Utilities;  
   
 namespace Sandbox.Data  
 {  
   public class Urls : BaseDB  
   {  
     public Model.UrlShortner Get(int PK)  
     {  
       // validate  
       if (PK < 1)  
       {  
         return new Model.UrlShortner();  
       }  
   
       // lookup  
       List<Model.UrlShortner> x = List(PK: PK);  
   
       // return  
       if (x.Count > 0)  
       {  
         return x.First();  
       }  
       else  
       {  
         return new Model.UrlShortner();  
       }  
     }  
   
     public Model.UrlShortner Get(string Key)  
     {  
       // validate  
       if (Key.IsBlank())  
       {  
         return new Model.UrlShortner();  
       }  
   
       // lookup  
       List<Model.UrlShortner> x = List(Key: Key);  
   
       // return  
       if (x.Count > 0)  
       {  
         return x.First();  
       }  
       else  
       {  
         return new Model.UrlShortner();  
       }  
     }  
   
     public List<Model.UrlShortner> List(Int32 PK = 0, string Key = "")  
     {  
       // variables  
       Database DB = DatabaseFactory.CreateDatabase();  
       List<Model.UrlShortner> Urls = new List<Model.UrlShortner>();  
   
       // command  
       using (DbCommand cmd = DB.GetStoredProcCommand("dbo.Urls_List"))  
       {  
         // parameters  
         DB.AddInParameter(cmd, "@PK", DbType.Int32, PK);  
         DB.AddInParameter(cmd, "@Key", DbType.String, Key);  
   
         // execute query and get results  
         using (DataTable DT = new DataTable())  
         {  
           using (IDataReader IDR = DB.ExecuteReader(cmd))  
           {  
             if (IDR != null)  
             {  
               DT.Load(IDR);  
             }  
           }  
   
           // convert to business object  
           Urls = DT.ToList<Model.UrlShortner>().ToList();  
         }  
       }  
   
       // return list  
       return Urls;  
     }  
   
     public Model.UrlShortner Save(string Url, string CreatedBy = "")  
     {  
       // variables  
       Database DB = DatabaseFactory.CreateDatabase();  
       Model.UrlShortner UrlShort = new Model.UrlShortner();  
   
       // command  
       using (DbCommand cmd = DB.GetStoredProcCommand("dbo.Urls_Save"))  
       {  
         // parameters  
         DB.AddInParameter(cmd, "@Url", DbType.String, Url);  
         DB.AddInParameter(cmd, "@CreatedBy", DbType.String, CreatedBy);  
   
         // execute query and get results  
         using (DataTable DT = new DataTable())  
         {  
           using (IDataReader IDR = DB.ExecuteReader(cmd))  
           {  
             if (IDR != null)  
             {  
               DT.Load(IDR);  
             }  
           }  
   
           // convert to business object  
           if (DT.Rows.Count > 0)  
           {  
             UrlShort = DT.Rows[0].ToObject<Model.UrlShortner>();  
           }            
         }  
       }  
   
       // return value  
       return UrlShort;  
     }  
   }  
 }  

It might seem like overkill for this example, but I'm using some extension methods to make the data layer much more generic. For example, instead of having to do manual column mapping to map the data from the DataTable to the model object I am able to use ToObject and ToList to do that generically. You wouldn't believe how much time generic extension methods like this will save you over the course of your project.

Extensions.cs
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Reflection;  
 using System.Data;  
 using System.ComponentModel;
   
 namespace Sandbox.Utilities  
 {  
   public static class Extensions  
   {  
     private static Dictionary<Type, IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();  
   
     public static IList<PropertyInfo> GetPropertiesForType<T>()  
     {  
       // variables  
       var type = typeof(T);  
   
       // get types  
       if (!typeDictionary.ContainsKey(typeof(T)))  
       {  
         typeDictionary.Add(type, type.GetProperties().ToList());  
       }  
   
       // return  
       return typeDictionary[type];  
     }  
   
     public static T ToObject<T>(this DataRow row) where T : new()  
     {  
       // variables  
       IList<PropertyInfo> properties = GetPropertiesForType<T>();  
   
       // return  
       return CreateItemFromRow<T>(row, properties);  
     }  
   
     public static IList<T> ToList<T>(this DataTable table) where T : new()  
     {  
       // variables  
       IList<T> result = new List<T>();  
   
       // foreach  
       foreach (DataRow row in table.Rows)  
       {  
         result.Add(row.ToObject<T>());  
       }  
   
       // return  
       return result;  
     }  
   
     private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()  
     {  
       // variables  
       T item = new T();  
   
       // foreach  
       foreach (var property in properties)  
       {  
         // make sure a column exists in the table with this property name  
         if (row.Table.Columns.Contains(property.Name))  
         {  
           // get the value from the current data row  
           object value = row[property.Name];  
   
           // set property accordingly  
           if (value != null & value != DBNull.Value)  
           {  
             SetProperty<T>(item, property.Name, value);  
           }  
         }  
       }  
   
       // return  
       return item;  
     }  
   
     public static string GetProperty<T>(this T obj, string Property)  
     {  
       // reflection  
       PropertyInfo propertyInfo = obj.GetType().GetProperty(Property, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);  
       object property = null;  
   
       // make sure property is valid  
       if (propertyInfo != null)  
       {  
         property = propertyInfo.GetValue(obj, null);  
       }  
   
       // return value  
       if (property != null)  
       {  
         return property.ToString();  
       }  
       else  
       {  
         return string.Empty;  
       }  
     }  
   
     public static T SetProperty<T>(this T obj, string Property, object Value)  
     {  
       // reflection  
       PropertyInfo prop = obj.GetType().GetProperty(Property, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);  
   
       // trim strings  
       if (Value.GetType() == typeof(string))  
       {  
         Value = Value.ToString().Trim();  
       }  
   
       // make sure property is valid  
       if (prop != null && prop.CanWrite)  
       {  
         prop.SetValue(obj, Value, null);  
       }  
   
       // return  
       return obj;  
     }  

     public static bool HasValue(this string Value)  
     {  
       return !Value.IsBlank();  
     }  
   
     public static bool IsBlank(this string Value)  
     {  
       bool ReturnValue = true;  
   
       if (Value != null)  
       {  
         ReturnValue = Value.Trim().Length == 0;  
       }  
   
       return ReturnValue;  
     }  
   }  
 }  

Now to round this all out we need to create a shortened url and use it. To create a shortened url you simply call the Save method from the data layer:
 // create shorterned url  
 using (Data.Urls DB = new Data.Urls())  
 {  
    using (Model.UrlShortner MyUrl = DB.Save(Url: "http://www.google.com", CreatedBy: "Testing"))  
    {  
       // MyUrl now has a reference to your shortened url
    }  
 }  

I typically would use a shortened url like this if I was going to include some kind of custom url for a user, for example resetting their password. The url may include an encrypted token and could look pretty ugly, so having a way to provide a nice clean url makes that much more appealing for the email.

Example Url:
http://dev.sandbox.com/forgot-password.aspx?q=urXsbqy9knIpqqOddkFICXTAspidUXCmPS2vJsLqkVanePU%2bGgxLahBGGl1EA%2f%2fXyXxGT4EX6kcqYSvy8BTpib6eEB61Q5WxNcNfEjk1OYx4HGyvO5oKs34JZ%2f1p9jMuAvVpkbkKpBBjYD2UiotIiYob%2baSzHmxRUuUJYkRepd6kSosnXOXssKVQJ%2bWbQQkYfNZWt2OUe9nGw1UOBd1aeO3O9JyEqSiMkYoIF1blW3f9MUx461LSnB9FL2Q4Vbn%2bDGyK0kEHPQVaz5fhpSXIPrfN3Q%2flRtjyqWDXrRnx2%2bE%3e

Example Shortened Url:
http://dev.sandbox.com/url/?Z4cgw

So now we can create the shortened url and know what it looks like, all that's left to do is allow your web application to actually recognize the shortened url and redirect to the actual url.

Start off by creating a new folder in your web project. I called mine url, which is why in the example shortened url above I used /url. This could literally be any folder name you wanted, but something short makes sense.

After you've got the folder created, create a Default.aspx page in that folder, with the following:

Default.aspx
 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Sandbox.Web.url.Default" %>  
   
 <!-- see code behind -->  

Default.aspx.cs
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Web;  
 using System.Web.UI;  
 using System.Web.UI.WebControls;  
 using Sandbox.Utilities;  
   
 namespace Sandbox.Web.url  
 {  
   public partial class Default : System.Web.UI.Page  
   {  
     protected void Page_Load(object sender, EventArgs e)  
     {  
       if (!Page.IsPostBack)  
       {  
         ProcessUrl();  
       }  
     }  
   
     private void ProcessUrl()  
     {  
       // variables  
       string Key = Request.QueryString.ToString();  
       string Url = string.Empty;  
   
       // check database for url  
       if (Key.HasValue())  
       {  
         using (Data.Urls DB = new Data.Urls())  
         {  
           using (Model.UrlShortner x = DB.Get(Key: Key))  
           {  
             Url = x.Url;  
           }  
         }  
       }  
   
       // redirect  
       if (Url.HasValue())  
       {  
         Response.Redirect(Url);  
         Response.End();  
       }  
       else  
       {  
         Response.Redirect("~/home.aspx");  
         Response.End();  
       }  
     }  
   }  
 }  

This page simply takes the querystring value and checks to see if there is a matching record in the Urls table with the specified Key. If found, it redirects to that url. If not, it falls back to redirecting the user to some default/home page.

This is just one way to build a URL shortner. You can strip out the requirement for the extension methods or change it to use your own style of model/data classes, but the overall concept doesn't change:

- Database table for the url data
- Views/Functions/Procs for creating/accessing the url data
- Model object for representing the url data
- Data layer class for calling the stored procedures and returning the model object
- Folder for processing the shortened url and redirecting appropriately

ASP.Net C# Get Page Name With Optional Parameters To Include Extension And QueryString

Here's a simple static function that I keep in my utilities/functions class to let you easily get the the page name being requested.

Functions.cs
 public static string GetPageName(bool IncludeExtension = false, bool IncludeQueryString = false)  
 {  
    string AbsolutePath = HttpContext.Current.Request.Url.AbsolutePath;  
    string PageName = Path.GetFileName(AbsolutePath);  
    string Extension = Path.GetExtension(AbsolutePath);  
    string QueryString = HttpContext.Current.Request.QueryString.ToString();  
   
    if (!IncludeExtension && !IncludeQueryString && PageName.HasValue())  
    {  
       PageName = PageName.Replace(Extension, string.Empty);  
    }  
   
    if (IncludeQueryString && PageName.HasValue() && QueryString.HasValue())  
    {  
       PageName = string.Format("{0}?{1}", PageName, QueryString);  
    }  
   
    return PageName;  
 }  

The GetPageName function is dependent on the following extension methods, although it could easily be re-factored to check the string length directly; however, I prefer to use these types of extension methods throughout the projects to keep things consistent and concise.

Extensions.cs
 public static bool HasValue(this string Value)  
 {  
    return !Value.IsBlank();  
 }  
   
 public static bool IsBlank(this string Value)  
 {  
    bool ReturnValue = true;  
   
    if (Value != null)  
    {  
       ReturnValue = Value.Trim().Length == 0;  
    }  
   
    return ReturnValue;  
 }  

The GetPageName function is very basic but a nice way to get the page name, and optionally lets you indicate whether or not to include the extension in the page name, and whether you want to include the querystring parameters in the page name.

Example usage and output:
 string Url = Functions.GetPageName(IncludeExtension: true, IncludeQueryString: true);  
Test.aspx?x=1

Example usage and output:
 string Url = Functions.GetPageName(IncludeExtension: true, IncludeQueryString: false);  
Test.aspx

Example usage and output:
 string Url = Functions.GetPageName(IncludeExtension: false, IncludeQueryString: false);  
Test