Load a Module on demand from multiple points - c#

I'm implementing a Unity PRISM module on demand using IModuleManager. Suppose there are multiple points where functionality from the module is required. Am I right to call moduleManager.LoadModule("MyModule") at each of these points even if the module might have already loaded previously? It's not going to reload is it?
And is this best practice?
Thanks

When You look at Prism Modularity Quickstart
You will find this method which is responsible for loading Module
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
if (!e.Handled)
{
if ((this.moduleTrackingState != null) && (this.moduleTrackingState.ExpectedInitializationMode == InitializationMode.OnDemand) && (this.moduleTrackingState.ModuleInitializationStatus == ModuleInitializationStatus.NotStarted))
{
this.RaiseRequestModuleLoad();
e.Handled = true;
}
}
}
Please note that there is a condition preventing call to this.RaiseRequestModuleLoad(); when ModuleInitializationStatus is not ModuleInitializationStatus.NotStarted.
I tried to get rid of that and discovered following:
There is no exception thrown when you call moduleManager.LoadModule("YourModule"); multiple times.
Module's Initialize method is called only the first time you load the module.
Module loading means that the module assembly is transferred from disk into memory. So... If it is reloaded every time You really shouldn't do that. Just to answer your question "Is this best practice?" I have to say... It's most certainly NOT. I am not able to tell you whether it's reloaded each time you call moduleManager.LoadModule("YourModule"); but IMHO You should load module only once simply because they do it once as well.

Agreeing with Viktor, the response in this thread suggests not to call LoadModule but to check the ModuleState using IModuleCatalog and IModuleManager.
Here is the code snippet from that post:
(...)
var module = this.moduleCatalog.Modules.FirstOrDefault(m => m.ModuleName == "MyModule");
if (module != null)
{
if (module.State != ModuleState.Initialized)
{
moduleManager.LoadModuleCompleted += moduleManager_LoadModuleCompleted;
moduleManager.LoadModule("MyModule");
}
else
{
//Initialization logic
}
}
}
void moduleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
{
moduleManager.LoadModuleCompleted -= moduleManager_LoadModuleCompleted;
if (e.ModuleInfo.ModuleName == "MyModule")
{
//Initialization logic
}
}
(...)

Related

Multiple binding to RelayCommand in WPF MVVM Light

I have started working with WPF MVVM Light and now I'am trying to navigate between pages.
In the MainWindow I have added a "BackButton"
<Button Command='{Binding Main.GoBack, Mode=OneWay}' />
which is binding to MainViewModel method "RelayCommand GoBack".
private RelayCommand _goBack;
public RelayCommand GoBack
{
get
{
return _goBack
?? (_goBack = new RelayCommand(
() =>
_navigationService.GoBack();
}));
}
}
Why is this button changing view only once? If I want to click it secound time
it doesn't work (nothing happend). If I change page for another by another button its starting work again and againg only for once.
Part of implementation of FrameNavigationService:
public FrameNavigationService()
{
_pagesByKey = new Dictionary<string, Uri>();
_historic = new List<string>();
}
public void GoBack()
{
if (_historic.Count > 1)
{
_historic.RemoveAt(_historic.Count - 1);
NavigateTo(_historic.Last(), null);
}
}
public void NavigateTo(string pageKey)
{
NavigateTo(pageKey, null);
}
public virtual void NavigateTo(string pageKey, object parameter)
{
lock (_pagesByKey)
{
if (!_pagesByKey.ContainsKey(pageKey))
{
throw new ArgumentException(string.Format("No such page: {0} ", pageKey), "pageKey");
}
var frame = GetDescendantFromName(Application.Current.MainWindow, "MainFrame") as Frame;
if (frame != null)
{
frame.Source = _pagesByKey[pageKey];
}
Parameter = parameter;
_historic.Add(pageKey);
CurrentPageKey = pageKey;
}
}
What can I do to handle this? May be I should do it tottaly differently?
You should possibly not be doing goback at all.
Unless you really want to use the journal, using a frame and pages is a bad idea. It's a rare requirement to go back to the last view in desktop apps. What with them not being a web browser.
Maybe you have that requirement though.
If you have a frame then you have it's journal and you can just call goback on the frame's navigationservice.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.navigation.navigationservice.goback?view=netframework-4.8
You set keepalive on pages.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.page.keepalive?view=netframework-4.8
You wrote that code and it seems to be largely reproducing navigationservice functionality. From what you've shown us.
As it is.
Use type rather than a magic string as the key. A type is checked at compile time, a magic string is not and you can make mistakes.
Have you explored this issue at all? I think maybe this is one of those times that telling someone what they did wrong isn't really helping as much as telling them how they ought to diagnose.
Debugging is a key skill for any developer.
You have the code running in front of you.
Put break points in, step through and examine what is happening.
When you navigate, what ends up in _historic?
When you goback, what happens exactly?
When you click the goback that second time what path does it go down and what state is causing that.
Make sure you are using RelayCommand in GalaSoft.MvvmLight.CommandWpf,not at GalaSoft.MvvmLight.Command.RelayCommand

