Archive for the 'VB.NET' Category

Problems with SQLServer Session State

Thursday, October 15th, 2009

Intro. In my last entry I discussed changing the session state mode from the default InProc which stores Session variables in memory to SQLServer which stores Session variables in a sql database.

The Problem. I had a lot of problems after doing that. Data that I was storing in session variables wasn't being saved. It turns out that the sql session does not work exactly like the memory session, although I never saw this documented anywhere.

The Solution. After diagnosing it and trying things for a few days, I ended up writing a program to store/retrieve variables to/from my own sql table.

By the way, here are some electronics components we have for sale on our website: LMH6645MF, EEU-FC1V101, ZHB6718TA, IRFI9Z34GPBF, CD74HC109E, NLV32T-100J-PF, 1N5242B-T.

Change to SQLServer Session State

Wednesday, September 16th, 2009

Intro. One of our subscription websites saves quite a bit of information in Session variables. This website is now ready to go to beta test, so it is time to change the session state mode from the default InProc which stores Session variables in memory to SQLServer which stores Session variables in a sql database.

The Problem. I followed the instructions I found on msdn.microsoft.com, but that resulted in several errors sending me to google to search for possible solutions. One error that I got was:

Failed to login to session state SQL server for user 'TESTSERVER\ASPNET'

where TestServer is our test webpage server. This error reminded me that TestServer is using IIS 5, so I moved the test to our production webpage server, Laurel, which is running IIS 6. The new error was:

Failed to login to session state SQL server for user 'NT AUTHORITY\NETWORK SERVICE'

That's the IIS 6 error, and it was solved by setting up the sessionState tag correctly, as shown below.

The Solution. Here are the steps that worked for me. Note this usage: our webpage server is Laurel, sql database server is Hardy, MyUser is an existing user on Hardy with db_owner access to the databases used by the new website, MyPass is MyUser's password. Also note that our web.config does not use impersonation and that our authentication mode="Forms".

1. Open a command window on Laurel, change directories, and run the setup:

    cd C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727

    aspnet_regsql.exe -S Hardy -E -ssadd

This created a database on Hardy named ASPState which contains all the stored procedures needed to do SQLServer session state. The data will actually be stored in tempdb, a system database.

2. We already had this in the web.config within the system.web and configuration tags:

< sessionState timeout="60" />

So, I changed it to:

< sessionState timeout="60" mode="SQLServer" sqlConnectionString="Data Source=HARDY;User Id=MyUser;Password=MyPass;" />

3. I used SQL Server Management Studio on Hardy to go to Security | Logins | MyUser | Properties | User Mapping. I checked the boxes for ASPState and db_owner, and tempdb and db_owner, and clicked OK.

I browsed to the webpage and everything worked fine.

Fixing My PayPal "Add To Cart" Button

Tuesday, August 25th, 2009

Intro. I previously blogged about how to create a PayPal "Add To Cart" button at runtime. We use this button on our Buy Now pages. To see an example, click on the following link, and then click any of the Buy Now links:

http://www.edxelectronics.com/PartSearch/PartSearch.aspx?SearchPart=AD5

On this page you'll see the "Add To Cart" button which is created at runtime.

The Problem. This week our users started reporting the following error from paypal.com after they clicked our "Add To Cart" button:

"Sorry — your last action could not be completed"

Yikes! Nobody can buy our products online.

A bunch of googling revealed that PayPal did an upgrade and changed some urls that we were using. And they didn't even tell us!

The Solution. We changed our runtime generated url's from:

    https://www.paypal.com/cart/add=1 . . .

to:

    https://www.paypal.com/cgi-bin/webscr?cmd=_cart&add=1 . . .

and everything went back to normal.

Cross-thread operation not valid

Tuesday, September 30th, 2008

Intro.  I am upgrading some old programs from VS2003 to VS2005.  These are old Windows apps that run either continuously or on a scheduled basis with little user involvement.  They have escaped upgrading up until this point because they work.

The Problem.  After upgrading a program I got this error when I ran it:

Cross-thread operation not valid: Control 'TextBox1' accessed from a thread other than the thread it was created on.

The program did have "Imports System.Threading" in it, but only so it could use "Thread.Sleep(x)".  The program had no intention of being multi-threaded – it thought it was single-threaded.  I removed these lines as a test, but the error did not go away.

