I have an auction site, not like ebay (as the most think).
Now my problem is follow: On the startpage are a lot of auctions displayed (ASP.NET). All Auction (let's say min. 10, max 50) have a timer. Now I must have a look nearly every second for each auction (filter is not started and not ended) if the timer is reseted (what happens when someone bid).
So I will have a query with a lot of amount back every second, must update 10-50 Textboxes every second and that for every user that visit the page.
Any ideas who solve it performance-good?
You don't have to query SQL every second to change the second counters in your textboxes. You should use Javascript to automatically adjust the seconds left from the start time.
The start time should only get changed when the start time in the SQL tables has been reset. You can use AJAX to update the Textboxes.
You do not have to query SQL once a second to tell when your timers have been reset. You should use a SQL cache dependency. Basically, a SQL Dependency tells you when when it's data has changed, instead of you having to ask SQL every second if anything has changed.
The workflow is as follows:
Make initial SQL query, with a SQL Dependency object, specifying a "callback" method
Cache the result
Return the cached result to your pages until the SQL data has changed
When the SQL data has changed, the SQL Dependency calls the "callback" method you defined
Callback method clears your SQL cache, and re-queries SQL
Repeat steps 2 - 6.
It's a little complicated if you're not used to working with Delegates and dependencies, but it's well worth the time spent because it eliminates repetitive querying.
Sql Cache Dependency Example C# ASP.Net 2.0
In your Global.aspx page, add this code:
<script runat="server">
string mySqlConnection = "<<Enter your connection string here.>>";
void Application_Start(object sender, EventArgs e)
{
// Start subscribing to SQL Server 2005 Notification Services.
System.Data.SqlClient.SqlDependency.Start(mySqlConnection);
}
void Application_End(object sender, EventArgs e)
{
// Stop subscribing to SQL Server 2005 Notification Services.
System.Data.SqlClient.SqlDependency.Stop(mySqlConnection);
}
</script>
In your Default.aspx page, add this code:
<%# Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!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 runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="lblCacheDisplay" runat="server" />
<br />
<asp:Label ID="lblWasQueryExecuted" runat="server" />
</div>
</form>
</body>
</html>
The Default.aspx page will display your data, and will say whether the data came from the Application Cache, or from SQL.
In your Default.aspx.cs page, add this code:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class _Default : System.Web.UI.Page
{
private static SqlDependency sqlDependency;
private static bool cacheIsValid = false; // cacheIsValid is set to "false" when the SQL data is changed.
private static string mySqlConnection = "<<Enter you connection string here. It MUST use a different user than the Global.aspx connection string.>>";
protected void Page_Load(object sender, EventArgs e)
{
string myCachedData = (string)HttpContext.Current.Cache.Get("myCachedData");
if (myCachedData == null || !cacheIsValid) // Remember that cached objects can be removed from the cache at any time by the garbage collector, you cannot assume that they exist!
{
myCachedData = GetMyDataFromSql();
cacheIsValid = true;
lblWasQueryExecuted.Text = "SQL was queried for this data.";
}
else
{
lblWasQueryExecuted.Text = "This data came from the Application-level cache. It should be deleted if the SQL data changes.";
}
lblCacheDisplay.Text = myCachedData;
}
public static string GetMyDataFromSql()
{
string returnSqlData = String.Empty;
string storedProcedureName = "<<Enter you stored procedure name here.>>";
SqlCommand cmd = new SqlCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = storedProcedureName;
cmd.Connection = new SqlConnection(mySqlConnection);
cmd.Connection.Open();
SqlDataReader sdr = cmd.ExecuteReader();
if (sdr.Read())
{
returnSqlData = sdr.GetString(1);
}
// Only one Sql dependency needs to be created per application start. Since each Sql dependency keeps a connection
// to the Sql database open at all times, we want to make sure we have only one Sql dependency at any given time.
if (!cacheIsValid)
{
HttpContext.Current.Cache.Remove("myCachedData");
}
object hasSqlDependency = HttpContext.Current.Cache.Get("myCachedData");
if (hasSqlDependency == null)
{
CreateSqlDependency();
HttpContext.Current.Cache.Add("myCachedData", returnSqlData, null, DateTime.MaxValue,
TimeSpan.Zero, System.Web.Caching.CacheItemPriority.Normal, null);
}
cmd.Connection.Close();
return returnSqlData;
}
public static void SqlDependency_OnChange(Object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
// The Sql data has changed, so the current cache is out-dated. Therefore, mark it as being invalid.
cacheIsValid = false; // We do not have access to HttpContext.Current right now, so we cannot clear the cache ourselves right now. Instead, we have to mark it as being invalid, and let other code update it.
// Recreate the Sql dependency, since it disappears after sqlDependency_OnChange is called. This will keep the
// connection to the Sql database open, so we can continue to be notified if the SQL data changes.
CreateSqlDependency();
}
}
private static void CreateSqlDependency()
{
SqlConnection sqlConn = new SqlConnection(mySqlConnection);
sqlConn.Open();
// If any tables in this query are modified (data changes, table definition changes, etc.), SqlDependency_OnChange will be called.
SqlCommand cmdDependency = new SqlCommand("<<SELECT column FROM myTable>>", sqlConn);
sqlDependency = new SqlDependency(cmdDependency);
sqlDependency.OnChange += new OnChangeEventHandler(SqlDependency_OnChange);
// Even though we don't do anything with the results of this query, it still needs to be executed in order to set the Sql dependency.
// If you comment out this code, the Sql dependency will not be properly created, and SqlDependency_OnChange will never be notified of
// changes to the SQL data.
SqlDataReader objReader = cmdDependency.ExecuteReader();
objReader.Close();
sqlConn.Close();
}
}
Make the appropriate changes marked by the "<<" and ">>", and you should be good to go.
Note that the two SQL user accounts must have very specific SQL 2005 permissions. Here's how you can setup the SQL user permissions.
All of the timers on the page should automatically decrement one tick every second (if it is below the "display seconds" threshold - otherwise update minute once a minute). This is handled through client-side javascript using a timer to trigger the updates.
Whenever an auction's time is reset (due to a bid) this is updated in the db, and also in a server-side cache. The cache is stores the time reset, the auctionID and the new end-time for the auction
Once every second or so, the page sends an Ajax request (JSON, preferably) back to the server, asking for all of the auctionIDs and new end-times for all auctions whose time has been reset since the last time this page requested it (a value that is stored on the client-side at every request). Based on the return value, only the updated auctions are updated on the client side. And the DB is only queried on the initial page load - all subsequent update requests hit the cache.
All you need to get it which textbox has been reset, the rest can use a gui timer.
so you need to make a reset log table and put a row each time someone reset (put a bid).
now - each sec you select * from log where lognumber > LAST_NUMBER_THAT_YOUR SCREEN_ALREADY_GOT
you get the list of only changed items.
I think it might do the work most efficient...
just make sure to delete old records from this log table....
Related
I have registration form and button. OnClick - I call function on server side which make a validation of user's zip code at Database with Zipcodes. If validation passed successfully - user's data stored in Database (here I continue use server function). But if ZipCode does not match - I call Javascript function where I ask if user still wants to save his data to DB. and If yes - I save it using Ajax request. Problem is when I call Javascript function - firstly it should receive user's data on client side. But when reading data happens - I receive an error "Unable to get property 'value' of undefined or null reference". But user's data still exist at the form's fields. It seems that the data that read by the server from the form once - reset somewhere - and can not be read a second time on the client.
Here is my ASP Form
<body>
<form id="frmZipValidation" runat="server">
<div>
<asp:Label runat="server">Registration Form</asp:Label>
<asp:TextBox runat="server" ID="txtbxName"></asp:TextBox>
<asp:TextBox runat="server" ID="txtbxZipCode"></asp:TextBox>
<asp:DropDownList runat="server" ID="DDLCountry">
<asp:ListItem Text="Select country" Value="Select" Selected="True"></asp:ListItem>
<asp:ListItem Text="USA" Value="USA"></asp:ListItem>
<asp:ListItem Text="Canada" Value="Canada"></asp:ListItem>
</asp:DropDownList>
<asp:TextBox runat="server" ID="txtbxState"></asp:TextBox>
<asp:TextBox runat="server" ID="txtbxCity"></asp:TextBox>
<asp:Button runat="server" ID="btnSubmit" Text="Submit" OnClick="btnSubmit_Click"/>
</div>
</form>
</body>
Here is my Server Side
public partial class Default : System.Web.UI.Page
{
string Name;
string ZipCode;
string Country;
string State;
string City;
bool IsMatch;
Addresses dbAddresses = new Addresses();
User newUser;
protected void Page_Load(object sender, EventArgs e)
{
if (Request["Action"] != null && Request["Action"].Trim() != "")
{
if (Request["Action"] == "AddUser")
{
AddUser(Request["Name"], Request["ZipCode"], Request["Country"], Request["State"], Request["City"]);
}
}
}
private void AddUser(string UserName, string UserZip, string UserCountry, string UserState, string UserCity)
{
newUser = new User(UserName, UserZip, UserCountry, UserState, UserCity);
dbAddresses.Users.Add(newUser);
dbAddresses.SaveChanges();
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
if (IsValid)
{
ZipCode = txtbxZipCode.Text;
Country = DDLCountry.Text;
State = txtbxState.Text;
City = txtbxCity.Text;
Name = txtbxName.Text;
IsMatch = false;
List<ZipCode> ZipC = (from z in dbAddresses.Zips
where z.Zip == ZipCode
select z).ToList();
//If ZipCode entered by client do not exists at Database return false
if (!ZipC.Any())
{
IsMatch = false;
}
else
{
for (int i = 0; i < ZipC.Count; i++)
{
if (ZipC[i].Country.ToString() == Country)
{
if (ZipC[i].State.ToString() == State)
{
if (ZipC[i].City.ToString() == City)
{
AddUser(Name, ZipCode, Country, State, City);
//Message to the user that all saved successfully
Page.ClientScript.RegisterClientScriptBlock(typeof(Page), "1", "<script>alert('Your data was saved successfully!');</script>");
IsMatch = true;
break;
}
else
{
IsMatch = false;
break;
}
}
else
{
IsMatch = false;
break;
}
}
else
{
IsMatch = false;
break;
}
}
}
//If user's data are not match, then go to JS client code where - If user wants in any case to save data - make it using AJAX request
if (!IsMatch)
{
string clientScript = "AjaxRequestSaveToDB();";
this.Page.ClientScript.RegisterStartupScript(this.GetType(), "MyClientScript", clientScript);
}
}
}
}
And here is Javascript:
function AjaxRequestSaveToDB()
{
var SaveData = confirm('Zip/Postal code doesn’t match region. Are you sure you want to save this data?');
if (SaveData)
{
var UserName = document.getElementById('txtbxName').value;
var UserZipCode = document.getElementById('txtbxZipCode').value;
var UserCountry = document.getElementById('DDLCountry').value;
var USerState = document.getElementById('txtbxState').value;
var UserCity = document.getElementById('txtbxCity').value;
SendDataToServer('AddUser', UserName, UserZipCode, UserCountry, USerState, UserCity);
alert("You data was saved successfully!");
}
else { alert('Not saved');
}
}
}
function SendDataToServer(RequestType, Name, ZipCode, Country, State, City)
{
var xmlHttp = getXmlHttp();
var Url = "Default.aspx?Action=" + escape(RequestType)
+ "&Name=" + escape(Name)
+ "&ZipCode=" + escape(ZipCode)
+ "&Country=" + escape(Country)
+ "&State=" + escape(State)
+ "&City=" + escape(City);
xmlHttp.open("GET", Url, true);
xmlHttp.send();
}
A short book about Client-Server Communications using "Custom" AJAX requests.
In ASP.net programming (almost) every time the client interacts with the server, the client sends all of its information to the server and then throws out its old content and replaces it with the response the client received from the server. So the problem you were running into is that your asp:button on the client machine was sending information to your .aspx page on the server and the server was interpreting the information, realizing something was wrong and telling the client it should ask the user for more information but throw out all the information that had been previously entered.
The best way that I have found to get around this problem is to use what I call "custom AJAX requests." Basically this means that we write a string of XML and send it to an ASP handler page which is set up to accept the XML string and do something with it. In my travels I have slimmed this down to basically 3 parts. The first is the user interface which contains all of the markup and CSS(and validation), the second is the JavaScript file that contains all of the data gathering and the actual AJAX request and lastly there is the ashx file that handles the request from the client.
So to start you will need to set up your user interface. Something along the lines of:
<body>
<form id="frmZipValidation" runat="server">
<div>
<div class="label">Registration Form<div>
<asp:TextBox ID="txtbxName" class="txtbxName" ClientIDMode="Static" runat="server"></asp:TextBox>
<asp:TextBox ID="txtbxZipCode" class="txtbxZipCode" ClientIDMode="Static" runat="server" ></asp:TextBox>
<asp:DropDownList ID="DDLCountry" class="DDLCountry" ClientIDMode="Static" runat="server" >
<asp:ListItem Text="Select country" Value="Select" Selected="True"></asp:ListItem>
<asp:ListItem Text="USA" Value="USA"></asp:ListItem>
<asp:ListItem Text="Canada" Value="Canada"></asp:ListItem>
</asp:DropDownList>
<asp:TextBox ID="txtbxState" class="txtbxState" ClientIDMode="Static" runat="server" ></asp:TextBox>
<asp:TextBox ID="txtbxCity" class="txtbxCity" ClientIDMode="Static" runat="server" ></asp:TextBox>
<input id="btnSubmit" class="btnSubmit" type="button" value="Save" onclick="SubmitForm()" />
</div>
</form>
</body>
Couple things to note with this:
The button to submit the form is NOT an ASP button but a HTML button.
All of the input controls are ASP controls but they have the ClientIDMode set to Static, this will only work in .NET 4.0 or higher.
We set the class to the same thing as the ID in case we aren't using .NET 4.0 or higher. Any CSS classes that you want to also add to the control can be added after the dummy ID class.(for my examples I'm assuming you are in .NET 4.0 but I can easily switch them to work without the ClientIDMode attribute if you need)
The second piece to the puzzle is the JavaScript. There are a couple ways that we can accomplish what we need. The first is by using vanilla JS without the help of any plugins or external libraries. This saves a very small amount of processing time, a marginal amount of loading time and can accomplish everything we ask of it. But, if we include an external library, JQuery, and plugin, JQuery Validation, then we can make our lives a whole heck of a lot easier during the programming phase by reducing the amount of code we have to write by a factor of about 10. And if we are really concerned about the load times then we can use the client cache to store the external libraries so that they only have to download them once. So whether or not you decide to use any external JavaScript libraries is up to what your project needs but since you are only concerned with validating that the zip code is not empty I will not use any JQuery but I just thought it would be worth mentioning because of how streamlined it makes the process.
Once you are ready to submit your form your first step will be to validate that the zipcode is valid. You can do this a couple ways depending on how in depth you want to get. The quickest check would just be to verify that the zip code text box is not empty when the button is clicked. So to do that we would just need to do:
function SubmitForm() { //This will be assigned as the click handler on your button in your HTML
if (document.getElementById('txtbxZipCode').value != null && document.getElementById('txtbxZipCode').value != '') {
Save('YourHandler', GetQueryString, GetXmlString, SuccessHandler, FailureHandler);
} else {
//Your user needs to know what went wrong...
}
}
So, down to the meat and potatoes of this whole situation. The AJAX request. I've come up with a reusable function that handles the entire AJAX request that looks like:
function Save(handlerName, GetQueryString, GetXmlString, SuccessHandler, FailureHandler) {
// Date.GetTime gets the number of milliseconds since 1 January 1970, so we divide by 1000 to get the seconds.
end = (new Date().getTime() / 1000) + 30;
//This variable is the actual AJAX request. This object works for IE8+ but if you want backwards compatability for earlier versions you will need a different object which I can dig up for you if you need.
var xmlhttp = new XMLHttpRequest();
//This is the function that fires everytime the status of the request changes.
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
//Get all the headers to determine whether or not the request was successful. This is a header you will need to add to the response manually.
var xx = xmlhttp.getResponseHeader("Success");
//the object xx will be a string that you designate. I chose to use True as the indicator that it was successful because it was intuitive.
var x1 = xx.trim();
if (x1 != undefined && x1 == 'True' && (new Date().getTime() / 1000) < end) {
//If the response was successful and the timeout hasn't elapsed then we get the XML from the response and call the success handler
var xmlResponse = xmlhttp.responseXML;
SuccessHandler(sender, xmlResponse);
} else if ((new Date().getTime() / 1000) < end) {
//If the response was not successful and the timeout hasn't elapsed then we get the XML from the response and call the failure handler
var xmlResponse = xmlhttp.responseXML;
FailureHandler(sender, xmlResponse);
} //If the request was successful
} //If the readystate is 4 and the status is 200
} //OnReadyStateChanged function
//This gets the query string to be added to the url
var varString = GetQueryString();
//Build XML string to send to the server
var xmlString = GetXmlString();
//Open the request using the handler name passed in and the querystring we got from the function passed in
xmlhttp.open("POST", "../RequestHandlers/" + handlerName + ".ashx" + varString, true);
//This tells the handler that the content of the request is XML
xmlhttp.setRequestHeader("Content-Type", "text/xml");
//Send the request using the XML we got from the other function passed in.
xmlhttp.send(xmlString);
}
This function has a built in timeout which makes it so that if the server takes more than 30 seconds to respond to a request then any response that the client receives is ignored. For my implementations this is combined with another function that displays something to the user to tell them that the website is working on their request and if the time out elapses it tells them that a time out occurred.
The second thing this function does is it assumes that all handlers will be in a folder next to the root of your website named RequestHandlers. I use this set up just to consolidate all of my handler files but you can really change where it is looking to wherever you want.
The function itself takes in a string and four function pointers. The string represents the name of the handler that will be waiting to interpret the request, the four function pointers all have very specific jobs.
The first function pointer is GetQueryString this represents a function you will have to write that will append any variables that you deem necessary to the end of the URL being posted back to. This site gives a pretty accurate explanation of what the query string should be used for. For me a common GetQueryString function looks something like:
function GetPaymentQueryString() {
var varString = '';
varString = "?CCPayment=True";
return varString;
}
The second function pointer, GetXMLString, is used to create the XML string(go figure...) that will be sent to the handler page that we are posting back to. This string will represent the bulk of the request. Everything that should not be shown to anyone snooping your requests should be sent as an XML string, if you are really paranoid you can send it as an encrypted XML string but that's not, strictly speaking, necessary. It all depends on what you are sending, if its complete credit card information then, yeah, maybe you would want to consider it, but if its first and last names then encrypting it would be overkill.
A common GetXMLString function might look like:
function GetPaymentXmlString() {
var xmlString = '';
xmlString = '<?xml version="1.0" encoding="UTF-8"?><Address><ZipCode>' + document.getElementById('txtbxZipCode').value + '</ZipCode></Address>';
return xmlString;
}
The important part of that function is to get your XML right. The first tag is pretty universal and should be fine to use in most situations and then after that its all just matching the tags up. I left out a lot of your fields to save space.
The last two function pointers are what you will want to call if everything goes as planned and if something fails respectively. The way that I normally handle successful requests is to hide the inputs as a whole(usually by putting them inside of their own div section) and displaying a confirmation message of some sort. Failed requests can be a bit trickier because you have to tell the user why they failed. The way that I do that is by having a dummy div section above everything else on the page with some sort of special CSS attached to it that makes the div stand out in some way and if the request fails then I send a string of text from the server with my best guess of why it failed and assign it to the be displayed in the div section. How you decide to display the results to the user is obviously all dictated by the project itself. Since what you do when it succeeds or fails is basically on a project by project basis I can't really give a good generic example of what you should do so for this part you are on your own.
Now that we have those pieces in place, the last piece to make is the handler.
Basically for all intents and purposes a handler is basically an ASPX webpage with nothing on it. So the HTML that makes up your handler pages, which have the extension .ashx, will look like:
<%# WebHandler Language="VB" CodeBehind="YourHandler.ashx.cs" Class="YourHandler" %>
And that's it. There should be no other markup in your actual .ashx file. Obviously the name of the handler will change depending on what you are doing.
The code behind when creating an ashx file by default will be a class that contains a single function named ProcessRequest. Basically you can treat this function as a sort of "request received" event. So in your case you would move the content of your btnSubmit_Click function to the ProcessRequest function in the ashx file. You can add any properties or other functions that you want but the ProcessRequest function must be present for the handler to work as far as I know.
One extra step that you will need to do is to get the information from the XML that was sent to your handler and also tell the response that you will be sending XML back to the client.
So to get the XML from the request you will need to do:
IO.StreamReader textReader = New IO.StreamReader(context.Request.InputStream);
context.Request.InputStream.Seek(0, IO.SeekOrigin.Begin);
textReader.DiscardBufferedData();
XDocument xml = XDocument.Load(textReader);
String zip = xml.Elements("Address").Elements("ZipCode").FirstOrDefault().Value;
In order to send XML back to the client you will need to add a couple headers to the response and you accomplish that by adding(I think this is the correct way to implement an interface in C# not positive on this point though):
class YourHandler : System.Web.IHttpHandler, System.Web.SessionState.IReadOnlySessionState
under your class definition and:
context.Response.ContentType = "text/xml";
context.Response.ContentEncoding = System.Text.Encoding.UTF8;
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.Response.Cache.SetAllowResponseInBrowserHistory(True);
to the beginning of your ProcessRequest function. Those six lines tell the client it will be receiving XML and not to cache any of the response which will ensure that your clients always see the most up-to-date content.
So. There it is. You should now have the framework to validate user input, create an AJAX request, send the request to a custom handler, accept XML from the client, write XML to the client and display the res-...I knew I forgot something...
What is the client supposed to do with the XML it gets from the server? throw it at the wall and see what sticks? No that won't work. You'll need a way to interpret the XML on the client side. Luckily the XMLHttpRequest object has been written to make this task a lot easier than it sounds.
You may have noticed that I set up my success and failure handlers to take a sender object and an XML object. The sender is really overkill and can be ignored(or removed) for this example to work fine. The XML object is what we are concerned with for now. Before we even get into the client side I must mention that you will have to go through the same process on the server side as you did on the client side and manually write your XML string including all the values you want the client to know about. For this example I'm going to assume you want to display a FriendlyMessage to the user. To write the response to the client you will do something like:
using (System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(context.Response.Output)) {
context.Response.AddHeader("Success", true);
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml("<?xml version='1.0' encoding='UTF-8'?><Response><FriendlyMessage>" + Message + "</FriendlyMessage></Response>");
doc.WriteTo(writer);
writer.Flush();
writer.Close();
}
On the client side to get the FriendlyMessage from the XML you will need to do:
xml.getElementsByTagName("FriendlyMessage")[0].childNodes[0].nodeValue
Now this line makes a few assumptions. Like, you may want to add some checks in to make sure xml.getElementsByTagName("FriendlyMessage") actually has children before trying to evaluate them. Those sorts of checks are up to your discretion.
This time I think I've actually covered all the steps. I hope my "little" guide helps you and I didn't bore you too much. I apologize for the length but its sort of a process so getting it right takes a few steps. Once you get the base line in place and working it really lends itself to any situation. This layout also makes your user experience much better than having them wait for full trips to the server each time.
I sincerely hope this helps you get your project done and that I haven't skipped a step or something equally as embarrassing...
I have a aspx called user-photo-upload.aspx and another aspx called get-photo.aspx. I set the Session["PhotoId"] in the page_load method of user-photo-upload.aspx.
If I visit the user-photo-upload.aspx through the browser normally, the session can be retrieved in get-photo.aspx.
But if the flash uploads photo to the user-photo-upload.aspx page, I can't get the Session["PhotoId"] in get-photo.aspx.
I discover that the Session ID is different when visiting the page using browser normally or by flash. I don't know why flash uses another session.
What should I do?
The Flash plugin acts as a separate client, so both the browser and the plugin receiving individual session IDs is the expected behavior.
You will have to work around this by setting the original session ID as a variable in the Flash movie, either as a FlashVar or via JavaScript, then sending it as a GET parameter to the server along with the first request issued, and replacing the new ID created for the Flash client with the one that was given to the server.
This question relates to the same problem in Java, perhaps it can help you to solve your problem.
We had the same problem with a Flash Uploader. This is how we solved it...
(1) We added a new parameter session_id to the client side init code of the Flash Uploader:
<script type="text/javascript">
var swfu<%=RandomId %>;
$(document).ready(function() {
swfu<%=RandomId %> = new SWFUpload({
// Backend Settings
upload_url: "./picupload.aspx",
post_params : {
"PictureCategory" : "<%= EncryptedPictureCategory() %>",
"picture_id": "<%= EncryptedPictureId() %>",
"session_id": "<%= HttpContext.Current.Session.SessionID %>"
},
// *snip* ...
</script>
(2) We altered our Session_Start method in Global.asax.cs to accept Session IDs from the request:
protected void Session_Start(Object sender, EventArgs e)
{
if (Request["session_id"] != null)
{
bool isRedirected, isCookieAdded;
string oldSessionId = Request["session_id"];
SessionIDManager manager = new SessionIDManager();
manager.RemoveSessionID(HttpContext.Current);
manager.SaveSessionID(HttpContext.Current, oldSessionId, out isRedirected, out isCookieAdded);
}
else
{
Response.Redirect(LoginPagePath);
}
}
I have some problem that happens when controls are loaded in init and it still doesn't help me to get proper postback event fired on time.
I am trying to create a rich wizard control that will enable switching, links with description, completely customized steps, integration of substeps - by using dynamic control load that is avoids standard asp.net wizard way of loading.
Idea is to have on left part navigation, on right part content, or substeps that are run from right part and that go over whole area.
Download source project
Ok, I re-read the question, and here is what you have to do. You have to re-load these controls on each postback, give them always the same "Id". This can be done in Page_Init or in Page_Load event. And of course, you have to re-attach event handlers on each post back.
Many thanks.. well i found the answer - id was the problem, in load control method. I was doing this wizard.. well most of things work now.
If someone is interested to see how does this works.. there are some updates:
public void LoadSplitViewControl(string path)
{
SwitchNavigationView(NavigationView.SplitView);
LastNavigationView = NavigationView.SplitView;
LoadControl(SplitControlLoader, path, "LoadedControlSplit");
}
public void LoadSingleViewControl(string path)
{
SwitchNavigationView(NavigationView.SingleView);
LastNavigationView = NavigationView.SingleView;
LoadControl(SingleControlLoader, path, "LoadedControlSingle");
}
public void LoadSingleViewControlAsClear(string path)
{
SwitchNavigationView(NavigationView.SingleView);
LastNavigationView = NavigationView.SingleView;
LoadControlAsClear(SingleControlLoader, path, "LoadedControlSingle");
}
private void LoadControl(PlaceHolder holder, string path, string ID)
{
UserControl ctrl = (UserControl)Page.LoadControl(path);
ctrl.ID = ID;
LastControlPath = path;
holder.Controls.Clear();
holder.Controls.Add(ctrl);
}
//as i am using steps loaded controls using splitview and substeps controls using single view sometimes viewstate will not be valid so error will be thrown but u can resolve this by using LoadSingleViewControlAsClear that will load below method.
private void LoadControlAsClear(PlaceHolder holder, string path, string ID)
{
UserControl ctrl = (UserControl)Page.LoadControl(path);
ctrl.ID = ID;
LastControlPath = path;
ctrl.EnableViewState = false;
holder.Controls.Add(ctrl);
}
/another cool idea i am using for such an wizard is that i am not using viewstate but rather session object for saving values collected over steps. My session object key is generated by authenticated username and pageguid - so u can have many loaded pages and each of them will handle different session object./
public Guid PageGuid
{
get
{
if (PageGuidField.Value == "")
{
var _pageGuid = Guid.NewGuid();
PageGuidField.Value = _pageGuid.ToString();
return _pageGuid;
}
return new Guid(PageGuidField.Value);
}
}
OK I keep getting this error after about 3-4 minutes of churning:
Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Data.SqlClient.SqlException: Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Source Error:
Line 93:
Line 94: DataSet getData;
Line 95: getData = SqlHelper.ExecuteDataset(ConfigurationManager.ConnectionStrings["connstr"].ConnectionString, CommandType.StoredProcedure, "Course_NewReportGet_Get_Sav", objPara);
Line 96:
Line 97: foreach (DataRow dr in getData.Tables[0].Rows)
Here is the code, I think I am not doing something properly, I set the timeout to 5000 seconds though so it must be something else. You'll notice it's quite the nested loop of procedure calls. I am getting every Company, then getting every Course assigned to each Company, then for each course I am getting a report of all the users activity. There is about 250 companies, anywhere from 2-70 courses per comapany, and from 8 to 1000 users per course report, per company... so we're dealing with a lot of data here. That final call to the get report is a pretty massive stored procedure also...
I am trying to transform the data into a new form that will make it faster and easier to work with later but for now I have to parse through what we have and post it in the new way. It is all in the same database but I am not exactly sure how I would do this all in just SQL. Basically am using a stored procedure that is used by our reporting tools to get the data to post to the new table. But I need to run the procedure for each course for each company and then post the data for each user returned in the report from each course from each company... It's huge...
using System;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Data.SqlClient;
using Mexico.Data;
public partial class admin_tools_Optimus : System.Web.UI.Page
{
protected int step = 0;
protected string[] companies = new string[260];
protected string[] coursestrings = new string[260];
protected int total = 0;
protected int[] totalcourses = new int[260];
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Proceed(object sender, EventArgs e)
{
DataSet getCompanies = SqlHelper.ExecuteDataset(ConfigurationManager.ConnectionStrings["connstr"].ConnectionString, CommandType.StoredProcedure, "Companies_All_Get");
int counter = 0;
foreach (DataRow dr in getCompanies.Tables[0].Rows)
{
lstData.Items.Add(dr["companyid"].ToString() + ": " + dr["companyname"].ToString());
companies[counter] = dr["companyid"].ToString();
counter++;
}
lblCurrentData.Text = counter.ToString() + " companies ready, click next to get all company courses.";
total = counter;
GetCompanies();
}
protected void GetCompanies()
{
string[,] courses = new string[260, 200];
for (int i = 0; i < total-1; i++)
{
DataSet getBundles = SqlHelper.ExecuteDataset(ConfigurationManager.ConnectionStrings["connstr"].ConnectionString, CommandType.StoredProcedure, "CompanyCourses_ByCompanyID_Get_Sav", new SqlParameter("#companyid", companies[i]));
int counter = 0;
foreach (DataRow dr in getBundles.Tables[0].Rows)
{
courses[i, counter] = dr["bundleid"].ToString();
counter++;
}
string allID = "";
allID += courses[i, 0];
for (int ii = 0; ii < counter; ii++)
{
allID += "," + courses[i, ii];
}
Response.Write(allID + " <br/>");
coursestrings[i] = allID;
totalcourses[i] = counter;
}
GetUsers();
}
protected void GetUsers()
{
for (int i = 0; i < total - 1; i++)
{
SqlParameter[] objPara = new SqlParameter[10];
objPara[0] = new SqlParameter("#CompanyID", companies[i]);
objPara[1] = new SqlParameter("#CourseID", coursestrings[i]);
objPara[2] = new SqlParameter("#DateRangeType", 1);
//objPara[3] = new SqlParameter("#StartDate", startDate);
//objPara[4] = new SqlParameter("#EndDate", System.DateTime.Now.ToString("MM/dd/yyyy"));
objPara[5] = new SqlParameter("#UserName", "");
objPara[6] = new SqlParameter("#StartIndex", 1);
objPara[7] = new SqlParameter("#MaximumRows", 100000);
DataSet getData;
getData = SqlHelper.ExecuteDataset(ConfigurationManager.ConnectionStrings["connstr"].ConnectionString, CommandType.StoredProcedure, "Course_NewReportGet_Get_Sav", objPara);
foreach (DataRow dr in getData.Tables[0].Rows)
{
Response.Write("user: " + dr["userid"].ToString() + " / course: " + dr["bundleid"].ToString() + " - progress: " + dr["viewed"].ToString() + " - scored: " + dr["scored"].ToString() + "<br/><br/>");
}
}
}
}
PAGE CODE:
<%# Page Language="C#" AutoEventWireup="true" CodeFile="Optimus.aspx.cs" Inherits="admin_tools_Optimus" Debug="true" %>
<!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 runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="lblCurrentData" runat="server" Text="Click next to get all Companies"/><br />
<asp:Button ID="btnNext" runat="server" Text="Next" OnClick="Proceed" />
<br/>
<asp:ListBox ID="lstData" runat="server" height="300" Width="300" />
</div>
</form>
</body>
</html>
There are five different timeouts you have to know about when talking to a database from an asp.net page:
The database connection timeout Set on your SQLConnection object, possibly via the connection string.
The database command timeout Set on your SQLCommand object.
The ASP.Net Script Timeout Set on your page via Server.ScriptTimeout.
Other IIS timeouts Imposed on your page by IIS. See this link: http://msdn.microsoft.com/en-us/library/ms525386.aspx?ppud=4
The internet browser timeout The browser/client will only wait so long. You don't control this so there's no point worrying about it, but you should still know it exists.
Make sure you check the first 4 I listed.
Normally I'd also say something to the effect that 3+ minutes is way to long to wait for a page to load. You might have enough data to justify the query time, but if so that's too much data for a user to really evaluate in one page. Consider breaking it up. But in this case it sounds like you're building a 'report' that needs to be run infrequently. While I still question the merits of such reports (it's just too much data to go through by hand — dump it into a fact table or somewhere similar for additional data mining instead), I understand that businesses often want them anyway.
Also, looking at your code I can see where you might be able to write that all as one big query instead of a bunch of little ones. That would be a lot more efficient for the database at the expense of some additionally complexity in assembling your results, but the result will run much faster. But for something that doesn't run often, when you already have the stored procedures built to do it this way, you may not be able to justify re-writing things.
So I'll give you a pass on the long-running page... this time.
It looks like the timeout is happening when you're running the sql stored procedure (Your exception is of type SqlException). You would need to increase the Timeout of your Sql Stored Procedure execution, but I don't think you can do that with the SqlHelper class.
You'll need to go with the SqlCommand class, and set the timeout there.
This is an example of an extremely bad re-use of code. Instead of reusing the stored proc which makes you loop through all input variables you want, write a new set-based proc that selects the information based on joins. It will be much faster (assuming you have properly indexed of course). In accessing databases, you do not want to do something through a loop or a cursor if a set-based alternative is possible.
I have a problem which is perfectly described here (http://www.bokebb.com/dev/english/1972/posts/197270504.shtml):
Scenario:
Windows smart client app and the CrystalReportViewer for windows.
Using ServerFileReports to access reports through a centralized and disconnected folder location.
When accessing a report which was designed against DB_DEV and attempting to change its LogonInformation through the CrystalReportViewer to point against DB_UAT, it never seems to actually use the changed information.
It always goes against the DB_DEV info.
Any idea how to change the Database connection and logon information for a ServerFileReport ????
Heres code:
FROM A PRESENTER:
// event that fires when the views run report button is pressed
private void RunReport(object sender, EventArgs e)
{
this.view.LoadReport(Report, ConnectionInfo);
}
protected override object Report
{
get
{
ServerFileReport report = new ServerFileReport();
report.ObjectType = EnumServerFileType.REPORT;
report.ReportPath = #"\Report2.rpt";
report.WebServiceUrl = "http://localhost/CrystalReportsWebServices2005/ServerFileReportService.asmx";
return report;
}
}
private ConnectionInfo ConnectionInfo
{
get
{
ConnectionInfo info = new ConnectionInfo();
info.ServerName = servername;
info.DatabaseName = databasename;
info.UserID = userid;
info.Password = password;
return info;
}
}
ON THE VIEW WITH THE CRYSTAL REPORT VIEWER:
public void LoadReport(object report, ConnectionInfo connectionInfo)
{
viewer.ReportSource = report;
SetDBLogon(connectionInfo);
}
private void SetDBLogon(ConnectionInfo connectionInfo)
{
foreach (TableLogOnInfo logOnInfo in viewer.LogOnInfo)
{
logOnInfo.ConnectionInfo = connectionInfo;
}
}
Does anyone know how to solve the problem?
I know this isn't the programatic answer you're looking for, but:
One thing that helps with this sort of thing is not creating your reports connected to the database directly, but first you create a "Data Dictionary" for Crystal Reports (this is done in the Report Designer). Then you link all of your reports to this dictionary which maps the fields to the proper databases.
Done this way, you only have one place to change the database schema/connection info for all reports.
Also, in your report designer set the report to not cache the results (sorry I don't remember the exact option). Reports can either have their initial results included or not.
Don't you have to browse all the "databaseTable" objects of the report to redirect the corresponding connections? You'll find here my VB version of the 'database switch' problem ...
In your CrystalReportViewer object you should set
AutoDataBind="true"