SqlDataSource reuse on different pages - asp.net

I am using ASP.NET. I have a ReportPage1 and ReportOutputPage1. These are different aspx files and has different MasterPages. However, I need the same SqlDataSource object to use on both pages. On ReportPage I need SqlDataSource to call StoredProcedure and import data to CSV file, but on ReportOutputPage I need to use SqlDataSource to call the same StoredProcedure and populate GridView.
ReportPage1 is "main" page - a button click from this page opens ReportOutputPage1 and displays it in new window. ReportPage is PreviousPage for ReportOutputPage1.
Above is example for Report1. The same idea is for Report2 (with SqlDataSource2) and for Report3 (SqlDataSource3) etc. - 10 different reports.
How to reuse SqlDataSource for every two pages (ReportPage & ReportOutputPage)?
First suggestion I found in web - using masterpage for both ReportPage and ReportOutputPage. This doesn't work, as I have already have different masterpages for ReportPage and ReportOutputPage, as well as then I need to create 10 different MasterPages for each Report.
Second suggestion was to define SqlDataSource on ReportPage and then reuse it using PrevousePage on ReportOutputPage, but this doesn't work for my special case (I am using Ajax staff and Partial page postbacks and I am loosing PreviousPage, also SqlDataSource could not be serialized to save it in ViewState or similar).
Create UserControl. Probably this could work, but it is time consuming to create UserControl every time for new Report (10 reports - 10 usercontrols?).
Simply Copy & Paste SqlDataSource (I did it for one Report) could work, but I would like something more like code reuse. Someone could simply forget to modify SqlDataSource in two different places if necessary.
Can you, please, give me some suggestions how to reuse code (particularly, SqlDataSource) for Report & ReportOutput pages?