The Solution.  The program also contained "Imports System.Timers" and "Dim oTimer As New System.Timers.Timer" and it turns out that this timer runs on a different thread than the rest of the program.  To avoid the error, add this line where you initialize the timer:

oTimer.SynchronizingObject = Me

 

Access DotNetNuke Passwords From Second App

Wednesday, April 2nd, 2008

Intro.  We have one website based on DotNetNuke (FirstSite) with some functions that are available only to the paid subscribers. We have another website (SecondSite) being written in vb.net that needs to be password protected.

The Problem.  The usernames and passwords are stored in the DotNetNuke table aspnet_Membership in an encrypted format (this is optional, but we want encrypted for security).  How can SecondSite access the usernames and passwords of FirstSite in a clear text format for its login page?

The Solution.  The password encrypt/decrypt is handled by the asp.net framework, so I tapped into that.  I added my data source info to my SecondSite's web.config within the < connectionStrings> tag.  I just copied this from FirstSite's web.config (note that I added a space after every "<" so that this editor wouldn't interpret my html):

< add name="SiteSqlServer"
    connectionString=" (your connection string) "
    providerName="System.Data.SqlClient"/>

Then I added these lines within the < system.web> tag.  Again, I just copied these from FirstSite's web.config:

< machineKey (your machine key line) />
< membership defaultProvider="AspNetSqlMembershipProvider"  
    userIsOnlineTimeWindow="15">
   < providers>
      < clear/>
      < add name="AspNetSqlMembershipProvider"
         type="System.Web.Security.SqlMembershipProvider"
         connectionStringName="SiteSqlServer"
         enablePasswordRetrieval="true"
         enablePasswordReset="true"
         requiresQuestionAndAnswer="false"
         minRequiredPasswordLength="7"
         minRequiredNonalphanumericCharacters="0"
         requiresUniqueEmail="false"
         passwordFormat="Encrypted"
         applicationName="DotNetNuke"
         description="Stores and retrieves membership data
         from the local Microsoft SQL Server database"/>
   < /providers>
< /membership>
 

Then I added these lines to SecondSite's login.aspx.vb:

Dim aspnetUser As System.Web.Security.MembershipUser = _
    System.Web.Security.Membership.GetUser(txtUsername.Text.Trim)
If Not aspnetUser Is Nothing Then
    Password = aspnetUser.GetPassword()
End If
If Password <> txtPassword.Text.Trim Then DoInvalidLogin()

Works like a champ!

 

Convert XML Object to DataTable

Tuesday, March 11th, 2008

Intro.  I have a legacy program that returns an object containing data in XML format.  A new requirement came down for this data to be shown in a grid on a new webpage. 

The Task.  Write a new webpage that calls the legacy program and then uses the resulting XML Object as the datasource for a gridview.

What Didn't Work.  I tried to cast the XML Object (MyXMLObject) to a DataSet or a DataTable using CType, but that just gave me this error:

System.InvalidCastException: {"Unable to cast COM object of type 'System.__ComObject' to class type 'System.Data.DataSet'. Instances of types that represent COM components cannot be cast to types that do not represent COM components; however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface."}

The Solution.  The first helpful thing that I ran into was this function:

Microsoft.VisualBasic.Information.TypeName(MyXMLObject)  

which return the name of the data type.  For me, it returned "IXMLDOMSelection".  MSDN gave me the members and related information.  My key discoveries were that MyXMLObject.item(0) contains the schema, and MyXMLObject.item(1) contains the data.  Here is my working code:

Dim MyXmlDataDoc As New System.Xml.XmlDataDocument
Dim sr As System.IO.StringReader
'First fetch and load the schema
sr = New System.IO.StringReader(MyXMLObject.item(0).xml)
MyXmlDataDoc.DataSet.ReadXmlSchema(sr)
'Then fetch and load the data.
sr = New System.IO.StringReader(MyXMLObject.item(1).xml)
MyXmlDataDoc.DataSet.ReadXml(sr)
'Attach the data to the grid.
Me.grdResultsGrid.DataSource = MyXmlDataDoc.DataSet.Tables(0)
Me.grdResultsGrid.DataBind()

See an example of the resulting grid here:

http://www.edxelectronics.com/PartSearch/PartSearch.aspx?SearchPart=LM393

 

Set Focus to Target Web Page

Friday, September 28th, 2007

Intro.  I have a web page that displays a gridview of sales order information for the user.  The user can click on the sales order number (sono) to display more information about any order.  The original code opens a new web page (target="_blank") for each click.

The Problem.  IE7 takes too long to load.  Users that want to look at the details for several orders have to suffer the long load time for each click.

What Didn't Work.  The original gridview had a HyperLinkField under the < Columns> tag (note that I added a space after every "<" so that this editor wouldn't interpret my html):

