• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1236
  • Last Modified:

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?
0
Beamson
Asked:
Beamson
  • 9
  • 8
2 Solutions
 
Carlos VillegasFull Stack .NET DeveloperCommented:
Hello, just assign an ID to your ShoppingCart control in your aspx page, example:
<myControls:ShoppingCart runat="server" ID="MyShoppingCart"/>

Open in new window

Then in your Button_AddToBasket_Click method:
  Sub Button_AddToBasket_Click(s As Object, e As RepeaterCommandEventArgs)
    If e.CommandName = "cmdItemID" Then
      Dim lngXrefId As Long
      lngXrefId = Convert.ToInt64(e.CommandArgument)
      'I have changed this line for what I think can work for you
      MyShoppingCart.AddToBasket(ItemId, PartCode, Brand, UnitPrice)
    End If
  End Sub

Open in new window

0
 
BeamsonAuthor Commented:
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.
0
 
BeamsonAuthor Commented:
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...
0
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 
Carlos VillegasFull Stack .NET DeveloperCommented:
Hello, when the "Invalid postback or callback argument" exception is raised? what you do?
0
 
BeamsonAuthor Commented:
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.
0
 
BeamsonAuthor Commented:
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.
0
 
Carlos VillegasFull Stack .NET DeveloperCommented:
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.
0
 
Carlos VillegasFull Stack .NET DeveloperCommented:
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

0
 
BeamsonAuthor Commented:
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.
0
 
Carlos VillegasFull Stack .NET DeveloperCommented:
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?
0
 
BeamsonAuthor Commented:
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

0
 
Carlos VillegasFull Stack .NET DeveloperCommented:
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?
0
 
Carlos VillegasFull Stack .NET DeveloperCommented:
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.
0
 
BeamsonAuthor Commented:
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.
0
 
Carlos VillegasFull Stack .NET DeveloperCommented:
Ok, now I see the problem... the repeater control is always bind with every post back? please consider implement this in your code:
Sub Page_Load()
    If Not IsPostBack Then
        Dim array As String() = {"un", "dois", "tres"}
        Repeater1.DataSource = array
        Repeater1.DataBind()
    End If
End Sub

Open in new window

0
 
BeamsonAuthor Commented:
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!
0
 
Carlos VillegasFull Stack .NET DeveloperCommented:
Glad to help
0

Featured Post

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 9
  • 8
Tackle projects and never again get stuck behind a technical roadblock.
Join Now