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) { }
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)