.Net Framework Service Pack 1 Postback Problem

I applied the .Net Service Pack 1 to a Windows 2003 server last night and the web application served from it stopped submitting postbacks. When the login page was sent to the client (IE 6 on XP, 98, and 2003 that's all I had to test with at the time), the javascript was preventing the postback from the submit button. The validators all worked, but the page just would not post back. Click the button...nothing. The development machine with XP Pro and the Service Pack 1 did not have the same problems. I ended up rolling back the patch to get things working as it was late and I didn't find any solutions right away, but this morining I found some other possible workarounds.

There is a discussion of the problem and several potential solutions on the Channel 9 site.

Posted: Sunday, October 03, 2004 1:00:42 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1

Custom Text File Publisher for Exception Management Application Block

I needed to create a publisher for exceptions to a text file (XML would be better but the requirement was for text...). I based my publisher on this article I found on C# Corner. But it has one problem associated with using files, that unfortunately loomed large. Concurrency. To a certain extent the code handled the problem by tossing the error into the Event Log if the provider threw an error, but this was not acceptable for my requriement. So I needed to modify the code to handle the issue. Essentially, I modified the overriden Publish procedure to trap the error related to the StreamWriter and then sleep for a set amount of time and try again. After too many failures at writing to the file the error eventually gets written to the event log (so it is not lost).

privateconst int WriteAttempts = 5;
private const int SleepTime = 500;

void IExceptionPublisher.Publish(Exception exception, NameValueCollection additionalInfo, NameValueCollection configSettings)
{

    // Read and set up configuration
    SetConfig(configSettings);

    // Create fileName
    string logFilePath = CreateLogFilePath();

    // Create Message
    string logMessage = CreateLogMessage(exception, additionalInfo);

    int attempts = 0;
    bool written = false;

    // Write the entry to the log file.
    while(!written && attempts <= WriteAttempts)
    {
       
try
       
{
            // Append to file if exists or create new file
           
using ( FileStream fs = File.Open(logFilePath, FileMode.Append,FileAccess.Write))
            {
               
using (StreamWriter sw = new StreamWriter(fs))
                {
                    sw.Write(logMessage);
                    written =
true;
                }
            }
        }
       
catch
       
{
            attempts++;
            Thread.Sleep(SleepTime);
         }
    }
    if(attempts > WriteAttempts)
    {
       
throw(exception);
    }

}

Posted: Thursday, September 09, 2004 4:49:42 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | Security

VBCommenter

After a little consternation, got VBCommenter PowerToy working today. Had a problem after installing that was solved on their message board.

It seems to be very useful, but really slows down compiles (of course I already need more RAM so this doesn't help). Looks like I'll have to let it build XML files separate from regular compiles.

Now I need to get up to speed on XSLT to turn those XML files into something readable for non-techies. My project is getting a new person to help write documentation and I am sure the info I generate with this tool will help her out significantly.

Posted: Monday, April 26, 2004 5:44:06 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | Visual Studio

Viewstate Error and Macintoshes?

The public site I maintain is gathering 500 errors of the dreaded “The viewstate is invalid for this page and might be corrupted” type. Strangely enough, the only users seeing this error are Macintosh users, using IE! The problem is not isloated to a single page, it seems to be happening on any page, but not necessarily every page for that user. In some cases it is clear that the user was getting around fine (they were logged in as members to the site), and later the problem occurred.

I cannot find anything on the web except a solitary Usenet post of a person describing the exact same problem.

Here are the agent strings of the browsers recorded when the error occurred:

Mozilla/4.0 (compatible; MSIE 5.0; Mac_PowerPC)
Mozilla/4.0 (compatible; MSIE 5.17; Mac_PowerPC)
Mozilla/4.0 (compatible; MSIE 5.15; Mac_PowerPC)
Mozilla/4.0 (compatible; MSIE 5.16; Mac_PowerPC)

According to the log files, MacPPC users are just under 1% of the site users, so this problem is clearly not a showstopper, but I hate for any users to have a problem. I am hoping to get a Mac set up to test the problem locally, but the agent string doesn't give me any Mac OS versions....

Posted: Tuesday, April 13, 2004 1:33:07 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ASP.NET

Burned by Trace

I found out the hard way that when using the trace on an ASP.Net page, the interaction of the trace observer is not totally separate from the page. I am working on a page where performance is a big issue, so I have been using Trace extensively to find the weak spots and shore them up. Essentially the page is searching both the Indexing Service and an Oracle database and finding where the two datasets intersect. Because the results are a grid of links, the user can click the links and then use the Back button to return to the results. Of course I was getting the dreaded “Warning: Page has Expired” message after using the back button. The page with the grid is the result of an HTTP post, so the post has expired. Clicking the Refresh button was the only way to get the data back initially. This is bad, as the page already was having performance problems, so rendering it again was not really an option. So I tried setting the page caching options to this:

<%@ OutputCache Duration="120" VaryByParam="None" Location="Client"%>

This should cause the page to cache on the client. No dice. No matter what I set, the http_cache_control header always reported “no-cache” in the trace results. I tried making the header changes in code. Again, no change. Still reporting “no-cache”. The simple solution to the problem is to turn off tracing on the page. Well, it turns out that with Trace=”true” in the @Page directive, the http_cache_control header is set to “no-cache” no matter what. When the Tracing is off, the “Warning: Page has Expired” message disappears and the page is back in control of setting the headers.

Also, a related tidbit I discovered when trying to solve this is that the page timeout is essentially set to infinity when compiling in debug mode.

Posted: Monday, March 29, 2004 1:07:41 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ASP.NET | Indexing Service

Two Good Solutions for Radio Buttons in a Repeater

Basically, the repeater ignores the GroupName attribute and gives each radiobutton in each row a unique name so they no longer operate as a group. This is an acknowledged bug by Microsoft.

A short, elegant solution posted on the Usenet.

A free control to solve the same problem.

I posted about a similar problem previously with checkboxes and the Repeater, but it was a very different solution than the two I found here.

Posted: Thursday, March 25, 2004 7:17:33 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ASP.NET

Query the Indexing Service with Ixsso and ASP.Net

As I am continuing to migrate our ASP app to ASP.Net, it has finally come time to address the Indexing Service search. It's a big feature in the application, and the transition of this piece needs to be seamless. We want to stick with Ixsso for the Indexing Service as opposed to using the Oledb driver. Ixsso is considered to be the faster of the two technologies, even using COM interop (See various of Hilary Cotter's comments in microsoft.public.inetserver.indexserver). Code snippets for using Ixsso with ASP.Net are pretty sparse compared to using Oledb, so I figured I should post mine.  First, I used the IDE to create a reference to the ixsso Control Library dll and let the IDE make the .Net wrapper for the COM object (christened Cisso by the IDE).

Imports Cisso
Imports System.Security.Principal
Imports System.Data.OleDb

 

Private Function GetIndexResults(ByVal Query As String) As DataTable Dim Q As New CissoQueryClass Dim util As CissoUtilClass Dim da As New OleDbDataAdapter Dim ds As New DataSet("IndexServerResults")
Q.Query = Query Q.SortBy = "rank[d]" Q.Columns = "filename, rank, write" Q.Catalog = "query://DocumentServer/Resumes" Q.MaxRecords = 1000 util.AddScopeToQuery(Q, "\", "deep") Q.LocaleID = util.ISOToLocaleID("EN-US") Dim impContext As WindowsImpersonationContext = impersonateAnonymous() da.Fill(ds, Q.CreateRecordset("nonsequential"), "IndexServerResults")
Q = Nothing util = Nothing impContext.Undo()
Return myDS.Tables("IndexServerResults")
End Function

The impersonateAnonymous function is described in a previous post of mine. In our case the anonymous user on the machine has appropriate privledges to query the remote Indexing Service, but the ASP.Net worker process does not so impersonation is in order for the function. That part is probably optional depending on the situation. The rest of it is not very tricky. I tried to fill the DataTable directly without the DataAdapter, but that didn't work. The CreateRecordset function of the CissoQueryClass returns an ADO recordset and I couldn't find a cast that worked. The DataAdapter seems to be doing the casting work during the call to Fill.

Posted: Wednesday, March 03, 2004 8:12:51 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ADO.Net | ASP.NET | Indexing Service

Sharing Timeout Management with ASP.Net and ASP

I am in a situation where my application is mixed classic ASP and ASP.Net. I am currently migrating the classic ASP app page-by-page into ASPX as I am developing new functionality. When I have to touch an old page, I convert it.

In an attempt to make them appear to be one app to the user, I need to manage the timeout of the two applications to appear as one. The classic ASP app was using Session variables for this, but obviously that would not work easily in a mixed environment. Luckily, the ASP app is not heavily dependent on Session variables; it only uses a few that are set during login, and a couple on particular pages. So after logging in the login redirects to a classic ASP page to set the few necessary Session variables, and that page redirects to the home page.

  1. Login using the ASPX login page
  2. Create a cookie containing context
  3. Redirect to asp page setting session variables
  4. Redirect to ASPX home page

Initially, I thought it would be easy to manage the timeouts, just use the cookie generated by the ASPX login page. The classic ASP pages can check the cookie and update its expiration if needed. Of course, it was not that simple. The cookie that is created by the ASPX pages contains the authentication ticket generated by the current http context. Its expiration is essentially the same as the cookie, based on the time set in web.config. The problem lies with the classic ASP pages. They can update the cookie’s expiration, but not the ticket. If a user spends too much time using classic ASP pages, the ticket in the cookie will be expired next time the user visits an ASPX page, even if the cookie is good. The ASPX pages consider a user timed out if either the cookie has expired or the ticket has expired. They will need to login again without actually timing out simply due to using classic ASP pages for a little too long.

My solution results from the ratio of classic ASP to ASPX pages. Right now it is about 5 classic ASP per ASPX page. Using this ratio I changed the authentication ticket timeout to be 5 times as long as the cookie timeout. In order to make this work the ASPX application needs to manage the cookie created by ASPX pages instead of allowing it to occur manually. At the login page, I allow the ASPX cookie to be created as normal using my own CreateAuthCookie.

Public Shared Function CreateAuthCookie(ByVal UserID As String) As HttpCookie
    Const TicketLife As Double = 4
    Dim Timeout As String = ConfigurationSettings.AppSettings("Timeout").ToString
Dim principalText As String Dim buffer As New IO.MemoryStream Dim formatter As New Runtime.Serialization.Formatters.Binary.BinaryFormatter
    formatter.Serialize(buffer, HttpContext.Current.User)
    buffer.Position = 0
    principalText = Convert.ToBase64String(buffer.GetBuffer())
    'create a forms ticket
Dim ticket As New FormsAuthenticationTicket(1, HttpContext.Current.User.Identity.Name, _
DateTime.Now, DateTime.Now.AddMinutes(Double.Parse(Timeout) * TicketLife), _
False, principalText)

    'Encrypt the ticket
    Dim encTicket As String = FormsAuthentication.Encrypt(ticket)
    'This is the cookie used by both ASP and ASP.Net, ASP never retrieves the value
    Dim TheCookie As New HttpCookie(FormsAuthentication.FormsCookieName)
    TheCookie.Path = FormsAuthentication.FormsCookiePath
    TheCookie.Value = encTicket
    TheCookie.Expires =    DateTime.Now.AddMinutes(Double.Parse(Timeout))

    Return TheCookie
End Function

In the Global.asax, I added code to take the cookie, get the ticket from it and use the ticket to set the HttpContext.Current.User and update its expiration if necessary. If the cookie or the ticket has expired here it will automatically redirect to the login page as a function of ASP.Net.

Private Sub Global_AcquireRequestState(ByVal sender As Object, ByVal e As EventArgs) _
Handles MyBase.AcquireRequestState Dim cookie As HttpCookie = Request.Cookies.Get(FormsAuthentication.FormsCookieName) If Not cookie Is Nothing Then 'Cookie found, decrypt the value and recreate the authentication ticket
' and user context for the page
Dim ticket As FormsAuthenticationTicket Try If cookie.Value = "" Then
'For some reason the cookie exists but there is no value,
' remove the cookie try again
cookie.Expires = DateTime.Now.AddMinutes(-15) Response.Redirect("../login/login.aspx", False) Else ticket = FormsAuthentication.Decrypt(cookie.Value) End If FormsAuthentication.RenewTicketIfOld(ticket) Dim buffer As New IO.MemoryStream(Convert.FromBase64String(ticket.UserData)) Dim formatter As New Runtime.Serialization.Formatters.Binary.BinaryFormatter HttpContext.Current.User = CType(formatter.Deserialize(buffer), IPrincipal) Catch ex As Exception
'Could not set the ticket, remove the cookie cookie.Expires = DateTime.Now.AddMinutes(-15) Response.Redirect("../login/login.aspx", False) End Try End If End Sub

All my ASPX pages inherit from my own base page instead of System.Web.UI.Page. In the PreRender event for my base page my CreateAuthCookie gets set again and added to the Response. My base page has already taken the UserID from the HttpContext and set it as a page property.

Private Sub Page_PreRender(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.PreRender Dim AuthCookie As HttpCookie = CreateAuthCookie(Me.User_ID.ToString) Response.Cookies.Add(AuthCookie) End Sub

The classic ASP pages use an include file that just increments the cookie timeout. It also checks the status of the ASP session and resets it if the cookie is still good.


Dim xQString if Request.QueryString <> "" then 
xQString = "?" & Request.QueryString
end if
If
Request.Cookies("EnCoreUser") = "" then
'No cookie, session expired
Session.Abandon
Response.Cookies("EnCoreUser").Expires = DateAdd("n", -15, Now())
response.redirect "../login/login.aspx?ReturnUrl=" & _
Server.URLEncode(Request.ServerVariables("URL") & xQString)
else
'Got aspx cookie, check asp session
if Session("SessionId") <> Session.SessionID then
if Request.Cookies("asp") = "" then
response.redirect "../login/login.aspx?ReturnUrl=" & _
Server.URLEncode(Request.ServerVariables("URL") & xQString)
else
'Cookie is good but classic ASP session ended. Recreate the
' session without forcing another login.
Response.Redirect "../login/verify.asp?userid=" & user_id & "&url=" & _
Server.URLEncode(Request.ServerVariables("URL") & xQString)
end if
else
'All is well, so reset the cookie
Dim sTempx sTempx = CStr(Request.Cookies("EnCoreUser"))
Response.Cookies("EnCoreUser").Path = "/"
Response.Cookies("EnCoreUser").Expires = DateAdd("n", 35, Now())
Response.Cookies("EnCoreUser") = sTempx
end if
end if

As the ratio classic ASP pages to ASPX pages changes, all I need to do is reduce the multiplier to shorten the ticket length accordingly. Eventually, of course, all this work will get tossed out when there are no more classic ASP pages.

Posted: Wednesday, January 28, 2004 5:21:58 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ASP.NET

Forms Authentication Problem

An ASP.Net site we created is having infrequent problems with logins using forms authentication. Essentially what happens is that the user attempts to login and is successful, but then is redirected back to the login page immediately. So it looks like an infinite loop of logins. We have been able to deduce that the cookie is related to the problem. If the user deletes their cookies in IE the problem goes away. The problem is very intermittent, so it is very difficult to reproduce. It is not generating 500 errors or errors in the logs. From extensive Googling, the best I can come up with is the fact that we allowed the cookie to persist across sessions, and the problem is related to that. So I changed the createPersistentCookie parameter to false:

FormsAuthentication.SetAuthCookie(nResult.ToString, False)

Of course, solving the problem is only a wait-and-see in this case, since I can't reproduce the problem directly. I thought our login code was pretty straightforward, letting ASP.Net do as much of the work as possible.


Imports System.Web.Security.FormsAuthentication
....
.... 'txtEmail, txtPassword are textboxes on the form, lblMessage is a label control Public Sub Login_Click(ByVal snd As System.Object, ByVal e As System.EventArgs) _
Handles LoginButton.Click
Dim NotRegistered As String = " is not a registered email address. “ & _
“Please use the Create A Profile link to register." Dim nResult As Integer
If Page.IsValid Then Dim sPassword As String





sPassword = HashPasswordForStoringInConfigFile(txtPassword.Text, "sha1") nResult = LoginResult(txtEmail.Text, sPassword) 'Validate against the database If nResult = -1 Then 'Not a registered user, display error message lblMessage.Text = txtEmail.Text & NotRegistered ElseIf nResult = -2 Then 'Bad password, set error message lblMessage.Text = "The password for " & txtEmail.Text & _
" is incorrect" ElseIf nResult > 0 Then 'Registered user, nResult is their ID number If Request.QueryString("ReturnUrl") <> "" Then 'Redirect to requsted page RedirectFromLoginPage(nResult.ToString, False) Else 'Go to My Jobs by default SetAuthCookie(nResult.ToString, False) Response.Redirect("../MyJobs/My_Jobs.aspx") End If
End if End If End Sub
Posted: Wednesday, January 07, 2004 1:36:54 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ASP.NET | Security | Strange Problems

Checkboxes and the Repeater

I used a repeater for the first time in my app, and in this instance it is essentially a list of people with checkboxes to the left of their names so the user can select one or more of them and perform an operation (like delete, etc.). When using a datagrid, this is really easy because you can bind the person's ID as a hidden column, making it very easy to retrieve the primay key. This is not the case with the repeater or datalist as far as I can tell. To top that off, the asp:checkbox control does not have a value attribute where I could store the primary key, like the HTML version. But I needed the asp:checkbox functionality, not the HTML checkbox.

Again, this seemed like a common problem so I decided to perform a Google search. No good solution jumped out at me, until I read this thread from microsoft.public.dotnet.framework.aspnet.webcontrols. It is basically a guy griping about the lack of a value property for the asp:checkbox and a MS Rep apologizing for it. At this point I realized neither of these guys in the thread were “thinking in .Net” (and neither was I when I started my search). Everything is now object-oriented. If a class does not have a feature you need, add it! So that is what I did, and it works great:



Public Class RepeaterCheckBox
    Inherits CheckBox
    Property Value() As String
        Get
            Return CType(ViewState("value"), String)
        End Get

        Set(ByVal Input As String)
            ViewState("value") = Input
        End Set
    End Property
End Class


The important thing to remember here is to make sure the value is going into viewstate so it persists.

Posted: Wednesday, December 10, 2003 3:02:25 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ASP.NET

Multiple Select Results in ASP.Net

In our new ASP.Net app, we are using one list box that allows multiple-select. Recently, choosing one of the items in the list produced no results. But items would not be in the list if there were no results, so there must be a problem in the SQL statement related to the multiple-select list or the data.  In classic ASP, you would access the multiple items like this:

    Request.Form(i)(j)

where i is the form item and j is the result of the multiple-select. Unfortunately, that does not work in ASP.Net. If you access the form item directly in ASP.Net, like this:

    Request.Form(i)

it provides a comma-delimited list of the multiple form items. It's pretty easy to then split the list on the commas, and that was what we were doing. Ahh, the bad item in the list had a comma embedded in its text. So it was splitting inappropriately and creating a bad SQL statement for fetching results. This was the source of the problem here. There must be a way to access the items individually, but it was not obvious to me. I must have stared at the MSDN docs forever trying to find the answer. I had to try three or four different keyword searches on Google before I found an excellent explanation here.

The results of a mulitple-select list are also a NameValueCollection, just like the Request.Form. Once I found that, it was easy to get at the individual items. The entire collection is accessed like this:

    Request.Form.GetValues(i)

and the individual items are accessed like this:

    Request.Form.GetValues(i)(j)

I was really surprised at how difficult it was to find this information. Both of my favorite ASP.Net books, Essential ASP.Net by Fritz Onion and Programming ASP.Net by Dino Esposito both had no mention of the embedded NameValueCollection. This seems like it would be a common problem for developers using forms, but I did not find the answer to be as obvious.

Posted: Friday, November 14, 2003 1:22:40 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ASP.NET

Temporary Tables in Oracle8i

Our application uses some fairly complex PL/SQL procedures to build reports in temporary tables so that we can access the data as a ref cursor and bind to a grid. We built the tables using “on commit delete rows” when creating the Oracle table. See DBASupport.com for a quick explanation of Oracle's temporary tables. But calling commit inside the PL/SQL package did not delete the rows as implied by the Oracle documentation. The new data on subsequent calls simply added to what already existed in the table. We are using an Oracle Provider version of the Data Access Application Block, and called the stored procedure like this:

DataResults = Daab.ExecuteDataTable(CommandType.StoredProcedure, ProcName, ProcParametersArray)

To solve the problem, we wrapped the call to the stored procedure in a transaction, even though the procedure only issued select sql statements.

Dim FakeTransaction As OracleTransaction

FakeTransaction = Conn.BeginTransaction(IsolationLevel.Serializable)
DataResults = Daab.ExecuteDataTable(FakeTransaction, CommandType.StoredProcedure, ProcName, ProcParameters)
FakeTransaction.Rollback()

Oddly enough, calling .Commit() did not work as implied by the “on commit delete rows“ command added when creating the table. The data persisted in the table. Calling .Rollback() worked though, as I would expect for any transaction. I was unable to find documentation to tell if there is any perfomance drawback to using IsolationLevel.Serializable versus IsolationLevel.ReadCommitted or IsolationLevel.Unspecified as all three give the desired effect on the temporary table.

Posted: Tuesday, November 04, 2003 7:06:10 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ADO.Net

Data Access Application Block and Oracle

I read about the Data Application Block yesterday and was intrigued, although for the life of me I cannot remember where I read about it originally to pass along some credit. So I checked it out and it is very nice and all, but alas my project uses Oracle not SQL Server. Initially I thought about porting it to Oracle, but I figured that it has already been done. So I did a little Googling and voila!, Microsoft themselves had already done the work in the Nile 3.0 demo application. Sadly, it was in C# and my current project is using Oracle and VB.Net. So now I am back to porting.

Posted: Friday, September 19, 2003 12:43:39 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ADO.Net | Oracle

Query a Novell LDAP server with VB.Net

Once again, I am amazed at how simple tasks have become using .Net. Something I thought would be complex turns out to be completely handled by the framework. Kudos to the .Net team.

For my current project, I need to validate that a person is an internal employee before allowing them continued acccess to the web site. My company uses a Novell infrastructure, and luckily has an LDAP server that I can access for employee validation.

Luckily, Novell provides a resource for developers to begin working with LDAP at the Novell Developer Labs. You can create an account and query their server for free. So that is where I started, but eventually the Novell network admins in my company got around to my request and I was able to use the code almost without modification against our internal server.

Here is my quick console application I created to test against the Novell Developer Lab LDAP site (my container name is Fender and my login is admin.  The only modifications I had to do to get it to work with my company's LDAP server was to learn the container names and the fields I could query.  

Imports System.DirectoryServices

Sub Main()
Dim
ds As New DirectorySearcher
Dim resultset As SearchResultCollection
Dim result As SearchResult
'Return the securityEquals field and the cn field
Dim ResultFields() As String = {"securityEquals", "cn"}

With ds
    'Set the container I want to search (.admin.Fender.user.novell)
    .SearchRoot = New DirectoryEntry(
LDAP://192.108.102.215/ou=Fender,ou=user,o=novell)
    'Use the array set above for return fields
    .PropertiesToLoad.AddRange(ResultFields)
   
'Set a filter/query
    .Filter = "cn=ad*"
End With

Try
  
'Perform the search

   
resultset = ds.FindAll()
    If resultset.Count > 0
Then
       
For Each result In resultset
            Console.WriteLine(result.Properties("securityEquals")(0))
       
Next
    Else
        'No results
       
Console.WriteLine("No Data Found")
    End If

Catch ex As Exception
   Console.WriteLine("Error: ")
   Console.WriteLine(ex.Message)
End Try

End Sub

Posted: Friday, September 12, 2003 2:43:44 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | Novell | Security

Excel as a Web Service Client

As a small project, we were tasked with creating a data feed from a text report genereated by JD Edwards (not OneWorld, an older version) and our application. Instead of going the FTP and text file parsing route, I decided to try our first venture into a web service. Since the JD Edwards group were using Excel 2000 to manipulate the report before sending it to our system, I figured a VBA macro could send the data for them, using the XMLHTTP object.

'Notes: Using late binding so the project won't need explicit references
'       Using MSXML 2.5 object model to be sure it will run on most PCs
Public Sub SendDataToEnCore()
    Dim oXML As Object
    Dim oDom
As Object
    Dim oNode
As Object
    Dim sXML As String
    Dim nResult As Integer
    Dim sResponse
As String
     
    On Error GoTo Handler
    Set oXML = CreateObject("Microsoft.XMLHTTP")
    Application.Cursor = xlWait 'Change the cursor to a wait cursor
    Application.ScreenUpdating = False   'Stop screen redraw
    With oXML
         'Call the service, it only takes one parameter, the XML string

        .Open "POST", & _
       
"http://test.com/Redeployment.asmx/Redeployment_Update", False
       
        'The following line is necessary for the web service to recognize the post
        .setRequestHeader "Content-Type", "application/x-www-form-urlencoded"

        Application.StatusBar = "Waiting for a response..."
        'Create XML is a function that loops the spreadsheet building an XML string
        .Send CreateXML
 
   End With
   
    sResponse = oXML.responseText
    With Application
        .Cursor = xlDefault
        .StatusBar = ""
        .ScreenUpdating = False
    End With
    Set oDom = CreateObject("MSXML.DOMDocument")
    oDom.loadXML (sResponse)
   
    If oDom.hasChildNodes Then
        'Display the resulting message from the web service
        Set oNode = oDom.documentElement.firstChild
        nResult = MsgBox(oNode.Text, vbInformation, "EnCore Data Transfer")
    Else 'No response at all
        MsgBox ("The JDE upload failed. Please contact Development
for assistance.")
   
End If
    Set oXML = Nothing
    Set oDom = Nothing
    Set oNode = Nothing
    Exit Sub
Handler:
    With Application
        .Cursor = xlDefault
        .StatusBar = ""
        .ScreenUpdating = False
    End With
    MsgBox Err.Description
    Set oXML = Nothing
    Set oDom = Nothing
    Set oNode = Nothing
End Sub

So far this has worked well for us. One of the issues we encountered was URLEncoding the XML string before sending it. Otherwise, it just won't parse properly on the web service end. Here is an abbreviated version of the web service function that is called by the above Excel VBA function.

Public Function Redeployment_Update(Byval sInput As string) As String

    Dim dt As DataTable
   
dim n as Integer

    sInput = cstr(sInput)

    n = sInput.Length
    If n <= 0 Then
       
return "File contains no data"
   
End If

    ' Load the input XML string into a DataTable
    dt = LoadDataTable(sInput)

   
Dim drCurrent As DataRow
    For Each drCurrent In dt.Rows
       
ProcessRow(drCurrent) 'Our custom function to parse a row
    next

   
return "File received OK. Characters Received=" & n

End Function

Posted: Monday, September 08, 2003 1:23:20 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | Office

Impersonation

All right, now that I have done a bunch of talking, how about some code? I stumbled across this recently while pouring through MSDN. The code lets you impersonate any other user, provided you know the credentials. In our case we needed to become the IIS user so we could access files on a remote file server. In our situation, there are multiple web applications within our domain, so the network admin has set up the IIS sites to all use a common domain-wide anonymous user, so it is easier for him to manage permissions. As we are the first .Net project, the ASPNet user has no rights whatsoever on the network. We talked with the admin and he was not interested in giving the ASPNet users from a bunch of different web servers rights to other network resources. We looked at changing the ASPNet user credentials Machine.config, but this broke debugging locally immediately. So I looked into impersonation and found out how to impersonate the IIS user in code:

Imports System.Security.Principal

Function impersonateAnonymous() As WindowsImpersonationContext

     'Grab the current Http context
    
Dim context As HttpContext = HttpContext.Current

    'Set up a Service Provider based on this context
     Dim
iServiceProvider As iServiceProvider = CType(context, iServiceProvider)

     'Create a type which represents an HTTPContext
     Dim
httpWorkerRequestType As Type = GetType(HttpWorkerRequest)

     'Get the HttpWorkerRequest service from the service provider
     Dim
workerRequest As HttpWorkerRequest = _
         
CType(iServiceProvider.GetService(httpWorkerRequestType), HttpWorkerRequest)

     'Get the token passed by IIS from the workerRequest service
     Dim
ptrUserToken As IntPtr = workerRequest.GetUserToken()

     'Create a Windows Identity from the token
     Dim
winIdentity As New WindowsIdentity(ptrUserToken)

    'Send back the IIS identity
     Return
winIdentity.Impersonate

End Function

To use the function, simply call it like so before the code that needs proper permissions:

Dim impContext As WindowsImpersonationContext = impersonateAnonymous()

Now the subsequent lines of code operate in the context of the user assigned to IIS. And then when you are done impersonating:

impContext.Undo()

I based this function on some C# code I found in a Patterns & Practices document on MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/thcmch10.asp

Posted: Tuesday, September 02, 2003 6:00:54 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ASP.NET | Security

Migrating our ASP app to .Net

We are migrating an ASP app to ASP.Net, but not all at one time as my business customers within the company won't pay for that kind of time all at once. So we are migrating piece-by-piece as we develop new functions within the app. Our plan is to develop all new pages using .Net, and convert classic ASP pages as we have to touch them due to new development. To get the remaining pages converted, we are just going to squeeze them in as often as possible along with the new development, mostly on a one or two at a time basis. So probably of the next 6-12 months the ASP and ASP.Net applications will coexist and appear to the users to be one application.

Right now, we have done what we considered the basic conversion necessary to implement our plan:

  • Convert the login process to .Net. We are using Forms authentication since users are checked from our own database. We may migrate this to LDAP sometime in the future. My company uses Novell Netware for network login and GroupWise for email. I would love to hear from anyone who has a web app that can create appointments in GroupWise!!
  • Move frequently used Session variables to cookies. Luckily, we were not using lots of Session variables in the classic ASP. We encrypt all the data written to the cookies so spoofing is harder. We are currently not a web farm, but that is also in our future so the Session variables need to go anyway.
  • Create a common time-out scheme between the applications using a cookie. The ASP app times out after 35 minutes of inactivity for what the HR folks call “security“, as the app contains lots of personal information about both employees and non-employees.
  • Convert common functions to .Net (like checking user roles, encryption, database access, search engine, etc.)
  • Convert ASP includes for page structure into .ascx files.

Of course, none of this has gone into production yet (sigh...). It is on the test sever and hopefully will roll out by the end of the month.

Posted: Tuesday, September 02, 2003 5:22:45 PM (Eastern Standard Time, UTC-05:00)  #    Comments - Trackback
.Net 1.1 | ASP.NET | Novell | Oracle