Amazon.com Widgets All posts by williarob

WilliaBlog.Net

I dream in code

About the author

Robert Williams is an internet application developer for the Salem Web Network.
E-mail me Send mail
Go Daddy Deal of the Week: 30% off your order at GoDaddy.com! Offer expires 11/6/12

Recent comments

Archive

Authors

Tags

Code Project Associate Logo

Disclaimer

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


Asynchronous DataSet

If you haven't yet read the article "Scalable Apps with Asynchronous Programming in ASP.NET" by Jeff Prosise or seen his presentation at TechEd then you should cetainly take the time to do so, however I'll summarize the key points briefly here. Basically, there is a finite number of Threads available to ASP.Net for request handling, and by making database calls the way that most textbooks and articles recommend, many of the available threads that should be handling requests are actually tapping their feet waiting for your database request to complete, before it can serve your page and return to the thread pool. When all the threads are busy, incoming requests are queued, and if that queue becomes too long then users start to see HTTP 503 Service unavailable errors. In other words there is a glass ceiling to scalability, with synchronous IO requests in asp.net.

 To make optimum use of the thread pool all IO requests that you know could take a second or more to process should be made asynchronously and the links above will give you plenty of examples of how you should do this. The purpose of this article is to demonstrate how you can return a DataSet asynchronously, which is not something I could find an example of anywhere. Another thing I could not find in my research was how to use asynchronous database calls when you have a datalayer, as opposed to a page or usercontrol that contacts the database directly, and I will provide you with both here.

If you have looked at examples of asynchronous database calls elsewhere on the web before arriving here you have probably become familair with the BeginExecuteReader and EndExecuteReader methods of the SQLClient namespace, but where is the BeginExecuteDataSet method? If you need a dataset, why should that have to be a synchronous request? I could not find any asynchronous methods for the DataAdapter and while I did find ways to call a WebMethod or WebService that returns a dataset asynchronously, why should you have to break your methods that return datasets out to webservices? I also found some examples of how to create delegates or use System.Threading.Thread.Start to create datasets in their own thread, but, according to Mr. Prosise these are worthless in ASP.Net because both of these methods actually steal threads for the same thread pool ASP.Net is using anyway! So by using System.Threading.Thread.Start to create your dataset all you are doing is returning a thread to the threadpool and immediately grabbing another one. So how can you do it?

For this example I borrowed the Datalayer from the Job Site Starter Kit and added some Asynchronous methods to it. Here is my BeginExecuteDataSet method:

       Public Function BeginExecuteDataSet(ByVal callback As System.AsyncCallback, _
          ByVal stateObject As Object, ByVal behavior As CommandBehavior) As System.IAsyncResult   
             Dim res As IAsyncResult = Nothing
             Me.Open()
             res = cmd.BeginExecuteReader(callback, stateObject, behavior)
             Return res
       End Function

But how is that different to BeginExecuteReader? It is not it is exactly the same, I don't see the need to rewrite the DataAdapter class from scratch to support Asynchronous functions when I can simply use a datareader to populate a dataset. The key differences are in the EndExecuteDataSet Method:

       Public Function EndExecuteDataSet(ByVal asyncResult As System.IAsyncResult) As DataSet   
             Dim ds As Dataset = Nothing
             Dim rdr As SqlClient.SqlDataReader = cmd.EndExecuteReader(asyncResult)
             Dim dt As DataTable = New DataTable()
             dt.Load(rdr)
             ds = New DataSet
             ds.Tables.Add(dt)
             Return ds
       End Function

Calling these methods is therefore no different to calling BeginExecuteReader. For example the following code would work from both an .aspx page or an .ascx control.


    Dim db As Classes.Data.DAL
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        'Append async attribute to connection string
        db = New Classes.Data.DAL(String.Concat _
        (ConfigurationManager.ConnectionStrings("MyDB")ConnectionString, ";async=true"))
        db.CommandText = "asyncTest"
        ' Launch data request asynchronously using page async task  
        Page.RegisterAsyncTask(New PageAsyncTask(New BeginEventHandler(AddressOf BeginGetData), _
        New EndEventHandler(AddressOf EndGetData), New EndEventHandler(AddressOf GetDataTimeout), _
        Nothing,True))
    End Sub
    Function BeginGetData(ByVal sender As Object, ByVal e As EventArgs, _
        ByVal cb As AsyncCallback, ByVal state As Object) As IAsyncResult
       Return db.BeginExecuteDataSet(cb, state)  
    End Function
    Sub EndGetData(ByVal ar As IAsyncResult)
        Try
           gv1.DataSource = db.EndExecuteDataSet(ar)
           gv1.DataBind()
        Catch ex As Exception
           lblMsg.Text = ex.ToString()
        Finally
           db.Dispose()
        End Try
    End Sub
    Sub GetDataTimeout(ByVal ar As IAsyncResult)
        db.Dispose()
        lblMsg.Text = "Async connection timed out"
    End Sub