< asp:HyperLinkField DataNavigateUrlFields="sono"
    DataTextField="sono" HeaderText="Order Number"
    SortExpression="sono" Target="_blank"
    DataNavigateUrlFormatString="orderdetails.aspx?sono={0}" >
    < ItemStyle HorizontalAlign="Center" />
< /asp:HyperLinkField>

I replaced Target="_blank" with Target="OrdDetail".  This made the browser reuse the same web page window for every click instead of opening a new page.  Very fast!

However, the target page does not receive focus after the first use.  So, the question now is how to set focus to the target web page, or how to bring the target web page to the front.

The older DataGrid component used to allow javascript in the DataNavigateUrlFormatString field, so I tried setting it to:

"javascript:window.open('orderdetails.aspx?sono={0}')"

This didn't work.  The order numbers were no longer links.  I found a couple of blogs that said that this was a known Microsoft bug.

The Solution.  What finally worked was replacing the HyperLinkField with a TemplateField:

< asp:TemplateField HeaderText="Order Number" SortExpression="sono">
    < ItemStyle HorizontalAlign="Center" />
    < ItemTemplate>
        < a href="javascript:w=window.open
            ('orderdetails.aspx?sono=< %# Eval("sono") %>',
            'OrdDetail','width=100,height=100');
            w.focus();">< %# Eval("sono") %>
        < /a>
    < /ItemTemplate>
< /asp:TemplateField>

The javascript calls the orderdetails.aspx page, passing the sales order number (sono), and using the same window (OrdDetail) over and over, and then calls w.focus to bring the target page to the front.  Very fast, and very cool!

 

Stored Procedure, Temp Table, and Fill

Wednesday, June 20th, 2007

Intro. Management purchased a new piece of software which came with a SQL Server database.  I needed to write a program to extract data from the database to use on our website.  The database includes many well-named stored procedures, so I used a code snippet that I use often to call a stored procedure to fill a table with data that I need to manipulate before displaying.

Dim dr As DataRow
Dim dt As New DataTable
Dim conn As New OleDbConnection(MyConnectionString)
Dim da As New OleDbDataAdapter("MyStoredProcedure", conn)
da.SelectCommand.CommandType = CommandType.StoredProcedure
Dim sparam0 As OleDbParameter
sparam0 = New OleDbParameter
da.SelectCommand.Parameters.Add("@MyParam", sparam0)
da.SelectCommand.Parameters("@MyParam").Value = "ALL"
da.Fill(dt)
I = 0
While I < dt.Rows.Count
    dr = dt.Rows(I)
    'Insert the processing for each row here.
    I = I + 1
End While

The Problem. The datatable never got filled.  I could connect using the same connection string and run the stored procedure from Visual Studio, and this snippet worked on any of the stored procedures that I wrote, so what was different about the new stored procedures?

The Solution.  The new stored procedures used Temp Tables and did not include "SET NOCOUNT ON".  This means that the stored procedures first pass back the count of rows and then the select results.  Why this happens only if the stored procedure uses Temp Tables, I don't know, but it means that my da.Fill(dt) never sees the rows from the select.  Including "SET NOCOUNT ON in the body of the stored procedure fixed my problem, but I did not want to modify the new stored procedures. 

The real solution was to swap out my datatable for a dataset.

Dim dr As DataRow
Dim ds As New DataSet
Dim conn As New OleDbConnection(MyConnectionString)
Dim da As New OleDbDataAdapter("MyStoredProcedure", conn)
da.SelectCommand.CommandType = CommandType.StoredProcedure
Dim sparam0 As OleDbParameter
sparam0 = New OleDbParameter
da.SelectCommand.Parameters.Add("@MyParam", sparam0)
da.SelectCommand.Parameters("@MyParam").Value = "ALL"
da.Fill(ds)
I = 0
While I < ds.Tables(0).Rows.Count
    dr = ds.Tables(0).Rows(I)
    'Insert the processing for each row here.
    I = I + 1
