I once found my way to a beautiful lake in a nearby forest... Overjoyed at my good fortune, I want to share with you the directions to this lake, but I don't exactly know how I got there! How frustrated would you feel, if being a lover-of-lakes yourself, I told you that in order to find the lake, you should start by humming the song "A Whole New World" (the theme song to Disney's "Aladdin")?!
Assuming you were still paying me any attention, what if I revealed to you that when I came across this wonderful lake, I was humming that song and thus genuinely believed that is how I got "there." How reliable would my "directions" to the lake seem to you?
I contend that every spiritual tradition, every technique, every "practice" to "achieve" so-called "enlightenment" is merely a description of how the originator of such methods themselves "believed" they arrived at their own bliss (in the story above, that would be by humming that tune).
Does the arrival at a new perspective (e.g., the lake in the analogy above) automatically grant the one who arrives there with the precise knowledge of how they got there, or even more importantly, with the necessary skill to instruct others on how to do the same?
As you may have guessed, I don't think so.
Back to the lake analogy: what if on the other hand, instead of telling you to hum a particular tune, I told you that as I approached the lake, I remember hearing faint sounds of ducks, that I smelled a marshy odor, and noticed a slight decrease in temperature?
Ah! Now we're getting somewhere! This information might seem more useful than knowing what tune I was humming, but the conditions of when you stumble upon the lake (again an analogy for "enlightenment") might be completely different than when I found the lake, so my experience may not help you at all!
For example, will it help you to find the lake by "working on" your duck-call recognition skills? What if you are hard of hearing? Surely you will come upon the lake regardless of hearing the ducks as you approach!
Will it help you to attend "satsangs" where I instruct you on how to improve your sense of smell? Again, you may have a cold when you stumble across the lake and may not have smelled anything as you approached it!
Would it help you to practice three to five times a day special "techniques" to "strengthen" your ability to notice slight temperature changes? The day you arrive at the lake may be so cold, that you cannot feel any temperature change underneath all your layers, but the lake will appear before you all the same!
Am I making any sense here? Don't even get me started on selling you my special anti-oxidant, gluten-free herb diet to help attune your senses to marsh smells!!!
I mean let's say I really sincerely want you to find the lake... Let’s get one thing straight here: I consider myself to be a continual seeker who makes no pronouncements that I have "achieved" so-called enlightenment myself, and I’m tired of a certain feeling of frustration. But I want to put myself in the shoes of someone who believes they have found that “something.” Would any of these stories of my experience help anyone?! What could I actually do to help other seekers on their journeys to find the “lake?”
What if I simply told you about how lovely the lake was, and how the water glistened and the birds sang? What if you could feel my sense of joy?
Now we're getting closer, because that is motivating to hear, but at the same time still frustrating, because I didn't tell you where it is or how to get there.
What if instead of telling you about the beautiful lake, I simply exuded a joy of experiencing life right now and never mentioned the lake (because compared to this moment right now) the lake moment was no different than all subsequent moments, and thus I didn't care about the lake nor instructing you how to find it?
That feels a little better.
I propose that any spiritual method or technique be heard not as "directions to the lake," but rather as an account of that person's private experience. Period. They have no "credentials" at all to instruct others how to arrive anywhere - the only thing they can offer is an account of what they experienced, but most importantly, what they are experiencing right now!!!
I'm tired of "working" on my “smelling.” I have wasted so much time humming someone else's tune waiting for the lake. As is probably clear by now, I'm feeling frustrated with all well-meaning "instruction" towards spiritual "growth." I'm making a case for "inaction" here. No more "practice."
Instead of "opening my heart" when it is feeling closed, or trying to "be aware" of what is happening right now... Do whatever I am doing, feel whatever I am feeling. No practice. No technique.
Someone is either going to find enlightenment or they won't, but right now I say that no "practices" are going to help! This is my case for "inaction"... Do nothing special. Or do whatever you are doing.
I relish in a good story, and love a great myth. But if you were standing on your toilet when you had your realization of ultimate reality, I’m no longer going to stand on your toilet too!
How much faster will a plant grow if "coached" on the best techniques to reach the top of the trellis and fully realize its "plantness?" Does any amount of "advice" for a growing tree help it reach the "nirvanah" of full "treehood?"
Adam E's Software Blog (mostly ASP.Net)
Saturday, February 15, 2014
Thursday, January 5, 2012
GridView sorting with ViewState off - a solution to double querying and "why doesn't my Sorting event handler fire?"
I like to live a ViewState free lifestyle... call me prudish... My software engineering friends and I refer to ViewState as the "crack" or the "heroin" of ASP.Net development - darn hard to withdraw from once you've "sucked on the pipe" for a while, but once you're ViewState free, life is indeed more beautiful...
I pride myself in having resolved 99% of the withdrawal symptoms experienced when you make the leap to EnableViewState="false" on all your ASP.Net pages... I hope to share some of that knowledge with you all, gentle readers...
This blog post shall be devoted to one such solution I arrived at today: how to get a GridView to recognize that a user has clicked on a Sort header, with ViewState disabled, and without having to DataBind the GridView on every Page_Load.
THE PROBLEM
Your sorting event handler never fires! (could be other event handlers too btw)... You bang your head on your desk... your desk gets a dent... Your head gets a bruise... nothing helps... You go go over the steps you've taken:
You've EnableViewState="false" on your aspx page (because you like to live a "crack" free life!). You AllowSorting="true" on your GridView. You added a SortExpression to a column header. You added an asp:Button control and have a click event handler in your code behind. You believe in the use of "manual" databinding for your GridView, meaning GridView.DataSource = collection of custom objects queried from the database or middle tier of your choice, instead of using a GridView.DataSourceID (btw: those handy data source controls are VERY ViewState dependent and thus another form of "crack" or "heroin" IMO so I avoid them)... From the click event handler you call GridView.DataBind() . You also call GridView.DataBind() upon intial load of the page (i.e., when Page.IsPostBack is false)... You add a GridView.Sorting event handler (OnSorting=handler) and from that handler you call GridView.DataBind() again...
BUT, when you load your page in your browser, and you click on one of the grid column headings to sort your page... it doesn't sort! The page POSTs back, but the grid does not even bind: it even shows up empty! When you set break points in your code, you find that the Sorting event handler is never even hit! WTF you say?!
You do a google search and you find this wonderful blogpost that you hope will help you out!
http://www.aarongoldenthal.com/post/2009/04/19/Manually-Databinding-a-GridView.aspx
You read this blog post with enthusiasm, until you reach the part that says you need to use ViewState ( ViewState["SortExpression"] = something or other )... You sigh in agony that you just can't seem to beat this ViewState monster!
But then you go on to read the comments!... You read the following excellent comment from Aaron!
With glee you refactor your code and get rid of the if(!Page.IsPostBack) section and simply call the GridView.DataBind() code on every Page_Load or Page_Init! You fire up your browswer, click a sort heading, and lo and behold, your Sort event handler fires! Problem solved! ... so you think!...
Indeed, this was the solution I used for years, but it is somewhat of a compromise... The drawback is that for every Sort or button click on the page, the Grid will bind twice: once on page load and then again on the sort or click event handler. If you are doing a SQL query on every DataBind() and you don't have any fancy caching strategy, having two SQL queries on every Page_Load is of course wasteful. I lived for years with the double querying drawback since where I worked at the time had an amazing distributed caching architecture (both on the web server and on the middle tier), and double querying was not expensive... But today I am working with a plain old SQL server database, and I'd rather not query the DB twice, just so I can get Sorting working...
THE SOLUTION (BETA!!!)
I want a simple syntax on my aspx page, with minimal code, with a pattern I can use over and over. I want to use "manual" databinding to a collection of custom objects that comes from a middle tier or database.
Let's get right to code shall we? Stick a textBox, grid view and a button on to your aspx page:
Next, add this to your Page_Load event:
Add this to your code behind also... This is the method that loads the data from your SQL Server DB, middle-tier, or whatever:
OK, the above code is the BARE MINIMUM of what you need on any given aspx page. Of course, it calls some custom code below. The above assumes that grid.AutoGenerateColumns="true" which is rarely the case in the real world... The above solution only supports searching by the value in the txtName TextBox if you actually implement that feature yourself. It also only supports Sorting in one direction (ASCENDING). See other blog posts for how to sort both ways...
I prefer to define my grids in the code behind as of late and I do it in complete strongly typed fashion:
Next, for the magic part... Create a public static class. Give it a name of your choice (e.g., public static class DataTableUtils). Put the following code in it:
OK, the above requires some explanation, doesn't it? Read the comments in the code... I hope they are self-explanatory... I hate hacking ASP.Net and getting the __EVENTARGUMENT field, but I feel it is justified in this case. I couldn't find any other way for the Page to know that someone had clicked on a Sortable column heading BEFORE I call DataBind(). That is what I want. I want to discover that a user has clicked on a column, BEFORE the grid is bound. Normally, with ViewState on, the grid re-binds to the (bloated) data posted in the ViewState, notices that a Sort event needs to be handled, and then rebinds the grid again. In my case, I don't rebind the grid again! In my generic Sort event handler I do nothing at all! Intentionally...
There are any number of improvements that can be made to the code above, but I'll leave that to you gentle reader! For starters, I could implement an .OrderBy() explicitly for each type of SortExpression, but I want as automagic as possible in this case.
I took the time to post this blog posting because I want to give back somehow to the "Internets" a little bit of what I've taken over the years! Let me know if this works for you!
I pride myself in having resolved 99% of the withdrawal symptoms experienced when you make the leap to EnableViewState="false" on all your ASP.Net pages... I hope to share some of that knowledge with you all, gentle readers...
This blog post shall be devoted to one such solution I arrived at today: how to get a GridView to recognize that a user has clicked on a Sort header, with ViewState disabled, and without having to DataBind the GridView on every Page_Load.
THE PROBLEM
Your sorting event handler never fires! (could be other event handlers too btw)... You bang your head on your desk... your desk gets a dent... Your head gets a bruise... nothing helps... You go go over the steps you've taken:
You've EnableViewState="false" on your aspx page (because you like to live a "crack" free life!). You AllowSorting="true" on your GridView. You added a SortExpression to a column header. You added an asp:Button control and have a click event handler in your code behind. You believe in the use of "manual" databinding for your GridView, meaning GridView.DataSource = collection of custom objects queried from the database or middle tier of your choice, instead of using a GridView.DataSourceID (btw: those handy data source controls are VERY ViewState dependent and thus another form of "crack" or "heroin" IMO so I avoid them)... From the click event handler you call GridView.DataBind() . You also call GridView.DataBind() upon intial load of the page (i.e., when Page.IsPostBack is false)... You add a GridView.Sorting event handler (OnSorting=handler) and from that handler you call GridView.DataBind() again...
BUT, when you load your page in your browser, and you click on one of the grid column headings to sort your page... it doesn't sort! The page POSTs back, but the grid does not even bind: it even shows up empty! When you set break points in your code, you find that the Sorting event handler is never even hit! WTF you say?!
You do a google search and you find this wonderful blogpost that you hope will help you out!
http://www.aarongoldenthal.com/post/2009/04/19/Manually-Databinding-a-GridView.aspx
You read this blog post with enthusiasm, until you reach the part that says you need to use ViewState ( ViewState["SortExpression"] = something or other )... You sigh in agony that you just can't seem to beat this ViewState monster!
But then you go on to read the comments!... You read the following excellent comment from Aaron!
Aaron 8/17/2010 8:59 PM @Guy, That sounds like what you'd see if ViewState was disabled for the GridView. If that's the case, try databinding on every page load, not just on postback. This is yet another case where data source controls are helping you behind the scenes - if ViewState is disabled then the GridView will be databound as the post data is being processed (which needs to be done to re-populate the original data in the GridView, data that's otherwise stored in ViewState).You jump for joy! Someone else had the same problem as you and lo and behold the solution is to bind your GridView on EVERY PageLoad - regardless of whether IsPostBack or not!
With glee you refactor your code and get rid of the if(!Page.IsPostBack) section and simply call the GridView.DataBind() code on every Page_Load or Page_Init! You fire up your browswer, click a sort heading, and lo and behold, your Sort event handler fires! Problem solved! ... so you think!...
Indeed, this was the solution I used for years, but it is somewhat of a compromise... The drawback is that for every Sort or button click on the page, the Grid will bind twice: once on page load and then again on the sort or click event handler. If you are doing a SQL query on every DataBind() and you don't have any fancy caching strategy, having two SQL queries on every Page_Load is of course wasteful. I lived for years with the double querying drawback since where I worked at the time had an amazing distributed caching architecture (both on the web server and on the middle tier), and double querying was not expensive... But today I am working with a plain old SQL server database, and I'd rather not query the DB twice, just so I can get Sorting working...
THE SOLUTION (BETA!!!)
I want a simple syntax on my aspx page, with minimal code, with a pattern I can use over and over. I want to use "manual" databinding to a collection of custom objects that comes from a middle tier or database.
Let's get right to code shall we? Stick a textBox, grid view and a button on to your aspx page:
<asp:TextBox ID="txtMemberName" runat="server" /> <asp:gridview id="grid" runat="server"/> <asp:Button ID="btnSearch" runat="server" Text="Search" OnClick="btnSearch_Click" />
Next, add this to your Page_Load event:
protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { //TODO: this is optional. If you expect your //result set to be too large, //you would obviously NOT bind on initial load of page loadData(); } else { //On PostBack, check if grid is sorting //(this is a custom extension method). //The native GridView Sorting event handler is not //called at this point in lifecycle so it's no good to us. if (grid.IsSorting()) { loadData(); } } } protected void btnSearch_Click(object sender, EventArgs e) { loadData(); }
Add this to your code behind also... This is the method that loads the data from your SQL Server DB, middle-tier, or whatever:
private void loadData() { //replace this with a custom call of your choice to your data layer or middle tier IListmyList = BusinessLayer.SearchMethod(_someId, txtName.Text); //OR if you want a quick demo of how this works use this //instead and comment out line above var myList = (new int[] { 4, 3, 2, 1 }).Select(i => new { ID = i, Name = i + "Adam" }).ToList(); //Normally, you would set grid.DataSource = myList but instead call custom //SetDataSourceSortable method to make grid sortable automagically grid.SetDataSourceSortable("ID", SortDirection.Descending, myList); //call grid databind grid.DataBind(); }
OK, the above code is the BARE MINIMUM of what you need on any given aspx page. Of course, it calls some custom code below. The above assumes that grid.AutoGenerateColumns="true" which is rarely the case in the real world... The above solution only supports searching by the value in the txtName TextBox if you actually implement that feature yourself. It also only supports Sorting in one direction (ASCENDING). See other blog posts for how to sort both ways...
I prefer to define my grids in the code behind as of late and I do it in complete strongly typed fashion:
//define GridView - note this code will NOT compile on your //machine... I'm simply including it as an example of good coding //practices grid.Columns.Add(new HyperLinkField() { HeaderText = "ID", DataTextField = MemberTable.MemberIDColumn, DataNavigateUrlFields = new string[] { MemberTable.MemberIDColumn }, DataNavigateUrlFormatString = PageName.MemberDetails.GetNavigateUrlFormatString() }); grid.Columns.Add(new BoundField() { HeaderText = "Name", DataField = MemberTable.NameColumn }); grid.Columns.Add(new EnumField<MemberStatusType>() { HeaderText = "Status", DataField = MemberTable.MemberStatusTypeIDColumn });
Next, for the magic part... Create a public static class. Give it a name of your choice (e.g., public static class DataTableUtils). Put the following code in it:
/// <summary> /// Alternative to setting .DataSource property directly, takes in an IList of any type and /// converts that list to easily sortable DataView /// </summary> public static void SetDataSourceSortable<T>(this GridView grid, string defaultSortExpression, SortDirection defaultSortDirection, IList<T> dataSource) { //turn on sorting so Headers have hyperlinks on them grid.AllowSorting = true; //call a dummy handler so we don't get a Sorting event not handled error!!! grid.Sorting += new GridViewSortEventHandler(grid_Sorting); //add sort expressions to all Header links AddSortExpressions(grid); //convert the IList<T> to a DataTable for easy sorting DataTable table = dataSource.ToDataTable(); //TODO: get sort direction per column from post back - that's another blog post! //for now just default to "ASC" for post backs string sortDirection = grid.Page.IsPostBack ? "ASC" : (defaultSortDirection == SortDirection.Ascending ? "ASC" : "DESC"); //Get Sort Expression from PostBack (if any) table.DefaultView.Sort = GetSortExpressionFromPost(grid, defaultSortExpression) + " " + sortDirection; //set the datasource of the grid to the DataView of DataTable grid.DataSource = table.DefaultView; } static void grid_Sorting(object sender, GridViewSortEventArgs e) { //DO NOTHING!!! IT'S TOO LATE! Instead, in calling page load need to call "if (Page.IsPostBack && grid.IsSorting()) loadCustomData()..."; } /// <summary> /// Checks the Request for Posts for example: /// __EVENTARGUMENT=Sort$FirstName /// __EVENTTARGET = ctl00$ctl00$Main$m$grid /// </summary> public static string GetSortExpressionFromPost(GridView grid, string defaultSortExpression) { //__EVENTARGUMENT=Sort$FirstName //__EVENTTARGET = ctl00$ctl00$Main$m$grid string sortExpression = defaultSortExpression; string arg = HttpContext.Current.Request.Form["__EVENTARGUMENT"]; string target = HttpContext.Current.Request.Form["__EVENTTARGET"]; //check if target matches if (grid.UniqueID == target) { if (!string.IsNullOrEmpty(arg)) { string[] args = arg.Split('$'); if (args.Length == 2 && args[0] == "Sort") { sortExpression = args[1]; } } } return sortExpression; } /// <summary> /// Checks POST for __EVENTARGUMENT that is a sort expression which is the way /// a grid SHOULD work in .Net IMHO /// In calling page load need to call "if (Page.IsPostBack && grid.IsSorting()) loadCustomData()..."; /// </summary> public static bool IsSorting(this GridView grid) { return GetSortExpressionFromPost(grid, "DEFAULTSORT") != "DEFAULTSORT"; } /// <summary> /// Loops through all columns and adds a sort expression automatically. /// This is Experimental. /// </summary> public static void AddSortExpressions(GridView grid) { foreach (DataControlField column in grid.Columns) { if (column is BoundField) { column.SortExpression = ((BoundField)column).DataField; } else if (column is HyperLinkField) { column.SortExpression = ((HyperLinkField)column).DataTextField; } //TODO: add more column types or simply add SortExpressions manually } } /// <summary> /// WARNING! This uses reflection and is slow! /// http://stackoverflow.com/questions/564366/generic-list-to-datatable /// The only reason to use this is to gain the built in sorting features /// of DataViews on DataTables :( /// http://www.codeproject.com/KB/cs/GenericListControl.aspx /// </summary> public static DataTable ToDataTable<T>(this IList<T> data) { PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T)); DataTable table = new DataTable(); for (int i = 0; i < props.Count; i++) { PropertyDescriptor prop = props[i]; table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType); } object[] values = new object[props.Count]; foreach (T item in data) { for (int i = 0; i < values.Length; i++) { values[i] = props[i].GetValue(item) ?? DBNull.Value; } table.Rows.Add(values); } return table; }
OK, the above requires some explanation, doesn't it? Read the comments in the code... I hope they are self-explanatory... I hate hacking ASP.Net and getting the __EVENTARGUMENT field, but I feel it is justified in this case. I couldn't find any other way for the Page to know that someone had clicked on a Sortable column heading BEFORE I call DataBind(). That is what I want. I want to discover that a user has clicked on a column, BEFORE the grid is bound. Normally, with ViewState on, the grid re-binds to the (bloated) data posted in the ViewState, notices that a Sort event needs to be handled, and then rebinds the grid again. In my case, I don't rebind the grid again! In my generic Sort event handler I do nothing at all! Intentionally...
There are any number of improvements that can be made to the code above, but I'll leave that to you gentle reader! For starters, I could implement an .OrderBy() explicitly for each type of SortExpression, but I want as automagic as possible in this case.
I took the time to post this blog posting because I want to give back somehow to the "Internets" a little bit of what I've taken over the years! Let me know if this works for you!
Subscribe to:
Posts (Atom)