Using SQL CLR Stored Procedure To Track IP Address

by Prashant 24. April 2010 09:24

Recently I was asked by one of my friend how to call a web service directly from a stored procedure. He needs to track IP addresses using a stored procedures so, I suggested him two ways to call a web service using the SQL Procedure.

  • Using SP_OACREATE with MSXML
  • Create an assembly using SQL CLR using C#.

I personally tried using the first way to call a web service but was not successful. Then I learnt about SQL CLR programming with Visual Studio and C# and get it done in a first go. As a good friend I create the whole project for my friend. And if your thinking that this is going to be a difficult, this is not so.

To start creating a project, open Visual Studio and create a new project.

Before the project gets loaded completely, you will be asked to add database reference. Here select the database you want to build the assembly for. If you don’t want to add a database reference then just simply cancel. But if you choose the reference to add at this point then this would really be of help as this will allow you to deploy your assembly directly to the SQL Server database but if you have not then you need to write some SQL scripts.

NOTE: If you have added a database reference then it doesn’t mean that your assembly is build specifically for that database you can still deploy your assembly in other database also.

As I am creating a stored procedure, I then have to add a New Item to my project. Right-click project choose Add>Stored Procedure.  Here give a name of your choice to stored procedure. In my case it is IPInfo.

Change the static method here to accept a parameter. Our parameter will be an IP address, which I have set as of string type.

public static void GetIPInfo(string IP)
{  
//Do something here
}

Now the question here is from where we can the information of the IP Addresses? I do some search and found a website http://www.geobytes.com. It offers some rich information of the IP Address you want to track, so out of five sources I choose to go with geobytes.

Lets take a quick look at the code. This is not a web service as you can see from the code below, I am just making a http request and then get the response in an XML form so I can easily traverse the nodes and read/save the information I received.

string XMLResponse;
IPAddress = IP;
byte[] XMLResults;
string str = "http://www.geobytes.com/IpLocator.htm?GetLocation&template=xml.txt"
+ "&IpAddress=" + IP;
XMLResults = new WebClient().DownloadData(str.ToString());
XMLResponse = Encoding.UTF8.GetString(XMLResults);

Note the fourth line where I have built the URL. The parameter Template should always have a value to xml.txt. If you change the name, it won’t throw any error but the data received cannot be read and saved in a database table. Convert the response to string format and then read the nodes one by one using the below code.

try
        {
            XmlDocument document = new XmlDocument();
            document.LoadXml(XMLResponse);
            XmlNode documentElement = document.DocumentElement;
            if (documentElement.HasChildNodes)
            {
                for (int i = 0; i < documentElement.ChildNodes.Count; i++)
                {
                    if (documentElement.ChildNodes[i].NodeType == XmlNodeType.Element)
                    {
                        switch (documentElement.ChildNodes[i].Name)
                        {
                            case "locationcode":
                                LocationCode = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "fips":
                                FIPS104 = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "iso2":
                                ISO2 = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "iso3":
                                ISO3 = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "ison":
                                ISON = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "internet":
                                Internet = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "countryid":
                                CountryID = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "country":
                                Country = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "regionid":
                                RegionID = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "region":
                                Region = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "code":
                                RegionCode = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "adm":
                                Admn1Code = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "cityid":
                                CityID = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "city":
                                City = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "latitude":
                                Latitude = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "longitude":
                                Longitude = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "timezone":
                                TimeZone = documentElement.ChildNodes[i].InnerText;
                                break;

                            case "certainty":
                                Certainty = documentElement.ChildNodes[i].InnerText;
                                break;
                        }
                    }
                }
            }
        }
        catch (Exception) { }

I traversed through each node and save the response data in a variable I have already declared in my program using a switch case. Once all the response has been assigned to the variable save the response to the database.

