This project is read-only.

Custom Control

May 11, 2010 at 9:53 AM

Hello,

 I try to create a custom control with your Framework, as you wrote in your article (http://ajaxcontrol.codeplex.com/wikipage?title=How%20to%20attach%20AJAX%20capabilities%20to%20your%20custom%20controls), I’ve checked the UpdateRegion.cs file. So I create my own ControlScriptStrategy, and do all as well as I can. But it doesn’t work…

I search where the problem is, and:

  • I delete this method in my ControlScriptStrategy to generate JavaScript :        public override void GenerateControlScript(ref TextWriter writer) { /* Do nothing. */ }
  • I change this line in your ControlScriptStrategy.cs (in order to find my AjaxControlMethod):

MethodInfo[] methods = TargetControl.GetType().BaseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);

With

MethodInfo[] methods = TargetControl.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);

Now everything seems to work find, my custom control and my user controls, (and your BingMapsSample too). What do you think about the changes?

By the way, your Framework seems very good, and I think I will use it in my next project.

Thanks in advance for your feedback.

Guillaume

May 11, 2010 at 11:53 AM

As I read your suggestion I had to hit myself on the head. Duh! Why aren't I doing that? 

I'm pretty sure I've gone back and forth with what you've provided and what's in the code now a couple times. I've written so many revisions of that class I can't remember exactly. I think I know why I did that though. There are some straaaange rules about how the GetMethods method will search the class hierarchy for the method you want. Read through the "Remarks" section of this the page in the MSDN docs: http://msdn.microsoft.com/en-us/library/4d848zkb(v=VS.90).aspx

What you've provided though is better than what I have. That's very clear. My code is probably the way it is now because I was testing mainly with User Controls that implemented IAjaxControl. When you include a User Control in your project a namespace_usercontrol_ascx class is generated when a site dynamically compiles or pre-compiles. I was probably trying to target the actual type of the User Control instead of the dynamically generated type. I don't know why I did it that way because just GetType().GetMethods() should work fine most of the time. 

Anyways, I'm actually planning on removing the call to TargetControl.GetType().GetMethods() in a future release and replacing it with a MethodCache class that works kinda like the TypeCache class does, making use of DynamicMethod delegates to handle the reflection. (BTW, do you know about the DynamicMethod technique for Reflection? You'll see it all over the place in my code. It removes almost all of the overhead involved with reflections and performs reflective calls nearly as fast as plain old method calls in a normal context. Do check it out.) I'm planning on having the MethodCache implementation scan through the class hierarchy manually so that those who want to extend such classes like the ControlScriptStrategy won't have to worry about whether or not the GetMethods() method will actually find the private methods embedded 4 levels deep into a class hierarchy that they're trying to target. (Once again, refer to the "Remarks" section of that MSDN page I mention above.)

Oh, also, if you're not planning on emitting any special JavaScript in addition to what the ControlScriptStrategy already emits, you don't have to create your own ControlScriptStrategy class. The UpdateRegion has its own extension of the class because it needs to emit an additional JavaScript file automatically whenever its on a page (it would really suck if developers would have to include the <script> reference for the UpdateRegion themselves). So, think of the ControlScriptStrategy as a means of packaging your custom scripts into own framework. This particular component is only meant for developers who are writing their own Custom Control library or are extending the AJAX Control Framework with their own features. If you don't manually set the ScriptStrategy property of your AJAX enabled class, the call to the AjaxControl.Initialize() method will ensure the default ControlScriptStrategy implementation is attached for you. :-) Pretty nifty ay? So if you need to include any JavaScript that is required by your custom controls, and the controls are specific to the project you're working on and not part of a framework of your own, its best to just use the ScriptManager to include the scripts on your page.