There you have it, a DataSet returned asynchronously, from a Data Access Layer. Download the entire Data Access Layer (zip file contains both Visual Basic and C# versions - 3.05 kb).


Posted by Williarob on Wednesday, December 19, 2007 8:07 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Sample Scrolling Silverlight Video Playlist

 A real world example of this technique can be seen at http://www.thejamesbondmovies.com

TAKE ME TO THE VERSION FOR SILVERLIGHT 2

It only took me about an hour to create this example. I started by dragging all 8 videos into Microsoft Expression Encoder, selected the "Expression" player template and clicked encode. This gave me the full player functionality you see here, play, stop, pause, etc. and by default it simply played all 8 videos one after the other. But I wanted my users to be able to pick and chose which videos to play, so I opened the project in Microsoft Expression Blend 2 September preview and resized the outer, root canvas by setting the height to 593 to give me room to place the thumbnails, and gave it a black background color.

The XAML 

Next I created the arrows that move the playlist left and right. If you have arrows as PNG images you can use them, but I chose to create them using XAML by drawing a white square, filling it with white color converting it to a path, and then using the pen tool to delete a corner and thereby converting it to a triangle which I simply rotated, moved and sized until it looked right. Set the cursor property to "Hand" so that users know it is a button. I then copied it, rotated it to point the other way and moved the second arrow into position on the other side. This gave me the XAML below which appeared just before the closing </canvas> tag.

<!-- Playlist region starts here -->
<!-- Navigation Arrows -->
<Path x:Name="LeftArrow" Opacity="0.74" Width="38" Height="38" Stretch="Fill" Stroke="#FF000000" Canvas.Left="11" Canvas.Top="514" Data="M37.5,0.5 L37.5,37.5 0.5,37.5 z" Fill="#FFFFFFFF" Cursor="Hand" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="134.119"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Path.RenderTransform>
</Path>
<Path x:Name="RightArrow" Opacity="0.74" Width="38" Height="38" Stretch="Fill" Stroke="#FF000000" Canvas.Left="588" Canvas.Top="514" Data="M37.5,0.5 L37.5,37.5 0.5,37.5 z" Fill="#FFFFFFFF" Cursor="Hand" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="314.365"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Path.RenderTransform>
</Path>

Next I dragged the first thumbnail (for movie # 1) onto the area, roughly where you see it now, resized it and moved it into position. I named it "play0", set its opacity to 74% to make an easy rollover effect that you'll see later on; then went into the XAML editor and simply copied the Image element 3 more times, renaming each one in turn to play1, play2, etc. and updating the Source property of each to point to the right thumbnail image. Next I had to move the new images, since they all sat on top of one another, using the basic formula left=previousImageLeft + Image Width + 4 pixels. Then playing with the spacing between them until it was roughly even. Given the width of my thumbnail and the width of the canvas it turned out to be impossible without further resizing and in the end I decided that for this example it was good enough. So I now had my four thumbnail "buttons" making up my playlist and if you can fit all of your items on the screen you can skip ahead to the JavaScript, but if you want to have them scroll keep reading.

I grouped these four images into a canvas - have Blend do this for you by control + clicking on the object names (in this case play0, play1, etc.) in the Objects and Timeline area and then pressing ctl + G or right clicking and choosing Group Into > Canvas. It is better to have blend create the canvas for you as it will update all the canvas.left, canvas.top properties for you. I called this new canvas "playlist1", then using the XAML editor copied it and created "playlist2", naming the objects play4, play5, etc. and updating the Source of each as before. Then I moved this canvas off the screen by simply setting the canvas.Left Property to a value I knew would push it out of sight. Finally, I grouped playlist1 and playlist2 into another canvas, this time calling it "Library".

Using the timeline editor I created a simple animation that moved the "Library" Canvas 613 pixels to the left over the course of 2 seconds. If you have never done this before it is really easy:

The video content presented here requires JavaScript to be enabled and the latest version of the Macromedia Flash Player. If you are you using a browser with JavaScript disabled please enable it now. Otherwise, please update your version of the free Flash Player by downloading here.

As you can hopefully see on the video, you simply click on "Open, create or manage Storyboards", click "Create new", give it a name, make sure "Create as Resource" is checked so that we can access it through code, move 2 seconds into the timeline and add a keyframe to start the recorder, then make your changes. In this case we are changing the Left poperty so that it moves 613 pixels to the left - just far enough to bring the second "page" of buttons onto the screen. Stop the recorder and as you scrub through the timeline you can see the animation or click the play button to preview it.

Making it animate back the other way was a little trickier to do using the IDE, so I simply copied the XAML and changed the values myself.

Now if you have been trying this yourself you might be wondering why your thumbnails can be seen moving underneath, or even on top of the left and right arrows created earlier, while mine do not. The answer is that I have put my "Library" Canvas inside another Canvas called "ClippedCanvas" which has been "clipped", or cropped if you prefer using RectangleGeometry. Everything that falls outside the geometry you provide is hidden, or "clipped." The numbers represent X coordinates, Y coordinates, Width & Height in that order. X & Y in this case are relative to the container canvas ("ClippedCanvas"). So basically I am cropping an area from the top left of where Clipped Canvas begins, 550 pixels wide and 114 high, anything within the canvas that falls outside that region will not be seen. If you click on "ClippedCanvas" in the Objects and Timeline you will see it outlined in Blend and have a better understanding of where it is drawn.

So, the Final XAML for my Playlist region looks like this:

<!-- The outer canvas here is clipped: only the area defined by the rectangle geometry is visible  -->
<!-- This is necessary as when we animate the 'Library' canvas inside it we do not want to see the thumbnails slide under the navigation arrows and off the screen-->
<Canvas x:Name="ClippedCanvas" Canvas.Top="491" Canvas.Left="43" Width="550" Height="90">
<Canvas.Clip>
<RectangleGeometry Rect="0, 0, 550, 114"/>
</Canvas.Clip>
<!-- Animations to move the playlist left and right. They are numbered so that we can call them logically from code -->
<Canvas.Resources>
<Storyboard x:Name="MoveLeft01">
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="Library" From="13" To="-613" Duration="0:0:2" />
</Storyboard>
<Storyboard x:Name="MoveRight02">
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="Library" From="-613" To="13" Duration="0:0:2" />
</Storyboard>
</Canvas.Resources>
<!-- The Library Canvas groups the playlist buttons into a single element that can be easily animated left - right. -->
<Canvas Width="1157.275" Height="82.96" Canvas.Left="0" Canvas.Top="0" x:Name="Library">
<Canvas Width="550.275" Height="82.96" x:Name="playlist1">
<Image x:Name="play0" Opacity="0.74" Width="133.275" Height="82.96" Source="1.png" Stretch="Fill" Cursor="Hand" />
<Image x:Name="play1" Opacity="0.74" Width="133.275" Height="82.96" Source="2.png" Stretch="Fill" Cursor="Hand" Canvas.Left="139"/>
<Image x:Name="play2" Opacity="0.74" Width="133.275" Height="82.96" Source="3.png" Stretch="Fill" Cursor="Hand" Canvas.Left="278"/>
<Image x:Name="play3" Opacity="0.74" Width="133.275" Height="82.96" Source="4.png" Stretch="Fill" Cursor="Hand" Canvas.Left="417"/>
</Canvas>
<Canvas Width="550.275" Height="82.96" x:Name="playlist2" Canvas.Left="607">
<Image x:Name="play4" Opacity="0.74" Width="133.275" Height="82.96" Source="5.png" Stretch="Fill" Cursor="Hand" />
<Image x:Name="play5" Opacity="0.74" Width="133.275" Height="82.96" Source="6.png" Stretch="Fill" Cursor="Hand" Canvas.Left="139"/>
<Image x:Name="play6" Opacity="0.74" Width="133.275" Height="82.96" Source="7.png" Stretch="Fill" Cursor="Hand" Canvas.Left="278"/>
<Image x:Name="play7" Opacity="0.74" Width="133.275" Height="82.96" Source="8.png" Stretch="Fill" Cursor="Hand" Canvas.Left="417"/>
</Canvas>
</Canvas>
</Canvas>

The JavaScript

Code that I added or changed is in bold, the rest is straight from the Encoder's original output.

var curPos = 1; //track the current position of the playlists
var maxPos = 2; //How many pages of clips do we have?
var cVideos = 8; //How many video Clips do we have?
function get_mediainfo(mediainfoIndex) {
switch (mediainfoIndex) {
case 0:
return { "mediaUrl": "Movie1.wmv",
"placeholderImage": "",
"chapters": [
] };
case 1:
return { "mediaUrl": "Movie2.wmv",
"placeholderImage": "",
"chapters": [
] };
case 2:
return { "mediaUrl": "Movie3.wmv",
"placeholderImage": "",
"chapters": [
] };
case 3:
return { "mediaUrl": "Movie4.wmv",
"placeholderImage": "",
"chapters": [
] };
case 4:
return { "mediaUrl": "Movie5.wmv",
"placeholderImage": "",
"chapters": [
] };
case 5:
return { "mediaUrl": "Movie6.wmv",
"placeholderImage": "",
"chapters": [
] };
case 6:
return { "mediaUrl": "Movie7.wmv",
"placeholderImage": "",
"chapters": [
] };
case 7:
return { "mediaUrl": "Movie8.wmv",
"placeholderImage": "",
"chapters": [
] };
default:
throw Error.invalidOperation("No such mediainfo");
}
}
function StartWithParent(parentId, appId) {
new StartPlayer_0(parentId);
}
function StartPlayer_0(parentId) {
this._hostname = EePlayer.Player._getUniqueName("xamlHost");
Silverlight.createObjectEx( { source: 'player.xaml',
parentElement: $get(parentId ||"divPlayer_0"),
id:this._hostname,
properties:{ width:'100%', height:'100%', version:'1.0', background:document.body.style.backgroundColor, isWindowless:'false' },
events:{ onLoad:Function.createDelegate(this, this._handleLoad) } } );
this._currentMediainfo = 0;
}
StartPlayer_0.prototype= {
_handleLoad: function(plugIn) {
this._player = $create( ExtendedPlayer.Player,
{ // properties
autoPlay : true,
volume : 1.0,
muted : false
},
{ // event handlers
mediaEnded: Function.createDelegate(this, this._onMediaEnded),
mediaFailed: Function.createDelegate(this, this._onMediaFailed)
},
null, $get(this._hostname) );
//wire up the rollover and click events for each of our play buttons
for (var i = 0; i < cVideos; i++)
{
var element = plugIn.Content.findName('play' + i);
element.addEventListener("MouseEnter", Function.createDelegate(this,this._rollOver));
element.addEventListener("MouseLeave", Function.createDelegate(this,this._rollOut));
element.addEventListener("MouseLeftButtonUp", Function.createDelegate(this,this._playX));
}
plugIn.Content.findName('LeftArrow').addEventListener("MouseEnter", Function.createDelegate(this,this._rollOver));
plugIn.Content.findName('LeftArrow').addEventListener("MouseLeave", Function.createDelegate(this,this._rollOut));
plugIn.Content.findName('LeftArrow').addEventListener("MouseLeftButtonUp", Function.createDelegate(this,this._slideLeft));
plugIn.Content.findName('RightArrow').addEventListener("MouseEnter", Function.createDelegate(this,this._rollOver));
plugIn.Content.findName('RightArrow').addEventListener("MouseLeave", Function.createDelegate(this,this._rollOut));
plugIn.Content.findName('RightArrow').addEventListener("MouseLeftButtonUp", Function.createDelegate(this,this._slideRight));

this._playNextVideo();
},
_rollOver: function(sender, eventArgs) {
sender.opacity=1;
},
_rollOut: function(sender, eventArgs) {
sender.opacity=0.74;
},
_playX: function(sender, eventArgs) {
var X = Number(sender.Name.substring(4));
this._currentMediainfo = X;
this._player.set_mediainfo( get_mediainfo( X ));
sender.opacity=1;
},
_slideLeft: function(sender, eventArgs) {
switch(curPos)
{
case 1:
break;
default:
sender.findName("MoveRight0" + curPos).Begin();
curPos--;
}
},
_slideRight: function(sender, eventArgs) {
switch(curPos)
{
case maxPos:
break;
default:
sender.findName("MoveLeft0" + curPos).Begin();
curPos++;
}
},

_onMediaEnded: function(sender, eventArgs) {
//window.setTimeout( Function.createDelegate(this, this._playNextVideo), 1000);
},
_onMediaFailed: function(sender, eventArgs) {
alert(String.format( Ee.UI.Xaml.Media.Res.mediaFailed, this._player.get_mediaUrl() ) );
},
_playNextVideo: function() {
if (this._currentMediainfo<cVideos)
this._player.set_mediainfo( get_mediainfo( this._currentMediainfo++ ) );
}
}

First I added a reference to the plug-in the HandleLoad function, as described here. Then, because I had named all of my play buttons sequentially, it was easy to loop though them all adding some event handlers for rollover effects and the click event. Next I added similar event handlers to the navigation arrows. The rollover effect, as hinted at earlier was simply to set the opacity to 100% on mouseover and back to 74% on mouse out.

The click event for the play buttons simply play the selected movie, based on the number parsed from the name of the sender ("play0", "play1", etc.). The navigation arrows call the _slideLeft and _slideRight functions which simply play the animations to move the buttons left and right. if you have more than 2 pages of play buttons, then it gets slightly more complicated, obviously you have to create more animations, and they have to be carefully numbered so that you play the appropriate animation based on which 'page' of buttons you are currently on. Go to TheJamesBondMovies.com and take a look at the StartPlayer.js on that site for a better understanding of how to make this technique work with multiple pages.

Well, that was my solution, I'm sure there are other ways to do this, but I don't think this way is overly complicated and I hope someone finds it helpful.

Download the Project files: PlaylistSample.zip (364.40 kb)

Download the Silverlight 2 version: ScrollingPlaylist2.zip (1.22 mb)


Posted by Williarob on Wednesday, November 21, 2007 12:13 PM
Permalink | Comments (28) | Post RSSRSS comment feed

Use Regex to block specific IP addresses or ranges

Perhaps your feedback page is being hammered by spammers, perhaps your customers are receiving a lot of scam emails from Nigeria, perhaps you are having trouble with stolen credit card information being entered on your site. You have identified some Bad IP addresses you need to block but how do you go about blocking them if you have your site hosted somewhere and you don't have access to the apache or IIS web server directly? I wrote the functions below for just this purpose.

using System;
using System.Data;
using System.Web;
using System.Web.Caching;
using System.Text.RegularExpressions;
namespace BlockIPs
{
public partial class _Default : System.Web.UI.Page
{
public Cache MyCache = HttpContext.Current.Cache;
private static readonly Object lock_object = new Object();
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(isIpBlocked(Request.ServerVariables["Remote_Addr"]));
}
/// <summary>
/// Compares the passed IP address to an external list of Bad IP Addresses
/// </summary>
/// <param name="strIP"></param>
/// <returns>boolean result</returns>
/// <remarks>some of the ips in the block list are like xxx.xxx.0.0 this means all Ips that start xxx.xxx should be blocked...</remarks>
bool isIpBlocked(string strIP)
{
if (!IsValidIP(strIP))
{
return false;
}
String CacheKey = "IPBlocklist";
DataSet DS = (DataSet)MyCache[CacheKey];
if (DS == null)
{
lock(lock_object) //If this file is being hit 1000s times per second only need to make 1 call to the file, the rest will wait until cache is ready.
{
DS = new DataSet();
DS.ReadXml(Server.MapPath("BlockedIPs.xml"));
DS.Tables[0].PrimaryKey = new DataColumn[] {DS.Tables[0].Columns["IP"]};
CacheDependency cd = new CacheDependency(Server.MapPath("BlockedIPs.xml"));
MyCache.Insert(CacheKey, DS, cd, System.DateTime.Now.AddMinutes(10), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null);
}
}
// first check to see if the ip is in the table
if (DS.Tables[0].Rows.Contains(strIP))
{
return true;
}
// split the incoming ip into octets
string [] octets = strIP.Split('.');
// set up some regex patterns
string pattern1 = String.Format(@"^{0}\.{1}\.{2}\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))$", octets[0],octets[1], octets[2]);
string pattern2 = String.Format(@"^{0}\.{1}\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))$", octets[0], octets[1]);
string pattern3 = String.Format(@"^{0}\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))$", octets[0]);
//create our Regular Expression objects
Regex check1 = new Regex(pattern1); //Checks for xxx.xxx.xxx.0
Regex check2 = new Regex(pattern2); //Checks for xxx.xxx.0.0
Regex check3 = new Regex(pattern3); //Checks for checks for xxx.0.0.0
foreach (DataRow dr in DS.Tables[0].Rows)
{
if(IsValidIP(dr["IP"].ToString()))
{
string[] checkOctets = dr["IP"].ToString().Split('.');
if((checkOctets[1] == "0") && (checkOctets[2] == "0") && (checkOctets[3] == "0"))
{
if(check3.IsMatch(dr["IP"].ToString(),0))
{
return true;
}
}else if ((checkOctets[2] == "0") && (checkOctets[3] == "0"))
{
if (check2.IsMatch(dr["IP"].ToString(), 0))
{
return true;
}
}else if (checkOctets[3] == "0")
{
if (check1.IsMatch(dr["IP"].ToString(), 0))
{
return true;
}
}
}
}
return false;
}
/// <summary>
/// method to validate an IP address
/// using regular expressions. The pattern
/// being used will validate an ip address
/// with the range of 1.0.0.0 to 255.255.255.255
/// </summary>
/// <param name="addr" class="success">Address to validate</param>
/// <returns></returns>
public bool IsValidIP(string addr)
{
//create our match pattern
string pattern = @"^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$";
//create our Regular Expression object
Regex check = new Regex(pattern);
//boolean variable to hold the status
bool valid = false;
//check to make sure an ip address was provided
if (addr == "")
{
//no address provided so return false
valid = false;
}
else
{
//address provided so use the IsMatch Method
//of the Regular Expression object
valid = check.IsMatch(addr, 0);
}
//return the results
return valid;
}
}
}

Download the complete ASP.Net 2.0 Solution which also includes the same functions presented as a Visual Basic Webservice and the xml file containing a starter set of known bad IP addresses to block that I found on this site. You could use this technique to check for bad IPs on Application Start in the Global.asax to block visitors to your site completely, or just on specific pages, or prior to processing a credit card transaction, or prior to posting a comment or feedback form, etc., etc.  


Posted by Williarob on Tuesday, November 20, 2007 9:48 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Customizing Expression Encoder Output

I finished building my first 100% pure Silverlight 1.0 application, the result of which can be viewed at http://www.thejamesbondmovies.com, and I wanted to share a few of the techniques I learned. Anyone who has played with Microsoft Expression Encoder will probably recognize the player as coming from one of the built in templates. Expanding the XAML to include my own elements was not too difficult, the Blend 2 September Preview made that quite easy. When I have more time I will describe in detail how I made the scrollable playlist as I could find no tutorials anywhere on how to do that, but for right now I just want to address the basics of Scripting the output. I am by no means a JavaScript Guru, which is perhaps why I struggled a bit to understand exactly how to interact with my new elements, and exactly where to put my code. When you build a new Silverlight application in Blend 2 or Visual Studio the JavaScript that is provided to you as a starting point appears to be quite different to that outputted by the encoder. Visual Studio gives you the following in a file called (by default) Scene.xaml.js:

if (!window.SilverlightJSApplication1)
window.SilverlightJSApplication1 = {};
SilverlightJSApplication1.Scene = function() 
{
}
SilverlightJSApplication1.Scene.prototype =
{
handleLoad: function(plugIn, userContext, rootElement) 
{
this.plugIn = plugIn;
// Sample button event hookup: Find the button and then attach event handlers
this.button = rootElement.children.getItem(0);	
this.button.addEventListener("MouseEnter", Silverlight.createDelegate(this, this.handleMouseEnter));
this.button.addEventListener("MouseLeftButtonDown", Silverlight.createDelegate(this, this.handleMouseDown));
this.button.addEventListener("MouseLeftButtonUp", Silverlight.createDelegate(this, this.handleMouseUp));
this.button.addEventListener("MouseLeave", Silverlight.createDelegate(this, this.handleMouseLeave));
},
// Sample event handlers
handleMouseEnter: function(sender, eventArgs) 
{
// The following code shows how to find an element by name and call a method on it.
var mouseEnterAnimation = sender.findName("mouseEnter");
mouseEnterAnimation.begin(); 
},
handleMouseDown: function(sender, eventArgs) 
{
var mouseDownAnimation = sender.findName("mouseDown");
mouseDownAnimation.begin(); 
},
handleMouseUp: function(sender, eventArgs) 
{
var mouseUpAnimation = sender.findName("mouseUp");
mouseUpAnimation.begin(); 
// Put clicked logic here
alert("clicked");
},
handleMouseLeave: function(sender, eventArgs) 
{
var mouseLeaveAnimation = sender.findName("mouseLeave");
mouseLeaveAnimation.begin(); 
}
} 

Blend 2 gives you an almost identical file called Page.xaml.js:

if (!window.MyProjName)
window.MyProjName = {};
MyProjName.Page = function() 
{
}
MyProjName.Page.prototype =
{
handleLoad: function(control, userContext, rootElement) 
{
this.control = control;
// Sample event hookup:	
rootElement.addEventListener("MouseLeftButtonDown", Silverlight.createDelegate(this, this.handleMouseDown));
},
// Sample event handler
handleMouseDown: function(sender, eventArgs) 
{
// The following line of code shows how to find an element by name and call a method on it.
// this.control.content.findName("Timeline1").Begin();
}
} 

Both of these files make it very easy to pick up your own named elements, and add event handlers to them because the handleLoad function passes the plugIn (visual Studio) or control (Blend 2) argument that can be used to easily find a reference to your own XAML element:

	control.content.findName("MyXamlElement"); //or plugIn.content.findName("MyXamlElement");

But having played with that model a few times and become comfortable with it, you then navigate to your Expression encoder's output location and find as many as six JavaScript files. Obviously, Silverlight.js and MicrosoftAjax.js aren't what you're looking for, PlayerStrings.js is pretty empty, and BasePlayer.js clearly wasn't designed for easy editing as it has been compressed. So that just leaves player.js and StartPlayer.js. I'll come back to player.js in a moment, for it is StartPlayer.js that is the Expression Encoder's equivalent to Scene.xaml.js. 

function get_mediainfo(mediainfoIndex) {
switch (mediainfoIndex) {        
case 0:
return  { "mediaUrl": "MyVideo.wmv",
"placeholderImage": "",
"chapters": [               
] };                                                                
default:
throw Error.invalidOperation("No such mediainfo");
}
}
function StartWithParent(parentId, appId) {
new StartPlayer_0(parentId);
}
function StartPlayer_0(parentId) {
this._hostname = EePlayer.Player._getUniqueName("xamlHost");
Silverlight.createObjectEx( {   source: 'player.xaml', 
parentElement: $get(parentId ||"divPlayer_0"), 
id:this._hostname, 
properties:{ width:'100%', height:'100%', version:'1.0', background:document.body.style.backgroundColor, isWindowless:'false' }, 
events:{ onLoad:Function.createDelegate(this, this._handleLoad) } } );
this._currentMediainfo = 0;      
}
StartPlayer_0.prototype= {
_handleLoad: function() {
this._player = $create(   ExtendedPlayer.Player, 
{ // properties
autoPlay    : true, 
volume      : 1.0,
muted       : false
}, 
{ // event handlers
mediaEnded: Function.createDelegate(this, this._onMediaEnded),
mediaFailed: Function.createDelegate(this, this._onMediaFailed)
},
null, $get(this._hostname)  ); 
this._playNextVideo();     
},    
_onMediaEnded: function(sender, eventArgs) {
window.setTimeout( Function.createDelegate(this, this._playNextVideo), 1000);
},
_onMediaFailed: function(sender, eventArgs) {
alert(String.format( Ee.UI.Xaml.Media.Res.mediaFailed, this._player.get_mediaUrl() ) );
},
_playNextVideo: function() {
var cVideos = 1;
if (this._currentMediainfo<cVideos)
this._player.set_mediainfo( get_mediainfo( this._currentMediainfo++ ) );    
}        
}
 

The first thing I noticed was that unlike the Visual Studio output, the _handleLoad function from Expression Encoder does not pass in a control or PlugIn reference, so how do you get one? Well the way I did it (and I could find no documentation anywhere on how to do this) was simply to add my own (new code is bold):

 StartPlayer_0.prototype= {
_handleLoad: function(control) {
this._player = $create(   ExtendedPlayer.Player, 
{ // properties
autoPlay    : true, 
volume      : 1.0,
muted       : false
}, 
{ // event handlers
mediaEnded: Function.createDelegate(this, this._onMediaEnded),
mediaFailed: Function.createDelegate(this, this._onMediaFailed)
},
null, $get(this._hostname)  ); 
control.Content.findName("MyXamlElement"); 
this._playNextVideo();     
},

 

That's all there is to it. Now go ahead and add your events as before. But wait, so what is player.js for then? Glad you asked. Player.js allows you to override the code in the BasePlayer.js file that so clearly was not meant for editing. For example, suppose you had created an animation in your XAML that made the video screen appear, perhaps from behind theatre style curtains that parted, or from behind some other element. You could override the play() function in BasePlayer.js to play your animation before the video:

Type.registerNamespace('ExtendedPlayer');
ExtendedPlayer.Player = function(domElement) {
ExtendedPlayer.Player.initializeBase(this, [domElement]);  
}
ExtendedPlayer.Player.prototype =  {
play: function() {    
this.get_element().content.findName('OpenCurtains').begin();
ExtendedPlayer.Player.callBaseMethod(this, 'play');
},
stop: function() {    
this.get_element().content.findName('CloseCurtains').begin();
ExtendedPlayer.Player.callBaseMethod(this, 'stop');
},
 	pause: function() {
 		alert('You clicked Pause');
ExtendedPlayer.Player.callBaseMethod(this, 'pause');
} 
}
ExtendedPlayer.Player.registerClass('ExtendedPlayer.Player', EePlayer.Player);

Posted by Williarob on Monday, November 19, 2007 11:21 AM
Permalink | Comments (0) | Post RSSRSS comment feed

RegisterStartUpScript not working

If, like me, you have just spent an hour or so trying to figure out why your simple alert box isn't popping up on page load after registering the script with Page.ClientScript.RegisterStartUpScript; you might find you are missing a form with a runat="server" tag on your page. It doesn't matter where it is, but it does have to be on the page somewhere, in order for the Javascript to be injected into the page. In the end I figured it out the hard way - close examination of pages where it worked vs pages where it didn't work.


Posted by Williarob on Wednesday, October 31, 2007 6:04 AM
Permalink | Comments (7) | Post RSSRSS comment feed

How to Make a Media Element Loop Indefinitely

I was surprised to see that the XAML media element did not have a loop property built into it. Surely having a clip loop is basic functionality? In any case I came up with a JavaScript solution that seemed much simpler to implement than the pure XAML solution offered on the MSDN website

JavaScript Solution

    handleLoad: function(control, userContext, rootElement)
    {
        this.control = control;

        //  Get a reference to your media element (mine is called "MainMovie")         
        this.movie = control.content.findName("MainMovie");
        // Add an Event Listener for the "MediaEnded" Event
       this.movie.addEventListener("MediaEnded", Silverlight.createDelegate(this,this.movieMediaEnded));
    },

    //When the end of the movie is reached, return the movie to the start and play it again
    movieMediaEnded: function(sender, eventArgs)
    {
        sender.Position = "00:00:00";
        sender.play();
    }

See my solution in action at The Daily Prophet Online.

MSDN's Pure XAML Solution

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >

<StackPanel>

<!-- The MediaElement control plays the sound. -->

<MediaElement Name="myMediaElement" >

<MediaElement.Triggers>

<EventTrigger RoutedEvent="MediaElement.Loaded">

<EventTrigger.Actions>

<BeginStoryboard>

<Storyboard>

<!-- The MediaTimeline has a RepeatBehavior="Forever" which makes the media play

over and over indefinitely.-->

<MediaTimeline Source="media\tada.wav" Storyboard.TargetName="myMediaElement"

RepeatBehavior="Forever" />

</Storyboard>

</BeginStoryboard>

</EventTrigger.Actions>

</EventTrigger>

</MediaElement.Triggers>

</MediaElement>

</StackPanel>

</Page>


Posted by Williarob on Tuesday, October 30, 2007 12:27 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Explore Silverlight Showcase XAML & JS

When I first learned about Silverlight I thought it would be compiled into a package rather like Flash does with an SWF file. While it is not impossible to decompile a Flash file and many tools exist to assist you, it does help to ensure that only the most determined individuals can find out how you made that effect and use it on their own site. 

With Silverlight, all you need is something like Firebug and you can easily locate and download the xaml files, and of course a simple View Source reveals where all the JavaScript is. What this means is that if you visit the Silverlight Showcase, find an example that has a player skin you really like, but you are not a designer and couldn't recreate it yourself, or one that does something that you cannot find "how to" examples of anywhere, you can peek under the hood and find out on your own.

All you need to do is this:

  • Open Firebug
  • Look at the rendered HTML
  • Find the object tag with a type of "application/x-silverlight"
  • Look at the "Source" Parameter and then type it into the address bar.

For Example, the Fox movies demo has a source of "XAML/player.xaml" so typing  http://silverlight.net/fox/XAML/player.xaml into your browser will bring up the xaml file, that you can download and open in the editor of your choice. That xaml contains links to the external graphics and you can use the same technique to grab them from the site e.g. http://silverlight.net/fox/images/logobg.png. Download the Javascript files listed in the <HEAD> section, arrange it all into the same folder structure as it was on the server. Finally create a new silverlight project in Blend, replace the page.xaml with the contents of player.xaml and you have an excellent starting point for your own site. 

Silverlight 1.1 sites are a little more complex. Luckily, someone has written an add-in for Lutz Roeder's Reflector that should make it easier.

Obviously you should respect their copyrights and not simply reuse their code and graphics, but my point is that here in the early days of Silverlight, while most of us are floundering around in the dark - very few Silverlight books have been published at the time of writing - the showcase offers us not only inspiration on what can be produced with Silverlight, but gives us everyting we need to see how it was done, so that we as developers can take it to the next level in our own Silverlight applications.


Categories: Silverlight
Posted by Williarob on Friday, October 26, 2007 9:20 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Security Error Running Blend 2 September Preview on Vista

In the welcome screen or on the file menu, click New or Open project. The error appears in an alert box as "Requested registry access is not allowed." While this bug has been reported to Microsoft and they claim to have fixed it for the next release, that doesn't help you if you are getting this error right now. I came up with the following work around on my machine:

Using Process Monitor I was able to find the problem registry key:

HKCR\AgControl.AgControl\CLSID

Read access was denied. When I went to check the permissions I was informed that I could not view the current permissions, but I could change them, so I made myself the owner and added read permissions to the Everyone user for

HKCR\AgControl.AgControl
HKCR\AgControl.AgControl\CLSID
HKCR\AgControl.AgControl\CurVer

As I changed permissions on each key, regedit told me it failed, but if I closed the permissions dialog and reopened it, I could see the new permissions in place and Microsoft Expression Blend was able to open my solutions without error.


Posted by Williarob on Friday, October 26, 2007 9:15 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Move View State to the Server

In his talk "Hidden Gems in ASP.NET 2.0" at Tech Ed 2007 Jeff Prosise showcased a number of really useful but little known features built into ASP.Net 2.0. The one feature that I implemented immediately on all my sites after his talk was his SessionPageStateAdapter class.

This class moves the bulk of the Viewstate text out of the hidden field on the page and into a session variable on the server, and requires remarkably few lines of code. Follow these steps to implement it on your site:

  1. Create  a folder in your application root and name it "App_Browsers"
  2. Add a new XML File to this folder. It doesn't matter what you call it as long as it has a ".browser" extention. I called mine "Default.browser".
  3. Paste in the following code:

    <browsers>
      <browser refID="Default">
        <controlAdapters>
          <adapter controlType="System.Web.UI.Page" adapterType="SessionPageStateAdapter" />
        </controlAdapters>
      </browser>
    </browsers>

  4. Create a new class file (in your App_Code dir for a web site or anywhere in a Web Project) and name it "SessionPageStateAdapter"
  5. Paste in the following code:

    using System;
    using System.Web.UI;
    /// <summary>
    /// Summary description for SessionPageStateAdapter
    /// </summary>
    public class SessionPageStateAdapter : System.Web.UI.Adapters.PageAdapter
    {
      public override PageStatePersister GetStatePersister()
      {
        return new SessionPageStatePersister(this.Page);
      }
    }

That's it! It reduced the viewstate field on this page (at the time of writing) from 9440 characters to just 129 characters!

  


Categories: ASP.Net
Posted by Williarob on Thursday, October 25, 2007 1:15 PM
Permalink | Comments (0) | Post RSSRSS comment feed