End While

I don't know what the dataset does with the count of rows, but the first table has the select results that I need.

 

Search Engine Friendly Buttons

Thursday, May 10th, 2007

Intro.  I have a page on our website (http://www.edxelectronics.com/) that displays information about an electronic part.  The information is passed in to a common page template.  At the bottom of the page is a link to a page allowing the user to request a quote for this part.

The Task.  I wanted to replace the link with a search engine friendly button.  Here were my requirements:

  • The url for the link is created at runtime.
  • The url is easily followed by any search engine.
  • The url is shown in the status bar.
  • The text for the link is created at runtime (eg.  Submit RFQ for Part: ABC123).  This is to give the search engine another instance of the part number to index.
  • The link should look and feel like a button.  More users will click on a good looking button than a text link.

The Solution.  I added this table to my page (note that I added a space after every “<" so that this editor wouldn't interpret my html):

    < asp:Table ID="tblButtons" runat="server" width="600">
    < /asp:Table>

Note the ID and the runat.

I created 2 button images (plain and mouseover).  Then I added this code to the Page_Load event:

Dim tRow As New TableRow()
Dim tCell As New TableCell()
tCell.Text = "< a href='" _
    + "../SubmitRFQ/" _
    + Request.QueryString("partno").Trim _
    + ".aspx' onmouseover='document.btnRFQ.src=" _
    + Chr(34) + "../images/SubmitRFQOver.bmp" + Chr(34) _
    + "' onmouseout='document.btnRFQ.src=" _
    + Chr(34) + "../images/SubmitRFQ.bmp" + Chr(34) + "' > " _
    + "< img NAME='btnRFQ' src='../images/SubmitRFQ.bmp' " _
    + alt='Submit RFQ: " _
    + Request.QueryString("partno").Trim _
    + "' style='border-width:0px;' />" _
    + "< /a>"
tCell.HorizontalAlign = HorizontalAlign.Center
tRow.Cells.Add(tCell)
Me.tblButtons.Rows.Add(tRow))

This adds a row with one cell to tblButtons.  The cell contains an anchor which contains an image (named btnRFQ).  The anchor has the url, and mouse events to simulate button action.  The only thing that I couldn't do was create the button text at runtime.  Instead, I made do with creating the alt text. 

To see this dynamic, runtime Submit RFQ button in action, use the following link, and then click any of the part numbers:

http://www.edxelectronics.com/InStock/InStockPage1.aspx

 

Adding AJAX to an Existing Webpage

Monday, May 7th, 2007

Intro.  I have a page on my website named BuyNow.aspx.  It recalculates the extended price based on the user's entered quantity.

Enhancement.  Here's how I added AJAX to this existing page on my website (note that I added a space after every “<" so that this editor wouldn't interpret my html):

  1. Downloaded Asp.net 2.0 Ajax Extensions 1.0 from ajax.asp.net.
  2. Unzipped to get AspAjaxExtSetup.msi.
  3. Ran AspAjaxExtSetup.msi to install on my development PC.
  4. Opened my MasterPage for editing in VS 2005 (with sp 1).
  5. Used the toolbox to add ScriptManager. I put it under the < form> which is under the < body>.
  6. Opened my BuyNow.aspx page for editing.
  7. Used the toolbox to add an UpdatePanel near the labels that will refresh when the user clicks the Change button.
  8. Added the < ContentTemplate> tag under the < UpdatePanel> tag.
  9. Dragged the < table> holding the controls that should update into the < ContentTemplate> .
  10. Added the < Triggers> tag after the < ContentTemplate> tag.
  11. Added the < AsyncPostBackTrigger> under the < Triggers> tag to tell which button triggers the partial update.  Set ControlID to the button name and EventName to Click.
  12. Note:  These changes modified my web.config!
  13. Added these lines to my web.config, just after the < machineKey> tag:
  14. < httpHandlers>
    < remove verb="*" path="*.asmx"/>
    < add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    < add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    < add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>>
    < /httpHandlers>

    < httpModules>
    < add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    < /httpModules>

  15. Ran AspAjaxExtSetup.msi on my target webpage server.
  16. Deployed my enhanced BuyNow page.
  17. To see this in action, use the following link, and then click any of the BuyNow links to get to the BuyNow.aspx page:

    http://www.edxelectronics.com/PartSearch/BuyNowPageA.aspx