For another example on how to attach the AJAX functionality, check out the AJAX enabled controls of the BingMapsSample project. I believe the BaseAjaxControl class (I think it's called that) manually implements the IAjaxControl interface. Don't forget about the AjaxControl and AjaxUserControl classes that are in the AjaxControlFramework namespace. Both of these are pre-enabled with AJAX so you don't have to manually implement the IAjaxControl interface.

Oh, and a note on writing your own implementations. If you include any modified source code of the AJAX Control Framework in your own work, that modified code must be made available as open source according to the LGPL license. So, if you're using the framework for a work project, create a whole new Visual Studio project (a Class Library would be best) that will contain your code and create "extensions" of the AJAX Control Framework classes. This way you can work around the open source license and keep your "extensions" closed source as your company may require. The open source licenses can be a real pain in the rear. 

 

One last thing, I've got a working prototype of the JavaScriptBuildProvider. Keep your eyes peeled for that. I'm going to release a beta of the build provider that will include a resolution for the problem you've brought up here within the next week or two.

Thanks for contacting me. I'm always happy to help. If you have any more questions, ask away.

"Peanutbutter" Lou

 

 

 

May 25, 2010 at 9:55 AM

Thanks for your answer,

I have a question: I want to create some "Ajax Web Control" with their own Ajax functionalities (all must be embed in a dll), so I thnik I must create a control script strategy for each control, is it alright?

I have some other problems:

  • ·     I have 2 Master Pages like this:

Master Page 1

Master Page 2

Page

 

  

 

  

 

 

 

 

 

So in InternalScriptManager this line doesn't work:

List<HtmlControl> controls = AjaxControlUtils.FindChildControlsByTagName((currentPage.Master != null ? (Control)currentPage.Master : currentPage), "body");

So I write this code:

               Control arg = currentPage;

               MasterPage master = currentPage.Master;

               while (master != null)

                {

                   arg = (Control)master;

                   master = master.Master;

               }

               List<HtmlControl> controls = AjaxControlUtils.FindChildControlsByTagName(arg, "body");

What do you think about it?

  • Last but not Least in a Web App in Asp.Net 4.0 I have this exception:

"Operation could destabilize the runtime". in line 221 in StateStrategy:

ViewState = SaveViewStateRecursive(TargetControl);

If you want to convert your framework in .NET 4.0, I think you need to replace this line :

MethodInfo saveViewStateRecursiveMethodInfo = TypeOfControl.GetMethod("SaveViewStateRecursive", (BindingFlags.NonPublic | BindingFlags.Instance), null, Type.EmptyTypes, null );

By: MethodInfo saveViewStateRecursiveMethodInfo = TypeOfControl.GetMethod("SaveViewStateRecursive", (BindingFlags.NonPublic | BindingFlags.Instance));

Thanks,

Guillaume

May 26, 2010 at 5:44 AM

Hey Guillaume,

Thank you SO MUCH for submitting your problems. I use my own framework in a very narrow way and you've come across an issue or two that I may not have uncovered myself for a loooong time. 

Concerning your question about the custom ScriptStrategies; I'm finally adding content to the How to attach AJAX capabilities to your custom controls tutorial. I will be providing a thorough answer in the content once I finish it in a few hours. I'll post you a note when I'm all done with it. But, in short, yes you'll want to create a custom ScriptStrategy for each control if you're emitting a custom JavaScript file (whether embedded or rendered inline from a template) for the control. Since you're creating custom WebControls that must be embedded in the .dll, you'll probably went to embed your JavaScript in the assembly (make sure you add an [assembly:WebResource] attribute...look at the ScriptStrategy class for guidance on this) and override the RegisterFrameworkScript method, register your embedded script with the InternalScriptManager, set its dependencies, and then call the base RegisterFrameworkScript method. I'm sure you already figured that...but just backing up your thoughts. You probably won't ever ever need to extend the ControlCallbackStrategy so be sure to use that class as your CallbacStrategy. Basically, follow the example of the UpdateRegion class's constructor.

So I committed a .NET v4.0 branch of the framework where I fully remedy the "Operation could destabalize the runtime" exception you were getting. That exception happens with the DynamicMethod class when you're not binding correctly to a MethodInfo object. The MethodInfo object for the SaveViewStateRecursive internal method was returning null. The SaveViewStateRecursive method now accepts a single parameter of type ViewStateMode in v4.0. I've updated the code in the branch to how it should be. Thanks for point out this problem. I wasn't going to be testing this against .NET 4.0 for another week or so. We've got a good head start on this.

About a week ago I also implemented a robust solution for the GetMethods problem you were having. It's in the latest source code. It will work in almost any case. Thanks again for the heads up on that. 

Lastly, I fixed the InternalScriptManager code so that it finds the <body> element when master pages are nested. The code I included is very robust and should now work in every combination of control nesting. This code is also in the latest source code committed to CodePlex SVN. 

 

I need your opinion on something. I'm working on v2.0 right now, but I've come across a number of bugs that I've fixed (in addition to the ones you've reported). I feel that other developers might be having problems with the code as a result of the bugs. What would you personally prefer: I put out an interim release that contains the bug releases and maybe one new feature from v2.0 before I finish v2.0 (I might not be done for over a month), or I wait until v2.0 is finished before creating an new official release? 

You'll hear from me again very soon. Thanks again for all your help.

 

 

Jun 22, 2010 at 1:02 PM
Edited Jun 22, 2010 at 2:19 PM

Hi,

