If you look for solutions to get rid of huge ViewStates from your pages you will get a lot of people telling you to override SavePageStateToPersistenceMedium and LoadPageStateFromPersistenceMedium in your pages and do complicated stuff like keeping the ViewState in the Cache or in a database, calculating strange keys, etc.
No more! Net 2.0 has something called a PageStatePersister. It is an abstract class and every Page has one. In case no override occurs, the default is a HiddenFieldPageStatePersister, but you can also use the provided SessionPageStatePersister like this:
protected override PageStatePersister PageStatePersister { get { return new SessionPageStatePersister(this); } }
And that's it! It works with Ajax and UpdatePanel, too.
However, this is no "Silver Bullet", as the SessionPageStatePersister will have issues with multiple windows open with the same session (like pop up windows) as exampled in this nice article. Also check out this situation when, during Ajax callbacks, a full ViewState is returned due to the ever troublesome ImageButtons.
There is no reason not to create your own PageStatePersister, though. The abstract class is public (not internal and sealed as Microsoft likes their most useful classes) and you can inherit it. You can even store the state in the Cache! :)
A very comprehensive article on ViewState is here.
A while ago I wrote a post about the bug in the DataTable.Select method with columns with comma in their names.
Today I discovered another bug, when using DataTable.Select with an empty sort string, but not an empty filter string, there is an implicit sorting by the first column. Short example: a DataTable with a single column called "words" containing values c,b,a,d , when Selected with a filter like "words is not null" and a null or empty sort string, will return a,b,c,d.
The only solution for this was to drop DataTable.Select entirely and use DataView, with its NET 2.0 method DataView.ToTable. So the code to take a DataTable and return the filtered and sorted table would look like this:
But DataView has the same problem with columns with comma in their names. We solve it in the same way we solved it in the previous post: we change the column names, the sort and filter strings, we select, then we change the column names back:
publicstatic DataTable SelectSafe(this DataTable table, string filter, string sort) { var originalColumnNames = new Dictionary<string, string>();
foreach (DataColumn dc in table.Columns) { if (dc.ColumnName.IndexOf(',') > -1) { var columnName = dc.ColumnName; var safeColumnName = columnName.Replace(",", ";"); var reg = new Regex(Regex.Escape("[" + columnName + "]"), RegexOptions.IgnoreCase); dc.ColumnName = safeColumnName; if (!String.IsNullOrEmpty(filter)) { filter = reg.Replace(filter, "[" + safeColumnName + "]"); } if (!String.IsNullOrEmpty(sort)) { sort = reg.Replace(sort, "[" + safeColumnName + "]"); } originalColumnNames[safeColumnName] = columnName; } }
There is an article here that explains it better than I would. Basically, the new Visual Studio is finally released in beta and is supposed to be the IDE for .NET and Vista. The .NET Framework 3.5 (that is 2.85 in normal versioning? :D ) is also to be released here.
A previous post of mine detailed the list of ASP.Net controls that cannot be used with UpdatePanel and ASP.Net Ajax. Since I provided a fix for the validators earlier on, I've decided to try to fix the Menu, as well. And I did! At least for my problem which involved using a two level dynamic menu inside an UpdatePanel. Here is the code:
<script> function FixMenu() { if (typeof(IsMenuFixed)!='undefined') return; if (!window.Menu_HideItems) return; window.OldMenu_HideItems=window.Menu_HideItems; window.Menu_HideItems=function(items) { try { OldMenu_HideItems(items); } catch(ex) { if (items && items.id) { PopOut_Hide(items.id); } } } IsMenuFixed=true; } </script>
Now all you have to do is load it at every page load:
Explanation: the error I got was something like "0.cells is null or not an object", so I looked a little in the javascript code, where there was something like " for(i = 0; i < rows[0].cells.length; i++) {" and rows[0] was null. All this in a function called Menu_HideItems.
Solution 1: Copy the entire function (pretty big) and add an extra check for rows[0]==null.
Solution 2: Hijack the function, put it in a Try/Catch block and put the bit of the original function that appeared after the error in the catch. That I did.
Did you get a "0.cells is null or not an object" Javascript error while trying to use UpdatePanel and something like Menu or TreeView? The reason is that some controls are incompatible with UpdatePanel! Most amazingly, the newest controls seem to be the least compatible.
You just copied a directory with an ASP.Net web site in your wwwroot directory, you created an ASP.Net application from IIS/Web Sites, yet you get an error, no matter what page you try to access: The web application you are attempting to access on this web server is currently unavailable. It's an access issue. Give access to the directory to the ASPNET user.
Update 2nd of July 2008: The solution below doesn't always work. For a validator to not work you need to have .NET 2.0 installed without installing Service Pack 1 for it and you also need to add the validator dynamically (so it is not there when the page is loaded, but it appears there during an async postback).
The problem lies in the BaseValidator class itself, after the postback, the validator is not in the Page_Validators array. I've tried rehooking the validators, even enumerating all validators in the page and manually entering them in the Page_Validators array. It does not work. Mainly because the html spans of the validators are not the same thing as the validators and stuff like the id of the control to validate and other details are never rendered.
So you absolutely need to install the .Net 2.0 SP1 in order to use UpdatePanels with validators. =====
Today I've encountered a strange error, where the validation did work inside an UpdatePanel, but the validator message would not appear. This in a project where I used validation in GridViews inside their own UpdatePanel and they worked!
So I guess part of the reason the error occurs could be any one of these:
validated control and validator are both in a UserControl
validator is loaded at start of the page, it does not appear during Ajax calls, but it is replaced during Ajax calls
validated control is a ListBox, worse, an object inherited from a ListBox
Anyway, the true reason why the validators behaved in this fashion was that they were different from the html node of the validators. In other words, the Javascript array Page_Validators was filled with the validators correctly, did have the evaluation function, did return isvalid=false, but then, when the style.display attribute was changed to inline, the validator was not part of the html page DOM, as it was changed by Ajax.
Solution: a Javascript function that enumerates the validators, gets the validator DOM node by way of document.getElementById, stores the validator properties into this node and then replaces the element in the Page_Validators array. Problem: where should one load it? Possible solutions include submit button onclick events, form onsubmit events and in the Page_Load method. I would not use onclick, since I should have added onclick events on all possible submit buttons. I would not use RegisterOnSubmitStatement, since it ran the code after checking if the validators are valid or not (even if the validation itself took place afterward; now that is weird). The only solution is to use Page_Load.
There are various ways of doing that, too, since you could use MasterPages, custom made Page objects and even HttpHandlers or HttpModules. You also could have custom controls or objects that don't have a reference to the Ajax ScriptManager object or even to the Ajax library itself. Yes, I know, I am very smart. In my project, all the pages were inherited from a custom Page object, also in the project. So it was relatively easy.
Here is the code:
// in Page_Load string script=@" if (typeof(Page_Validators)!='undefined') for (var i=0; i<Page_Validators.length; i++) { // get DOM node var vld=document.getElementById(Page_Validators[i].id); if (vld) { for (var key in Page_Validators[i]) // check if the Page_Validators element has extra attributes // and add them to the node if ((vld[key]==null)&&(Page_Validators[i][key]!==null)) vld[key]=Page_Validators[i][key]; // replace the Page_Validators element with the reconstructed validator Page_Validators[i]=vld; } } "; // get current ScriptManager for this page ScriptManager sm=ScriptManager.GetCurrent(this); if ((sm!=null)&&(sm.IsInAsyncPostback)) { // if we did an Ajax postback, fix validators ScriptManager.RegisterStartupScript(Page, GetType(),"FixValidators",script,true); }
In order to access an object even after postbacks, you need to put it either in the Session or the ViewState. The ViewState is preserved only between postbacks, not between different pages and it is a Page property, so it is more efficient to use it. The problem with this method is that every object you put in the ViewState must be serializable. So, the quick and dirty path: if you don't have strange custom serializing to do, all you have to do it to decorate the object with the [Serializable] flag, like this:
Say you wouldn't have done this, you would have probably met with the "Class is not marked as Serializable". Duh!. However, in this situation above I have inherited from an object that implements ISerializable. I will get an error "The constructor to deserialize an object of type ... was not found". What that means is that the object must have a constructor that accepts two parameters, a SerializationInfo and a StreamingContext object. So we must add it to the object, like this:
public MyDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { }
public MyDictionary() {}
}
I added the second constructor because when adding a parametrized constructor, the default empty one is no longer inherited. So no more new MyDictionary() unless one adds it.
That does it! Please do check out the entire ISerializable interface documentation, since it requires, besides the constructor, a GetObjectData method, with the same parameters as the constructor, which controls the custom serialization of the object.
Update October 6 2014: New stuff, compare Levenstein vs Sift here:
Algorithm: Levenstein Sift
String 1: String 2:
Result:
Update June 25th 2013: I've decided to play a little with the suggestions in the comments and check for validity. This was spurned by the realization that a lot of people use my algorithm. So, in order to celebrate this, here is the "3B" version of the Sift3 algorithm: It is made in Javascript, this time, as it was easier to test and has the following extra features:
a maxDistance value that tells the algorithm to stop if the strings are already too different.
two pointers c1 and c2, rather than a single pointer c and two offsets
Instead of dividing to 2 the total length of the strings compared, now I divide it with 1.5. Why? Because this way the value is closer to the Levenshtein distance computed per random strings
Happy usage! The variant I posted was totally buggy. I removed it. Just use sift3Distance.
A while ago I wrote an entry here about Sift2, an improvement of Sift, the original and silly string distance algorithm. Now I am publishing Sift3, which is way more accurate and even simpler as an algorithm.
I found out that my algorithm is part of a class of algorithms that solve the Longest Common Substring problem, therefore I calculated the LCS, not the distance, then the distance from the LCS. The result is way more robust, easy to understand and closer to the Levenshtein algorithm both on random strings and user databases. Not to mention that there is no goto in this one.
BTW, if you are looking for an algorithm that detects switched words, this is not it :) This just looks for typos and small regional differences between the strings. I mean, you could normalize the strings, so that words are ordered by some mechanism, then it would work because the words wouldn't be switched :)
I promise to work on a word switching algorithm, but not in the near future. Without further ado, here is the code:
The C# code is a method in an object that has a private member maxOffset. As in Sift2 maxOffset should be around 5 and it represents the range in which to try to find a missing character.
publicfloat Distance(string s1, string s2, int maxOffset) { if (String.IsNullOrEmpty(s1)) { return String.IsNullOrEmpty(s2) ? 0 : s2.Length; } if (String.IsNullOrEmpty(s2)) { return s1.Length; } int c = 0; int offset1 = 0; int offset2 = 0; int lcs = 0; while ((c + offset1 < s1.Length) && (c + offset2 < s2.Length)) { if (s1[c + offset1] == s2[c + offset2]) lcs++; else { offset1 = 0; offset2 = 0; for (int i = 0; i < maxOffset; i++) { if ((c + i < s1.Length) && (s1[c + i] == s2[c])) { offset1 = i; break; } if ((c + i < s2.Length) && (s1[c] == s2[c + i])) { offset2 = i; break; } } } c++; } return (s1.Length + s2.Length)/2 - lcs; }
And here is the T-Sql code. This version is actually an improvement of my original source, gracefully provided by Todd Wolf:
CREATEFUNCTION [DBO].[Sift3distance2] ( @s1 NVARCHAR(3999),@s2 NVARCHAR(3999),@maxOffset INT ) RETURNSFLOAT AS BEGIN DECLARE @s1LEN INT,@s2LEN INT
IF(@s1Pos>0 AND (@s1Dist<=@s2Dist OR @s2Pos<1) AND @s1Dist<@maxOffset) SET @s1Offset=(@s1Pos-@wrkPos)+1 ELSE IF(@s2Pos>0 AND (@s2Dist<@s1Dist OR @s1Pos<1) AND @s2Dist<@maxOffset) SET @s2Offset=(@s2Pos-@wrkPos)+1 END
SET @currPos=@currPos+1 END
RETURN(@s1LEN+@s2LEN)/2.0-@matchCnt END
It doesn't give the same exact results as my own code, yet the result is close enough and the speed is about 20% higher.
You sometimes need to copy the exact structure of a database to an Sql2000 server, even if the source server is 2005.
Follow these steps:
open 2005 Sql Server Management Studio
right click on the offending database and go to Tasks -> Generate Scripts
do NOT check Script all objects in the selected database
click Next
set Include if NOT EXISTS to False
set Script for Server Version to SQL Server 2000
try to check only the objects and types of objects you actually need
create the script
delete all occurences of "WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)" from the generated script
Now the script should work on an SQL 2000 Server.. For the copying of data, the Server Management Studio has an option called Copy DatabaseExport Data, also in Tasks, that now accepts an Sql 2000 Server as destination.
I've found this interesting article by John Cronan about using the Abstract Factory pattern to access files, no matter if they are on FTP, HTTP or the local or networked file system. Basically he uses WebRequest.Create rather than any *Stream* class.
Interesting enough, he seems to be the only one providing a solution to the problem of accessing local file system resources when the default access rights do not allow you to, even if the logged on credentials would normally give you the access, thus solving an issue of the FileWebRequest class. Unfortunately he uses P/Invoke, which kind of contradicts the whole "more flexible than thou" approach of the article.
Overall an interesting read which gives you flexibility of file support, while taking away some of the specific advantages like seeking or appending. It's a definitely better approach than StreamReader and the ugly "URI formats are not supported." error.
A bonus for using this method is that it is compatible with the Office 2007/Vista Open Packaging addressing model, by way of the PackWebRequest class.
In other words: those curly bracket things in SQL. What? curly brackets in SQL? Yes! Imagine that :)
The idea is that most database systems adhere to the ODBC standard, at least ODBC 1.0. That means that, when you communicated with a database, you can send so called ODBC escape sequences that are translated into the SQL engine native objects.
Quick example: SELECT {d '2007-03-15'} will work in all ODBC 1.0 compliant DBMSs, including Microsoft SQL Server, MySql, PostgreSQL, Oracle, etc. and select a date object from 15 of March 2007, no matter the server configured country or language.
Interested yet? You can read the ODBC Programmer's Reference for more details. Short story shorter, here are the working and interesting parts (to me) of the ODBC escape sequences: select {d '2007-02-13' } select {t '22:20:30' } select {ts '2007-02-13 22:20:30' } select {fn curdate()} select {fn curtime()} select {fn User()} select {fn Database()} select {fn week(getdate())} select {fn quarter(getdate())} select {fn monthname(getdate())} select {fn dayname(getdate())} select {fn curdate()} select {fn dayofweek(getdate())} select {fn dayofyear(getdate())} select {guid '12345678-1234-1234-1234-123456789012'}
This is because the namespace has changed since the writing of Elion's article from Microsoft.Web.UI to System.Web.UI and there are two methods named RegisterClientScriptResource and two named RegisterStartupScript so you have to get the right one. Else you get the "Ambiguous match found" error.
The .NET validation framework has two parts, the client Javascript validation and the server validation. That means that the Javascript code needs a value to validate and the server validation needs a property to validate.
So, first step, you create your web user control by putting some controls in it. Then, you want to add a validator to the page to reference the newly created user control. And you get the error "Control '{0}' referenced by the ControlToValidate property of '{1}' cannot be validated.". Why? because every control to be validated needs to be decorated with the ValidationProperty attribute:
[ValidationProperty("Text")] public partial class ucDate : System.Web.UI.UserControl
Adding the first line to the control tells the validation framework to use the Text property of the UserControl.
Next step, you run the page and you notice the javascript doesn't work. The client validation works on html controls, by looking (recursively) for a 'value' attribute. When one looks at the source code, though, there is no html control that has the id of the user control. It doesn't use a span or a div to encapsulate its controls. All the controls have the id to show they are children to the user control, but the actual user control does not appear in the html source. So what is there to do?
<div id='<%=ClientID %>'></div>
You put all the controls in the ascx file of the User Control into this div. There you go! The validation works!
There is one more quirk regarding web user controls that have more children that render an html object with a 'value' attribute. In that case, remember that the validation starts from the very top, in our case the div. One could build simple javascript functions on the onchange or onsubmit javascript events, for example, to add a value attribute to the div. Best way would be using the onsubmit event, but be careful that the validation sequence also runs on the onsubmit event.
I've seen many a forum and blog entries that look like the title of this entry. I was frantically trying to find the solution for this a few hours ago and found a lot of questions of the same type, most of them abruptly ending into nothing. No solution, only the questions. What was I to do? Debug!
The problem was that buttons with __doPostBack seemed to work and those with WebForm_DoPostBackWithOptions did not. I pressed them and nothing happened.
Debugging the hell out of the two javascript functions (both used by NET 2.0, on who knows what conditions) I realized that the problem was not in the javascript functions! The problem was a false one.
The real problem is in the validators. If you have a validator on a field and the field has some wrong values in it and both field and validators are hidden, the submit will not work and you will not see what the problem is. Let me make this simple: if you have problems with submit buttons that seem not to work, check your validators!
Now, why the field was hidden and its values filled and the validator enabled is a problem, but that I can fix easily. The "Oh, I am so stupid" phenomenon probably stopped a lot of people posting the solution after they found it.