Xamarin iOS memory leaks everywhere

We've been using Xamarin iOS for the last 8 months and developed a non-trivial enterprise app with many screens, features, nested controls. We've done our own MVVM arch, cross platform BLL & DAL as "recommended". We share code between Android and even our BLL/DAL is used on our web product.
All is good except now in release phase of project we discover irreparable memory leaks everywhere in the Xamarin iOS-based app. We've followed all the "guidelines" to resolve this but the reality is that C# GC and Obj-C ARC appear to be incompatible garbage collection mechanisms in the current way they overlay each other in monotouch platform.
The reality we've found is that hard cycles between native objects and managed objects WILL occur and FREQUENTLY for any non-trivial app. It's extremely easy for this to happen anywhere you use lambdas or gesture recognizers for example. Add in the complexity of MVVM and it's almost a guarantee. Miss just one of these situations and entire graphs of objects will never get collected. These graphs will lure other objects in and grow like a cancer, eventually resulting in a prompt and merciless extermination by iOS.
Xamarin's answer is an uninterested deferral of the issue and an unrealistic expectation that "devs should avoid these situations". Careful consideration of this reveals this as an admission that Garbage Collection is essentially broken in Xamarin.
The realization for me now is that you don't really get "garbage collection" in Xamarin iOS in the traditional c# .NET sense. You need employ "garbage maintanence" patterns actually get the GC moving and doing its job, and even then it'll never be perfect - NON DETERMINISTIC.
My company has invested a fortune trying to stop our app from crashing and/or running out of memory. We've basically had to explicitly and recursively dispose every damn thing in sight and implement garbage maintanence patterns into the app, just to stop the crashes and have a viable product we can sell. Our customers are supportive and tolerant, but we know this cannot hold forever. We are hoping Xamarin have a dedicated team working on this issue and get it nailed once and for all. Doesn't look like it, unfortunately.
Question is, is our experience the exception or the rule for non-trivial enterprise-class apps written in Xamarin?
UPDATE
See answer for DisposeEx method and solution.
I have shipped a non-trivial app written with Xamarin. Many others have as well.
"Garbage collection" isn't magic. If you create a reference that is attached to the root of your object graph and never detach it, it will not be collected. That's not only true of Xamarin, but of C# on .NET, Java, etc.
button.Click += (sender, e) => { ... } is an anti-pattern, because you don't have a reference to the lambda and you can never remove the event handler from the Click event. Similarly, you have to be careful that you understand what you're doing when you create references between managed and unmanaged objects.
As for "We've done our own MVVM arch", there are high profile MVVM libraries (MvvmCross, ReactiveUI, and MVVM Light Toolkit), all of which take reference/leak issues very seriously.
I used the below extension methods to solve these memory leak issues. Think of Ender's Game final battle scene, the DisposeEx method is like that laser and it disassociates all views and their connected objects and disposes them recursively and in a way that shouldn't crash your app.
Just call DisposeEx() on UIViewController's main view when you no longer need that view controller. If some nested UIView has special things to dispose, or you dont want it disposed, implement ISpecialDisposable.SpecialDispose which is called in place of IDisposable.Dispose.
NOTE: this assumes no UIImage instances are shared in your app. If they are, modify DisposeEx to intelligently dispose.
public static void DisposeEx(this UIView view) {
const bool enableLogging = false;
try {
if (view.IsDisposedOrNull())
return;
var viewDescription = string.Empty;
if (enableLogging) {
viewDescription = view.Description;
SystemLog.Debug("Destroying " + viewDescription);
}
var disposeView = true;
var disconnectFromSuperView = true;
var disposeSubviews = true;
var removeGestureRecognizers = false; // WARNING: enable at your own risk, may causes crashes
var removeConstraints = true;
var removeLayerAnimations = true;
var associatedViewsToDispose = new List<UIView>();
var otherDisposables = new List<IDisposable>();
if (view is UIActivityIndicatorView) {
var aiv = (UIActivityIndicatorView)view;
if (aiv.IsAnimating) {
aiv.StopAnimating();
}
} else if (view is UITableView) {
var tableView = (UITableView)view;
if (tableView.DataSource != null) {
otherDisposables.Add(tableView.DataSource);
}
if (tableView.BackgroundView != null) {
associatedViewsToDispose.Add(tableView.BackgroundView);
}
tableView.Source = null;
tableView.Delegate = null;
tableView.DataSource = null;
tableView.WeakDelegate = null;
tableView.WeakDataSource = null;
associatedViewsToDispose.AddRange(tableView.VisibleCells ?? new UITableViewCell[0]);
} else if (view is UITableViewCell) {
var tableViewCell = (UITableViewCell)view;
disposeView = false;
disconnectFromSuperView = false;
if (tableViewCell.ImageView != null) {
associatedViewsToDispose.Add(tableViewCell.ImageView);
}
} else if (view is UICollectionView) {
var collectionView = (UICollectionView)view;
disposeView = false;
if (collectionView.DataSource != null) {
otherDisposables.Add(collectionView.DataSource);
}
if (!collectionView.BackgroundView.IsDisposedOrNull()) {
associatedViewsToDispose.Add(collectionView.BackgroundView);
}
//associatedViewsToDispose.AddRange(collectionView.VisibleCells ?? new UICollectionViewCell[0]);
collectionView.Source = null;
collectionView.Delegate = null;
collectionView.DataSource = null;
collectionView.WeakDelegate = null;
collectionView.WeakDataSource = null;
} else if (view is UICollectionViewCell) {
var collectionViewCell = (UICollectionViewCell)view;
disposeView = false;
disconnectFromSuperView = false;
if (collectionViewCell.BackgroundView != null) {
associatedViewsToDispose.Add(collectionViewCell.BackgroundView);
}
} else if (view is UIWebView) {
var webView = (UIWebView)view;
if (webView.IsLoading)
webView.StopLoading();
webView.LoadHtmlString(string.Empty, null); // clear display
webView.Delegate = null;
webView.WeakDelegate = null;
} else if (view is UIImageView) {
var imageView = (UIImageView)view;
if (imageView.Image != null) {
otherDisposables.Add(imageView.Image);
imageView.Image = null;
}
} else if (view is UIScrollView) {
var scrollView = (UIScrollView)view;
// Comment out extension method
//scrollView.UnsetZoomableContentView();
}
var gestures = view.GestureRecognizers;
if (removeGestureRecognizers && gestures != null) {
foreach(var gr in gestures) {
view.RemoveGestureRecognizer(gr);
gr.Dispose();
}
}
if (removeLayerAnimations && view.Layer != null) {
view.Layer.RemoveAllAnimations();
}
if (disconnectFromSuperView && view.Superview != null) {
view.RemoveFromSuperview();
}
var constraints = view.Constraints;
if (constraints != null && constraints.Any() && constraints.All(c => c.Handle != IntPtr.Zero)) {
view.RemoveConstraints(constraints);
foreach(var constraint in constraints) {
constraint.Dispose();
}
}
foreach(var otherDisposable in otherDisposables) {
otherDisposable.Dispose();
}
foreach(var otherView in associatedViewsToDispose) {
otherView.DisposeEx();
}
var subViews = view.Subviews;
if (disposeSubviews && subViews != null) {
subViews.ForEach(DisposeEx);
}
if (view is ISpecialDisposable) {
((ISpecialDisposable)view).SpecialDispose();
} else if (disposeView) {
if (view.Handle != IntPtr.Zero)
view.Dispose();
}
if (enableLogging) {
SystemLog.Debug("Destroyed {0}", viewDescription);
}
} catch (Exception error) {
SystemLog.Exception(error);
}
}
public static void RemoveAndDisposeChildSubViews(this UIView view) {
if (view == null)
return;
if (view.Handle == IntPtr.Zero)
return;
if (view.Subviews == null)
return;
view.Subviews.ForEach(RemoveFromSuperviewAndDispose);
}
public static void RemoveFromSuperviewAndDispose(this UIView view) {
view.RemoveFromSuperview();
view.DisposeEx();
}
public static bool IsDisposedOrNull(this UIView view) {
if (view == null)
return true;
if (view.Handle == IntPtr.Zero)
return true;;
return false;
}
public interface ISpecialDisposable {
void SpecialDispose();
}
Couldn't be agree more with the OP that "Garbage Collection is essentially broken in Xamarin".
Here's an example shows why you have to always use a DisposeEx() method as suggested.
The following code leaks memory:
Create a class the inherits UITableViewController
public class Test3Controller : UITableViewController
{
public Test3Controller () : base (UITableViewStyle.Grouped)
{
}
}
Call the following code from somewhere
var controller = new Test3Controller ();
controller.Dispose ();
controller = null;
GC.Collect (GC.MaxGeneration, GCCollectionMode.Forced);
Using Instruments you will see that there are ~ 274 persistent objects with 252 KB never collected.
Only way to fix this is add DisposeEx or similar functionality to the Dispose() function and call Dispose manually to ensure disposing == true.
Summary: Creating a UITableViewController derived class and then disposing/nulling will always cause the heap to grow.
iOS and Xamarin have a slightly troubled relationship. iOS uses reference counts to manage and dispose of its memory. The reference count of an object gets incremented and decremented when references are added and removed. When the reference count goes to 0, the object is deleted and the memory freed. Automatic Reference Counting in Objective C and Swift help with this, but it’s still difficult to get 100% right and dangling pointers and memory leaks can be a pain when developing using native iOS languages.
When coding in Xamarin for iOS, we have to bear reference counts in mind as we will be working with iOS native memory objects. In order to communicate with the iOS operating system, Xamarin creates what are known as Peers which manage the reference counts for us. There are two types of Peers – Framework Peers and User Peers. Framework Peers are managed wrappers around well-known iOS objects. Framework Peers are stateless and therefore hold no strong references to the underlying iOS objects and can be cleaned up by the garbage collectors when required – and don’t cause memory leaks.
User Peers are custom managed objects that are derived from Framework Peers. User Peers contain state and are therefore kept alive by the Xamarin framework even if your code has no references to them – e.g.
public class MyViewController : UIViewController
{
public string Id { get; set; }
}
We can create a new MyViewController, add it to the view tree, then cast a UIViewController to a MyViewController. There may be no references to this MyViewController, so Xamarin needs to ‘root’ this object to keep this it alive whilst the underlying UIViewController is alive, otherwise we will lose the state information.
The problem is that if we have two User Peers that reference each other then this creates a reference cycle that cannot be automatically broken – and this situation happens often!
Consider this case:-
public class MyViewController : UIViewController
{
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear (animated);
MyButton.TouchUpInside =+ DoSomething;
}
void DoSomething (object sender, EventArgs e) { ... }
}
Xamarin creates two User Peers that reference each other – one for MyViewController and another for MyButton (because we have an event handler). So, this will create a reference cycle that will not be cleared up by the garbage collector. In order to have this cleared up, we must unsubscribe the event handler, and this is usually done in the ViewDidDisappear handler – e.g.
public override void ViewDidDisappear(bool animated)
{
ProcessButton.TouchUpInside -= DoSomething;
base.ViewDidDisappear (animated);
}
Always unsubscribe to your iOS event handlers.
How to diagnose these memory leaks
A good way to diagnose these memory problems is to add some code in debug to the Finalisers of the classes derived from iOS wrapper classes – such as UIViewControllers. (Although only put this in your debug builds and not in release builds because it’s reasonably slow.
public partial class MyViewController : UIViewController
{
#if DEBUG
static int _counter;
#endif
protected MyViewController (IntPtr handle) : base (handle)
{
#if DEBUG
Interlocked.Increment (ref _counter);
Debug.WriteLine ("MyViewController Instances {0}.", _counter);
#endif
}
#if DEBUG
~MyViewController()
{
Debug.WriteLine ("ViewController deleted, {0} instances left.",
Interlocked.Decrement(ref _counter));
}
#endif
}
So, Xamarin’s memory management is not broken in iOS, but you do have to be aware of these ‘gotchas’ which are specific to running on iOS.
There is an excellent page by Thomas Bandt called Xamarin.iOS Memory Pitfalls that goes into this in more detail and also provides some very useful hints and tips.
I noticed in your DisposeEx method you dispose of the collection view source and table view source before you kill the visible cells of that collection. I noticed when debugging that the visible cells property gets set to an empty array therefore, when you start to dispose visible cells, they no longer "exist" hence it becomes an array of zero elements.
Another thing I noticed is that you will run into inconsistency exceptions if you don't remove the parameter view from its super view, I've noticed especially with setting the layout of the collection view.
Other than that I've had to implement something similar on our side.

Difference between LoadState and navigationHelper_LoadState

I am new to windows store app development. Currently I am looking into passing and receiving parameter between xamls, using c#.
Can someone help explain difference between LoadState() and navigationHelper_LoadState() with some examples? Which should I go for receiving parameter?
So, NavigationHelper.LoadState requires two things:
OnNavigatedTo Invoked when this page is about to be displayed in a Frame.
NavigationMode.New Navigation is to a new instance of a page (not forward or back)
MSDN says:
In addition to providing the implementations described earlier, NavigationHelper also needs to be called from the OnNavigatedTo() and OnNavigatedFrom() event handlers that are implemented on each page. When these events occur, NavigationHelper calls a page-specific implementation of LoadState() and SaveState(). You can customize the implementation of these functions on each page. They should be used in place of OnNavigatedTo() and OnNavigatedFrom() respectively.
The raw code is:
public void OnNavigatedTo(NavigationEventArgs e)
{
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
this._pageKey = "Page-" + this.Frame.BackStackDepth;
if (e.NavigationMode == NavigationMode.New)
{
var nextPageKey = this._pageKey;
int nextPageIndex = this.Frame.BackStackDepth;
while (frameState.Remove(nextPageKey))
{
nextPageIndex++;
nextPageKey = "Page-" + nextPageIndex;
}
if (this.LoadState != null)
{
this.LoadState(this, new LoadStateEventArgs(e.Parameter, null));
}
}
else
{
if (this.LoadState != null)
{
this.LoadState(this, new LoadStateEventArgs(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]));
}
}
}
For the sake of your question, there is no LoadState() override unless you define your own like this blog. He simply does this:
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
LoadState(e);
}
private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
SaveState(e);
}
protected virtual void LoadState(LoadStateEventArgs e) { }
protected virtual void SaveState(SaveStateEventArgs e) { }
See, they are identical. Nothing different between them except the execution pipeline which might impact timing a little, but not likely. In the end, no real difference. People who need to use one over the other... they have to be mistaken, attributing a cause where something else is the influence.
Best of luck.
There are a lot of example online. You might be better off going through those and then coming back here for more specific questions.
http://marcominerva.wordpress.com/2013/10/10/a-base-page-class-for-windows-8-1-store-apps-with-c-and-xaml/

