tag:blogger.com,1999:blog-13017398905033597432024-03-10T23:20:30.384-04:00Matthew Pavey's BlogMatthew Pavey is a follower of Christ, devoted husband and father, avid reader, and software developer.Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.comBlogger165125tag:blogger.com,1999:blog-1301739890503359743.post-23483651925815103192018-01-03T09:19:00.002-05:002018-01-03T09:19:36.521-05:00 One Solitary Life<blockquote>
<p>He was born in an obscure village, the child of a peasant. He grew up in another village, where he worked in a carpenter shop until he was thirty. Then, for three years, he was an itinerant preacher.</p>
<p>He never wrote a book. He never held an office. He never had a family or owned a home. He didn't go to college. He never lived in a big city. He never traveled two hundred miles from the place where he was born. He did none of the things that usually accompany greatness. He had no credentials but himself.</p>
<p>He was only thirty-three when the tide of public opinion turned against him. His friends ran away. One of them denied him. He was turned over to his enemies and went through the mockery of a trial. He was nailed to a cross between two thieves. While he was dying, his executioners gambled for his garments, the only property he had on earth. When he was dead, he was laid in a borrowed grave, through the pity of a friend.</p>
Twenty centuries have come and gone, and today he is the central figure of the human race. I am well within the mark when I say that all the armies that ever marched, all the navies that ever sailed, all the parliaments that ever sat, all the kings that ever reigned, put together, have not affected the life of man on this earth as much as that one, solitary life.
<br />
<strong>- James Allan Francis, One Solitary Life</strong>
</blockquote>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-85711506322674914472017-04-15T23:23:00.000-04:002017-04-25T12:52:17.969-04:00He Has Risen<img class="custom-image" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZIzQo2qH-G8pUYXBmFRZm7L_MmJqLuPdWyAH3nZ25qmwlhuJzZy7MQ3X3Dyf_KfIH5gs_dMq0xQZMwktsnO95omnsQSLH0u4H8Pczk76gn8trdPD154N_oRviXb2j4PipVSXAcfvoFnGO/s1600/he-has-risen.jpg" />
It's around 5:00 AM in Jerusalem as I write this post, and all I can think about is the women arriving at the <a href="http://biblehub.com/nlt/luke/24.htm" target="_blank">empty tomb</a> of Jesus Christ.<br />
<br />
<blockquote class="tr_bq">
<i>The women were terrified and bowed with their faces to the ground. Then the men asked, “Why are you looking among the dead for someone who is alive? He isn’t here! He is risen from the dead!”</i><br />
<b>- Luke 24 5-6</b></blockquote>
<i>Why are you looking among the dead for someone who is alive?</i> Can you imagine that moment? Not even death could keep our Messiah from fulfilling His mission. I think about it all the time. Starting with the moment He prayed in the Garden of Gethsemane. Two chapters earlier in Luke 22:41 we hear <i>"Then Jesus went about a stones throw away from them. He kneeled down and prayed."</i> This was the final prayer of Jesus before He would be arrested, beaten, tortured, and sent to the cross. What's more remarkable is that He is praying for us. How do we know this? In Luke 22:42, Jesus asks God to take the cup of suffering away — yet when told "No", Jesus is still willing to go to the cross — for us!<br />
<br />
Then I think about Jesus on the cross in Luke 23:34 when Jesus said <i>"Father, forgive them, for they don't know what they are doing."</i> Is that not representative of a Messiah that you want to follow? Christ Jesus praying for the men who tortured him, mocked him, and nailed Him to the cross.<br />
<br />
Then in the Gospel of Matthew we read the following:<br />
<br />
<blockquote class="tr_bq">
<i>About three in the afternoon Jesus cried out in a loud voice, "Eli, Eli, lema sabachthani?" (which means "My God, my God, why have you forsaken me?").</i><br />
<b>- Matthew 27:46</b></blockquote>
This was a verse I used to struggle with. For the longest time I didn't take the time to understand what it meant, or why Jesus would cry out those words, or why God would forsake Jesus. But think about the moment Jesus took His final breath:<br />
<br />
<blockquote class="tr_bq">
<i>When Jesus had tasted it, he said, "It is finished!" Then he bowed his head and released his spirit.</i><br />
<b>- John 19:30</b></blockquote>
How was it possible for Jesus to feel that sense of abandonment? In that moment Jesus Christ took on all the sin of the world. Your sin. My sin. All sin. God simply had to look away for that singular moment when Jesus Christ carried the weight of <i>all sin</i>.<br />
<br />
It's certainly worth noting that Jesus was also quoting <a href="http://biblehub.com/nasb/psalms/22.htm" target="_blank">Psalm 22:1</a>, which is just further proof that He matched the prophetic fingerprint from the Old Testament of the coming Messiah . But we have to remember that Jesus was a human being, and this act of calling out to God was representative of our mortal struggle against death. Of course Christ wanted there to be another way. But there wasn't. We know that from Luke 22:42: <i>"Father, if you are willing, please take this cup of suffering away from me. Yet I want your will to be done, not mine."</i> Jesus didn't want to die. But that's what is the most remarkable part of it... He chose to die anyway.<br />
<br />
But make no mistake, although God may have looked away as Jesus cried out and took His last breath, there was no separation between God and Jesus Christ. There simply was no other way to pay the debt of our sin. A perfect sacrifice was required. Jesus Christ was the <i>only</i> person who could save us. He lived the life we <i>should </i>have lived. He died the death we <i>should </i>have died. He saved us from the snares of hell and overcame death. Why? One word. Love.<br />
<br />
Nothing we do will ever — ever — and I mean ever, be sufficient to repay the debt that Jesus Christ paid for our sins — yet He still pursues us relentlessly. He still extends grace and mercy to each of us. He still offers us all the chance to have a personal relationship with Him. Stop and think about that! You have the opportunity to have a <i>personal relationship</i> with Jesus Christ. All it takes is an invitation. And the invitation isn't from Jesus. <b><i>It's from you!</i></b> All you have to do is ask Jesus to be your Lord and Savior. To come in to your heart. It may not be easy. But it's that simple. And here's the best part. You don't have to have it all figured out to put your trust in Christ. It starts with a single act of obedience.<br />
<br />
<blockquote class="tr_bq">
<i>Taste and see that the LORD is good; blessed is the one who takes refuge in him.</i><br />
<b>- Psalm 34:8</b></blockquote>
As I reflect on the Resurrection this evening, I can't help but think about the lyrics to one of my favorite songs that we sang tonight at church, and when I say "sang", picture a thousand people standing and worshiping in awe, at the grace, mercy, and love that Christ showed for us <i>when death was arrested and my life began</i>.<br />
<br />
<b>Death Was Arrested</b><br />
<a href="https://www.youtube.com/watch?v=m1fEJC0JtBw" target="_blank">https://www.youtube.com/watch?v=m1fEJC0JtBw</a><br />
<br />
<b>[Verse 1]</b><br />
<i>Alone in my sorrow and dead in my sin</i><br />
<i>Lost without hope with no place to begin</i><br />
<i>Your love Made a way to let mercy come in</i><br />
<i>When death was arrested and my life began</i><br />
<br />
<b>[Verse 2]</b><br />
<i>Ash was redeemed only beauty remains</i><br />
<i>My orphan heart was given a name</i><br />
<i>My mourning grew quiet my feet rose to dance</i><br />
<i>When death was arrested and my life began</i><br />
<br />
<b>[Chorus]</b><br />
<i>Oh your grace so free</i><br />
<i>Washes over me</i><br />
<i>You have made me new</i><br />
<i>Now life begins with you</i><br />
<i>It's your endless love</i><br />
<i>Pouring down on us</i><br />
<i>You have made us new</i><br />
<i>Now life begins with you</i><br />
<br />
<b>[Verse 3]</b><br />
<i>Released from my chains I'm a prisoner no more</i><br />
<i>My shame was a ransom he faithfully bore</i><br />
<i>He cancelled my debt and he called me his friend</i><br />
<i>When death was arrested and my life began</i><br />
<br />
<b>[Verse 4]</b><br />
<i>Our savior displayed on a criminal's cross</i><br />
<i>Darkness rejoiced as though heaven had lost</i><br />
<i>But then Jesus arose with our freedom in hand</i><br />
<i>That's when death was arrested and my life began</i><br />
<i>That's when death was arrested and my life began</i><br />
<br />
<b>[Chorus]</b><br />
<i>Oh your grace so free</i><br />
<i>Washes over me</i><br />
<i>You have made me new</i><br />
<i>Now life begins with you</i><br />
<i>It's your endless love</i><br />
<i>Pouring down on us</i><br />
<i>You have made us new</i><br />
<i>Now life begins with you</i><br />
<br />
<b>[Bridge]</b><br />
<i>We're free free</i><br />
<i>Forever we're free</i><br />
<i>Come join the song</i><br />
<i>Of all the redeemed</i><br />
<i>Yes we're free free</i><br />
<i>Forever amen</i><br />
<i>When death was arrested and my life began</i><br />
<i>(2x)</i><br />
<i>When death was arrested and my life began</i><br />
<i>That's when death was arrested and my life began</i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-17180353048958514132016-12-16T12:48:00.000-05:002016-12-16T12:48:16.774-05:00ASP.Net C# Convert HTML to PDF using iTextSharp (update)Here's a minor update to a <a href="http://www.pavey.me/2015/04/aspnet-c-convert-html-to-pdf-using.html">post from last year</a> how how to convert HTML to PDF using iTextSharp.
<br /><br />
I wanted to add a way to optionally render the PDF in <strong>landscape</strong> mode, so this was what I ultimately came up with.
<br /><br />
<pre class="prettyprint lang-cs"> public static ReturnValue ConvertHtmlToPdfAsBytes(string HtmlData, bool Landscape = false)
{
// variables
ReturnValue Result = new ReturnValue();
// do some additional cleansing to handle some scenarios that are out of control with the html data
HtmlData = HtmlData.ReplaceValue("<br>", "<br />");
// convert html to pdf
try
{
// create a stream that we can write to, in this case a MemoryStream
using (var stream = new MemoryStream())
{
// create an iTextSharp Document which is an abstraction of a PDF but **NOT** a PDF
using (var document = new Document())
{
// portrait vs landscape
if (Landscape)
{
document.SetPageSize(PageSize.A4.Rotate());
}
// create a writer that's bound to our PDF abstraction and our stream
using (var writer = PdfWriter.GetInstance(document, stream))
{
// open the document for writing
document.Open();
// read html data to StringReader
using (var html = new StringReader(HtmlData))
{
XMLWorkerHelper.GetInstance().ParseXHtml(writer, document, html);
}
// close document
document.Close();
}
}
// get bytes from stream
Result.Data = stream.ToArray();
// success
Result.Success = true;
}
}
catch (Exception ex)
{
Result.Success = false;
Result.Message = ex.Message;
}
// return
return Result;
}
</code></pre>
The <b>ReturnValue</b> class was simply a helper class that looks like this:<br /><br />
<pre class="prettyprint lang-cs"> // return value class
public class ReturnValue
{
// constructor
public ReturnValue()
{
this.Success = false;
this.Message = string.Empty;
}
// properties
public bool Success = false;
public string Message = string.Empty;
public Byte[] Data = null;
}
</code></pre>
We also had another method to physically create the PDF file in case you didn't want just the bytes array directly, for example:<br /><br />
<pre class="prettyprint lang-cs"> public static ReturnValue ConvertHtmlToPdfAsFile(string FilePath, string HtmlData)
{
// variables
ReturnValue Result = new ReturnValue();
try
{
// convert html to pdf and get bytes array
Result = ConvertHtmlToPdfAsBytes(HtmlData: HtmlData);
// check for errors
if (!Result.Success)
{
return Result;
}
// create file
File.WriteAllBytes(path: FilePath, bytes: Result.Data);
// result
Result.Success = true;
}
catch(Exception ex)
{
Result.Success = false;
Result.Message = ex.Message;
}
// return
return Result;
}
</code></pre>
It's important to remember that in order for this to work, you must have valid well-formed HTML; otherwise you can certainly expect for iTextSharp to throw an error. But if you have control over the HTML that you need to convert, this solution is great, and produces very nice PDF files.<br />
<br />
It's worth noting that in our case we didn't need to pass the CSS in separately using the overloaded ParseXHtml constructor, <i>ParseXHtml(PdfWriter writer, Document doc, Stream inp, Stream inCssFile)</i>, because we were including our CSS styles in our HTML data string instead, which for our solution was a bit cleaner.<br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>
Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-16135547601990566482016-12-11T19:13:00.000-05:002016-12-11T19:13:22.652-05:00ASP.Net C# Checking Request Header and/or Request Cookies in Global.asax Application_BeginRequestHere is a simple example using C# to get a custom token from either the HTTP header or HTTP cookie on any page in your application. This example relies on a few <a href="https://pavey.azurewebsites.net/resources/Extensions.txt" target="_blank">custom extension methods</a> that I regularly use, although they could be refactored out very easily.<br />
<br />
In this particular example we're checking the <strong>Request.Headers</strong> collection first for a custom key named <strong>Token</strong>. If found we use it, if not found we check the <strong>Request.Cookies</strong> collection, and if found we use it.
<br /><br />
<b>Global.asax.cs</b>
<pre class="prettyprint lang-cs">
protected void Application_BeginRequest(object sender, EventArgs e)
{
// variables
string Token = string.Empty;
// check for token in header
if (Token.IsBlank() && Request.Headers.AllKeys.Any(k => k.IsEqual("Token")))
{
Token = Server.UrlDecode(Request.Headers.GetValues("Token").First());
}
// check for token in cookie
if (Token.IsBlank() && Request.Cookies.AllKeys.Any(k => k.IsEqual("Token")))
{
Token = Server.UrlDecode(Request.Cookies.Get("Token").Value);
}
// if token specified try to parse it
if (Token.HasValue())
{
// todo
}
// debug
Response.Write("Application_BeginRequest<br />");
Response.Write(string.Format("Token: {0}<br />", Token));
}
</pre>
<br />
The idea behind this example was to be able to let an encrypted authentication token be passed in to automatically authenticate the user for any page within the application. Having it check both the <strong>Request.Headers</strong> and the <strong>Request.Cookies</strong> collection provides some additional flexibility for the processes that pass in the token. For example, some older versions of Android code don't easily allow a custom HTTP header to be specified, but could easily set a cookie.
<br /><br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-11703128970100099802016-04-13T22:25:00.000-04:002016-04-13T22:29:04.215-04:00ASP.Net C# Retrieving a list of videos from a YouTube playlistRecently when working on my <a href="http://www.pavey.me/p/favorites.html">favorites</a> page, as I was typing up my favorite songs and linking them to YouTube, I thought it would be pretty nice to just tap in to my "Favorites" list directly in YouTube. A quick Google search lead me to this Stack Overflow article:<br/>
<br />
<a href="http://stackoverflow.com/questions/34143202/get-all-videos-from-channel-youtube-api-v3-c-sharp" target="_blank">http://stackoverflow.com/questions/34143202/get-all-videos-from-channel-youtube-api-v3-c-sharp</a>
<br />
<br />
So building off of a couple of the answers in this article, I've put together a functional <a href="http://pavey.azurewebsites.net/youtube/" target="_blank">example</a> using C# to show how to connect to the YouTube Data API.<br />
<br />
For this example we'll be using the following NuGet package:<br />
<br />
<a href="https://www.nuget.org/packages/Google.Apis.youtube.v3/" target="_blank">Google.Apis.YouTube.v3 Client Library</a><br />
<br />
If you need to generate your own YouTube Data API key, you can visit the <a href="https://console.developers.google.com" target="_blank">Google Developer Console</a> and use the API Manager.<br />
<br />
<b>ASPX Page</b>
<pre class="prettyprint lang-html">
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Web.youtube._default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<!-- title -->
<title>YouTube Demo</title>
<!-- bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<!-- styles -->
<style>
body { margin: 25px !important; }
.video { margin-top: 25px; }
.video .title { font-weight: normal; white-space: nowrap; overflow: hidden; }
.video .thumbnail img { width: 192px; height: 144px; }
</style>
</head>
<body>
<form id="form1" runat="server">
<div class="container">
<div class="col-lg-12">
<h1>YouTube Demo</h1>
</div>
<!-- placeholder where we will render the results -->
<asp:PlaceHolder ID="pnlVideos" runat="server" />
<div class="col-lg-12" style="padding-top: 10px">
<asp:Label ID="lblMessage" Font-Bold="true" ForeColor="Red" ClientIDMode="Static" runat="server" />
</div>
</div>
<!-- template to help build each video container -->
<asp:Literal ID="lblTemplate" Visible="false" runat="server">
<div class="video col-lg-4">
<div class="title">
<a href="{Url}" target="_blank">
{Title}
</a>
</div>
<div class="thumbnail">
<a title="{ToolTip}" href="{Url}" target="_blank">
<img title="{ToolTip}" src="{Image}" alt="" />
</a>
</div>
</div>
</asp:Literal>
</form>
</body>
</html>
</pre>
<br />
<b>Code-Behind</b>
<pre class="prettyprint lang-cs">
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Web.youtube
{
public partial class _default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
try
{
// initialize call to the youtube service
var YouTubeService = new YouTubeService(new BaseClientService.Initializer() { ApiKey = "<b>YourApiKey</b>" });
// indicate the parts we need for the request
var ChannelListRequest = YouTubeService.Channels.List("contentDetails");
// indicate the username for the channel you are requesting
ChannelListRequest.ForUsername = "<b>UserNameOfChannelYouAreRequesting</b>";
// execute the request and get the response
var ListResponse = ChannelListRequest.Execute();
// iterate through the results
foreach (var channel in ListResponse.Items)
{
// indicate the list you are requesting
// if your favorites list is unlisted you will need to explicitly set this value
// var ListId = channel.ContentDetails.RelatedPlaylists.Favorites;
var ListId = "<b>YouTubeListId</b>";
// the page token will be used if the results span multiple pages
var PageToken = "";
// iterate until there is no more data
while (PageToken != null)
{
// indicate the parts we need for the request
var PlaylistRequest = YouTubeService.PlaylistItems.List("snippet");
// playlist request properties
PlaylistRequest.PlaylistId = ListId;
PlaylistRequest.MaxResults = 50;
PlaylistRequest.PageToken = PageToken;
// execute the request and get the response
var PlaylistResponse = PlaylistRequest.Execute();
// iterate through the results
foreach (var Video in PlaylistResponse.Items)
{
// variables
string Template = lblTemplate.Text;
string VideoId = Video.Snippet.ResourceId.VideoId;
string Title = Video.Snippet.Title;
string Url = string.Format("https://www.youtube.com/watch?v={0}", VideoId);
string Image = Video.Snippet.Thumbnails.High.Url;
// replace placeholders
Template = Template.Replace("{VideoId}", VideoId);
Template = Template.Replace("{Title}", Title);
Template = Template.Replace("{Url}", Url);
Template = Template.Replace("{Image}", Image);
Template = Template.Replace("{ToolTip}", HttpUtility.HtmlEncode(Title));
// add to videos panel
pnlVideos.Controls.Add(new LiteralControl(Template));
}
// get the next page token
PageToken = PlaylistResponse.NextPageToken;
}
}
}
catch (Exception ex)
{
lblMessage.Text = ex.Message;
}
}
}
}
</pre>
<br />
<a href="http://pavey.azurewebsites.net/youtube/" target="_blank">Click here to see a fully functional demo.</a><br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com5tag:blogger.com,1999:blog-1301739890503359743.post-50624960144665605162016-04-11T12:42:00.000-04:002016-04-11T12:51:03.535-04:00ASP.Net C# DownloadString vs DownloadData Encoding ExamplesI recently came across an issue using the WebClient.DownloadString method in System.Net that was causing UTF8 encoded data to not come through properly, and instead was showing odd characters.<br/>
<br />
Researching the issue led me to the following Stack Overflow articles:<br />
<ul>
<li><a href="http://stackoverflow.com/questions/7137165/webclient-downloadstring-results-in-mangled-characters-due-to-encoding-issues-b" target="_blank">http://stackoverflow.com/questions/7137165/webclient-downloadstring-results-in-mangled-characters-due-to-encoding-issues-b</a></li>
<li><a href="http://stackoverflow.com/questions/4716470/webclient-downloadstring-returns-string-with-peculiar-characters" target="_blank">http://stackoverflow.com/questions/4716470/webclient-downloadstring-returns-string-with-peculiar-characters</a></li>
</ul>
<br />
The accepted answers on both work well, so I've put together a functional <a href="https://dotnetfiddle.net/eIdm1D" target="_blank">example</a> using C# to illustrate the behavior and results.<br />
<br />
<b>DownloadString (no encoding specified)</b>
<pre class="prettyprint lang-cs">// variables
string Url = "https://mysafeinfo.com/api/data?list=states&format=json&alias=nm=name,ab=code,cp=capital,yr=year&select=capital&ab=GU";
// DownloadString (no encoding specified)
using (WebClient client = new WebClient())
{
Console.WriteLine(client.DownloadString(Url));
}
// result
[
{
"capital": "Hagåtña Dededo"
}
]
</pre>
<br />
<b>DownloadString (encoding specified)</b>
<pre class="prettyprint lang-cs">// variables
string Url = "https://mysafeinfo.com/api/data?list=states&format=json&alias=nm=name,ab=code,cp=capital,yr=year&select=capital&ab=GU";
// DownloadString (encoding specified)
using (WebClient client = new WebClient())
{
// specify encoding
client.Encoding = System.Text.UTF8Encoding.UTF8;
// output
Console.WriteLine(client.DownloadString(Url));
}
// result
[
{
"capital": "Hagåtña Dededo"
}
]
</pre>
<br />
<b>DownloadData (encoding specified)</b>
<pre class="prettyprint lang-cs">// variables
string Url = "https://mysafeinfo.com/api/data?list=states&format=json&alias=nm=name,ab=code,cp=capital,yr=year&select=capital&ab=GU";
// DownloadData (encoding specified)
using (WebClient client = new WebClient())
{
Console.WriteLine(System.Text.UTF8Encoding.UTF8.GetString(client.DownloadData(Url)));
}
// result
[
{
"capital": "Hagåtña Dededo"
}
]
</pre>
<br />
<a href="https://dotnetfiddle.net/eIdm1D" target="_blank">Click here to see a fully functional demo.</a><br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-47550353120634052862016-04-10T13:01:00.001-04:002016-04-10T13:04:03.751-04:00ASP.Net C# Exporting and Importing Excel DataHere is a simple <a href="https://pavey.azurewebsites.net/excel" target="_blank">example</a> using C# to export data to an Excel file and to read data from an Excel file. This example demonstrates the following:<br />
<br />
- <a href="https://mysafeinfo.com/content/documentation" target="_blank">MySafeInfo Data API</a><br />
- <a href="https://www.nuget.org/packages/Newtonsoft.Json" target="_blank">Json.NET</a> (Newtonsoft.Json)<br />
- <a href="https://www.nuget.org/packages/EPPlus" target="_blank">EPPlus</a><br />
- Response.BinaryWrite<br />
- WebClient.DownloadData<br />
- StreamReader<br />
- Serialization<br />
- <a href="https://pavey.azurewebsites.net/resources/Extensions.txt" target="_blank">Custom Extension Methods</a><br />
<br />
<b>Code-Behind</b>
<pre class="prettyprint lang-cs">using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Utilities;
namespace Web.excel
{
public partial class _default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// empty
}
public void DownloadFile(byte[] Data, string FileType = "application/csv", string FileName = "data.csv")
{
// headers
Response.BufferOutput = true;
Response.ClearHeaders();
// explicitly append preamble for csv files so excel recognizes utf-8 encoding
if (FileType.ToLower().Contains("csv"))
{
Response.BinaryWrite(System.Text.Encoding.UTF8.GetPreamble());
}
// encoding
Response.ContentEncoding = System.Text.Encoding.UTF8;
// content type
Response.ContentType = FileType;
// content disposition
Response.AddHeader("Content-Disposition", string.Format("attachment; filename=\"{0}\"", FileName));
// data
Response.BinaryWrite(Data);
// end
Response.End();
}
protected void btnDownload_Click(object sender, EventArgs e)
{
// variables
string Result = string.Empty;
string Url = "https://mysafeinfo.com/api/data?list=states&format=json&alias=nm=name,ab=code,cp=capital,yr=year";
List<State> States = new List<State>();
DataTable DT = new DataTable();
byte[] Data = null;
// download json data
using (WebClient client = new WebClient())
{
Result = System.Text.UTF8Encoding.UTF8.GetString(client.DownloadData(Url));
}
// deserialize json data into a List<State>
// Json.NET (Newtonsoft.Json)
// https://www.nuget.org/packages/Newtonsoft.Json
States = Newtonsoft.Json.JsonConvert.DeserializeObject<List<State>>(Result);
// convert List<State> to a DataTable using ToDataTable extension method
DT = States.ToDataTable();
// convert DataTable to byte[] using ToExcel extension method
// EPPlus
// https://www.nuget.org/packages/EPPlus
Data = DT.ToExcel(WorksheetName: "States");
// download excel file
DownloadFile(Data: Data, FileType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", FileName: "states.xlsx");
}
protected void btnUpload_Click(object sender, EventArgs e)
{
// variables
DataTable DT = new DataTable();
List<State> States = new List<State>();
// read excel file in to a StreamReader
using (StreamReader sr = new StreamReader(Server.MapPath("states.xlsx")))
{
// read the underlying stream and convert to a DataTable using ToDataTable extension method
DT = sr.BaseStream.ToDataTable(HasHeader: true);
}
// convert DataTable to a List<State> using ToList extension method
States = DT.ToList<State>().ToList();
// output serialized List<State> so we can see that the excel file was read successfully
lblOutput.Text = States.Serialize();
// show panel
pnlOutput.Visible = true;
}
// state class
public class State
{
public State()
{
Name = string.Empty;
Code = string.Empty;
Capital = string.Empty;
Year = string.Empty;
}
public string Name { get; set; }
public string Code { get; set; }
public string Capital { get; set; }
public string Year { get; set; }
}
}
}
</pre>
<br />
<b>ASPX Page</b>
<br />
<pre class="prettyprint lang-html"><%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Web.excel._default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<!-- title -->
<title>Excel Demo</title>
<!-- jquery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<!-- bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<!-- custom style -->
<style>
body {
margin: 25px !important;
}
.form-group {
width: 800px;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<div>
<p>
<asp:Button ID="btnDownload" Text="Download Excel File" CssClass="btn btn-default" Width="200px" OnClick="btnDownload_Click" ClientIDMode="Static" runat="server" />
</p>
<p>
<asp:Button ID="btnUpload" Text="Read Excel File" CssClass="btn btn-default" Width="200px" OnClick="btnUpload_Click" ClientIDMode="Static" runat="server" />
</p>
<asp:PlaceHolder ID="pnlOutput" Visible="false" runat="server">
<p>
<pre><asp:Literal ID="lblOutput" runat="server" /></pre>
</p>
</asp:PlaceHolder>
</div>
</form>
</body>
</html>
</pre>
<br />
<b>Extensions</b>
<br />
<pre class="prettyprint lang-cs">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
using System.Xml.Serialization;
using System.IO;
using System.Collections.Specialized;
using System.Web;
using System.ComponentModel;
using System.Xml;
using System.Xml.Linq;
using System.Web.UI;
using System.Text.RegularExpressions;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Linq.Expressions;
using System.Net;
using OfficeOpenXml;
namespace 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 string Serialize<T>(this T obj)
{
// Json.NET (Newtonsoft.Json)
// https://www.nuget.org/packages/Newtonsoft.Json
return Newtonsoft.Json.JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.Indented);
}
public static T Deserialize<T>(string data) where T : new()
{
// Json.NET (Newtonsoft.Json)
// https://www.nuget.org/packages/Newtonsoft.Json
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(data);
}
public static DataTable ToDataTable<T>(this IList<T> data)
{
// variables
PropertyDescriptorCollection Properties = TypeDescriptor.GetProperties(typeof(T));
object[] values = new object[Properties.Count];
DataTable DT = new DataTable();
// columns
foreach (PropertyDescriptor PropertyInfo in Properties)
{
// data column
DataColumn DataColumn = new DataColumn();
// name
DataColumn.ColumnName = PropertyInfo.Name;
// data type
if (PropertyInfo.PropertyType.Name.Contains("Nullable"))
{
DataColumn.DataType = typeof(String);
}
else
{
DataColumn.DataType = PropertyInfo.PropertyType;
}
// add to table
DT.Columns.Add(DataColumn);
}
// rows
foreach (T item in data)
{
for (int i = 0; i < values.Length; i++)
{
values[i] = Properties[i].GetValue(item);
}
DT.Rows.Add(values);
}
// return
return DT;
}
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;
}
public static byte[] ToExcel(this DataTable DT, string WorksheetName = "Data", bool AutoFormatDates = false)
{
// EPPlus
// https://www.nuget.org/packages/EPPlus
// variables
byte[] Data = null;
ExcelPackage ExcelPackage = new ExcelPackage();
ExcelWorksheet ExcelWorksheet = ExcelPackage.Workbook.Worksheets.Add(WorksheetName.HasValue() ? WorksheetName : "Data");
// load data table with column headings
ExcelWorksheet.Cells["A1"].LoadFromDataTable(DT, true);
// auto-format dates
if (AutoFormatDates)
{
var DateColumns = from DataColumn d in DT.Columns where d.DataType == typeof(DateTime) || d.ColumnName.Contains("Date") select d.Ordinal + 1;
foreach (var Column in DateColumns)
{
ExcelWorksheet.Cells[2, Column, DT.Rows.Count + 1, Column].Style.Numberformat.Format = "mm/dd/yyyy";
}
}
// get data as byte array
Data = ExcelPackage.GetAsByteArray();
// cleanup
ExcelPackage.Dispose();
// return byte array
return Data;
}
public static DataTable ToDataTable(this Stream Data, bool HasHeader = true)
{
// EPPlus
// https://www.nuget.org/packages/EPPlus
// variables
DataTable DT = new DataTable();
var RowStart = HasHeader ? 2 : 1;
using (ExcelPackage ExcelPackage = new ExcelPackage())
{
// load data from stream
ExcelPackage.Load(Data);
// worksheet
ExcelWorksheet ExcelWorksheet = ExcelPackage.Workbook.Worksheets.First();
// create column headings
foreach (var Cell in ExcelWorksheet.Cells[1, 1, 1, ExcelWorksheet.Dimension.End.Column])
{
DT.Columns.Add(HasHeader ? Cell.Text : string.Format("Column {0}", Cell.Start.Column));
}
// copy data from each row to the data table
for (int RowNumber = RowStart; RowNumber <= ExcelWorksheet.Dimension.End.Row; RowNumber++)
{
// variables
ExcelRange Row = ExcelWorksheet.Cells[RowNumber, 1, RowNumber, ExcelWorksheet.Dimension.End.Column];
DataRow DataRow = DT.NewRow();
// copy the data from each cell to the data row
foreach (ExcelRangeBase cell in Row)
{
DataRow[cell.Start.Column - 1] = cell.Text;
}
// add row to data table
DT.Rows.Add(DataRow);
}
}
// return data table
return DT;
}
}
}
</pre>
<br />
<a href="https://pavey.azurewebsites.net/excel" target="_blank">Click here to see a fully functional demo.</a><br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-63079656337556246882016-03-31T20:44:00.000-04:002016-04-02T08:28:26.812-04:00ASP.Net C# Extracting parts of URL including domain and sub-domainHere is a simple <a href="https://pavey.azurewebsites.net/debug/?x=1&y=2" target="_blank">example</a> using C# to extract the various parts of a URL.<br />
<br />
<b>Code-Behind</b>
<pre class="prettyprint lang-cs"> protected void Page_Load(object sender, EventArgs e)
{
// debug
Response.Write(string.Format("{0}: {1}<br />", "Protocol", Request.Protocol()));
Response.Write(string.Format("{0}: {1}<br />", "Host", Request.Host()));
Response.Write(string.Format("{0}: {1}<br />", "SubDomains", string.Join(".", Request.SubDomains())));
Response.Write(string.Format("{0}: {1}<br />", "SubDomain", Request.SubDomain()));
Response.Write(string.Format("{0}: {1}<br />", "Domain", Request.Domain()));
Response.Write(string.Format("{0}: {1}<br />", "BaseUrl", Request.BaseUrl()));
Response.Write(string.Format("{0}: {1}<br />", "RawUrl", Request.RawUrl));
Response.Write(string.Format("{0}: {1}<br />", "AbsoluteUri", Request.Url.AbsoluteUri));
Response.Write(string.Format("{0}: {1}<br />", "AbsolutePath", Request.Url.AbsolutePath));
Response.Write(string.Format("{0}: {1}<br />", "FileName", Path.GetFileName(Request.Url.AbsolutePath)));
Response.Write(string.Format("{0}: {1}<br />", "VirtualPath", VirtualPathUtility.ToAbsolute("~/")));
Response.Write(string.Format("{0}: {1}<br />", "UrlHttps", Request.UrlHttps()));
Response.Write(string.Format("{0}: {1}<br />", "UrlHttp", Request.UrlHttp()));
Response.Write(string.Format("{0}: {1}<br />", "PageName v1", Request.PageName()));
Response.Write(string.Format("{0}: {1}<br />", "PageName v2", Request.PageName(IncludeExtension: true)));
}
</pre>
<br />
<b>Extensions</b>
<br />
<pre class="prettyprint lang-cs"> // e.g. https
public static string Protocol(this HttpRequest Request)
{
return Request.Url.Scheme;
}
// e.g. pavey.azurewebsites.net
public static string Host(this HttpRequest Request)
{
return Request.Url.Host.ToLower();
}
// e.g. https://pavey.azurewebsites.net/debug/?x=1&y=2
public static string UrlHttps(this HttpRequest Request)
{
return string.Format("https://{0}{1}", Request.Host(), Request.RawUrl);
}
// e.g. http://pavey.azurewebsites.net/debug/?x=1&y=2
public static string UrlHttp(this HttpRequest Request)
{
return string.Format("http://{0}{1}", Request.Host(), Request.RawUrl);
}
// e.g. true if value is formatted as a valid IPv4 address
public static bool IsValidIPAddress(this string Value)
{
return Regex.Match(Value, "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b").Success;
}
// e.g. true if testing using an IP address instead of a fully qualified host name
public static bool IsIPAddress(this HttpRequest Request)
{
return Request.Host().IsValidIPAddress();
}
// e.g. true if testing using localhost
public static bool IsLocalHost(this HttpRequest Request)
{
return Request.Host().ToLower().Equals("localhost");
}
// e.g. dev.www
public static List<string> SubDomains(this HttpRequest Request)
{
// variables
string[] MyArray = Request.Host().Split(".".ToCharArray());
List<string> MySubDomains = new List<string>();
// make sure this is not an ip address
if (Request.IsIPAddress())
{
return MySubDomains;
}
// make sure we have all the parts necessary
if (MyArray == null)
{
return MySubDomains;
}
// last part is the tld (e.g. .com)
// second to last part is the domain (e.g. mydomain)
// the remaining parts are the sub-domain(s)
if (MyArray.Length > 2)
{
for (int i = 0; i <= MyArray.Length - 3; i++)
{
MySubDomains.Add(MyArray[i]);
}
}
// return
return MySubDomains;
}
// e.g. www
public static string SubDomain(this HttpRequest Request)
{
if (Request.SubDomains().Count > 0)
{
// handle cases where multiple sub-domains (e.g. dev.www)
return Request.SubDomains().Last();
}
else
{
// handle cases where no sub-domains
return string.Empty;
}
}
// e.g. azurewebsites.net
public static string Domain(this HttpRequest Request)
{
// variables
string[] MyArray = Request.Host().Split(".".ToCharArray());
// make sure this is not an ip address
if (Request.IsIPAddress())
{
return string.Empty;
}
// special case for localhost
if (Request.IsLocalHost())
{
return Request.Host().ToLower();
}
// make sure we have all the parts necessary
if (MyArray == null)
{
return string.Empty;
}
// make sure we have all the parts necessary
if (MyArray.Length > 1)
{
return string.Format("{0}.{1}", MyArray[MyArray.Length - 2], MyArray[MyArray.Length - 1]);
}
// return empty string
return string.Empty;
}
// e.g. https://pavey.azurewebsites.net/
public static string BaseUrl(this HttpRequest Request)
{
// variables
string Authority = Request.Url.GetLeftPart(UriPartial.Authority).TrimStart('/').TrimEnd('/');
string ApplicationPath = Request.ApplicationPath.TrimStart('/').TrimEnd('/');
// add trailing slashes if necessary
if (Authority.Length > 0)
{
Authority += "/";
}
if (ApplicationPath.Length > 0)
{
ApplicationPath += "/";
}
// return
return string.Format("{0}{1}", Authority, ApplicationPath);
}
// e.g. default, default.aspx
public static string PageName(this HttpRequest Request, bool IncludeExtension = false, bool IncludeQueryString = false)
{
// variables
string AbsolutePath = Request.Url.AbsolutePath;
string PageName = Path.GetFileName(AbsolutePath);
string Extension = Path.GetExtension(AbsolutePath);
string QueryString = Request.QueryString.ToString();
// remove extension
if (!IncludeExtension && !IncludeQueryString && PageName.HasValue())
{
PageName = PageName.Replace(Extension, string.Empty);
}
// include querystring
if (IncludeQueryString && PageName.HasValue() && QueryString.HasValue())
{
PageName = string.Format("{0}?{1}", PageName, QueryString);
}
// return
return PageName;
}
</pre>
<br />
<a href="https://pavey.azurewebsites.net/debug/?x=1&y=2" target="_blank">Click here to see a demo with sample output.</a><br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com2tag:blogger.com,1999:blog-1301739890503359743.post-89682187717762879632015-06-30T09:29:00.001-04:002015-06-30T09:29:24.248-04:00Bucket List Demo (JavaScript and .Net examples using RESTful API and JSON)Here are some examples I put together that I really enjoy, because of their simplicity.<br />
<br />
The HTML example demonstrates the following:<br />
<br />
- MySafeInfo's <a href="https://mysafeinfo.com/examples/bucketlist" target="_blank">WICK API</a> (RESTful)<br />
- JavaScript<br />
- jQuery (2.1.4)<br />
- Bootstrap (3.3.5)<br />
- Font Awesome (4.3.0)<br />
- AJAX (asynchronous and synchronous)<br />
<br />
<pre class="prettyprint lang-html"> <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Bucket List</title>
<style type="text/css">
body {
margin: 0px 25px !important;
}
td.icons {
font-size: 18px !important;
}
td.icons i {
cursor: pointer;
padding: 0 15px;
}
.icon-cancel.disabled {
color: #cccccc;
cursor: not-allowed;
}
</style>
<!-- jquery -->
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<!-- bootstrap -->
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<!-- font awesome -->
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" />
<!-- custom script -->
<script type="text/javascript">
// variables
var readerKey = "28SFkEKyZucdjEvaqYGLD3SY2d9dsnqb";
var insertKey = "3InDNvlJnYzSGAHRjT2tUEB4MRygJtuV";
var updateKey = "iP3wwSUtuSLFoi4HNzmZIOazxckoldaN";
var deleteKey = "LqNQuiJ5Eg8FSzWBJZj2FLO0n0PDrM7L";
var table = "BucketList"
// format
String.prototype.format = function () {
var args = arguments;
return this.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] != 'undefined'
? args[number]
: match
;
});
};
$(function () {
// initialize
BindData();
// bind data
function BindData() {
// variables
var grid = $("#grid");
// ajax variables
var type = "GET";
var url = "https://mysafeinfo.com/wick/{0}/tables/{1}".format(readerKey, table);
var contentType = "application/json; charset=utf-8";
var dataType = "json";
var data = "";
var async = true;
// get the data from database table
$.ajax({
type: type,
url: url,
contentType: contentType,
dataType: dataType,
data: data,
async: async
}).done(function (result) {
// variables
var result = $(result);
var rows = "";
var columns = "";
// heading
$("h3.table-results").text("{0} record{1}".format(result.length, (result.length > 1 | result.length == 0) ? "s" : ""));
// get the template (schema) for this table
var template = GetTableTemplate();
// append a blank record so they can do an inline add in the grid
result.splice(0, 0, template);
// for each row
result.each(function (i, item) {
// variables
var key = "ID";
var id = item[key];
// clear columns
columns = "";
// begin new row
rows = "{0}<tr data-id='{1}'>".format(rows, id);
// get column list
for (var column in item) {
// variables
var value = item[column];
// get column headers
columns = "{0}<th>{1}</th>".format(columns, column);
// check to see if this is an existing record or the blank record for the inline add
// if an existing record show the data as-is
// if the blank record show NEW for the ID column and empty textboxes for the other fields
if (id > 0) {
rows = "{0}<td data-column='{2}' data-value='{1}'>{1}</td>".format(rows, value, column);
}
else {
if (column == key) {
rows = "{0}<td data-column='{2}' data-value='0'><i class='fa fa-plus-circle icon-new' alt='New' title='New'></i></td>".format(rows, value, column);
}
else {
rows = "{0}<td data-column='{2}' data-value='{1}'><input type='text' value='' maxlength='100' data-column='{2}'/></td>".format(rows, value, column);
}
}
}
// check to see if this is an existing record or the blank record for the inline add
// if an existing record show the edit and delete icons
// if the blank record show the save and cancel icons
if (id > 0) {
rows = "{0}<td class='icons text-right'><i class='fa fa-pencil icon-edit' alt='Edit' title='Edit'></i><i class='fa fa-trash-o icon-delete' alt='Delete' title='Delete'></i><i class='fa fa-save icon-save hidden' title='Save' alt='Save'></i><i class='fa fa-times icon-cancel hidden' title='Cancel' alt='Cancel'></i></td></tr>".format(rows);
}
else {
rows = "{0}<td class='icons text-right'><i class='fa fa-plus icon-save' title='Save' alt='Save'></i><i class='fa fa-times icon-cancel disabled' title='Cancel' alt='Cancel'></i></td></tr>".format(rows);
}
});
// columns
columns = "{0}<th></th>".format(columns);
// table
var table = "<table id='table-data' class='table table-striped table-responsive table-hover results'><thead><tr>{0}</tr></thead><tbody>{1}</tbody></table>".format(columns, rows);
// add data to grid
grid.html(table);
}).always(function () {
});
}
// edit
$("body").delegate("table.results tbody tr td i.icon-edit", "click", function () {
// variables
var td = $(this).parent("td");
var tr = $(td).parent("tr");
// toggle icons
$(td).children("i.icon-edit").addClass("hidden");
$(td).children("i.icon-delete").addClass("hidden");
$(td).children("i.icon-save").removeClass("hidden");
$(td).children("i.icon-cancel").removeClass("hidden");
// add input fields to each cell
for (var i = 1; i < tr.children("td").length - 1; i++) {
// variables
var cell = tr.children("td")[i];
// add input to cell
$(cell).html("<input type='text' value='{1}' maxlength='100' data-column='{0}'/>".format($(cell).data("column"), $(cell).text().trim()));
// set focus to the first input
if (i == 1) { $(cell).children("input").focus(); }
};
// keydown event to capture enter key
$(window).keydown(function (event) {
if (event.which == 13) {
event.preventDefault();
td.children("i.icon-save").trigger("click");
}
});
});
// delete
$("body").delegate("table.results tbody tr td i.icon-delete", "click", function () {
if (confirm("Are you sure you want to delete this record?")) {
// variables
var tr = $(this).parent("td").parent("tr");
var id = $(tr).children("td").first().text();
// ajax variables
var type = "DELETE";
var url = "https://mysafeinfo.com/wick/{0}/tables/{1}/{2}".format(deleteKey, table, id);
var contentType = "application/json; charset=utf-8";
var dataType = "json";
var data = "";
var async = false;
// delete
$.ajax({
type: type,
url: url,
contentType: contentType,
dataType: dataType,
data: data,
async: async
}).done(function (result) {
// bind data
BindData();
// show message
alert(result.Message);
}).always(function () {
});
}
});
// save
$("body").delegate("table.results tbody tr td i.icon-save", "click", function () {
// variables
var tr = $(this).parent("td").parent("tr")
var id = tr.data("id");
var request = "";
var isValid = false;
// create request
for (var i = 1; i < tr.children("td:not(.icons)").length; i++) {
// controls
var input = $(tr.children("td")[i]).children("input");
// variables
var column = $(input).data("column");
var value = $(input).val().trim();
// append to request
request = '{0} "{1}":"{2}",'.format(request, column, value)
// is valid flag if at least one field has data
if (value.length > 0) {
isValid = true;
}
}
// make sure at least one field has data, otherwise nothing to add/update
if (!isValid) {
alert("Please enter a value for at least one field.");
tr.children("td").find("input").first().focus();
tr.children("td").find("input").first().select();
return;
}
// format json request
request = "{{0}}".format(request.substring(1, request.length - 1));
// check to see if we need to do an insert or an update
if (id > 0) {
// ajax variables
var type = "PUT";
var url = "https://mysafeinfo.com/wick/{0}/tables/{1}/{2}".format(updateKey, table, id);
var contentType = "application/json; charset=utf-8";
var dataType = "json";
var data = request;
var async = false;
// update
$.ajax({
type: type,
url: url,
contentType: contentType,
dataType: dataType,
data: data,
async: async
}).done(function (result) {
// bind
BindData();
// message
alert(result.Message);
}).always(function () {
// remove key event listener
RemoveListener();
});
}
else {
// ajax variables
var type = "POST";
var url = "https://mysafeinfo.com/wick/{0}/tables/{1}".format(insertKey, table);
var contentType = "application/json; charset=utf-8";
var dataType = "json";
var data = request;
var async = false;
// insert
$.ajax({
type: type,
url: url,
contentType: contentType,
dataType: dataType,
data: data,
async: async
}).done(function (result) {
// bind
BindData();
// message
alert(result.Message);
}).always(function () {
// remove key event listener
RemoveListener();
});
}
});
// cancel
$("body").delegate("table.results tbody tr td i.icon-cancel", "click", function () {
// variables
var tr = $(this).parent("td").parent("tr")
var id = tr.data("id");
// short-circuit if this is a new record
if (id < 1) {
return;
}
// set defaults
for (var i = 1; i < tr.children("td:not(.icons)").length; i++) {
var td = $(tr.children("td")[i]);
td.text(td.data("value"));
}
// toggle icons
$(this).parent("td").children("i.icon-edit").removeClass("hidden");
$(this).parent("td").children("i.icon-delete").removeClass("hidden");
$(this).parent("td").children("i.icon-save").addClass("hidden");
$(this).parent("td").children("i.icon-cancel").addClass("hidden");
// remove key event listener
RemoveListener();
});
function GetTableTemplate() {
// variables
var template;
// ajax variables
var type = "GET";
var url = "https://mysafeinfo.com/wick/{0}/tables/{1}/template".format(readerKey, table);
var contentType = "application/json; charset=utf-8";
var dataType = "json";
var data = "";
var async = false;
// get table template
$.ajax({
type: type,
url: url,
contentType: contentType,
dataType: dataType,
data: data,
async: async
}).done(function (result) {
template = result;
}).always(function () {
});
// return
return template;
}
function RemoveListener() {
$(window).unbind("keydown");
}
});
</script>
</head>
<body>
<!-- container for header to indicate how many records were retrieved -->
<h3 class="table-results">0 records</h3>
<!-- container for html table built dynamically from our ajax call -->
<div class="table-responsive">
<div id="grid"></div>
</div>
</body>
</html>
</pre>
<br />
I've also included examples using .Net 4.5 (C# and VB.Net) for those looking to call a RESTful API from the server-side, which can be helpful when working with key-based APIs, so you can hide your API keys. The .Net 4.5 examples demonstrate the following:<br />
<br />
- MySafeInfo's <a href="https://mysafeinfo.com/examples/bucketlist" target="_blank">WICK API</a> (RESTful)<br />
- Json.NET (Newtonsoft.Json)<br />
- WebClient.UploadString<br />
<br />
<pre class="prettyprint lang-cs"> using System;
using System.Net;
using Newtonsoft.Json;
public class Program
{
public static void Main()
{
// variables
string Table = "BucketList";
string InsertKey = "3InDNvlJnYzSGAHRjT2tUEB4MRygJtuV";
string DeleteKey = "LqNQuiJ5Eg8FSzWBJZj2FLO0n0PDrM7L";
string InsertUrl = string.Format("https://mysafeinfo.com/wick/{0}/tables/{1}", InsertKey, Table);
PostResult PostResult = new PostResult();
WickResult WickResult = new WickResult();
BucketList BucketList = new BucketList { Description = "Run a mini-marathon", Notes = "2016 OneAmerica 500 Festival Mini-Marathon", Date = "May 2016" };
// use Json.NET (Newtonsoft.Json) to serialize the data object
// https://www.nuget.org/packages/Newtonsoft.Json
string Data = JsonConvert.SerializeObject(BucketList);
// post the data to the MySafeInfo WICK API
PostResult = SubmitRequest(Url: InsertUrl, Data: Data);
// use Json.NET (Newtonsoft.Json) to deserialize json result into a strongly typed data object
// https://www.nuget.org/packages/Newtonsoft.Json
if (PostResult.Success)
{
WickResult = JsonConvert.DeserializeObject<WickResult>(PostResult.Message);
}
// debug
Console.WriteLine(string.Format("WICK Input: {0}", Data));
Console.WriteLine(string.Format("WICK Output: {0}", PostResult.Message));
Console.WriteLine(string.Format("ID: {0}", WickResult.ID));
Console.WriteLine(string.Format("Success: {0}", WickResult.Success));
Console.WriteLine(string.Format("Message: {0}", WickResult.Message));
// just for fun we are going to delete the record to show how simple a delete is
if (WickResult.Success)
{
Console.WriteLine(string.Format("Delete: {0}", SubmitRequest(Url: string.Format("https://mysafeinfo.com/wick/{0}/tables/{1}/{2}", DeleteKey, Table, WickResult.ID), Method: "DELETE").Message));
}
}
public static PostResult SubmitRequest(string Url, string Data = "", string Method = "POST", string ContentType = "application/json; charset=utf-8")
{
// variables
PostResult Result = new PostResult();
try
{
// web client
using (WebClient Client = new WebClient())
{
// content type
Client.Headers[HttpRequestHeader.ContentType] = ContentType;
// post data and get result
Result.Message = Client.UploadString(address: Url, method: Method, data: Data);
Result.Success = true;
}
}
catch (Exception ex)
{
Result.Message = ex.Message;
}
// return
return Result;
}
// helper classes
public class BucketList
{
public string Description { get; set; }
public string Notes { get; set; }
public string Date { get; set; }
}
public class PostResult
{
public bool Success { get; set; }
public string Message { get; set; }
}
public class WickResult
{
public int ID { get; set; }
public bool Success { get; set; }
public string Message { get; set; }
}
}
</pre>
<br />
As you can see, the server side code is relatively simple since we can leverage Json.NET to serialize and deserialize our data, and we can use built in .Net namespaces (WebClient) to perform our RESTful operations against the API.<br />
<br />
Here are links to each example:<br />
<br />
- <a href="https://mysafeinfo.com/samples/bucketlist.html" target="_blank">Bucket List Demo (HTML, JavaScript, jQuery)</a><br />
- <a href="https://dotnetfiddle.net/krlsRZ" target="_blank">Bucket List Demo (.Net 4.5, C#)</a><br />
- <a href="https://dotnetfiddle.net/1RVuLi" target="_blank">Bucket List Demo (.Net 4.5, VB.Net)</a><br />
<div>
<br /></div>
<div>
Whether you are using client-side scripting or server side scripting, you can see that using JSON data with a RESTful API can be very easy to implement.</div>
<div>
<br /></div>
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-41616887173135265062015-06-29T17:03:00.004-04:002015-06-29T21:12:58.487-04:00ASP.Net C# Displaying Farmers Markets Data from MySafeInfo in Google MapsThis example uses a combination of technologies such ASP.Net, C#, JavaScript, jQuery, JSON, XML, and XSL to retrieve State Farmers Markets data from MySafeInfo and display the results in a Bootstrap table and on a Google Map.<br />
<br />
Here's the ASPX page:<br />
<br />
<pre class="prettyprint lang-html"> <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Web.farmers_markets._default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<!-- title -->
<title>State Farmers Markets</title>
<!-- jquery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<!-- bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<!-- google maps -->
<script language="javascript" type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key={key}&sensor=false&language=en"></script>
<!-- scripts -->
<script language="javascript" type="text/javascript" src="../js/functions.js?cache=2015062801"></script>
<script language="javascript" type="text/javascript" src="../js/spin.min.js?cache=2015062801"></script>
<!-- custom style -->
<style>
body {
margin: 25px !important;
}
.form-group {
width: 100%;
}
#map-canvas {
width: 100%;
height: 500px;
}
</style>
<script language="javascript" type="text/javascript">
// initialize
$(document).ready(function () {
InitializeMap();
});
function InitializeMap() {
// variables
var options = { center: new google.maps.LatLng(0, 0), mapTypeId: google.maps.MapTypeId.ROADMAP, scrollwheel: false, draggable: true };
var map = new google.maps.Map(document.getElementById("map-canvas"), options);
var bounds = new google.maps.LatLngBounds();
var locations = jQuery.parseJSON($("#lblLocations").val());
// add locations to map
$.each(locations, function () {
// variables
var latLong = new google.maps.LatLng(this.latitude, this.longitude);
var Url = this.url.replace(/&amp;/g, "&");
// marker
var marker = new google.maps.Marker({
map: map,
title: "{0} ({1})".format(this.market, this.city),
position: latLong,
draggable: false,
icon: "images/marker.png"
});
// bounds
bounds.extend(latLong);
// click event
if (Url.HasValue()) {
google.maps.event.addListener(marker, 'click', function () { window.open(Url) });
}
});
// fit bounds
map.fitBounds(bounds);
}
function btnSearch_Click() {
// controls
var txtZipCode = $("#txtZipCode");
// variables
var ZipCode = txtZipCode.val().trim();
// validation
if (!IsValidZip(ZipCode)) {
ShowMessage('Please enter a valid zip code.', 'txtZipCode', 'txtZipCode');
return false;
}
// show progress
ShowProgress();
// return value
return true;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div class="form-group">
<h2>
<asp:Literal ID="lblTitle" runat="server" />
</h2>
</div>
<!-- map -->
<div class="form-group">
<div id="map-canvas"></div>
</div>
<!-- filter -->
<div class="form-group" style="padding-top: 15px">
<asp:Panel ID="pnlSearch" DefaultButton="btnSearch" runat="server">
<asp:DropDownList ID="ddlList" CssClass="form-control" Width="250px" Style="display: inline" ClientIDMode="Static" runat="server" />
&nbsp;within&nbsp;
<asp:DropDownList ID="ddlRadius" CssClass="form-control" Width="100px" Style="display: inline" ClientIDMode="Static" runat="server">
<asp:ListItem>25</asp:ListItem>
<asp:ListItem>50</asp:ListItem>
<asp:ListItem>75</asp:ListItem>
<asp:ListItem>100</asp:ListItem>
</asp:DropDownList>
&nbsp;miles of:&nbsp;
<asp:TextBox ID="txtZipCode" Text="46237" MaxLength="5" CssClass="form-control" Width="100px" Style="display: inline; margin-right: 10px" ClientIDMode="Static" runat="server" />
<asp:Button ID="btnSearch" Text="Search" CssClass="btn btn-primary" OnClientClick="javascript: return btnSearch_Click();" OnClick="btnSearch_Click" ClientIDMode="Static" runat="server" />
</asp:Panel>
</div>
<!-- source -->
<div class="form-group">
<em>This demo is powered by data from mysafeinfo.com</em><br />
<asp:HyperLink ID="lnkList" Target="_blank" ClientIDMode="Static" runat="server" />
</div>
<!-- grid -->
<div class="form-group">
<asp:Literal ID="lblResults" runat="server" />
</div>
<!-- hidden fields -->
<asp:HiddenField ID="lblLocations" ClientIDMode="Static" runat="server" />
<!-- progess -->
<div id="loading">
<div id="loadingcontent">
<p id="loadingspinner">
</p>
</div>
</div>
<!-- message modal -->
<div class="modal fade" id="modal-message" tabindex="-1" role="dialog" aria-labelledby="lblModalMessage" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<h4 class="modal-title" id="lblModalMessage">Message</h4>
</div>
<div class="modal-body">
<div class="message"></div>
</div>
<div class="modal-footer">
<div class="pull-left">
<button id="btnMessageClose" type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<!-- hidden fields -->
<input type="hidden" name="lblFocus" id="lblFocus" />
<input type="hidden" name="lblSelect" id="lblSelect" />
</form>
</body>
</html>
</pre>
<br />
The only references not available on a content delivery network (CDN) are the following:<br />
<br />
<pre class="prettyprint lang-html"> <!-- scripts -->
<script language="javascript" type="text/javascript" src="../js/functions.js?cache=2015062801"></script>
<script language="javascript" type="text/javascript" src="../js/spin.min.js?cache=2015062801"></script>
</pre>
<br />
These files can be downloaded directly using the following:<br />
<br />
- <a href="http://pavey.azurewebsites.net/js/functions.js" target="_blank">http://pavey.azurewebsites.net/js/functions.js</a><br />
- <a href="http://pavey.azurewebsites.net/js/spin.min.js" target="_blank">http://pavey.azurewebsites.net/js/spin.min.js</a><br />
<br />
Next we'll look at the code-behind file:<br />
<pre class="prettyprint lang-cs"> using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml;
using System.Xml.Linq;
using Newtonsoft.Json;
using Utilities;
namespace Web.farmers_markets
{
public partial class _default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// bind lists and data
if (!Page.IsPostBack)
{
BindLists();
BindData();
}
}
private void BindData()
{
// variables
string List = ddlList.SelectedValue;
string Title = ddlList.SelectedItem.Text;
string Url = string.Format("https://mysafeinfo.com/api/data?list={0}&format=xml&select=cd,ct,nm,add,lat,lng,url&rootname=data&elementname=r&alias=cd=state,ct=city,nm=market,add=address,lat=latitude,lng=longitude,url=url", List);
XmlDocument XmlDoc = new XmlDocument();
string XmlData = string.Empty;
string XslData = string.Empty;
string XslFile = Path.Combine(Request.PhysicalApplicationPath, "farmers-markets", "xsl", "results.xsl");
string ZipCode = txtZipCode.Text.Trim();
int Radius = Convert.ToInt32(ddlRadius.SelectedValue);
List<Market> Markets = new List<Market>();
// labels
lblTitle.Text = Title;
lnkList.NavigateUrl = Url;
lnkList.Text = Url;
// geocode zip code
Coordinate Origin = GetLatLong(Address: ZipCode);
// request
using (WebClient client = new WebClient())
{
XmlData = client.DownloadString(Url);
}
// load xml into xml document
XmlDoc.LoadXml(XmlData);
// iterate through data first so we can do distance calculation
foreach(XmlElement x in XmlDoc.SelectNodes("//r"))
{
// variables
string Latitude = x.GetAttribute("latitude");
string Longitude = x.GetAttribute("longitude");
double OriginLatitude = Origin.Latitude;
double OriginLongitude = Origin.Longitude;
double DestinationLatitude = 0;
double DestinationLongitude = 0;
// parse
double.TryParse(Latitude, out DestinationLatitude);
double.TryParse(Longitude, out DestinationLongitude);
// calculate distance from origin
double Distance = GeoCodeCalc.CalculateDistance(OriginLatitude: OriginLatitude, OriginLongitude: OriginLongitude, DestinationLatitude: DestinationLatitude, DestinationLongitude: DestinationLongitude);
bool InRange = Distance <= Radius;
// add attribute indicating distance to origin
x.SetAttribute("distance", Distance.ToString());
// add attribute indicating if location is in range
x.SetAttribute("inrange", InRange.ToString().ToLower());
// add to markets list for easy conversion to json later
if (InRange)
{
Markets.Add(new Market() { state = x.GetAttribute("state"), city = x.GetAttribute("city"), market = x.GetAttribute("market"), address = x.GetAttribute("address"), latitude = x.GetAttribute("latitude"), longitude = x.GetAttribute("longitude"), url = x.GetAttribute("url") });
}
}
// xsl
using (Utilities.Xsl Xsl = new Utilities.Xsl())
{
using (Utilities.Xsl.XslConfiguration XslConfiguration = new Utilities.Xsl.XslConfiguration(XslFile))
{
// parameters
XslConfiguration.AddXslParameter("radius", Radius.ToString());
XslConfiguration.AddXslParameter("zipcode", ZipCode);
XslConfiguration.AddXslParameter("state", Title);
// transform
XslData = Xsl.TransformXml(XmlType: Utilities.Xsl.XmlTypes.XmlData, XmlSource: XmlDoc.OuterXml, XslConfig: XslConfiguration);
// check for errors
if (Xsl.ErrorDescription.HasValue())
{
XmlData = Xsl.ErrorDescription;
}
}
}
// bind grid
lblResults.Text = XslData;
// use Json.NET (Newtonsoft.Json) to serialize locations for the map
// https://www.nuget.org/packages/Newtonsoft.Json
lblLocations.Value = JsonConvert.SerializeObject(Markets);
}
private void BindLists()
{
// variables
string Result = string.Empty;
string Url = "https://mysafeinfo.com/api/data?list=all&format=json&sort=nm&select=lst,nm&lst=farmermarkets,startswith&alias=lst=list,nm=name";
List<MarketList> Lists = new List<MarketList>();
// request
using (WebClient client = new WebClient())
{
Result = client.DownloadString(Url);
}
// use Json.NET (Newtonsoft.Json) to deserialize json string into a strongly typed list
// https://www.nuget.org/packages/Newtonsoft.Json
Lists = JsonConvert.DeserializeObject<List<MarketList>>(Result);
// bind
ddlList.DataValueField = "List";
ddlList.DataTextField = "Name";
ddlList.DataSource = Lists;
ddlList.DataBind();
// default
ddlList.SetValue("farmermarketsin");
}
protected void btnSearch_Click(object sender, EventArgs e)
{
BindData();
}
public static Coordinate GetLatLong(string Address, string Country = "")
{
// variables
string Url = string.Empty;
// determine url
if (Address.HasValue() && Country.HasValue())
{
Url = string.Format("https://maps.googleapis.com/maps/api/geocode/xml?address={0}&components=country:{1}&sensor=false", Address.Trim(), Country.Trim());
}
else
{
Url = string.Format("https://maps.googleapis.com/maps/api/geocode/xml?address={0}&sensor=false", Address.Trim());
}
// return
return GetLatLongByUrl(Url);
}
public static Coordinate GetLatLongByUrl(string Url)
{
// variables
Coordinate Coordinate = new Coordinate();
XElement XmlElement = XElement.Load(Url);
XElement Status = (from x in XmlElement.Descendants() where x.Name.ToString().IsEqual("status") select x).FirstOrDefault();
// check status
if (Status != null && Status.Value.IsEqual("ok"))
{
// variables
string strLatitude = XmlElement.Element("result").Element("geometry").Element("location").Element("lat").Value;
string strLongitude = XmlElement.Element("result").Element("geometry").Element("location").Element("lng").Value;
double Latitude = 0;
double Longitude = 0;
// parse
double.TryParse(strLatitude, out Latitude);
double.TryParse(strLongitude, out Longitude);
// coordinates
Coordinate.Latitude = Latitude;
Coordinate.Longitude = Longitude;
}
//return value
return Coordinate;
}
private class MarketList
{
public string List { get; set; }
public string Name { get; set; }
}
private class Market
{
public string state { get; set; }
public string city { get; set; }
public string market { get; set; }
public string address { get; set; }
public string latitude { get; set; }
public string longitude { get; set; }
public string url { get; set; }
}
public class Coordinate
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
// Calculate Distance Between Geocodes in C# and JavaScript
// http://pietschsoft.com/post/2008/02/Calculate-Distance-Between-Geocodes-in-C-and-JavaScript
public static class GeoCodeCalc
{
public const double EarthRadiusInMiles = 3956.0;
public const double EarthRadiusInKilometers = 6367.0;
public static double ToRadian(double val) { return val * (Math.PI / 180); }
public static double DiffRadian(double val1, double val2) { return ToRadian(val2) - ToRadian(val1); }
public static double CalculateDistance(double OriginLatitude, double OriginLongitude, double DestinationLatitude, double DestinationLongitude)
{
return CalculateDistance(OriginLatitude, OriginLongitude, DestinationLatitude, DestinationLongitude, GeoCodeCalcMeasurement.Miles);
}
public static double CalculateDistance(double OriginLatitude, double OriginLongitude, double DestinationLatitude, double DestinationLongitude, GeoCodeCalcMeasurement Measurement)
{
// variables
double Radius = GeoCodeCalc.EarthRadiusInMiles;
if (Measurement == GeoCodeCalcMeasurement.Kilometers) { Radius = GeoCodeCalc.EarthRadiusInKilometers; }
// return
return Radius * 2 * Math.Asin(Math.Min(1, Math.Sqrt((Math.Pow(Math.Sin((DiffRadian(OriginLatitude, DestinationLatitude)) / 2.0), 2.0) + Math.Cos(ToRadian(OriginLatitude)) * Math.Cos(ToRadian(DestinationLatitude)) * Math.Pow(Math.Sin((DiffRadian(OriginLongitude, DestinationLongitude)) / 2.0), 2.0)))));
}
}
public enum GeoCodeCalcMeasurement : int
{
Miles = 0,
Kilometers = 1
}
}
}
</pre>
<br />
This example also relies on the following:<br />
<br />
- <a href="http://pavey.azurewebsites.net/resources/Extensions.txt" target="_blank">Extensions.cs</a><br />
- <a href="http://pavey.azurewebsites.net/resources/Xsl.txt" target="_blank">Xsl.cs</a><br />
- <a href="http://pavey.azurewebsites.net/farmers-markets/xsl/results.xsl" target="_blank">results.xsl</a><br />
- <a href="http://pavey.azurewebsites.net/farmers-markets/images/marker.png" target="_blank">marker.png</a><br />
<br />
This might look like a lot of code at first glance. But pulling all of this together in an ASP.Net Web Forms Application is very simple, and will help you understand the following key concepts:<br />
<br />
- Using WebClient to retrieve XML data from a 3rd party RESTful web service<br />
- Working with XML data using an XmlDocument and XmlElement<br />
- Using Google Maps API to geocode a zip code<br />
- Calculating the distance between two points<br />
- Using an XSL transformation to show the results in a Bootstrap table<br />
- Using Json.NET (Newtonsoft.Json) to serialize locations for the Google Map<br />
- Using Json.NET (Newtonsoft.Json) to deserialize JSON data into a strongly typed list<br />
- Using JavaScript and jQuery to iterate a JSON array<br />
- Using JavaScript and the Google Maps API to add locations to the Google Map<br />
- Using JavaScript and jQuery to validate a valid zip code is entered<br />
- Using JavaScript, jQuery, and Bootstrap to show validation errors in modal<br />
- Using JavaScript and jQuery to show custom progress spinner<br />
<br />
<a href="http://pavey.azurewebsites.net/farmers-markets/" target="_blank">Click here to see a full working example of this demo.</a><br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-2776711552211552682015-06-27T11:38:00.001-04:002015-06-29T09:22:38.612-04:00ASP.Net C# Finding Controls in Nested Repeaters on Button Click EventHere's a simple way to find controls in a nested repeater on a button click event.<br />
<br />
Let's start with the ASPX page.<br />
<pre class="prettyprint lang-html"> <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="Web.nested_repeaters._default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<!-- title -->
<title>Repeater Test</title>
<!-- jquery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<!-- bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<!-- custom style -->
<style>
body {
margin: 25px !important;
}
.form-group {
width: 800px;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:Repeater ID="lstCategories" runat="server">
<ItemTemplate>
<div class="col-sm-12">
<h2><%#Eval("Name")%></h2>
<asp:Repeater ID="lstTasks" DataSource='<%#Eval("Tasks")%>' runat="server">
<ItemTemplate>
<div class="form-group">
<div class="col-sm-12">
<div class="col-sm-2">
<%#Eval("Name")%>
</div>
<div class="col-sm-10">
<!-- hidden fields -->
<asp:HiddenField ID="lblID" Value='<%#DataBinder.Eval(Container.DataItem, "ID")%>' runat="server" />
<asp:HiddenField ID="lblValue" Value='<%#DataBinder.Eval(Container.DataItem, "Value")%>' runat="server" />
<!-- textbox to show current value and to allow updates -->
<asp:TextBox ID="txtValue" Text='<%#DataBinder.Eval(Container.DataItem, "Value")%>' MaxLength="100" CssClass="form-control" runat="server" />
</div>
</div>
</div>
</ItemTemplate>
</asp:Repeater>
</div>
</ItemTemplate>
</asp:Repeater>
<div class="col-sm-12" style="padding-top: 25px">
<asp:Button ID="btnSave" Text="Save Changes" CssClass="btn btn-primary" Style="margin-right: 25px" OnClick="btnSave_Click" ClientIDMode="Static" runat="server" />
<asp:Button ID="btnCancel" Text="Cancel" CssClass="btn btn-primary" OnClick="btnCancel_Click" ClientIDMode="Static" runat="server" />
</div>
<!-- debug -->
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12" style="padding-top: 25px">
<asp:PlaceHolder ID="pnlDebug" Visible="true" runat="server" />
</div>
</form>
</body>
</html>
</pre>
<br />
For this example I'm using <i>lstCategories </i>for the outer repeater and <i>lstTasks </i>for the nested repeater. The nested repeater (<i>lstTasks</i>) defines 3 controls:<br />
<br />
- <b>lblID</b> This is the hidden field for the ID to identify your record (e.g. primary key)<br />
- <b>lblValue</b> This is the hidden field representing the original value of the field<br />
- <b>txtValue</b> This is a textbox that defaults to the current value, but can be edited<br />
<br />
Then there are 2 buttons.<br />
<br />
- <b>btnSave</b> Iterates the repeaters to access the controls<br />
- <b>btnCancel</b> Reloads the page<br />
<br />
Then the code-behind looks like this:
<br />
<pre class="prettyprint lang-cs"> using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Web.nested_repeaters
{
public partial class _default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
BindData();
}
}
private void BindData()
{
lstCategories.DataSource = GetData();
lstCategories.DataBind();
}
private List<Category> GetData()
{
// variables
List<Category> Categories = new List<Category>();
// category 1
Category Category1 = new Category() { Name = "General", Tasks = new List<Task>() };
Category1.Tasks.Add(new Task() { ID = 1, Name = "Name", Value = "John Wick" });
Category1.Tasks.Add(new Task() { ID = 2, Name = "Email", Value = "info@pavey.net" });
Category1.Tasks.Add(new Task() { ID = 3, Name = "Phone", Value = "" });
Category1.Tasks.Add(new Task() { ID = 4, Name = "Website", Value = "http://www.pavey.me/" });
Category1.Tasks.Add(new Task() { ID = 5, Name = "Comments", Value = "" });
// category 2
Category Category2 = new Category() { Name = "Social Media", Tasks = new List<Task>() };
Category2.Tasks.Add(new Task() { ID = 6, Name = "Twitter", Value = "https://twitter.com/matthewpavey" });
Category2.Tasks.Add(new Task() { ID = 7, Name = "Facebook", Value = "" });
Category2.Tasks.Add(new Task() { ID = 8, Name = "LinkedIn ", Value = "" });
// category 3
Category Category3 = new Category() { Name = "Favorite Websites", Tasks = new List<Task>() };
Category3.Tasks.Add(new Task() { ID = 9, Name = "Website", Value = "http://www.pavey.me/" });
Category3.Tasks.Add(new Task() { ID = 10, Name = "Website", Value = "https://mysafeinfo.com/" });
Category3.Tasks.Add(new Task() { ID = 11, Name = "Website", Value = "" });
Category3.Tasks.Add(new Task() { ID = 12, Name = "Website", Value = "" });
Category3.Tasks.Add(new Task() { ID = 13, Name = "Website", Value = "" });
// add to list
Categories.Add(Category1);
Categories.Add(Category2);
Categories.Add(Category3);
// return
return Categories;
}
public void Debug(string Value = "")
{
pnlDebug.Controls.Add(new LiteralControl(string.Format("<div>{0}</div>", Value)));
}
protected void btnSave_Click(object sender, EventArgs e)
{
// iterate outer repeater (categories)
foreach (RepeaterItem Category in lstCategories.Items)
{
// iterate inner repeater (tasks)
foreach (RepeaterItem Task in ((Repeater)(Category.FindControl("lstTasks"))).Items)
{
// get reference to controls
HiddenField lblID = (HiddenField)Task.FindControl("lblID");
HiddenField lblValue = (HiddenField)Task.FindControl("lblValue");
TextBox txtValue = (TextBox)Task.FindControl("txtValue");
// extract values
int ID = Convert.ToInt32(lblID.Value);
string ValueOriginal = lblValue.Value;
string ValueNew = txtValue.Text.Trim();
// debug
Debug(string.Format("ID={0}; ValueOriginal={1}; ValueNew={2}", ID, ValueOriginal, ValueNew));
}
}
}
protected void btnCancel_Click(object sender, EventArgs e)
{
Response.Redirect("default.aspx");
Response.End();
}
private class Category
{
public string Name { get; set; }
public List<Task> Tasks { get; set; }
}
private class Task
{
public int ID { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
}
}
</pre>
<br />
The <b>btnSave_Click</b> event is really where everything is happening. We're simply doing a foreach around the <i>lstCategories </i>repeater items. Then inside that loop we're iterating the <i>lstTasks </i>repeater items. It's inside this nested repeater (<i>lstTasks</i>) that we can get reference to the controls (<i>lblID</i>, <i>lblValue</i>, <i>txtValue</i>). From that point it's just a matter of casting the controls, extracting the values, and then using the values. In this case we're just outputting the values for debugging. But this is where you could use the ID and compare the original/new values to determine if you need to apply an update to the record, etc.<br />
<br />
<a href="http://pavey.azurewebsites.net/nested-repeaters/" target="_blank">Click here to see a full working example of this demo.</a><br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-87374300124424707642015-06-14T10:41:00.001-04:002015-06-14T10:41:13.182-04:00SQL Code Generation ToolThe <a href="https://github.com/mpavey/sql-code-gen" target="_blank">SQL Code Generation Tool</a> 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.<br />
<br />
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#.<br />
<br />
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.<br />
<br />
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).<br />
<br />
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).<br />
<br />
Head over to the GitHub repository for more information.<br />
<br />
<a href="https://github.com/mpavey/sql-code-gen" target="_blank">https://github.com/mpavey/sql-code-gen</a>
<br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-11094683920304004952015-06-12T11:17:00.000-04:002015-06-29T09:34:22.725-04:00Using SQL Server SQLCMD and WinZip wzzip to backup and zip/encrypt a databaseHere's an example of how to create a <b>BAT </b>file to backup a SQL Server database using <b>SQLCMD</b> and to zip the backup file using WinZip <b>wzzip</b> with AES256 encryption.<br />
<pre class="prettyprint lang-basic"> @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%"
</code></pre>
<br />
You could then use the BAT file to create a scheduled job to automate your backup process.
<br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-49675582838790413672015-06-11T08:16:00.001-04:002015-06-29T09:34:53.194-04:00Shredding XML data (elements) using SQL ServerHere's a simple example of shredding XML data (elements) in SQL Server.<br />
<pre class="prettyprint lang-sql"> -- variables
DECLARE @xml XML = '
<dinosaurs>
<d>
<name>Aachenosaurus</name>
<url>http://en.wikipedia.org/wiki/Aachenosaurus</url>
</d>
<d>
<name>Aardonyx</name>
<url>http://en.wikipedia.org/wiki/Aardonyx</url>
</d>
<d>
<name>Abdallahsaurus</name>
<url>http://en.wikipedia.org/wiki/Abdallahsaurus</url>
</d>
<d>
<name>Abelisaurus</name>
<url>http://en.wikipedia.org/wiki/Abelisaurus</url>
</d>
<d>
<name>Abrictosaurus</name>
<url>http://en.wikipedia.org/wiki/Abrictosaurus</url>
</d>
<d>
<name>Abrosaurus</name>
<url>http://en.wikipedia.org/wiki/Abrosaurus</url>
</d>
<d>
<name>Abydosaurus</name>
<url>http://en.wikipedia.org/wiki/Abydosaurus</url>
</d>
<d>
<name>Acanthopholis</name>
<url>http://en.wikipedia.org/wiki/Acanthopholis</url>
</d>
<d>
<name>Achelousaurus</name>
<url>http://en.wikipedia.org/wiki/Achelousaurus</url>
</d>
<d>
<name>Achillesaurus</name>
<url>http://en.wikipedia.org/wiki/Achillesaurus</url>
</d>
</dinosaurs>'
-- use common table expression to shred data
;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;
</pre>
Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-10823007965154649162015-06-11T08:15:00.001-04:002015-06-29T09:35:15.523-04:00Shredding XML data (attributes) using SQL ServerHere's a simple example of shredding XML data (attributes) in SQL Server.<br />
<pre class="prettyprint lang-sql"> -- variables
DECLARE @xml XML = '
<dinosaurs>
<d name="Aachenosaurus" url="http://en.wikipedia.org/wiki/Aachenosaurus" />
<d name="Aardonyx" url="http://en.wikipedia.org/wiki/Aardonyx" />
<d name="Abdallahsaurus" url="http://en.wikipedia.org/wiki/Abdallahsaurus" />
<d name="Abelisaurus" url="http://en.wikipedia.org/wiki/Abelisaurus" />
<d name="Abrictosaurus" url="http://en.wikipedia.org/wiki/Abrictosaurus" />
<d name="Abrosaurus" url="http://en.wikipedia.org/wiki/Abrosaurus" />
<d name="Abydosaurus" url="http://en.wikipedia.org/wiki/Abydosaurus" />
<d name="Acanthopholis" url="http://en.wikipedia.org/wiki/Acanthopholis" />
<d name="Achelousaurus" url="http://en.wikipedia.org/wiki/Achelousaurus" />
<d name="Achillesaurus" url="http://en.wikipedia.org/wiki/Achillesaurus" />
</dinosaurs>'
-- use common table expression to shred data
;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;
</pre>
Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-90252852609450609412015-06-09T09:22:00.002-04:002015-07-01T09:51:36.739-04:00Using MVC Web API and SQL Server to create your own PayPal IPN ListenerRecently I was tasked with creating a PayPal IPN Listener, so I started with getting familiar with the PayPal Instant Payment Notification (IPN) documentation:<br />
<br />
<a href="https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNIntro/" target="_blank">https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNIntro/</a><br />
<br />
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:<br />
<br />
1) Listen<br />
2) Log<br />
3) React<br />
<br />
This is important because it allows the listener to do it's job quickly, and lets you focus on the processing later.<br />
<br />
Let's start with our SQL Server database <b>Notifications</b> table.<br />
<pre class="prettyprint lang-sql"> 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
</pre>
<br />
Next is the <b>Notifications_Save </b>stored procedure to save data in the Notifications table.<br />
<pre class="prettyprint lang-sql"> 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
</pre>
<br />
Next we'll create the <b>Notification</b> model object in our MVC project.<br />
<pre class="prettyprint lang-vb"> 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
</pre>
<br />
Then we'll create the <b>Notifications</b> data layer class for calling the stored procedure.<br />
<pre class="prettyprint lang-vb"> 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
</pre>
<br />
Next we have a few helper classes/extensions/methods that we are going to be calling from the listener controller.<br />
<br />
1) IsBlank (extension method)<br />
2) ContainsValue (extension method)<br />
3) Result (model object)<br />
4) PostContentToUrl (shared method)<br />
<br />
The <a href="http://www.pavey.me/2015/04/aspnet-c-extension-methods-to-check-if.html" target="_blank">IsBlank extension method</a> 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.<br />
<pre class="prettyprint lang-vb"> <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
</pre>
<br />
The <a href="http://www.pavey.me/2015/04/aspnet-c-extension-method-to-see-if.html" target="_blank">ContainsValue extension method</a> 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.<br />
<pre class="prettyprint lang-vb"> <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
</pre>
<br />
The <b>Result </b>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.<br />
<pre class="prettyprint lang-vb"> Public Class Result
Public Property Success As Boolean = False
Public Property Message As String = String.Empty
End Class
</pre>
<br />
Then we have our <b>PostContentToUrl </b>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.<br />
<pre class="prettyprint lang-vb"> 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
</pre>
<br />
Next we'll setup a <a href="http://www.pavey.me/2015/04/aspnet-mvc-using-base-controller-to.html" target="_blank">BaseController class</a> to expose several properties for the current HTTP request.<br />
<pre class="prettyprint lang-vb"> 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
</pre>
<br />
Now that we've got all the core pieces in place let's take a look at the <b>ListenerController</b>, which is the simplest part of the entire process.<br />
<pre class="prettyprint lang-vb"> 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
</pre>
<br />
Because we've used <i><RoutePrefix("listener")></i> for the controller and <i><Route("ipn")></i> for the <b>InstantPaymentNotification</b> function, our IPN listener service would be exposed as follows:<br />
<br />
http://yourdomain.com/listener/ipn<br />
<br />
At this point you have an IPN listener that can:<br />
<br />
1) Receive IPN notifications from PayPal<br />
2) Respond back instantly to PayPal for the verification handshake<br />
3) Log notification activity to the [Notifications] table in your database<br />
<br />
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 <a href="https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNandPDTVariables/#id08CTB0S055Z" target="_blank">transaction types (txn_type)</a> you support and what types of services you are selling (e.g. products, subscriptions, etc).<br />
<br />
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).<br />
<br />
We'll start with a <b>Split</b> table-valued function to help with splitting the IPN data vertically.<br />
<pre class="prettyprint lang-sql"> 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
</pre>
<br />
Next we'll use a SQL script to process any unprocessed notifications.<br />
<pre class="prettyprint lang-sql"> /*
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'
</pre>
<br />
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.<br />
<br />
<b><i>Remember this is just a starting point and your specific data processing implementation will vary based on your needs.</i></b>
<br />
<i><br /></i>
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-20474508388626719502015-05-08T07:26:00.000-04:002015-05-08T09:41:03.366-04:00Using AngularJS to bind JSON data from an HTTP service request to an HTML SELECT listHere's an example of how to bind JSON data from a $http service using AngularJS. In this example we'll use Zodiac <a href="http://mysafeinfo.com/api/data?list=zodiac&format=json&alias=nm=name" target="_blank">data</a> from <a href="https://mysafeinfo.com/content/options/zodiac" target="_blank">MySafeInfo</a>.<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="//jsfiddle.net/mpavey/L1ynj8xa/embedded/" width="100%"></iframe>
<br />
<br />
<i>This example relies on <a href="https://angularjs.org/" target="_blank">AngularJS</a> and uses <a href="https://mysafeinfo.com/content/services" target="_blank">mysafeinfo.com</a> to retrieve data in JSON format. MySafeInfo is a free service we offer to software developers to provide test data in a simple, flexible manner, in a variety of formats, including XML, JSON, JSONP, CSV, and more.</i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-83384774881051383602015-05-07T09:10:00.002-04:002015-05-07T09:19:50.416-04:00Using JavaScript, jQuery, XML, and XSL to bind a grid and automatically calculate column/row totalsThis <a href="https://jsfiddle.net/mpavey/0k7s0tpa/embedded/result/" target="_blank">example</a> 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.<br />
<br />
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, <i><b>if you're using Internet Explorer</b></i> 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.<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="500" src="//jsfiddle.net/mpavey/0k7s0tpa/embedded/js/" width="100%"></iframe>
<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="500" src="//jsfiddle.net/mpavey/0k7s0tpa/embedded/html/" width="100%"></iframe>
<br />
<br />
<a href="https://jsfiddle.net/mpavey/0k7s0tpa/embedded/result/" target="_blank">Click here to view the demo and test the functionality.</a><br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-63744897962735173652015-05-05T15:04:00.000-04:002015-05-07T09:21:22.438-04:00Using JavaScript and XSL to retrieve and display a list of NFL Teams in XML formatHere's an example of how to retrieve XML data using simple JavaScript and to display it using a client-side XSL transformation.<br />
<br />
In this example we'll use NFL Teams data from a free data service called <a href="https://mysafeinfo.com/content/services" target="_blank">MySafeInfo</a>. You can view the configurable options for this particular dataset by <a href="https://mysafeinfo.com/content/options/nflteams" target="_blank">clicking here</a>. Or you can view the raw data for this example by <a href="http://mysafeinfo.com/api/data?list=nflteams&format=xml&select=nm,con&elementname=team&alias=nm=name,con=conference" target="_blank">clicking here</a>.<br />
<br />
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, <i><b>if you're using Internet Explorer</b></i> 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.<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="500" src="//jsfiddle.net/mpavey/a9kdv3cm/embedded/" width="100%"></iframe>
<br />
<br />
<i>This example relies on JavaScript and uses <a href="https://mysafeinfo.com/content/options/nflteams" target="_blank">mysafeinfo.com</a> to retrieve NFL Teams data in XML format. MySafeInfo is a free service we offer to software developers to provide test data in a simple, flexible manner, in a variety of formats, including XML, JSON, JSONP, CSV, and more.</i><br />
<i><br /></i>
Here are some additional resources I came across while preparing this example.<br />
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest" target="_blank">https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Using_XML_Data_Islands_in_Mozilla" target="_blank">https://developer.mozilla.org/en-US/docs/Using_XML_Data_Islands_in_Mozilla</a></li>
</ul>
<i><br /></i>
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-16199459551504937972015-05-05T09:34:00.001-04:002015-05-07T09:31:04.264-04:00Using pure good ol' fashioned JavaScript to access The Beatles' albums in JSON formatHere's an example of how to retrieve JSON data using pure good ol' fashioned JavaScript. I'm a big fan of jQuery, and don't have anything against other JavaScript frameworks, but sometimes going back to the basics, and knowing how to do it with just JavaScript is good for you.<br />
<br />
In this example we'll use The Beatles' albums data from a free data service called <a href="https://mysafeinfo.com/content/examples" target="_blank">MySafeInfo</a>. You can view the configurable options for this particular dataset by <a href="https://mysafeinfo.com/content/options/beatlesalbums" target="_blank">clicking here</a>. Or you can view the raw data for this example by <a href="http://mysafeinfo.com/api/data?list=beatlesalbums&format=json&select=ent,typ,rd&alias=ent=artist,typ=album,rd=date" target="_blank">clicking here</a>.<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="400" src="//jsfiddle.net/mpavey/2mzjmhaj/embedded/" width="100%"></iframe>
<br />
<br />
<i>This example relies on JavaScript and uses <a href="https://mysafeinfo.com/content/options/beatlesalbums" target="_blank">mysafeinfo.com</a> to retrieve The Beatles' albums data in JSON format. MySafeInfo is a free service we offer to software developers to provide test data in a simple, flexible manner, in a variety of formats, including XML, JSON, JSONP, CSV, and more.</i><br />
<i><br /></i>
Here are some additional resources I came across while preparing this example.<br />
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest" target="_blank">https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest</a></li>
<li><a href="http://alistapart.com/blog/post/choosing-vanilla-javascript" target="_blank">http://alistapart.com/blog/post/choosing-vanilla-javascript</a></li>
<li><a href="http://youmightnotneedjquery.com/" target="_blank">http://youmightnotneedjquery.com/</a></li>
</ul>
<i><br /></i>
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-74371274261535442902015-05-04T19:41:00.000-04:002015-05-07T09:31:12.623-04:00Using Knockout and jQuery to retrieve list of U.S. Presidents in JSON format using MySafeInfo APIHere's an example of how to retrieve JSON data using jQuery and binding it to a simple table using Knockout. In this example we'll use U.S. Presidents data from a free data service called <a href="https://mysafeinfo.com/content/examples" target="_blank">MySafeInfo</a>. You can view the configurable options for this particular dataset by <a href="https://mysafeinfo.com/content/options/presidents" target="_blank">clicking here</a>. Or you can view the raw data for this example by <a href="https://mysafeinfo.com/api/data?list=presidents&format=json" target="_blank">clicking here</a>.<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="350" src="//jsfiddle.net/mpavey/9btrh2xe/embedded/" width="100%"></iframe>
<br />
<br />
<i>This example relies on <a href="http://knockoutjs.com/" target="_blank">Knockout</a> and uses <a href="https://mysafeinfo.com/content/options/presidents" target="_blank">mysafeinfo.com</a> to retrieve U.S. Presidents data in JSON format. MySafeInfo is a free service we offer to software developers to provide test data in a simple, flexible manner, in a variety of formats, including XML, JSON, JSONP, CSV, and more.</i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-1432018572504319372015-05-04T10:19:00.001-04:002015-05-07T09:31:26.207-04:00Using AngularJS to retrieve list of U.S. Presidents in JSON format using MySafeInfo APIHere's an example of how to bind JSON data from a $http service using AngularJS. In this example we'll use U.S. Presidents data from a free data service called <a href="https://mysafeinfo.com/content/examples" target="_blank">MySafeInfo</a>. You can view the configurable options for this particular dataset by <a href="https://mysafeinfo.com/content/options/presidents" target="_blank">clicking here</a>. Or you can view the raw data for this example by <a href="https://mysafeinfo.com/api/data?list=presidents&format=json" target="_blank">clicking here</a>.<br />
<br />
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="300" src="//jsfiddle.net/mpavey/rp7w0jz5/embedded/" width="100%"></iframe>
<br />
<br />
<i>This example relies on <a href="https://angularjs.org/" target="_blank">AngularJS</a> and uses <a href="https://mysafeinfo.com/content/options/presidents" target="_blank">mysafeinfo.com</a> to retrieve U.S. Presidents data in JSON format. MySafeInfo is a free service we offer to software developers to provide test data in a simple, flexible manner, in a variety of formats, including XML, JSON, JSONP, CSV, and more.</i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-26057359887032330512015-05-03T18:36:00.001-04:002015-06-29T09:38:31.063-04:00ASP.Net C# SQL Building Your Own Url ShortnerSeveral 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.<br />
<br />
First off you'll need a database table for the url data. In this example I'm using SQL Server 2008.<br />
<pre class="prettyprint lang-sql"> 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
</pre>
<br />
- <b>PK </b>column to act as the primary key<br />
- <b>Key </b>column is a unique nonclustered column, which is the unique key for the shortened url<br />
- <b>Url </b>column which is the fully qualified url that that the shortened url should redirect to<br />
- <b>CreatedBy </b>is a simple <i>varchar </i>field to provide optional auditing<br />
- <b>CreatedDate </b>is a simple <i>smalldatetime </i>field to provide optional auditing<br />
<br />
It's worth noting that I'm using <i>SQL_Latin1_General_CP1_CS_AS</i> for the collation on the <b>Key</b> column. This isn't absolutely necessary; however, it means that the <b>Key </b>column is case-sensitive and makes the universe much larger for the number of unique values that be be stored in that column.<br />
<br />
The next step is to create the view and scalar-valued functions.<br />
<br />
<b>GetUniqueIdentifierView</b><br />
<pre class="prettyprint lang-sql"> SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE VIEW [dbo].[GetUniqueIdentifierView]
AS
SELECT 'ID' = NEWID()
GO
</pre>
<br />
<b>GetUniqueIdentifier</b><br />
<pre class="prettyprint lang-sql"> 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
</pre>
<br />
<b>GetRandomString</b><br />
<pre class="prettyprint lang-sql"> 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
</pre>
<br />
At this point we have a simple function we can call to get a random string of a specified length, for example:<br />
<pre class="prettyprint lang-sql"> SELECT dbo.GetRandomString(5)
</pre>
<br />
Now we'll create the stored procedures to finish off the SQL side of things.<br />
<br />
The first stored procedure is <b>Urls_List</b>. This stored procedure has optional parameters to either return all urls, return a single url for the specified <b>PK</b>, or return a single url for the specified <b>Key</b>.<br />
<pre class="prettyprint lang-sql"> 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
</pre>
<br />
Now you have a simple way to get the data from the <b>Urls </b>table, for example:<br />
<pre class="prettyprint lang-sql"> -- 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'
</pre>
<br />
You'll want to pay special attention to the <i>{BaseUrl}</i> reference in the <b>Urls_List </b>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/) <i style="font-weight: bold;">OR</i> 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.<br />
<br />
The next stored procedure is <b>Urls_Save</b>. This stored procedure creates the record in the <b>Urls</b> table with a unique key and returns the record. If a record with the specified <b>Url </b>already exists it will return that record instead of creating a new one.<br />
<pre class="prettyprint lang-sql"> 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
</pre>
<br />
You'll notice in the <b>Urls_Save </b>stored procedure I'm using <i>@KeyLength = 5</i>. The <b>GetRandomString </b>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.<br />
<br />
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 <b>Model</b> project, and I use a <b>BaseModel </b>class, which for purposes of this example is empty, but gives you a place to provide base support for your model classes.<br />
<br />
<b>BaseModel.cs</b><br />
<pre class="prettyprint lang-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
}
}
</pre>
<br />
<b>UrlShort.cs</b><br />
<pre class="prettyprint lang-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; }
}
}
</pre>
<br />
Now we'll create the data layer class. I keep the data layer classes in a <b>Data</b> project, and I use a <b>BaseDB </b>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 <i>Microsoft Enterprise Library 5.0 – May 2011</i> to provide the data access.<br />
<br />
<b>BaseDB.cs</b><br />
<pre class="prettyprint lang-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
}
}
</pre>
<br />
<b>Urls.cs</b><br />
<pre class="prettyprint lang-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;
}
}
}
</pre>
<br />
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 <b>ToObject</b> and <b>ToList</b> 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.<br />
<br />
<b>Extensions.cs</b><br />
<pre class="prettyprint lang-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;
}
}
}
</pre>
<br />
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 <b>Save</b> method from the data layer:<br />
<pre class="prettyprint lang-cs"> // 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
}
}
</pre>
<br />
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.<br />
<br />
<b>Example Url:</b><br />
http://dev.sandbox.com/forgot-password.aspx?q=urXsbqy9knIpqqOddkFICXTAspidUXCmPS2vJsLqkVanePU%2bGgxLahBGGl1EA%2f%2fXyXxGT4EX6kcqYSvy8BTpib6eEB61Q5WxNcNfEjk1OYx4HGyvO5oKs34JZ%2f1p9jMuAvVpkbkKpBBjYD2UiotIiYob%2baSzHmxRUuUJYkRepd6kSosnXOXssKVQJ%2bWbQQkYfNZWt2OUe9nGw1UOBd1aeO3O9JyEqSiMkYoIF1blW3f9MUx461LSnB9FL2Q4Vbn%2bDGyK0kEHPQVaz5fhpSXIPrfN3Q%2flRtjyqWDXrRnx2%2bE%3e<br />
<br />
<b>Example Shortened Url:</b><br />
http://dev.sandbox.com/url/?Z4cgw<br />
<br />
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.<br />
<br />
Start off by creating a new folder in your web project. I called mine <b>url</b>, which is why in the example shortened url above I used <i>/url</i>. This could literally be any folder name you wanted, but something short makes sense.<br />
<br />
After you've got the folder created, create a <b>Default.aspx</b> page in that folder, with the following:<br />
<br />
<b>Default.aspx</b><br />
<pre class="prettyprint lang-html"> <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Sandbox.Web.url.Default" %>
<!-- see code behind -->
</pre>
<br />
<b>Default.aspx.cs</b><br />
<pre class="prettyprint lang-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();
}
}
}
}
</pre>
<br />
This page simply takes the querystring value and checks to see if there is a matching record in the <b>Urls </b>table with the specified <b>Key</b>. If found, it redirects to that url. If not, it falls back to redirecting the user to some default/home page.<br />
<br />
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:<br />
<br />
- Database table for the url data<br />
- Views/Functions/Procs for creating/accessing the url data<br />
- Model object for representing the url data<br />
- Data layer class for calling the stored procedures and returning the model object<br />
- Folder for processing the shortened url and redirecting appropriately<br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-10111115620434183762015-05-02T08:27:00.001-04:002015-06-29T09:39:55.998-04:00ASP.Net C# Get Page Name With Optional Parameters To Include Extension And QueryStringHere's a simple static function that I keep in my utilities/functions class to let you easily get the the page name being requested.<br />
<br />
<b>Functions.cs</b><br />
<pre class="prettyprint lang-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;
}
</pre>
<br />
The <b>GetPageName </b>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.<br />
<br />
<b>Extensions.cs</b><br />
<pre class="prettyprint lang-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;
}
</pre>
<br />
The <b>GetPageName </b>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.<br />
<br />
<b>Example usage and output:</b><br />
<pre class="prettyprint lang-cs"> string Url = Functions.GetPageName(IncludeExtension: true, IncludeQueryString: true);
</pre>
<i>Test.aspx?x=1</i><br />
<br />
<b>Example usage and output:</b><br />
<pre class="prettyprint lang-cs"> string Url = Functions.GetPageName(IncludeExtension: true, IncludeQueryString: false);
</pre>
<i>Test.aspx</i><br />
<br />
<b>Example usage and output:</b><br />
<pre class="prettyprint lang-cs"> string Url = Functions.GetPageName(IncludeExtension: false, IncludeQueryString: false);
</pre>
<i>Test</i><br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0tag:blogger.com,1999:blog-1301739890503359743.post-78160287119871238882015-04-30T11:26:00.002-04:002015-06-29T09:42:45.668-04:00ASP.Net C# Override Render event to get and manipulate page content before it's renderedHere's an easy way to intercept the content that an ASP.Net page is going to render and send back to the browser.<br />
<pre class="prettyprint lang-cs"> protected override void Render(HtmlTextWriter Writer)
{
// variables
string Content = string.Empty;
// get the fully rendered content of this page so we can do some additional translations if necessary
using (StringWriter StringWriter = new StringWriter())
{
using (HtmlTextWriter HtmlTextWriter = new HtmlTextWriter(StringWriter))
{
// render current page content to temp writer
base.Render(HtmlTextWriter);
// close writer
HtmlTextWriter.Close();
// get content
Content = StringWriter.ToString();
<b>// DO SOMETHING WITH THE CONTENT</b>
}
}
// render content
Writer.Write(Content);
}
</pre>
<br />
Using the override keyword for the <b>Render</b> event lets you get and manipulate the content <u>before</u> it's rendered. Using the example above, the <b>Content</b> string variable will hold the content that will be rendered back to the browser. Now you could manipulate the content before it is rendered. A good example would be some kind of string replacement. Say for example you want to define a custom pattern and use it through your pages, content, resource files, and such. In that case you could use the following code where the <i>DO SOMETHING WITH THE CONTENT</i> comment is.<br />
<br />
<pre class="prettyprint lang-cs"> // look for <translate></translate> pattern so we can translate them on the fly
foreach (Match m in Regex.Matches(Content, @"<translate>(.*?)</translate>", RegexOptions.IgnoreCase))
{
// get the pattern
// e.g. <translate>lblFirstName</translate>
string Pattern = m.Value.ToLower();
// remove the pattern so you are left with just the ID
// e.g. lblFirstName
string ID = Pattern.ReplaceValue("<translate>", string.Empty).ReplaceValue("</translate>", string.Empty);
// do some kind of lookup with the ID
// e.g. This could be something as simple as getting a value from a resource file for a specific language
string Translation = "Lookup value for the current ID";
// do a string replacement on the Content variable replacing the pattern with your translation
Content = Content.ReplaceValue(Pattern, Translation);
}
</pre>
<br />
The above code is dependent upon a simple <b>ReplaceValue</b> extension method:<br />
<pre class="prettyprint lang-cs"> public static string ReplaceValue(this string Value, string Pattern, string Replacement)
{
return Regex.Replace(Value, Regex.Escape(Pattern), Replacement, RegexOptions.IgnoreCase);
}
</pre>
<br />
This is a simplified version of a solution that I use regularly, and it works really well. This idea sort of evolved over time to handle more complex scenarios were you don't always have the luxury of using simpler techniques. For example, at first glance you might just think about using a simple resource file on your ASPX page. That can work for a lot of situations. Another thought would be to use a custom user control where you can define a "key" attribute and do your resource lookup within the user control. Again, a very good solution that can cover most situations; however, this particular pattern replacement technique allows you to handle other situations where you have data that is not directly within your ASPX page, for example, other resource files, or data from a database, etc.<br />
<br />
The pattern can be used literally anywhere on the page. Whether it's in your JavaScript, HTML, or even part of a more complex ASP.Net web control, for example:<br />
<br />
<b>JavaScript:</b><br />
<pre class="prettyprint lang-js"> alert("<translate>FirstNameRequired</translate>");
</pre>
<br />
<b>HTML:</b><br />
<pre class="prettyprint lang-html"> <div><translate>lblDisclaimer</translate></div>
</pre>
<br />
<b>GridView:</b><br />
<pre class="prettyprint lang-html"> <asp:BoundField DataField="FirstName" HeaderText="<translate>lblFirstName</translate>" HtmlEncode="false" />
</pre>
<br />
How it's used is really limitless, and for language driven websites, it can be a really nice way to make things more generic and flexible.<br />
<br />
<i>Matt Pavey is a Microsoft Certified software developer who specializes in ASP.Net, VB.Net, C#, AJAX, LINQ, XML, XSL, Web Services, SQL, jQuery, and more. Follow on Twitter <a href="https://twitter.com/matthewpavey" target="_blank">@matthewpavey</a></i>Matt Paveyhttp://www.blogger.com/profile/03559490834365710082noreply@blogger.com0