Could you define the need for using the same SqlDataSource? If they are two different pages and it sounds like two different uses why not use two different SqlDataSource? The pages are separate anyhow, your not going to be able to share an object on one with the other.
I would suggest you look at adding a data layer to your application for database interaction and binding your data to the datagrid at request time. That way you build your database interaction once and reuse that over different pages.
The other option is you simply use two SqlDataSources and copy/paste them to both the pages. If your trying to make a selection or some sort of criteria apply to your second page then consider using query strings and QueryStringParameters in your SqlDataSource.
Hope this helps.
Edit: Pop this in App_Code somewhere, pass in your specific usage requirements.
Public Shared Function GetData(connString As String, command As String, Optional params As SqlParameter() = Nothing) As DataTable
Dim conn As New SqlConnection(connString)
Dim da As New SqlDataAdapter(command, conn)
Dim dt As New DataTable
da.SelectCommand.CommandType = CommandType.StoredProcedure
da.SelectCommand.CommandTimeout = 6000 '6 seconds.
If Not IsNothing(params) Then
For Each p As SqlParameter In params
da.SelectCommand.Parameters.Add(p)
Next
End If
Try
conn.Open()
da.Fill(dt)
Return dt
Catch ex As Exception
Throw ex
Finally
conn.Close()
End Try
End Function
Then bind the datatable to your gridview, not sure how your outputing to file.
Private Sub BindData(gridview As GridView, data As DataTable)
gridview.DataSource = data
End Sub
You can now re-use the database interaction from the code behind:
Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
BindData(MyGridView,GetData(ConnectionStrings(connName).ConnectionString, _
"dbo.SomeSprocOrOther", _
New SqlParameter(){New SqlParameter("paramName","paramValue")})
End Sub

Related

DropDownList Data Items Shared With All Web Users?

I'm designing a custom ASP.NET drop down list for a Web application to display a list of a lot of organizations.
Btw: I reviewed every other SO question related to dynamically creating a drop-down-list and I couldn't find an answer.
Since there's a lot of organizations I want to build the item list as fast as possible.
Here's my ideas:
I'd share the list like this:
Imports System.Web.UI.WebControls
Public Class Organizations
Inherits DropDownList
Private Shared m_Organizations As List(Of Organization) = Nothing
Public Sub CreateChildControls()
' If list not populated get list from database.
If m_Organizations Is Nothing Then
m_Organizations = Get_Organizations()
End If
For Each Org As Organization in Organizations
Item_Obj = New ListItem(Org.Name, Org.OrgId)
Items.Add(Item_Obj)
Next
MyBase.CreateChildControls()
End Sub
End Class
I did a test. I created a single web page with two copies the drop down list. I ran the web page.
On initial startup of the web page I see m_Organization Is Nothing and Get_Organizations is called.
When I select an organization I see m_Organizations IsNot Nothing so Get_Organizations is not called. That's what I wanted.
When I click on a Submit button on the test page I see m_Organizations IsNot Nothing so Get_Organizations is not called. That's what I wanted.
So Organizations is persisted across page postbacks.
Now my question.
Suppose 3 users are using the web application at the same time and they display the web page with the Organizations drop down list.
Will m_Organizations be shared with all three web applications so it is loaded only once for all three users?
Or, is each user in their own process and therefore GET_Organizations will be called a total of three times?
Thanks, Ed
Each user has their own web page, controls, and code values. They each get their own code copy and their code runs just FOR each user.
So, to load up the dropdown list? It not clear why you pulling into a list?
The base .net objects such as row, and dateable run a lot faster then lists. And these base types can be directly binded to the drop down list (no need for a loop).
So, your on load would be something like:
So, in page load event, you have this:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack = False Then
Me.Instutions.DataSource = Myrst("select ID,Institution from Institutions")
Me.Instutions.DataBind()
End If
End Sub
So, only on the page load do we fill up the combobox (drop down list). You check the "isPostback" value.
Now it possible that you using datasets (or the newer EF) for the data. But even in that case, you can get/grab the data table from that, and shove it directly into the dropdown list data source. You as a general rule will NOT run loops to fill these types of controls (gridview, listview, dropdownlist). They can be bound directly to the result of the data source without loops. And since each users will get their own copy of the data, and your code runs for each user? Then you do want to avoid things like loops - since that code will run separate for each user and each page post back.
So yes, each user gets their own web page, gets their own variables, and runs their own code 100% separate from each user. And that includes a copy of the datasets you have.
When the user selects a value from the drop down list? (yes, we have auto postback = true), and thus the code for that wlll look like:
Protected Sub Instutions_SelectedIndexChanged(sender As Object,
e As EventArgs) Handles Instutions.SelectedIndexChanged
' pk id from drop list = Instutions.SelectedValue
' text value from drop list = Instutions.DataTextField
End Sub
So, it only takes two lines of code to load up the dropdown list - even when you don't have a dataset, or are using EF.
Now, I did use a helper routine called myrst(), and it can be used to fill dropdown lists, datagrids, listviews etc. and once again only two lines of code, and no looping.
that "handy" routine is this:
Public Function Myrst(strSQL As String) As DataTable
Dim mycon As New SqlConnection(My.Settings.Test4)
Dim oReader As New SqlDataAdapter
Dim rstData As New DataTable
oReader.SelectCommand = New SqlCommand(strSQL, mycon)
Try
oReader.Fill(rstData)
Return rstData
Catch
Debug.Print(Err.Description)
Return Nothing
End Try
End Function
So when they select a value in the drop down list? You do get a full page post back, and your on-load event will run. And yes, EACH user has their own web page, and own copy of that code running - they are not relegated to each other in ANY way, and all local, global variables and code is run 100% in a separate session for each user. And each user also has their own Session() variables - and again they are 100% separate and not shared between all users.
So yes, all 3 users will each call and each load up their own copy of that data. (yes, it called 3 times). if that was not the case, then what would occur if you wanted to filter only companies or records belonging to the one user? so you can consider all code and even loading of data a 100% separate thing/concept and process for each user.

Sql data reader in classes

Hi
On several pages of our website, I want to check if the currently logged in user has accepted our terms and conditions in the past. This boolean value is stored in the application database. Rather than creating a sql data reader afresh on each relevant page, I thought I could put it in a class and then assign the true/false to a variable. This is what I have so far and it does exactly what I want it to:
Public Shared ReadOnly Property termsCompleted As String
Get
Dim selectTerms As String = "SELECT Terms FROM tblPersonal WHERE Ref=#Ref"
Dim dbconn As String = ConfigurationManager.ConnectionStrings("ApplicationServices").ConnectionString
Using myConnection As New SqlConnection(dbconn)
myConnection.Open()
Dim cmdTerms As New SqlCommand(selectTerms, myConnection)
cmdTerms.Parameters.AddWithValue("#Ref", myUser.ToString())
Dim readerTerms As SqlDataReader = cmdTerms.ExecuteReader()
readerTerms.Read()
termsCompleted = readerTerms.Item(0)
readerTerms.Close()
myConnection.Close()
End Using
End Get
End Property
I am them using the following on each page that is relevant to deny access and redirect (in the page_load):
If Variables.termsCompleted = False Then
Response.Redirect("Terms.aspx")
End If
While this works ok, i'm interested in how secure it is, and is there a better way to do this?
Thanks
Have you considered retrieving the information once during Session_Start, and carrying it around in Session so that you can interrogate it any time you want?
If you can't retrieve the data during authentication/authorization, you would retrieve the data in the same way as you show above.
To put the value into Session: Session["termsCompleted"] = "true";
To read the value from Session: if (Session["termsCompleted"] == "true")....
As an alternative, you could add the information to HttpContext.Current.User.
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' Code that runs when a new session is started
Dim selectTerms As String = "SELECT Terms FROM tblPersonal WHERE Ref=#Ref"
If Request.IsAuthenticated = True Then
Dim dbconn As String = ConfigurationManager.ConnectionStrings("ApplicationServices").ConnectionString
Using myConnection As New SqlConnection(dbconn)
myConnection.Open()
Dim cmdTerms As New SqlCommand(selectTerms, myConnection)
cmdTerms.Parameters.AddWithValue("#Ref", Variables.myUser)
Dim readerTerms As SqlDataReader = cmdTerms.ExecuteReader()
readerTerms.Read()
Session("termsCompleted") = readerTerms.Item(0)
readerTerms.Close()
myConnection.Close()
End Using
End If
End Sub
And in the code-behind:
If Session("termsCompleted") = False Then
Response.Redirect("Terms.aspx")
End If
Unfortunately this is redirecting to the terms.aspx page every time regardless of what is in the database. From debugging it's picking up the reader item as 'False' even when it's true..
Thanks
Create a base page and have each page inherit from that. In this base page you can do the data access once to perform this check. Then store it in session state.
I don't think you have a security issue...I think it's more of a best practice issue. It's not good practice to put your data access requests in a property. In projects I work on, I typically will have a class that has functions that handle my data access with a buisiness layer that makes the calls to my data access. An n-tier project design may not fit your project...I'm just speaking from my experience.
If you need to reuse the bit flag, just store it in Session.
This logic doesn't really belong on a page. If accepting the terms of use is a requirement for accessing parts of your site then you should handle it that way. This problem is a very similar situation to having an administrator section of a site that only a few users can access.
Ideally this is something you would handle before the request gets to the page. There are (at least) two ways to approach this.
You could write a custom HTTP module that subscribes to the AuthorizeRequest event. It will check whether this page requires that you accept terms of agreement or not and if so checks to see if the user has or not. If not it will redirect the user to the terms of use page.
The other option is to put this code into your Global.ascx file. You would want to subscribe to the AuthorizeRequest event and perform your logic there.
I don't think it matters which option you chose (though the second one may be a little more straight forward to implement). The benefit is that this concern is handled outside of the page itself. This way as you add new pages to your site, you can't forget to add your validation code to it. If you ever decide that in addition to accepting terms of agreement users need to do something else, you now have one place to change instead of going through all of the pages, etc.
You should also take advice of some of the other answers and store this value into the Session to avoid having to to do a database request every time a page loads.
More information:
Http Modules and handlers
How To Create an ASP.NET HTTP Module Using Visual C# .NET (VB should be the same concept with VB syntax).
Application, Page and Control lifecycle (to help you better understand how ASP.NET application lifecycle works and what else is possible).

sql 2005 xml passed to asp.net grid with date formating

i have sql that uses FOR XM RAW to generate xml for my asp.net application to use to bind grids to and things like this.
the problem is that the date data is not taking the gridView formatting of the date because (and im taking a crack at this) the date value in the xml is a string and the formatting is not taking.
any thoughts on how to get this to work?
i like using xml because i can persists it to a log table and trace all the xml sent in and out. i would hate to loose this...
You can use code on the RowDataBound event to reformat programatically. Here is a try in VB.
Private Sub gvXML_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles gvXML.RowDataBound
If (e.Row.RowType = DataControlRowType.DataRow) Then
if IsDate(e.Row.Cells(1).Text) then
e.Row.Cells(1).Text = CDate(e.Row.Cells(1).Text).Format("LongDate")
End If
End If
End Sub
P.S. this is quickly modified from something else I am doing. It is not tested.

Databinding problems with postback

I've two issues currently preventing me from finishing two projects properly. I'll put them both here as I believe they're connected to the asp.net page lifecycle, but I can't find a way around them.
First I have a DropDownList which I must sort in codebehind. It only contains text, so I should be able to do that with the following method called in page load:
Dim alist As ArrayList = New ArrayList
For Each litem As ListItem In ltEsittelyDropDownList.Items
alist.Add(litem.Text)
Next
alist.Sort()
Dim uusiDDList As New DropDownList
For i As Integer = 0 To alist.Count - 1
Dim litem As New ListItem
litem.Text = alist(i).ToString
litem.Value = alist(i).ToString
uusiDDList.Items.Add(litem)
' Response.Write(alist(i).ToString)
Next
ltEsittelyDropDownList = uusiDDList
ltEsittelyDropDownList.DataBind()
As you can see, there's a commented response.write in there, which shows the list is actually sorted. So why, when I load the page, can't I see any effect?
The other problem, which is more critical and difficult, is as follows:
In the aspx page I'm binding a SQL Server 2005 datasource to a gridview. And in the code-behind I catch on to the RowDataBound event in which I handle some links and properties inside the gridviews' cells. But I cannot get this to work on the first page load, only after the first extra postback.
So, what is there to do? And thanks for all advice in front!
Your first problem is calling DataBind on a control you have filled manually. You likely have a DataSource specified in the control declaration, which is being used when DataBind is called. You can simplify the code by just adding the list items to the original control:
For i As Integer = 0 To alist.Count - 1
ltEsittelyDropDownList.Items.Add(New ListItem(alist(i).ToString())
Next
Alternatively, as you have a collection already, you can just bind it to the control:
ltEsittelyDropDownList.DataSource = alist
ltEsittelyDropDownList.DataBind()
For your second problem, some example code would help - specifically, where and how the control is databound and the code in RowDataBound.

Why is the DataBind() method necessary?

Simple question, I guess.
For a long time I've blindly followed a (supposedly) common pattern when programmatically databinding my ASP.NET controls. Namely:
gridView1.DataSource = someList;
gridView1.DataBind();
However, if I were setting my GridView to bind to a DataSource control via the DataSourceID property, the call to DataBind() is unnecessary. Namely:
gridView1.DataSourceID = LinqDataSource1;
is sufficient.
Furthermore, if you try to set the DataSource property in ASPX markup, you are greeted with the following:
You cannot set the DataSource property declaratively.
I assume these are related, but I am still stumped as to why DataBind() is necessary. The difference between DataSource and DataSourceID is secondary - I can understand some magic taking place there. The real question is why doesn't the DataSource propery setter cause databinding automatically? Are there any scenarios in which we want to set the DataSource but not bind to it?
In ASP.Net, it's often important to have certain data available and ready at certain points in the page life cycle, and not before. For example, you may need to bind to a drop down list early to allow setting the selected index on that list later. Or you might want to wait a bit to bind that large grid to reduce the amount of time you hold that connection active/keep the data in memory.
Having you explicitly call the .DataBind() method makes it possible to support scenarios at both ends of the spectrum.
DataSource is a property of the BaseDataBoundControl class. DataSourceID is a property of the DataBoundControl class, which inherits from BaseDataBoundControl and did not exist before ASP.NET 2.0.
Since DataBoundControl is explicitly for displaying data in a list or tabular form, and BaseDataBoundControl cannot make that assumption, binding is not automatic when DataSource is set because the type of control may not match the structure of the data.
Of course, this is all just a guess based on the MSDN documentation, so I could be wrong.
I noticed that without using DataBind() that nothing will be displayed in my GridView so I always include it as shown in this section of code:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
' TableAdapter object.
' Provide communication between this application and the database.
'-----------------------------------------------------------------
Dim suppliersAdapter As New SuppliersTableAdapter
' Get the data from the TableAdapter into the GridView.
'------------------------------------------------------
GridView1.DataSource = suppliersAdapter.GetSuppliers()
' Display the result set from the TableAdapter in the GridView.
'--------------------------------------------------------------
GridView1.DataBind()
End Sub
Please forgive the extra commenting as I'm also still learning ASP.Net as well and the comments will help me learn better "what and why" to use certain statements.
Try this:
if (GridView1.EditIndex == e.Row.RowIndex)
{
TextBox t2 = (TextBox)e.Row.FindControl("TextBox2");
DateTime dt2;
if (DateTime.TryParse(t2.Text, out dt2))
{
t2.Text = dt2.ToString("yyyy-MM-dd");
}
}

Resources