Resting a private variable that should not happen

I have a strange problem I'm checking in my code behind the user if he is active or not with as simple if .. in my Page_Load method as you can see here
private TimeReport paramTR;
private ZevUser zevUser;
protected void Page_Load(object sender, EventArgs e)
{
ZevUser user = ZevUser.GetById(Int32.Parse(Session["SessionId"].ToString()));
if (user == null)
{
this.Response.Redirect("~/About.aspx");
}
this.getParameters();
if (!this.IsPostBack)
{
if (paramTR.ZevUser.Active == 0)
{
this.Response.Redirect("~/TimeReporting/TimeReportPanel.aspx");
}
this.bindData();
}
}
But when I make a go throw to this method I get allays nullreferenceexception why so ever .. but the private ZevUser variable is not null it's full..
I really don't have a clue why is this happing, it would be really cool if someone could explain me this why this is happening
Thanks for help and fast answer
You need to break your code down so you can debug it easier or add logging if you cannot debug this code locally.
Remember that when debugging something, the worse mistake you can make is to make assumptions. Start from the beginning and follow the process through. Don't assume that the problem is something and don't assume that the problem can't be something:
I've included a broken down, more readable version below. You can now add logging around this or easily add breakpoints:
private TimeReport paramTR;
private ZevUser zevUser;
protected void Page_Load(object sender, EventArgs e)
{
this.getParameters();
if (!this.IsPostBack)
{
if ((this.paramTR != null) &&
(this.paramTR.ZevUser != null) &&
(this.paramTR.ZevUser.Active == 0))
{
this.Response.Redirect("~/TimeReporting/TimeReportPanel.aspx");
}
this.bindData();
}
string sessionId = Session["SessionId"] as string;
if (sessionId != null)
{
int session = int32.Parse(sessionId);
ZevUser user = ZevUser.GetById(session);
if (user == null)
{
this.Response.Redirect("~/About.aspx");
}
}
}
Why are you passing the session id to ZevUser.GetById()? I would expect this to take a user id, or for the method to be called something like ZevUser.GetBySessionId(). At the moment it's quite confusing.
This line is causing the issue:
ZevUser user = ZevUser.GetById(Int32.Parse(Session["SessionId"].ToString()));
This is because Session["SessionId"] can be null, and is null in this case.
If you are looking to get the SessionId that is set by ASP.net, then use this.Session.SessionID (source).
If you are storing a value in Session["SessionId"] that you are trying to retrieve, then do a null-check first:
if (Session["SessionId"] != null) { ...
You should consider testing the SessionId variable before using it by doing something like that :
if (!string.IsNullOrEmpty(Session["SessionId"].ToString()))
ZevUser user = ZevUser.GetById(Int32.Parse(Session["SessionId"].ToString()));
The best way to debug your exception is to enable debug when the exception is thrown.
For this, go to Debug>>Exceptions
and then enable first four checkboxes (maybe) and then try debugging the project. You will be halted at the position from where the exception will be thrown.

ASP.NET dynamic data: Table is not shown anymore after inserting data if FormView.Controls is accessed on Page.Initialized event

Lately, I received a bug report for Ninject.Web that it is not working properly together with ASP.NET dynamic data. The problem is that on postback (e.g. when Inserting, Deleting, Editing a record) the table is not shown anymore.
Some debuging showed that the problem is caused by a IHttpModule that recursively iterates through all controls of a page after it is initialized. As soon as this module accesses the Controls property get accessor of FormView or GridView the problem occurs. If this type of controls is skiped everything is fine. The following code shows the module:
public class NinjectHttpModule : DisposableObject, IHttpModule
{
private HttpApplication httpApplication;
public void Init(HttpApplication context)
{
this.httpApplication = context;
this.httpApplication.PreRequestHandlerExecute += this.OnPreRequestHandlerExecute;
}
private static void InjectUserControls(Control parent)
{
if (parent == null)
{
return;
}
foreach (Control control in parent.Controls)
{
if (control is UserControl)
{
// KernelContainer.Inject(control); This is irrelevant for the question.
}
InjectUserControls(control);
}
}
private void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
var page = this.httpApplication.Context.CurrentHandler as Page;
if (page == null)
{
return;
}
KernelContainer.Inject(page);
page.InitComplete += (src, args) => InjectUserControls(page);
}
}
If this code is changed so that the iteration through the child controls of DataBoundControls is delayed to the DataBound event everything is fine. Shown by the next code snippet:
private static void InjectUserControls(Control parent, bool skipDataBoundControls)
{
if (parent == null)
{
return;
}
if (skipDataBoundControls)
{
var dataBoundControl = parent as DataBoundControl;
if (dataBoundControl != null)
{
dataBoundControl.DataBound += InjectDataBoundControl;
return;
}
}
foreach (Control control in parent.Controls)
{
if (control is UserControl)
{
KernelContainer.Inject(control);
}
InjectUserControls(control, skipDataBoundControls);
}
}
private static void InjectDataBoundControl(object sender, EventArgs e)
{
var dataBoundControl = sender as DataBoundControl;
if (dataBoundControl != null)
{
dataBoundControl.DataBound -= InjectDataBoundControl;
InjectUserControls(dataBoundControl, false);
}
}
Because I'm completely unfamiliar with System.Web.DynamicData I'd like to know some things to get a better feeling about how to fix this bug:
Why does this problem occur? I mean it's only a simple read access to the Controls property.
What side effects can the change above have?
Is it still early enough to inject the controls after the data bound event?
Do you think this is a valid bug fix for this problem?
Certainly puzzling behavior, as can sometimes happen in WebForms with the many phases of execution.
Even though it's just a simple read access to the Controls property, this property can actually do a lot of work to return the child controls. In particular, it can't return the child controls unless they have been created, and that creation normally does not occur until later in the page life cycle. So by accessing it in InitComplete, the children end up getting created prematurely, before some important Dynamic Data hookups have happened, causing some controls to be missing. Yes, I realize that the end result behavior seems to make little sense, which is why some people favor the straightforwardness of MVC :)
As an alternate possible workaround, could you try moving your injection from InitComplete to PreLoad? e.g.
page.PreLoad += (src, args) => InjectUserControls(page);
I'm pretty sure that'll address the problem, though I'm less sure whether this will cause issues with your KernelContainer.Inject logic. Give it a try, since it's simpler than your workaround.
If that doesn't work, I think your workaround is ok, as it delays the enumeration until the children are created. As for 'Is it still early enough to inject the controls after the data bound event', I think that depends in exactly what KernelContainer.Inject does, and what expectations it has on the state of the control.

Categories