try
        {
            using (SqlConnection con = new SqlConnection("context connection=true"))
            {
                string strSQL = "INSERT INTO IPTrack values ('" + IPAddress + "','" + LocationCode + "','" + FIPS104 + "','" + ISO2 + "','" + ISO3 + "','" + ISON + "','" + Internet + "','" + CountryID + "','" + Country +
                    "','" + RegionID + "','" + Region + "','" + RegionCode + "','" + Admn1Code + "','" + CityID + "','" + City + "','" + Latitude + "','" + Longitude + "','" + TimeZone + "','" + Certainty + "')";
                SqlCommand cmd = new SqlCommand(strSQL, con);
                //SqlContext.Pipe.ExecuteAndSend(cmd);
                con.Open();
                SqlContext.Pipe.ExecuteAndSend(cmd);
                //cmd.ExecuteNonQuery();
            }
        }
        catch (Exception) { }
    }

Build you project and you will see a DLL file in your debug/release folder. But this will not work. You will still have to set some permission level to assembly and to SQL Server database.

Set Assembly Permissions

Before you build the final version of the assembly go the project properties and set the permissions as shown below.

Under Build select option for Generate Serialization Assembly to On.

Under Database select Permission Level to Unsafe

SQL Server Permissions

If you find problems while deploying your assembly, then make sure you have CLR enabled and set the Trustworthy property for the database to ON.

You can find all the scripts in the attached solution below.

Deploying your assembly

To deploy your assembly use the below SQL command:

CREATE ASSEMBLY IPTracker from 'C:\IPInfo.dll' WITH PERMISSION_SET = EXTERNAL_ACCESS
GO

To create a stored procedure whcih in trun calls the SQL method from the deployed assembly:

CREATE PROC TrackIP(@IP as nvarchar(50))AS 
-- [Assembly Name].[Class Name].[CLR function Name]
EXTERNAL NAME IPTracker.StoredProcedures.GetIPInfo 
GO

And execute the procedure to get the IP information

Download: IPInfo.zip (19.46 kb)

If you enjoyed this post, make sure you subscribe to my RSS feed!

Tags: , , , ,

C# | SQL Server | T-SQL


Passing Parameters and Loading Crystal Report Programmatically

by Prashant 1. March 2010 20:55

Reporting is an important part for every application. Crystal reports are widely used and also available in Visual Studio for reporting purposes. I personally never like designing application interfaces and working on web designs, it needs good designing skills which I lack and so I never put my hands on designing anything, but sometimes we have to. In crystal reports when you create a report using wizard or just adding a blank report to the project and then fetching data using code (after designing), the report works fine on the machine where you have designed and develop it and the problem occurs when you have to deploy or distribute your application with reports on multiple machines or clients. Of course, the connection string or you can say in simple words the server name, database name, user name and password is different than the name you used in your connection string while you test and make your application ready to be distributed. Therefore, to overcome this problem I wrote a class which will help me to achieve this in one line and also keep my code neat and clean. So this simple class will let you set the connection for your report dataset and let your reports work properly without any problems.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using CrystalDecisions.CrystalReports.Engine;
using CrystalDecisions.Shared;


namespace ReportExportDemo
{
    class Reports
    {
        static TableLogOnInfo crTableLogonInfo;
        static ConnectionInfo crConnectionInfo;
        static Tables crTables;
        static Database crDatabase;

        public static void ReportLogin(ReportDocument crDoc, string Server, string Database, string UserID, string Password)
        {
            crConnectionInfo = new ConnectionInfo();
            crConnectionInfo.ServerName = Server;
            crConnectionInfo.DatabaseName = Database;
            crConnectionInfo.UserID = UserID;
            crConnectionInfo.Password = Password;
            crDatabase = crDoc.Database;
            crTables = crDatabase.Tables;
            foreach (CrystalDecisions.CrystalReports.Engine.Table crTable in crTables)
            {
                crTableLogonInfo = crTable.LogOnInfo;
                crTableLogonInfo.ConnectionInfo = crConnectionInfo;
                crTable.ApplyLogOnInfo(crTableLogonInfo);
            }
        }

        public static void ReportLogin(ReportDocument crDoc, string Server, string Database)
        {
            crConnectionInfo = new ConnectionInfo();
            crConnectionInfo.ServerName = Server;
            crConnectionInfo.DatabaseName = Database;
            crConnectionInfo.IntegratedSecurity = true;
            crDatabase = crDoc.Database;
            crTables = crDatabase.Tables;
            foreach (CrystalDecisions.CrystalReports.Engine.Table crTable in crTables)
            {
                crTableLogonInfo = crTable.LogOnInfo;
                crTableLogonInfo.ConnectionInfo = crConnectionInfo;
                crTable.ApplyLogOnInfo(crTableLogonInfo);
            }
        }
    }
}

