Link to home
Start Free TrialLog in
Avatar of Craig Beamson
Craig BeamsonFlag for United Kingdom of Great Britain and Northern Ireland

asked on

ASP.NET (VB) How to Call a Sub on a .ascx page from a .aspx page?

I'm trying to set up a shopping basket on an ASP.NET (VB) site that I'm putting together.
The code I'm using for this is largely based on the sample website at the end of SAMS ASP.NET Unleashed 2nd Edition.

I have:
-  a VB class file (Namespace="StoreComponents", Public Class="ShoppingCart")
- a .ASCX control which raises an instance of ShoppingCart and displays existing ShoppingCart items and provides buttons and events for removing items from the cart.
- a .ASPX page which includes the .ASCX control, alongside a repeater with items I would like to add to the basket.

What I would like to do is use a Button_Click event in the ASPX's repeater area to run a sub which collates product info and, in turn, runs a sub in the .ASCX control to add a new item to the basket and update the basket display.

A shortened version of the ShoppingCart.ascx code is below:

 
<%@ Import Namespace="StoreComponents" %>
<script runat="server">
  Dim objShoppingCart As ShoppingCart
  
  Sub Page_Load()
    objShoppingCart = New ShoppingCart
    BindDataGrid()
  End Sub
   
  Public Sub AddToBasket(ItemId As Long, PartCode As String, Brand As String, UnitPrice As Decimal)
    objShoppingCart.Add(ItemId, PartCode, Brand, UnitPrice)
  End Sub
  
  Sub BindDataGrid()
    dgrdShoppingCart.DataSource = objShoppingCart.Items
	  dgrdShoppingCart.DataBind()
	End Sub
</script>
<asp:DataGrid ID="dgrdShoppingCart" runat="server"></asp:DataGrid>

Open in new window


A shortened version of the Products.aspx code is below:

 
<%@ Page Language="VB" %>
<%@ Register TagPrefix="myControls" TagName="ShoppingCart" Src="~/ShoppingCart.ascx" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<script runat="server">
  Sub Button_AddToBasket_Click(s As Object, e As RepeaterCommandEventArgs)
    If e.CommandName = "cmdItemID" Then
      Dim lngXrefId As Long
      lngXrefId = Convert.ToInt64(e.CommandArgument)
'SOME CODE WHICH RETRIEVES THE VALUES FOR PartCode, Brand etc
      AddToBasket(ItemId, PartCode, Brand, MatchBrand, Reference, MatchReference, UnitPrice)
    End If
  End Sub
</script>
<!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 id="Head1" runat="server">
</head>
<body>
    <form id="form1" runat="server">
        <myControls:ShoppingCart runat="server" />
            <h1>Parts</h1>
            <asp:Repeater ID="rptPartsForUniqueModel" OnItemCommand="Button_AddToBasket_Click" runat="server">
                <ItemTemplate>
                    <%#Container.DataItem( "PartCode" ) %>
                    <asp:Button Text="Add to basket" CommandArgument=<%#Container.DataItem( "ItemID" ) %> CommandName="cmdItemID" runat="server" /></ItemTemplate>
            </asp:Repeater>
    </form>
</body>
</html>

Open in new window

The problem comes trying to achieve line 11 of the above code.

I can call the AddToBasket() sub from within the ASCX page based on events on the ASCX page themselves.  However, I cannot get the AddToBasket() sub to be called from the script part of the .ASPX page.

The items I've Googled on this have left me with more questions than answers and tend to be C# examples (which I really struggle with)

Are there a few lines I can add or modify in my existing code to expose the Sub in the .ASCX file to the .ASPX file which uses this control?
ASKER CERTIFIED SOLUTION
Avatar of Carlos Villegas
Carlos Villegas
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Craig Beamson

ASKER

This looks very positive - Visual Web Developer seems to think it is fine.
I'm just going to spend 20 minutes putting some dummy data in to get it to test it but I think this solves my problem.  If so, I'll accept this as the solution and award the points.
Hm.  Nearly there I think.  However, when I run it, I get the following error message:

Invalid postback or callback argument.  Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page.  For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them.  If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.

I'll have a go at tweaking the <%@ Page code to accomodate this...
Hello, when the "Invalid postback or callback argument" exception is raised? what you do?
The page displays fine.
I get an (empty) basket displaying with just the datagrid headers FROm my ASCX control.
I also get the buttons from my repeater displaying.
When I click on a button in the repeater, that is when the error message occurs.
I've got to go to a meeting now - be back in an hour or so - this error message seems to be fixed by adding <%@ Page EnableEventValidation="false"  to the page, but the code still does not seem to work - I'll clarify when I'm back.
Hello, I don't recommend to disable EnableEventValidation, in the code that you are showing all look fine, that error should not be occur... the only observation, missing quotes when you set the CommandArgument binding, but I dont think that raises the error:
<asp:Button ID="Button2" Text="Add to basket" CommandArgument='<%# Container.DataItem("ItemID") %>' CommandName="cmdItemID" runat="server" />

Open in new window

About your empty shopping cart, when you call the AddToBasket method in your ShoppingCart control, I cant see code that refresh the datagrid data, so do this change in that method:
  Public Sub AddToBasket(ItemId As Long, PartCode As String, Brand As String, UnitPrice As Decimal)
    objShoppingCart.Add(ItemId, PartCode, Brand, UnitPrice)
    BindDataGrid()
  End Sub

Open in new window

That will refresh the datagrid content after you add an item on the cart.
Also, I recommend you to html encode the text that you write from your db to your item template, example:
<asp:Repeater runat="server" ID="rptPartsForUniqueModel" OnItemCommand="Button_AddToBasket_Click">
    <ItemTemplate>
        <%# Server.HtmlEncode(Container.DataItem("PartCode").ToString())%>
        <asp:Button runat="server" ID="Button2" Text="Add to basket" CommandName="cmdItemID" CommandArgument='<%#Container.DataItem("ItemID") %>' />
    </ItemTemplate>
</asp:Repeater>

Open in new window

Hm.

I've tried a few things now with mixed results.
1) Changed the CommandArgument= to include apostrophes ' '
2) Added the BindData() call to the AddToBasket() sub
3) Added a button and btnClick sub within the ASCX code which calls the AddToBasket sub with a few dummy values for the arguments.

Now, with <%@ Page EnableEventValidation="false" Language="VB" %> there are no page errors.

The button within the .ASCX code behaves as expected.  Clicking on it adds a new item to the basket and clicking it again increments the count of items by 1.

The buttons within the .ASPX code, when clicked, cause the page to flicker, do not cause errors, but do not seem to affect the basket contents.

When I change the PAGE directive back to <%@ Page EnableEventValidation="true" Language="VB" %>, the button in the .ASCX control works fine but clicking the button on the .ASPX page causes the same error as above.


I think this is now two different problems - the page event validation is one problem, the failure of the ASPX button to run the sub in the .ASCX control is another.
For testing purposes, I'm happy to override the event validation in the PAGE directive and look more closely at this later.
Hello, I dont have the full view of your aspx page code... let me see if I understand:
1. Now, when you click the button "Add to basket" inside your repeater, it works, add the item to your shopping cart and refresh the ShoppingCart control content.
2. You have another button (outside your repeater control) in your aspx page (does not appear in your sample code) that must add an item to your ShoppingCart control too, but it is not working.

I'm right about that?
Sorry, No.

A single button in the user control (ASCX page) works.  (I have added this since I sent the original code)
The buttons in the repeater portion of the .ASPX page do not work

Let me recheck my code for a few hours.
I've set up a really simple test to check your original suggestion - and it works  (see sample code below)
I need to extend it to match my real code to see where the problems start.

Test.ascx:
 
<%@ Control Language="VB" ClassName="Test" %>
<script runat="server">
  Sub DisplayTime()
    lblTime.Text = Now()
  End Sub
  Protected Sub UpdateTime(sender As Object, e As System.EventArgs)
    DisplayTime()
  End Sub
