Thursday, 11 September 2008

Slow web pages - updating some bits after the page has loaded

On one of my websites, I was finding that a particular database operation was taking a long time to execute. This in turn meant that the web pages were loading slowly as the user's browser was waiting for my server to return all of the data to display.

Some of this data was not needed to be on the page straightaway, so I needed to investigate if I could make a JavaScript update to the page after it had loaded.

I'm not that into JavaScript, but I found this wasn't as hard as I feared.

Firstly, I created a new Ajax enabled website. I won't go into the details here, but this can be done through Visual Studio.

Long running service

Next, I created a web service (WebService.asmx) which returned the data string - this can be seen below. The really important line is the <System.Web.Script.Services.ScriptService()> entry, as this indicates it can be called from Ajax. Note I've added an artifical 2 second sleep in the HelloWorld web method, so you can see the delay when you run this.

<%@ WebService
Language="VB" Class="WebService" %>


Imports System.Web

Imports System.Web.Services

Imports System.Web.Services.Protocols


<System.Web.Script.Services.ScriptService()> _

<WebService(Namespace:="http://blah.blah.com/")> _

<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _

Public Class WebService


Inherits System.Web.Services.WebService


<WebMethod()> _
Public Function HelloWorld() As String

System.Threading.Thread.Sleep(2000)


Return "Hello World"

End Function

End Class

Calling Page

A default page (default.aspx) was made, as shown below. Note there is a call to some JavaScript in the page header and some changes to the ScriptManager tag. This page will be run and shows the results of the webservice call.

<%@
Page Language="VB" AutoEventWireup="false"
%>


<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">
<title>Untitled Page</title>


<script
language="javascript"
type="text/javascript"
src="JScript.js">


</script>

</head>

<body>
<form id="form1"
runat="server">

<asp:ScriptManager
ID="ScriptManager1"
runat="server">

<Services>

<asp:ServiceReference
Path="~/WebService.asmx"
/>

</Services>

</asp:ScriptManager>

<div id="div1">

</div>

</form>

</body>

</html>

You could include the JavaScript in the header programmatically if you don't want it in the header. In my website, it's done as below as the service only needs to be called for some of the pages.

If (Not Page.ClientScript.IsClientScriptIncludeRegistered("jsfile")) Then

Page.ClientScript.RegisterClientScriptInclude(Me.GetType(), "jsfile", ResolveClientUrl("~/jsfile.js"))

End
If

The ServiceReference indicates the location of the WebService (which happens to be in the root of the web site)

JavaScript File

Finally, the JavaScript file is used to call the webservice and write the output to the 'div1' tag:

<!--

function pageLoad() {

ret = WebService.HelloWorld(OnCompleteOne, OnError, OnTimeOut);

}

function OnCompleteOne(result) {

document.getElementById("div1").innerHTML = result;

}

function OnTimeOut(result) {

document.getElementById("div1").innerHTML = 'TimeOut!';

}

function OnError(result) {

document.getElementById("div1").innerHTML = 'Error!';

}

// -->

The JavaScript above includes functions for error and timeout situations, so I could return different information in these two instances.

Last words

Believe it or not, that was it – I was very surprised it was so easy! I then started to adapt it to my situation, which included the slow database call in the web service.

Friday, 14 March 2008

An AJAX control to see search results as you type

I have a web form which displays search results. When I type in a new search string, press a button, it then updates the results.

I needed to change this so that, as I type, the keystrokes cause the search to be run immediately and so the more I type, the results are filtered more and more. This required use of the AutoPostBack method and a check for the keyup() event of text input.

Example:
Type 1 (search for 1*)
Add 2 (searches for 12*)
Add 3 (searches for 123*)
etc.

I did have some code from Alessandro Gallo, but a far better solution is to have the code in a separate project.

Fortunately, whilst browsing for some other function, I came across this post which nicely does the job. It is available as a project and provides a delayed postback so that you can specify the time after the keystroke that the related function is run.

Wednesday, 5 September 2007

Authenticating against Active Directory for Forms Authentication

The code snippet below is used on some private sites (not exposed to the internet) which enables a windows forms web page to allow input of an active directory username and password and authenticate. The machine hosting the web page is a member of the domain (or trusts the domain).

It's probably not ultra secure, but fulfills a purpose. Beware of some word wrap below.

Imports Microsoft.VisualBasic
Imports System.Security.Principal

Namespace UsefulASPNET

Public Class Security
Private Shared LOGON32_LOGON_NETWORK As Integer = 3
Private Shared LOGON32_PROVIDER_DEFAULT As Integer = 0
Private Declare Auto Function LogonUser Lib "advapi32.dll" (ByVal lpszUsername As String, ByVal lpszDomain As String, ByVal lpszPassword As String, ByVal dwLogonType As Integer, ByVal dwLogonProvider As Integer, ByRef phToken As IntPtr) As Boolean

Public Shared Sub ADlogin(ByVal username As String, ByVal password As String, ByVal cookieEnable As Boolean)
Dim encodedUser As String = HttpUtility.HtmlEncode(username)
Dim myDomain As String = "DOMAIN"

If impersonateValidUser(encodedUser, mydomain, password) Then
FormsAuthentication.RedirectFromLoginPage(encodedUser, cookieEnable)
End If
End Sub

Private Shared Function impersonateValidUser(ByVal userName As String, ByVal domain As String, ByVal password As String) As Boolean
return LogonUser(userName, domain, password, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, IntPtr.Zero)
End Function
End Class
End Namespace

On the ASP.NET Web Form, there are two items:
  1. First, the Login Control - all this has is the 'OnAuthenticate' parameter set to 'Login1_Authenticate'
  2. The subroutine itself references the code above:

    Protected Sub Login1_Authenticate(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.AuthenticateEventArgs)
    UsefulASPNET.Security.CODElogin(Login1.UserName, Login1.Password, Login1.RememberMeSet)
    End Sub