I'm currently working with Guillaume on his project.
Right now, we created some controls that works fine. But it seems there's a problem when we want to create a "hierarchy" of control. To summarize, we have an "Ajax Control" created dynamically inside other Webcontrol.
This control is a custom repeater and on each item of this repeater we are creating our "AjaxControl" (this control performs an event to permit to call our "AjaxControlMethod").
All our event is working fine until the call of the "AjaxControlMethod". The javascript proxy is working pretty well, we are entering into the js file of the WebControl.
But, when we are calling our "AjaxControlMethod" we are facing to this current error in response of JS method:
- The target [AJAXControl(UNIQUE_ID)] for the callback could not be found or did not implement ICallbackEventHandler.
I understood that "CallBackStrategy" enables to overload the ASP.NET Page Life Cycle so shall we implement our custom CallBackStrategy in our case or each control of our control tree should inherit of your "AjaxControl"?
Maybe i missed something, I would like to hear your opinion about this.
If you have some suggestions, details or best practices in developping Custom WebControl using your framework it would be very helpfull.

Thanks,

Farid

Jun 22, 2010 at 2:51 PM

Coincidentally I have experienced this very same problem just recently. In my case, I'm nesting a UserControl within the ItemTemplate of a ListView when I get this error. I believe the problem is that the IAjaxControl instance cannot be found (as mentioned in the first half of the error message you're getting). It can't be found because the framework will only restore the ViewState of the controls that implement IAjaxControl (and their children). That means the Repeater won't be rebuilt from ViewState during the callback. (On another note, nearly all of the ASP.NET data bound controls (GridView, Repeater, ListView, etc...) have code under-the-hood that restrict it from rebuilding its control hierarchy during ASP.NET Callbacks. Since the AJAX Control Framework DOES restore ViewState for AJAX controls during Callbacks, when you use a GridView or a Repeater within an UpdateRegion or a custom IAjaxControl the control hierarchy will not be rebuilt like you'd expect it to. I've implemented a utility property in the AjaxControlUtils class called "CreateChildControls." It's a reflective delegate that calls the CreateChildControls on whatever Control you pass to it. Manually invoking the CreateChildControls method forces the hierarchy to be build off of the available ViewState. So that means, whenever you nest a Repeater within an UpdateRegion or within one of your custom AJAX controls, you'll need to do the following in your Page_Load:

if (base.Page.IsCallback)
{
    AjaxControlUtils.CreateChildControls(repeater);
}

The AjaxControlUtils.CreateChildControls property is available now in the latest revision of the source code.

This doesn't sound like it will help you here because the data bound control (which is a Repeater in this case) is a parent of your custom IAjaxControl so its ViewState will not be persisted and restored during callbacks. I'm getting v1.0.2.0 release ready and I'm considering persisting and restoring the ViewState of an IAjaxControl's Parent or NamingContainer if the Parent/NamingContainer derives from DataBoundControl. This would most likely fix your problem. But, I won't be releasing v1.0.2.0 for probably a week from now so you'll want to try your other option. (I'm also looking into a viable way to automate the calling of the AjaxControlUtils.CreateChildControls property whenever there is a DataBoundControl in the child controls collection of an IAjaxControl instance.)

Your other option (and probably the best one available to you) is to rebind the Repeater on every request. So, if you're binding the Repeater in your Page_Load within an if (!base.Page.IsPostback) statement, you'll want to remove that IF statement. On the other hand, best practice says to bind the repeater in the Page_Init and to disable ViewState on your user control or page. Developing ASP.NET sites following this best practice is always difficult. So much odd behavior can happen when you develop with ViewState off. But, ViewState bloats your page's output and should be avoided whenever possible. So, if you disable ViewState, you HAVE to rebind your data bound controls on every request anyway, so the problem you're experiencing would naturally disappear.

Give that a try and let me know how it works out for you. 

This problem of controls not being available during postbacks/callbacks is a very common one when developing with ASP.NET WebForms. ViewState is not easy to master. It has taken me a couple years of working with ASP.NET to really get a firm understanding of it and its pitfalls. Hopefully I can figure out a way to implement workarounds behind-the-scenes in the framework for as many ViewState related problems as I can so that developers who use the AJAX Control Framework will naturally have more success with their code than if they would without the framework. Thank you for bringing up this problem. I'm anxious to see how you break the code next :-). 

 

"Peanutbutter" Lou

 

Jun 22, 2010 at 7:25 PM
Hi, Firstly, thank you for your quick answer. Just for your understanding, i'm creating custom control without Viewstate for most of my WebControl. For the moment, i choose to bind my controls on each request because there are a lot of level in my control hierarchy (i don't have enough time to test again). I also looked around the code you submited and your property "CreateChildControls" seems to be interesting. I'll wait for your next version and if i have some new problems i'll let you know :) Thank you, Farid