</script>
<asp:Label ID="lblTime" runat="server" /><br />
<asp:Button ID="btnControl" OnClick="UpdateTime" Text="Update Time (Within ASCX Control)" runat="server" />

Open in new window

Test.aspx:
 
<%@ Page Language="VB" %>
<%@ Register TagPrefix="myControls" TagName="Test" Src="~/Test.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
  Protected Sub Button_Aspx_Click(sender As Object, e As System.EventArgs)
    myTest.DisplayTime()
  End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <myControls:Test ID="myTest" runat="server" />
        <br />
    <asp:Button text="Update Time (Within ASPX Page)" runat="server" 
        onclick="Button_Aspx_Click" />
    </div>
    </form>
</body>
</html>

Open in new window

mm ok, please verify that your method Button_AddToBasket_Click is called when you click on the buttons inside your repeater:
Sub Button_AddToBasket_Click(s As Object, e As RepeaterCommandEventArgs)
    Response.Write("Yes Button_AddToBasket_Click was called")
End Sub

Open in new window


I have made a test page with the repeater and everything work as expected, what .net version are you using?
Observation, in your last example you need to expose the DisplayTime method of your control as Public:
<%@ Control Language="VB" ClassName="Test" %>
<script runat="server">
  Public Sub DisplayTime()
    lblTime.Text = Now()
  End Sub
  Protected Sub UpdateTime(sender As Object, e As System.EventArgs)
    DisplayTime()
  End Sub
</script>
<asp:Label ID="lblTime" runat="server" /><br />
<asp:Button ID="btnControl" OnClick="UpdateTime" Text="Update Time (Within ASCX Control)" runat="server" />

Open in new window

Otherwise it can't be accessed by your aspx page, because it will be a private member.

Also, try adding the protected access modifier to your Button_AddToBasket_Click method:
Protected Sub Button_AddToBasket_Click(s As Object, e As RepeaterCommandEventArgs)
    Response.Write("Yes Button_AddToBasket_Click was called")
End Sub

Open in new window

I'm using the .net framework 4.0 to do my tests, if you are using another version maybe it requires the Protected keyword to wire up your events, i'm not sure.
I added the response.write line but it does not write anything to screen.
The problem seems to be that button clicks from repeater objects seem to be treated differently to plain asp:button items.

The attached code is a proper example of what happens, using a repeater:

   
<%@ Page Language="VB" %>

<%@ Register TagPrefix="myControls" TagName="Test" Src="~/Test.ascx" %>
<%@ Import Namespace="System.Data" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
  
  Sub Page_Load()
    Dim array As String() = {"un", "dois", "tres"}
    Repeater1.DataSource = array
    Repeater1.DataBind()
  End Sub
  
  Public Sub Button_Aspx_Click(sender As Object, e As System.EventArgs)
    myTest.DisplayTime()
  End Sub

  Sub Button_AddToBasket_Click(s As Object, e As RepeaterCommandEventArgs)
    myTest.DisplayTime()
  End Sub
  
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <myControls:Test ID="myTest" runat="server" />
    <br />
    <h2>Simple Button Area (in the ASPX code)</h2>
    <asp:Button Text="Update Time (Within ASPX Page)" runat="server" OnClick="Button_Aspx_Click" />
    <br />
   <h2>Repeater Area</h2>
    <asp:Repeater ID="Repeater1" OnItemCommand="Button_AddToBasket_Click" runat="server">
      <ItemTemplate>
        <asp:Button ID="Button1" Text="Update Time (From Repeater)" runat="server" /></ItemTemplate>
      <SeparatorTemplate>
        <hr />
      </SeparatorTemplate>
    </asp:Repeater>
  </div>
  </form>
</body>
</html>

Open in new window

Used with the test.ascx code in my response above, you can see exactly what happens.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
The original answer was the right one but with my sample code, it needed that final answer to get it to work.

Many thanks for all of your help!
Glad to help