Custom state provider problem

Page 1 of 2 12 LastLast
  1. #1

    Custom state provider problem

    Hello

    I have a problem to setup custom state provider, can you please take a look what is wrong in code below?

    problem: State is not restored

    Code is bit longer as I tried to limit number of files.
    So here is sample:

    httpStateProviderHandler.ashx:
    (just a pretty simple state handler, in my project is database driven state provider)

    <%@ WebHandler Language="C#" Class="HttpStateProviderHandler" %>
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.SessionState;
    using Ext.Net;
    
    public class HttpStateProviderHandler : IHttpHandler,IReadOnlySessionState {
    	public class StateData
    	{
    		public int UserId { get; set; }
    		public string Key { get; set; }
    		public string Value { get; set; }
    	}
    
    	private static Dictionary<string, StateData> _dctState = new Dictionary<string, StateData>();
    
    	public void ProcessRequest (HttpContext context)
    	{
    
    		if (context.Request.HttpMethod == "POST")
    		{
    			StateData data = new StateData()
    			{
    				UserId = int.Parse(context.Request["UserId"].ToString()),
    				Key = context.Request["Key"],
    				Value = context.Request["Value"]
    			};
    
    			if (_dctState.ContainsKey(data.Key))
    			{
    				_dctState[data.Key] = data;
    			}
    			else
    			{
    				_dctState.Add(data.Key,data);
    			}
    
    			
    		}
    		else if (context.Request.HttpMethod == "GET")
    		{
    			var userId = int.Parse(context.Request["userId"]);
    
    			
    			var ret = JSON.Serialize(_dctState.Select(item => new {item.Key,item.Value.Value}).ToDictionary(item => item.Key));
    			context.Response.Write(ret);
    		}
    		else if (context.Request.HttpMethod == "DELETE")
    		{
    			var userId = int.Parse(context.Request["userId"]);
    			var key = context.Request["key"];
    
    			_dctState.Remove(key);
    
    			var result = new Dictionary<string, string>();
    			result.Add("success", "true");
    			JSON.Serialize(result);
    		}
    	}
    
    	public bool IsReusable {
    		get {
    			return false;
    		}
    	}
    
    }

    Page itself:

    <%@ Page Language="C#" %>
    
    <script runat="server">
       
    	[DirectMethod(RethrowException = true)]
    	public static object LoadData(string action, Dictionary<string, object> extraParams)
        {
                DateTime now = DateTime.Now;
    		StoreRequestParameters prms = new StoreRequestParameters(extraParams);
    
                var ret = new object[]
                {
                    new object[] {1, "3m Co", 71.72, 0.02, 0.03, now },
                    new object[] {2, "Alcoa Inc", 29.01, 0.42, 1.47, now },
                    new object[] {3, "Altria Group Inc", 83.81, 0.28, 0.34, now },
                    new object[] {4, "American Express Company", 52.55, 0.01, 0.02, now },
                    new object[] {5, "American International Group, Inc.", 64.13, 0.31, 0.49, now },
                    new object[] {6, "AT&T Inc.", 31.61, -0.48, -1.54, now },
                    new object[] {7, "Boeing Co.", 75.43, 0.53, 0.71, now },
                    new object[] {8, "Caterpillar Inc.", 67.27, 0.92, 1.39, now },
                    new object[] {9, "Citigroup, Inc.", 49.37, 0.02, 0.04, now },
                    new object[] {10, "E.I. du Pont de Nemours and Company", 40.48, 0.51, 1.28, now },
                    new object[] {11, "Exxon Mobil Corp", 68.1, -0.43, -0.64, now },
                    new object[] {12, "General Electric Company", 34.14, -0.08, -0.23, now },
                    new object[] {13, "General Motors Corporation", 30.27, 1.09, 3.74, now },
                    new object[] {14, "Hewlett-Packard Co.", 36.53, -0.03, -0.08, now },
                    new object[] {15, "Honeywell Intl Inc", 38.77, 0.05, 0.13, now },
                    new object[] {16, "Intel Corporation", 19.88, 0.31, 1.58, now },
                    new object[] {17, "International Business Machines", 81.41, 0.44, 0.54, now },
                    new object[] {18, "Johnson & Johnson", 64.72, 0.06, 0.09, now },
                    new object[] {19, "JP Morgan & Chase & Co", 45.73, 0.07, 0.15, now },
                    new object[] {20, "McDonald\"s Corporation", 36.76, 0.86, 2.40, now },
                    new object[] {21, "Merck & Co., Inc.", 40.96, 0.41, 1.01, now },
                    new object[] {22, "Microsoft Corporation", 25.84, 0.14, 0.54, now },
                    new object[] {23, "Pfizer Inc", 27.96, 0.4, 1.45, now },
                    new object[] {24, "The Coca-Cola Company", 45.07, 0.26, 0.58, now },
                    new object[] {25, "The Home Depot, Inc.", 34.64, 0.35, 1.02, now },
                    new object[] {26, "The Procter & Gamble Company", 61.91, 0.01, 0.02, now },
                    new object[] {27, "United Technologies Corporation", 63.26, 0.55, 0.88, now },
                    new object[] {28, "Verizon Communications", 35.57, 0.39, 1.11, now },
                    new object[] {29, "Wal-Mart Stores, Inc.", 45.45, 0.73, 1.63, now }
                };
    
    		return new { data = ret.Skip(prms.Start).Take(prms.Limit), total = 29 };
        }
    </script>
    
    <!DOCTYPE html>
    
    <html>
    <head runat="server">
        <title>Simple Array Grid With Paging and Remote Reloading - Ext.NET Examples</title>
    
        
    
     
    </head>
    <body>
        <form runat="server">
            <ext:ResourceManager runat="server" Namespace="" />
    
            <ext:GridPanel
                ID="GridPanel1"
                runat="server"
                Stateful="True"
                StateID="GridPanel1"
                Title="Array Grid"
                Width="800">
                <Store>
                    <ext:Store  ID="Store1" runat="server"  AutoLoad="True" PageSize="10" RemoteSort="true" RemotePaging="True">
                        <Model>
                            <ext:Model runat="server" IDProperty="id">
                                <Fields>
    	                            <ext:ModelField Name="id" />
                                    <ext:ModelField Name="company" />
                                    <ext:ModelField Name="price" Type="Float" />
                                    <ext:ModelField Name="change" Type="Float" />
                                    <ext:ModelField Name="pctChange" Type="Float" />
                                    <ext:ModelField Name="lastChange" Type="Date" />
                                </Fields>
                            </ext:Model>
                        </Model>
    	                <Proxy>
    		                <ext:PageProxy DirectFn="Ext.net.DirectMethods.LoadData">
    		                </ext:PageProxy>
    	                </Proxy>
                    </ext:Store>
                </Store>
                <ColumnModel runat="server">
                    <Columns>
                        <ext:Column runat="server" Text="Company" DataIndex="company" Flex="1" />
                        <ext:Column runat="server" Text="Price" Width="75" DataIndex="price">
                        </ext:Column>
                        <ext:Column runat="server" Text="Change" Width="75" DataIndex="change">
                        </ext:Column>
                        <ext:Column runat="server" Text="Change" Width="75" DataIndex="pctChange">
                        </ext:Column>
                        <ext:DateColumn runat="server" Text="Last Updated" Width="125" DataIndex="lastChange" Format="H:mm:ss" />
                    </Columns>
                </ColumnModel>
             
                <View>
                    <ext:GridView runat="server" StripeRows="true" />
                </View>
                <BottomBar>
                     <ext:PagingToolbar runat="server">
                       
                    </ext:PagingToolbar>
                </BottomBar>
            </ext:GridPanel>
        </form>
    
    
    <script>
    
    	Ext.define('App.util.HttpStateProvider', {
    		extend: 'Ext.state.Provider',
    		requires: ['Ext.state.Provider', 'Ext.Ajax'],
    		alias: 'util.HttpProvider',
    		processStateSave: false,
    		config: {
    			userId: null,
    			url: null,
    			stateRestoredCallback: null
    		},
    
    		constructor: function (config) {
    			if (!config.userId) {
    				throw 'App.util.HttpStateProvider: Missing userId';
    			}
    			if (!config.url) {
    				throw 'App.util.HttpStateProvider: Missing url';
    			}
    
    			this.initConfig(config);
    			var me = this;
    
    			//me.restoreState();
    			me.callParent(arguments);
    		},
    		get: function () {
    			return this.callParent(arguments);
    		},
    		set: function (name, value) {
    
    			//if (this.processStateSave == false)
    			//return;
    
    			var me = this;
    
    			if (typeof value == 'undefined' || value === null) {
    				me.clear(name);
    				return;
    			}
    
    			me.saveStateForKey(name, value);
    			me.callParent(arguments);
    		},
    
    		initStateFromData: function (result) {
    			for (var property in result) {
    				if (result.hasOwnProperty(property)) {
    					this.state[property] = this.decodeValue(result[property]);
    				}
    			}
    		},
    		// private
    		restoreState: function () {
    
    			var me = this,
    				callback = me.getStateRestoredCallback();
    
    			//debugger
    			//if (localStorage["alaState"] != null) {
    			//	this.initStateFromData(localStorage["alaState"]);
    			//	console.info('init state from storage');
    			//	return;
    			//}
    			Ext.Ajax.request({
    				url: me.getUrl(),
    				method: 'GET',
    				params: {
    					userId: me.getUserId()
    				},
    				success: function (response, options) {
    					var result = JSON.parse(response.responseText.trim());
    					//localStorage["alaState"] = result;
    					this.initStateFromData(result);
    
    					if (callback) {
    						callback();
    					}
    				},
    				failure: function () {
    					console.log('App.util.HttpStateProvider: restoreState failed', arguments);
    					if (callback) { callback(); }
    				}
    			});
    		},
    
    		// private
    		clear: function (name) {
    
    			var me = this;
    
    			me.clearStateForKey(name);
    			me.callParent(arguments);
    		},
    
    		// private
    		saveStateForKey: function (key, value) {
    
    			var me = this;
    			Ext.Ajax.request({
    				url: me.getUrl(),
    				method: 'POST',
    				params: {
    					userId: me.getUserId(),
    					key: key,
    					value: me.encodeValue(value)
    				},
    				failure: function () {
    					console.log('App.util.HttpStateProvider: saveStateForKey failed', arguments);
    				}
    			});
    		},
    
    		// private
    		clearStateForKey: function (key) {
    
    			var me = this;
    
    			Ext.Ajax.request({
    				url: me.getUrl(),
    				method: 'DELETE',
    				params: {
    					userId: me.getUserId(),
    					key: key
    				},
    				failure: function () {
    					console.log('App.util.HttpStateProvider: clearStateForKey failed', arguments);
    				}
    			});
    		}
    	});
    
    	var alaStateProvider = new App.util.HttpStateProvider({
    		userId: 1,
    		url: 'HttpStateProviderHandler.ashx',
    		stateRestoredCallback: function () {
    
    		}
    	});
    	Ext.state.Manager.setProvider(alaStateProvider);
    </script>
    </body>
    </html>
  2. #2
    Hello, Jiri!

    I have played your example, but it's not even sorting the data. Maybe before you worry with state management you should provide the actual state handling functionality? I simply can't tell state works because there's "remote sorting" set up in the grid but no sorting in code behind. I mean, the LoadData() method takes start/limit information but disregards sorting. So no matter how you may have sorting (more about that coming) saved in the state provider, it's not going to sort.

    That said... How exactly am I supposed to check your example, if the state you're considering is not just sorting (column position, widths perhaps?). There seems to be no remote filtering logic in the sample as well, so I believe that's not the aspect you're checking with state management. So, what should I be looking at?

    About the state provider, I'm getting syntax error from your ASHX code, line 48 => Error CS1061 'Dictionary<string, HttpStateProviderHandler.StateData>' does not contain a definition for 'Select' and no accessible extension method 'Select' accepting a first argument of type 'Dictionary<string, HttpStateProviderHandler.StateData>' could be found (are you missing a using directive or an assembly reference?). The System.Linq import says it's good so I wonder if we have an issue between the versions of our project environments? I believe you want to add something to that line to actually limit the results for the specified user id?

    I kindly ask you to clarify these points on what should we be testing and the syntax errors, and then probably will be able to give you a better diagnostic, hopefully with a solution, for your issue.

    For instance, it may be that just setting RemoteSort="false" in your page's line 70 is enough to the context of the test case, but if for some reason you think remote sorting is required, that's your take.

    Looking forward to your follow-up!
    Last edited by fabricio.murta; May 11, 2023 at 7:03 PM.
  3. #3
    Ok
    Sorry for troubles with example

    I made shorter one below, sorting should work now ( before I was trying on column position)

    Hovewer I'm not sure what to do with error in the StateProvider handler, the Select method is present on the Enumerable class for some time, it's defined here: C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6\ System.Core.dll

    Not sure if it will tell you anything - in any case state provider is just single user provider for this example - real one in our app is database based.

    Does this helped you a little?

    Updated sample

    
    <%@ Page Language="C#" %>
    
    <script runat="server">
    
    	public class DTO
    	{
    		public int Id { get; set; }
    		public string Name { get; set; }
    	}
    	[DirectMethod(RethrowException = true)]
    	public static object LoadData(string action, Dictionary<string, object> extraParams)
        {
                DateTime now = DateTime.Now;
    		StoreRequestParameters prms = new StoreRequestParameters(extraParams);
    
    
    		var ret = new List<DTO>();
    		for (var i = 0; i < 300; i++)
    		{
    			ret.Add(new DTO
    			{
    				Id = i,
    				Name = "Company"+i
    			});
    		}
    		
    		if (prms.Sort.Length > 0)
    		{
    			if (prms.Sort[0].Property == "Name")
    			{
    				if (prms.Sort[0].Direction == Ext.Net.SortDirection.ASC)
    				{
    					ret = ret.OrderBy(item => item.Name).ToList();
    				}
    				else
    				{
    					ret = ret.OrderByDescending(item => item.Name).ToList();
    				}
    			}
    			if (prms.Sort[0].Property == "Id")
    			{
    				if (prms.Sort[0].Direction == Ext.Net.SortDirection.ASC)
    				{
    					ret = ret.OrderBy(item => item.Id).ToList();
    				}
    				else
    				{
    					ret = ret.OrderByDescending(item => item.Id).ToList();
    				}
    			}
    
    		}
    		return new { data = ret.Skip(prms.Start).Take(prms.Limit), total = 29 };
    
        }
    </script>
    
    <!DOCTYPE html>
    
    <html>
    <head runat="server">
        <title>Simple Array Grid With Paging and Remote Reloading - Ext.NET Examples</title>
    
        
    
     
    </head>
    <body>
        <form runat="server">
            <ext:ResourceManager runat="server" Namespace="" />
    
            <ext:GridPanel
                ID="GridPanel1"
                runat="server"
                Stateful="True"
                StateID="GridPanel1"
                Title="Array Grid"
                Width="800">
                <Store>
                    <ext:Store  ID="Store1" runat="server"  AutoLoad="True" PageSize="10" RemoteSort="true" RemotePaging="True">
                        <Model>
                            <ext:Model runat="server" IDProperty="Id">
                                <Fields>
    	                            <ext:ModelField Name="Id" />
                                    <ext:ModelField Name="Name" />
                                   
                                </Fields>
                            </ext:Model>
                        </Model>
    	                <Proxy>
    		                <ext:PageProxy DirectFn="Ext.net.DirectMethods.LoadData">
    		                </ext:PageProxy>
    	                </Proxy>
                    </ext:Store>
                </Store>
                <ColumnModel runat="server">
                    <Columns>
    	                <ext:Column runat="server" Text="Id" DataIndex="Id" Flex="1" />
                        <ext:Column runat="server" Text="Name" DataIndex="Name" Flex="1" />
                    </Columns>
                </ColumnModel>
             
                <View>
                    <ext:GridView runat="server" StripeRows="true" />
                </View>
                <BottomBar>
                     <ext:PagingToolbar runat="server">
                       
                    </ext:PagingToolbar>
                </BottomBar>
            </ext:GridPanel>
        </form>
    
    
    <script>
    
    	Ext.define('App.util.HttpStateProvider', {
    		extend: 'Ext.state.Provider',
    		requires: ['Ext.state.Provider', 'Ext.Ajax'],
    		alias: 'util.HttpProvider',
    		processStateSave: false,
    		config: {
    			userId: null,
    			url: null,
    			stateRestoredCallback: null
    		},
    
    		constructor: function (config) {
    			if (!config.userId) {
    				throw 'App.util.HttpStateProvider: Missing userId';
    			}
    			if (!config.url) {
    				throw 'App.util.HttpStateProvider: Missing url';
    			}
    
    			this.initConfig(config);
    			var me = this;
    
    			//me.restoreState();
    			me.callParent(arguments);
    		},
    		get: function () {
    			return this.callParent(arguments);
    		},
    		set: function (name, value) {
    
    			//if (this.processStateSave == false)
    			//return;
    
    			var me = this;
    
    			if (typeof value == 'undefined' || value === null) {
    				me.clear(name);
    				return;
    			}
    
    			me.saveStateForKey(name, value);
    			me.callParent(arguments);
    		},
    
    		initStateFromData: function (result) {
    			for (var property in result) {
    				if (result.hasOwnProperty(property)) {
    					this.state[property] = this.decodeValue(result[property]);
    				}
    			}
    		},
    		// private
    		restoreState: function () {
    
    			var me = this,
    				callback = me.getStateRestoredCallback();
    
    			//debugger
    			//if (localStorage["alaState"] != null) {
    			//	this.initStateFromData(localStorage["alaState"]);
    			//	console.info('init state from storage');
    			//	return;
    			//}
    			Ext.Ajax.request({
    				url: me.getUrl(),
    				method: 'GET',
    				params: {
    					userId: me.getUserId()
    				},
    				success: function (response, options) {
    					var result = JSON.parse(response.responseText.trim());
    					//localStorage["alaState"] = result;
    					this.initStateFromData(result);
    
    					if (callback) {
    						callback();
    					}
    				},
    				failure: function () {
    					console.log('App.util.HttpStateProvider: restoreState failed', arguments);
    					if (callback) { callback(); }
    				}
    			});
    		},
    
    		// private
    		clear: function (name) {
    
    			var me = this;
    
    			me.clearStateForKey(name);
    			me.callParent(arguments);
    		},
    
    		// private
    		saveStateForKey: function (key, value) {
    
    			var me = this;
    			Ext.Ajax.request({
    				url: me.getUrl(),
    				method: 'POST',
    				params: {
    					userId: me.getUserId(),
    					key: key,
    					value: me.encodeValue(value)
    				},
    				failure: function () {
    					console.log('App.util.HttpStateProvider: saveStateForKey failed', arguments);
    				}
    			});
    		},
    
    		// private
    		clearStateForKey: function (key) {
    
    			var me = this;
    
    			Ext.Ajax.request({
    				url: me.getUrl(),
    				method: 'DELETE',
    				params: {
    					userId: me.getUserId(),
    					key: key
    				},
    				failure: function () {
    					console.log('App.util.HttpStateProvider: clearStateForKey failed', arguments);
    				}
    			});
    		}
    	});
    
    	var alaStateProvider = new App.util.HttpStateProvider({
    		userId: 1,
    		url: 'HttpStateProviderHandler.ashx',
    		stateRestoredCallback: function () {
    
    		}
    	});
    	Ext.state.Manager.setProvider(alaStateProvider);
    </script>
    </body>
    </html>
  4. #4
    Hello again, Jiri!

    Thanks for the reviewed test case! It helps a lot by not leaving question about why a given aspect doesn't work (if it should, etc).

    Quote Originally Posted by jirihost
    Hovewer I'm not sure what to do with error in the StateProvider handler, the Select method is present on the Enumerable class for some time, it's defined here: C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6\ System.Core.dll
    Yes, this looks like just intellisense not being as smart as it should. Today the error was "magically" gone in the Visual Studio session. The test project is targeted to .NET 4.7.2.

    Quote Originally Posted by jirihost
    I made shorter one below, sorting should work now ( before I was trying on column position)
    From what I've seen here, all that's left to make the example work is (in the page version provided in your last post):
    - uncomment line 139 -- move it below line 140 (after me.callParent(arguments))
    - change line 164 to decode the result's value, instead of the key-value pair entirely:
    this.state[property] = this.decodeValue(result[property].Value);
    With just that, the example worked fine here.

    You'll probably want to drop the userId parameter and config from the state provider and instead fetch it from the ASP.NET session that's probably set up when the user logs on the system. So maybe you can simplify a bit more the logic of the state provider client-side, and avoid the chance of users fetching/changing state of somebody else.

    Hope this helps!
    Fabrício Murta
    Developer & Support Expert
  5. #5
    Ok thanks

    can you confirm I did suggested modifications correctly?


    <%@ Page Language="C#" %>
    
    <script runat="server">
    
    	public class DTO
    	{
    		public int Id { get; set; }
    		public string Name { get; set; }
    	}
    	[DirectMethod(RethrowException = true)]
    	public static object LoadData(string action, Dictionary<string, object> extraParams)
        {
                DateTime now = DateTime.Now;
    		StoreRequestParameters prms = new StoreRequestParameters(extraParams);
    
    
    		var ret = new List<DTO>();
    		for (var i = 0; i < 300; i++)
    		{
    			ret.Add(new DTO
    			{
    				Id = i,
    				Name = "Company"+i
    			});
    		}
    		
    		if (prms.Sort.Length > 0)
    		{
    			if (prms.Sort[0].Property == "Name")
    			{
    				if (prms.Sort[0].Direction == Ext.Net.SortDirection.ASC)
    				{
    					ret = ret.OrderBy(item => item.Name).ToList();
    				}
    				else
    				{
    					ret = ret.OrderByDescending(item => item.Name).ToList();
    				}
    			}
    			if (prms.Sort[0].Property == "Id")
    			{
    				if (prms.Sort[0].Direction == Ext.Net.SortDirection.ASC)
    				{
    					ret = ret.OrderBy(item => item.Id).ToList();
    				}
    				else
    				{
    					ret = ret.OrderByDescending(item => item.Id).ToList();
    				}
    			}
    
    		}
    		return new { data = ret.Skip(prms.Start).Take(prms.Limit), total = 29 };
    
        }
    </script>
    
    <!DOCTYPE html>
    
    <html>
    <head runat="server">
        <title>Simple Array Grid With Paging and Remote Reloading - Ext.NET Examples</title>
    
        
    
     
    </head>
    <body>
        <form runat="server">
            <ext:ResourceManager runat="server" Namespace="" />
    
            <ext:GridPanel
                ID="GridPanel1"
                runat="server"
                Stateful="True"
                StateID="GridPanel1"
                Title="Array Grid"
                Width="800">
                <Store>
                    <ext:Store  ID="Store1" runat="server"  AutoLoad="True" PageSize="10" RemoteSort="true" RemotePaging="True">
                        <Model>
                            <ext:Model runat="server" IDProperty="Id">
                                <Fields>
    	                            <ext:ModelField Name="Id" />
                                    <ext:ModelField Name="Name" />
                                   
                                </Fields>
                            </ext:Model>
                        </Model>
    	                <Proxy>
    		                <ext:PageProxy DirectFn="Ext.net.DirectMethods.LoadData">
    		                </ext:PageProxy>
    	                </Proxy>
                    </ext:Store>
                </Store>
                <ColumnModel runat="server">
                    <Columns>
    	                <ext:Column runat="server" Text="Id" DataIndex="Id" Flex="1" />
                        <ext:Column runat="server" Text="Name" DataIndex="Name" Flex="1" />
                    </Columns>
                </ColumnModel>
             
                <View>
                    <ext:GridView runat="server" StripeRows="true" />
                </View>
                <BottomBar>
                     <ext:PagingToolbar runat="server">
                       
                    </ext:PagingToolbar>
                </BottomBar>
            </ext:GridPanel>
        </form>
    
    
    <script>
    
    	Ext.define('App.util.HttpStateProvider', {
    		extend: 'Ext.state.Provider',
    		requires: ['Ext.state.Provider', 'Ext.Ajax'],
    		alias: 'util.HttpProvider',
    		processStateSave: false,
    		config: {
    			userId: null,
    			url: null,
    			stateRestoredCallback: null
    		},
    
    		constructor: function (config) {
    			if (!config.userId) {
    				throw 'App.util.HttpStateProvider: Missing userId';
    			}
    			if (!config.url) {
    				throw 'App.util.HttpStateProvider: Missing url';
    			}
    
    			this.initConfig(config);
    			var me = this;
    
    			me.callParent(arguments);
    			me.restoreState();
    			
    		},
    		get: function () {
    			return this.callParent(arguments);
    		},
    		set: function (name, value) {
    
    			//if (this.processStateSave == false)
    			//return;
    
    			var me = this;
    
    			if (typeof value == 'undefined' || value === null) {
    				me.clear(name);
    				return;
    			}
    
    			me.saveStateForKey(name, value);
    			me.callParent(arguments);
    		},
    
    		initStateFromData: function (result) {
    			for (var property in result) {
    				if (result.hasOwnProperty(property)) {
    					//this.state[property] = this.decodeValue(result[property]);
    					this.state[property] = this.decodeValue(result[property].Value);
    				}
    			}
    		},
    		// private
    		restoreState: function () {
    
    			var me = this,
    				callback = me.getStateRestoredCallback();
    
    			//debugger
    			//if (localStorage["alaState"] != null) {
    			//	this.initStateFromData(localStorage["alaState"]);
    			//	console.info('init state from storage');
    			//	return;
    			//}
    			Ext.Ajax.request({
    				url: me.getUrl(),
    				method: 'GET',
    				params: {
    					userId: me.getUserId()
    				},
    				success: function (response, options) {
    					var result = JSON.parse(response.responseText.trim());
    					//localStorage["alaState"] = result;
    					this.initStateFromData(result);
    
    					if (callback) {
    						callback();
    					}
    				},
    				failure: function () {
    					console.log('App.util.HttpStateProvider: restoreState failed', arguments);
    					if (callback) { callback(); }
    				}
    			});
    		},
    
    		// private
    		clear: function (name) {
    
    			var me = this;
    
    			me.clearStateForKey(name);
    			me.callParent(arguments);
    		},
    
    		// private
    		saveStateForKey: function (key, value) {
    
    			var me = this;
    			Ext.Ajax.request({
    				url: me.getUrl(),
    				method: 'POST',
    				params: {
    					userId: me.getUserId(),
    					key: key,
    					value: me.encodeValue(value)
    				},
    				failure: function () {
    					console.log('App.util.HttpStateProvider: saveStateForKey failed', arguments);
    				}
    			});
    		},
    
    		// private
    		clearStateForKey: function (key) {
    
    			var me = this;
    
    			Ext.Ajax.request({
    				url: me.getUrl(),
    				method: 'DELETE',
    				params: {
    					userId: me.getUserId(),
    					key: key
    				},
    				failure: function () {
    					console.log('App.util.HttpStateProvider: clearStateForKey failed', arguments);
    				}
    			});
    		}
    	});
    
    	var alaStateProvider = new App.util.HttpStateProvider({
    		userId: 1,
    		url: 'HttpStateProviderHandler.ashx',
    		stateRestoredCallback: function () {
    
    		}
    	});
    	Ext.state.Manager.setProvider(alaStateProvider);
    </script>
    </body>
    </html>
  6. #6
    Hello again, Jiri!

    Actually... I forgot to mention a syntax error still lying in the provided code. Didn't you catch it in console messages (F12 dev tools) while running the example?

    The problem left is that there's a reference for this within a callback function which tries to access members of the state provider class. Callback functions actually don't share the context with their setting environment as an AJAX call is asynchronous. But the variables from its context are somehow carried, thus from your last reviewed code, line 191 becomes:

    me.initStateFromData(result);
    I have just checked it, it's all that was left for the sample to work. You were actually pretty close to having the working from the last code sample.

    Hope this helps!
    Fabrício Murta
    Developer & Support Expert
  7. #7
    Hello

    OK
    I got it working now. When I tried to comapre my sample and real application I found hovewer the difference - as we are having database based provider, its processing is no that quick like the in-memory one and state is not leaded ( even it was saved sucesfully)

    You can reproduce the same problem on same sample, just putting Thread.Sleep(2000) on some place in HttpStateProviderHandler.ashx

    when then grid is loaded ( even if I wait enough time to save previou state), the state is not applied

    Is there a something we can do about that?


    just fo giving you complete example, here is new stateproviderhandler - difference is just adding Thread.sleep on line 69

    <%@ WebHandler Language="C#" Class="HttpStateProviderHandler" %>
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Web;
    using System.Web.SessionState;
    using Ext.Net;
    
    public class HttpStateProviderHandler : IHttpHandler,IReadOnlySessionState {
    	public class StateData
    	{
    		public int UserId { get; set; }
    		public string Key { get; set; }
    		public string Value { get; set; }
    	}
    
    	private static Dictionary<string, StateData> _dctState = new Dictionary<string, StateData>();
    
    	public void ProcessRequest (HttpContext context)
    	{
    
    		if (context.Request["clear"] == "1")
    		{
    			_dctState.Clear();
    			return;
    
    		}
    		if (context.Request.HttpMethod == "POST")
    		{
    			StateData data = new StateData()
    			{
    				UserId = int.Parse(context.Request["UserId"].ToString()),
    				Key = context.Request["Key"],
    				Value = context.Request["Value"]
    			};
    
    			if (_dctState.ContainsKey(data.Key))
    			{
    				_dctState[data.Key] = data;
    			}
    			else
    			{
    				_dctState.Add(data.Key,data);
    			}
    
    			
    		}
    		else if (context.Request.HttpMethod == "GET")
    		{
    			var userId = int.Parse(context.Request["userId"]);
    
    			
    			var ret = JSON.Serialize(_dctState.Select(item => new {item.Key,item.Value.Value}).ToDictionary(item => item.Key));
    			context.Response.Write(ret);
    		}
    		else if (context.Request.HttpMethod == "DELETE")
    		{
    			var userId = int.Parse(context.Request["userId"]);
    			var key = context.Request["key"];
    
    			_dctState.Remove(key);
    
    			var result = new Dictionary<string, string>();
    			result.Add("success", "true");
    			JSON.Serialize(result);
    		}
    		Thread.Sleep(2000);   // THE ONLY DIFFERENCE
    	}
    
    	public bool IsReusable {
    		get {
    			return false;
    		}
    	}
    
    }
  8. #8
    Hello again, Jiri!

    Yes, you must be careful with race conditions. Did I mention early that you are doing asynchronous operations here? When that's the case, one must be aware of the possibilities involved. You just found one that needs to be handled.

    The solution then would involve getting your grid ready to apply the state, and disallowing any interaction with it until it's applied, to avoid overriding the upcoming state from the server. You also need to handle when no answer comes from the server at all.

    Should Ext.NET display an error message and require a manual retry to load the grid + apply state?
    Should Ext.NET just give up and display the grid in default settings anyway, risking to overwrite all hard-earned customization in the grid?
    Should Ext.NET try a few times before giving up and falling back to either alternative above?

    That's not all, depending on the changes of the grid, you may also need to merge a state saved for an old version of the grid (say, when you upload a new version to production); an extra column placed in a given order, but removed from the new version of the grid could just be ignored, but what to do with a new column introduced? Show first, last? Give an option to reset to defaults?

    Unfortunately Ext.NET can't really tell by itself what's best to be done in these situations, so you should at least check if its default behavior is okay.

    So, taking into account only the race condition to the grid, what can be done is:

    mask the grid on load.

    As soon as the grid gets rendered should be the right time to do so. The mask should prevent user interaction with it, but if extra certainty is desired, set the grid as initially Disabled="True" and re-enable when removing the mask.

    apply the state when the grid is ready

    Race-wise, instead of just handling the state and mask at once, do check if the grid is actually rendered, as the state manager calls for a restoreState() really early in the page life cycle, keep an Ext.defer() polling for the grid readiness. Maybe add a "guard" to avoid it from endlessly deferring for a grid. And then only apply the state, as soon as it is received from the server (from the callback) _and_ the grid is there.

    release the mask

    You may want to make the mask opaque so the user don't see the grid configuring around as it's changed on data received.

    updated sample code

    Enough theory, most of what was said above could be put together into this revision of your own page code:

    <%@ Page Language="C#" %>
    
    <script runat="server">
    
        public class DTO
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    
        [DirectMethod(RethrowException = true)]
        public static object LoadData(string action, Dictionary<string, object> extraParams)
        {
            DateTime now = DateTime.Now;
            StoreRequestParameters prms = new StoreRequestParameters(extraParams);
    
            var ret = new List<DTO>();
            for (var i = 0; i < 300; i++)
            {
                ret.Add(new DTO
                {
                    Id = i,
                    Name = "Company" + i
                });
            }
    
            if (prms.Sort.Length > 0)
            {
                if (prms.Sort[0].Property == "Name")
                {
                    if (prms.Sort[0].Direction == Ext.Net.SortDirection.ASC)
                    {
                        ret = ret.OrderBy(item => item.Name).ToList();
                    }
                    else
                    {
                        ret = ret.OrderByDescending(item => item.Name).ToList();
                    }
                }
                if (prms.Sort[0].Property == "Id")
                {
                    if (prms.Sort[0].Direction == Ext.Net.SortDirection.ASC)
                    {
                        ret = ret.OrderBy(item => item.Id).ToList();
                    }
                    else
                    {
                        ret = ret.OrderByDescending(item => item.Id).ToList();
                    }
                }
    
            }
            return new { data = ret.Skip(prms.Start).Take(prms.Limit), total = 29 };
    
        }
    </script>
    
    <!DOCTYPE html>
    
    <html>
    <head runat="server">
        <title>Simple Array Grid With Paging and Remote Reloading - Ext.NET Examples</title>
    </head>
    <body>
        <form runat="server">
            <ext:ResourceManager runat="server" Namespace="" />
    
            <ext:GridPanel
                ID="GridPanel1"
                runat="server"
                Stateful="True"
                StateID="GridPanel1"
                Title="Array Grid"
                Width="800">
                <Store>
                    <ext:Store ID="Store1" runat="server" AutoLoad="True" PageSize="10" RemoteSort="true" RemotePaging="True">
                        <Model>
                            <ext:Model runat="server" IDProperty="Id">
                                <Fields>
                                    <ext:ModelField Name="Id" />
                                    <ext:ModelField Name="Name" />
    
                                </Fields>
                            </ext:Model>
                        </Model>
                        <Proxy>
                            <ext:PageProxy DirectFn="Ext.net.DirectMethods.LoadData">
                            </ext:PageProxy>
                        </Proxy>
                    </ext:Store>
                </Store>
                <ColumnModel runat="server">
                    <Columns>
                        <ext:Column runat="server" Text="Id" DataIndex="Id" Flex="1" />
                        <ext:Column runat="server" Text="Name" DataIndex="Name" Flex="1" />
                    </Columns>
                </ColumnModel>
    
                <View>
                    <ext:GridView runat="server" StripeRows="true" />
                </View>
                <BottomBar>
                    <ext:PagingToolbar runat="server">
                    </ext:PagingToolbar>
                </BottomBar>
                <Listeners>
                    <AfterRender Handler="this.setLoading('Getting everything ready...');" />
                </Listeners>
            </ext:GridPanel>
        </form>
    
    
        <script>
            Ext.define('App.util.HttpStateProvider', {
                extend: 'Ext.state.Provider',
                requires: ['Ext.state.Provider', 'Ext.Ajax'],
                alias: 'util.HttpProvider',
                processStateSave: false,
                config: {
                    userId: null,
                    url: null,
                    stateRestoredCallback: null
                },
    
                constructor: function (config) {
                    if (!config.userId) {
                        throw 'App.util.HttpStateProvider: Missing userId';
                    }
                    if (!config.url) {
                        throw 'App.util.HttpStateProvider: Missing url';
                    }
    
                    this.initConfig(config);
                    var me = this;
    
                    me.callParent(arguments);
                    me.restoreState();
                },
                get: function () {
                    return this.callParent(arguments);
                },
                set: function (name, value) {
                    var me = this;
    
                    if (typeof value == 'undefined' || value === null) {
                        me.clear(name);
                        return;
                    }
    
                    me.saveStateForKey(name, value);
                    me.callParent(arguments);
                },
    
                initStateFromData: function (result) {
                    for (var property in result) {
                        if (result.hasOwnProperty(property)) {
                            this.state[property] = this.decodeValue(result[property].Value);
                        }
                    }
                },
    
                // private
                restoreState: function () {
    
                    var me = this,
                        callback = me.getStateRestoredCallback();
    
                    Ext.Ajax.request({
                        url: me.getUrl(),
                        method: 'GET',
                        params: {
                            userId: me.getUserId()
                        },
                        success: function (response, options) {
                            var result = JSON.parse(response.responseText.trim());
                            me.initStateFromData(result);
                            if (callback) {
                                callback();
                            }
                        },
                        failure: function () {
                            console.log('App.util.HttpStateProvider: restoreState failed', arguments);
                            if (callback) { callback(); }
                        }
                    });
                },
    
                // private
                clear: function (name) {
    
                    var me = this;
    
                    me.clearStateForKey(name);
                    me.callParent(arguments);
                },
    
                // private
                saveStateForKey: function (key, value) {
    
                    var me = this;
                    Ext.Ajax.request({
                        url: me.getUrl(),
                        method: 'POST',
                        params: {
                            userId: me.getUserId(),
                            key: key,
                            value: me.encodeValue(value)
                        },
                        failure: function () {
                            console.log('App.util.HttpStateProvider: saveStateForKey failed', arguments);
                        }
                    });
                },
    
                // private
                clearStateForKey: function (key) {
    
                    var me = this;
    
                    Ext.Ajax.request({
                        url: me.getUrl(),
                        method: 'DELETE',
                        params: {
                            userId: me.getUserId(),
                            key: key
                        },
                        failure: function () {
                            console.log('App.util.HttpStateProvider: clearStateForKey failed', arguments);
                        }
                    });
                }
            });
    
            var alaStateProvider = new App.util.HttpStateProvider({
                userId: 1,
                url: 'HttpStateProviderHandler.ashx',
                stateRestoredCallback: function () {
                    var clearGridLoading = function () {
                        if (window.GridPanel1 && GridPanel1.rendered) {
                            console.log("Grid panel is here, applying state and clearing mask...");
                            var savedState = Ext.state.Manager.get("GridPanel1");
                            if (savedState)
                                GridPanel1.applyState(Ext.state.Manager.get("GridPanel1"));
    
                            if (GridPanel1.isMasked())
                                GridPanel1.setLoading(false);
                        } else {
                            console.log("Grid panel not ready, polling for it...")
                            Ext.defer(clearGridLoading, 100);
                        }
                    };
                    clearGridLoading();
                }
            });
            Ext.state.Manager.setProvider(alaStateProvider);
        </script>
    </body>
    </html>
    The key points here should be around the introduced GridPanel1's listener to mask it, and then I used your instance-bound stateRestoreCallback() to hold the new code to apply the grid state when it is available. The code could be just merged into the custom provider definition, and it is tightly tied to the specific grid. If you're going to use it elsewhere, you may want to make a more "generic" handler to find which components are assigned to that state provider and feed them.

    You may add a grain of pepper to your testing by making the server take a random amount of time to return the call. Then reload the page several times to see how sometimes the provider sets up the grid at once and some other it pings once or twice in console about "not having the grid ready" to apply state.

    Something like this in your ASHX code should do:

        private static Random rng = new Random();
    
        public void ProcessRequest(HttpContext context)
        {
            System.Threading.Thread.Sleep(rng.Next(2500));
    Next, you should handle it when the AJAX response never comes back in the mean time; that is, a delay (by default) of 30 seconds in the server code should trigger. Ext.NET handles it when no response is received, and should trigger the failure callback for you to handle the event of no response.

    Hope this helps!
    Fabrício Murta
    Developer & Support Expert
  9. #9
    Absolutelly wonderfull answer, thanks a lot

    Sure I understand the Race condition problem, I was not just sure how to avoid it

    I understand what you are suggesting, for given sampel ( and likelly many others) it would work, I'm just thinking about something different - would it be possible to render somehow state directly to the page ( to JSON/Javascript) so when Ext.isReady (i.e scripts loaded) the state would be ready to?

    I understand it would slow down loading of the page for few seconds, but as we have big single page loading about 15 seconds, it should not harm too much.

    So pratically speaking instead of asynchonous loading of state push it sychronously so it's ready before page rendering (extjs render method) even starts

    Would somethink like that be possible?
  10. #10
    Hello again, Jiri!

    Glad to hear we are having progress in the matter.

    If you wanted some initial setting to be bound to the page load, you can just inline the state value into the custom provider's constructor, completely removing the restoreState() definition (and calls for it).

    You could do that during Page_Load and X.AddScript() to set a client-side variable with the object to feed me.initStateFromData(), or you could (literally) inline the resulting object straight to the custom provider's constructor, something like:

    me.initStateFromData(<%= StateObjectExpressionOrVariable %>);
    Now it's but an ordinary ASP.NET issue; you already know how to apply the state, it's just a matter of choosing how best you'd want to feed your page with the saved object.

    For reference, the end client-side command should look like this:

    me.initStateFromData({ GridPanel1: { Key: "GridPanel1", Value: "o%3Acolumns%3Da%253Ao%25253Aid%25253Ds%2525253Ah1%255Eo%25253Aid%25253Ds%2525253Ah2%5EstoreState%3Do%253Asorters%253Da%25253Ao%2525253Aroot%2525253Ds%252525253Adata%2525255Eproperty%2525253Ds%252525253AId%2525255Edirection%2525253Ds%252525253AASC%2525255Eid%2525253Ds%252525253AId" } });
    A world of options awaits you, have fun! :)

    This would also allow you to let the grid fully load at once (without masking or looking for the right time to unmask it), but you'd still need to watch out for the save (POST) to complete; if you change a state and quickly refresh the page, the operation may just be aborted and the state not saved at all -- or saved after the following load happened. Back to the race condition.

    Hope this helps!
    Fabrício Murta
    Developer & Support Expert
Page 1 of 2 12 LastLast

Similar Threads

  1. Replies: 2
    Last Post: Jul 28, 2011, 5:08 PM
  2. ASP.NET Membership Provider Problem
    By gayancc in forum 1.x Help
    Replies: 0
    Last Post: Jan 15, 2011, 8:47 AM
  3. Problem of Radio State
    By gdboy2002 in forum 1.x Help
    Replies: 2
    Last Post: Nov 26, 2010, 12:41 AM
  4. asp.net membership provider
    By kikopico in forum 1.x Help
    Replies: 3
    Last Post: Mar 04, 2010, 7:00 AM
  5. State in cookie problem....
    By mrozik in forum 1.x Help
    Replies: 1
    Last Post: Sep 09, 2009, 5:14 AM

Posting Permissions