This class contains ReportLogIn method which is overloaded which lets you choose the type of datasourde you want to connect to. The first method in the class requires SQL Server authentication to log-on to the server and the second method is used when the server is configured on windows authentication, similar to Integrated Security = true.

Sample usage of the Reports class is as follows. I have used AdventureWorks database for this example. If you dont have adventure works database then you can download the sample database from Microsoft's website or from codeplex or you can create your own report and check it with this example.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using CrystalDecisions.CrystalReports.Engine;
using CrystalDecisions.Shared;


namespace ReportExportDemo
{
    public partial class frm_main : Form
    {
        public frm_main()
        {
            InitializeComponent();
        }

        private void btn_prvrpt_Click(object sender, EventArgs e)
        {
            int Cust_Id = Convert.ToInt32(txt_customerid.Text);
            //string Cust_Id = txt_customerid.Text.Trim();

            //Initialize report document object and load the report in the report document object
            ReportDocument crReportDocument = new ReportDocument();
            crReportDocument.Load(Application.StartupPath+"\\Reports\\AdventureCustReport.rpt");
            
            //login to the server to get details from the server and populate to the report           
            Reports.ReportLogin(crReportDocument, "MX\\SERVER", "AdventureWorks", "sa", "pass#w0rd1");
            
            //Pass parameter to the report object
            crReportDocument.SetParameterValue("id", Cust_Id);

            //To create PDF from the crystal report
            crReportDocument.ExportToDisk(ExportFormatType.PortableDocFormat, "CustomerReport" + Cust_Id + ".pdf");
            
            //Preview the generated PDF
            System.Diagnostics.Process.Start(Application.StartupPath + "\\CustomerReport" + Cust_Id + ".pdf");
        }
    }
}

Note the namespaces I have used in the above code and also how I have used the Reports class ReportLogin method. For more clarification of the code download the sample application with the report below.

One important thing that you have to keep in mind while designing report is to set the parameter. After you have set the parameter and you use your report with the above code, the report will then populate the whole lot of records in the table trrespective of the parameter you pass to the report. So to avoid this and to view only the record of your choice you needs to set the formula in the crystal report. the formula goes like this:

Sample Syntax:

{<Table's Column Name>}={?<Parameter Name>}

In my report this formula looks something like this:

{CustomerID}={?ID}

Download: ReportExportDemo.zip (55.76 kb)

If you enjoyed this post, make sure you subscribe to my RSS feed!

Tags: , ,

C# | SQL Server


SQL Server performance and NOCOUNT

by Prashant 26. February 2010 05:44

Performance of SQL server matters while working with large enterprise applications and where performance really bothers the business workflow. When NOCOUNT is ON, the count of rows by the execution of the query (T-SQL statements) is not returned. You must have noticed that when you perform operations by executing INSERT, UPDATE, DELETE or SELECT statements, the server returns the number of rows. The count of rows is necessary while debugging your queries, when you are done with debugging you can turn NOCOUNT ON. To view the number of rows affected use the command SET NOCOUNT OFF. You can try writing a normal select query with NOCOUNT as OFF, when NOCOUNT is OFF you can see the number of rows affected, but when you set NOCOUNT ON, you will not see any message related to the count of rows which are affected when you execute select statement or T-SQL statement.

If you enjoyed this post, make sure you subscribe to my RSS feed!

Tags: ,

SQL Server | T-SQL


Using OpenXML to insert data in SQL Server

by Prashant 20. February 2010 07:44

Microsoft SQL Server provides the mechanism to save XML data to the SQL table using OpenXML. We can use a stored procedure and pass a XML text as a string. It is useful when we have a large data in XML format and we want to save the whole data in different tables.

So first start declaring the variables. The first declare the document variable and a XML document variable as Varchar (8000) or Varchar(MAX) - It's a same thing

DECLARE @idoc int
DECLARE @doc varchar(8000)

Now we can set the value to the @doc variable, the @doc variable will accept the XML file as a parameter. We can also have a complete XML data in the parameter only if the XML data is less.

OpenXML takes 3 parameters:

  • The handler which we have declare at the begining will holds the XML document in the memory.
  • XPath variable to access the various elements of the XML document.
  • The last parameter here '2' allows us to access XPath as elements.

Here I have read a XML file in the document variable and store in the table. Here is the full SQL query to achieve this. You can also convert the below code to stored procedure and pass XML file or string as a parameter

DECLARE @idoc int
DECLARE @doc varchar(8000)
SET @doc ='<ROOT><student>
<id>1</id>
<name>Prashant</name>
<age>32</age>
</student>
<student>
<id>2</id>
<name>Swami</name>
<age>42</age>
</student>
<student>
<id>3</id>
<name>Ash</name>
<age>23</age>
</student>
<student>
<id>4</id>
<name>Kris</name>
<age>12</age>
</student>
<student>
<id>5</id>
<name>Derek</name>
<age>75</age>
</student>
</ROOT>'

--Create an internal representation of the XML document.
EXEC sp_xml_preparedocument @idoc OUTPUT, @doc
-- Execute a SELECT statement that uses the OPENXML rowset provider.
Insert into Students SELECT id, name, age from 
OPENXML (@idoc, '/ROOT/student',2)
WITH (id  int, name varchar(50), age int)

If you enjoyed this post, make sure you subscribe to my RSS feed!

Tags: , ,

SQL Server | T-SQL


Use Sql query to write/create a file

by Prashant 11. December 2009 23:33

This SQL stored procedure will allow you to write to the file on your file system. Note the file system will be the the same on which the SQL server is running. If you are using this with your local SQL server then it will write and create files on your local file system and if you are on the remote machine, the file system will be the remote file system.

Create Procedure  [dbo].[USP_SaveFile](@text as NVarchar(Max),@Filename Varchar(200)) 
AS 
Begin 
 
declare @Object int,
        @rc int, -- the return code from sp_OA procedures 
        @FileID Int 
 
EXEC @rc = sp_OACreate 'Scripting.FileSystemObject', @Object OUT 
EXEC @rc = sp_OAMethod  @Object , 'OpenTextFile' , @FileID OUT , @Filename , 2 , 1 
Set  @text = Replace(Replace(Replace(@text,'&','&'),'<' ,'<'),'>','>')
EXEC @rc = sp_OAMethod  @FileID , 'WriteLine' , Null , @text  
Exec @rc = master.dbo.sp_OADestroy @FileID   
 
Declare @Append  bit
Select  @Append = 0
 
If @rc <> 0
Begin
    Exec @rc = master.dbo.sp_OAMethod @Object, 'SaveFile',null,@text ,@Filename,@Append
       
End

Exec @rc = master.dbo.sp_OADestroy @Object 
    
End 

But before you start using this procedure you need to reconfigure some advanced SQL server settings. Use the below configuration query to enable 'OLE Automation Procedures'. If this is not enabled and you try executing the procedure you will get errors.

Use master
GO
-- To allow advanced options to be changed.
EXEC sp_configure 'show advanced options', 1
GO
--To enable Ole automation feature
EXEC sp_configure 'Ole Automation Procedures', 1;
GO
RECONFIGURE;
GO

The first parameter will take the text to be written to the file and the second parameter will take the complete path of the file to be created with the text in it. You can also use the same procedure to wite binary files to the file system, you just need to check and change the file extension in the second parameter. Usage:

EXEC USP_SaveFile 'Microsoft SQL Server 2008', 'C:\MSSQL.txt' 

If you enjoyed this post, make sure you subscribe to my RSS feed!

Tags: , ,

SQL Server | T-SQL


Visit blogadda.com to discover Indian blogs Computers Blogs

About Me

Name of authorPrashant Khandelwal.
Programmer and tech enthusiast. More...

Feeds Subscribe Twitter Facebook Google Plus Linked In Delicious

Badges

MVB

MVP Blog Badge.

HTML5 Powered with CSS3 / Styling, Graphics, 3D & Effects, Multimedia, Performance & Integration, Semantics, and Offline & Storage

Month List

Blog Stats

414,225 Hits

Adverts

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2012

Creative Commons License