This project has moved. For the latest updates, please go here.

Generic LoadContent, VM and DataLoader

Apr 11, 2011 at 9:14 PM

Hi all.

Really like what I've seen of AgFx so far.  I'm using it in a scenario where I have some generics, reason I have a model type hierarchy that would be more elegantly implemented this way.  Problem is, the data loader initialisation fails.

Details:

LoadContext:  public class QueryLoadContext<TModel> : LoadContext where TModel : BaseItem    { .. }

DataLoader: public class QueryResultLoader<TModel> : IDataLoader<QueryLoadContext<TModel>> where TModel : BaseItem { .. }

VM (Model wrapper)
[CachePolicy(CachePolicy.AutoRefresh, 10)]    
[DataLoader(typeof(QueryResultLoader<TModel>))] 
public class QueryResult<TModel> : ModelItemBase<QueryLoadContext<TModel>> where TModel : BaseItem    {

Now, the problem with the above is that C# doesn't seem to allow the type parameters for an attribute argument.

Likewise, If I embed the QueryResultLoader inside my QueryResult as a nested class, I get a not supported exception from the DataManager, in particular here:

loader = Activator.CreateInstance(loaderType);

NotSupportedException
   at System.RuntimeType.GetConstructorImplInternal(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers, Boolean verifyAccess)   at System.RuntimeType.GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)   at System.Type.GetConstructor(BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers)   at System.Activator.InternalCreateInstance(Type type, Boolean nonPublic, StackCrawlMark& stackMark)   at System.Activator.CreateInstance(Type type)   at AgFx.DataManager.GetDataLoader(CacheEntry entry)   at AgFx.DataManager.Get[T](LoadContext loadContext, Action`1 completed, Action`1 error, Boolean resetCallbacks)   at AgFx.DataManager.Load[T](LoadContext loadContext, Action`1 completed, Action`1 error)   at AgFx.DataManager.Load[T](Object id)   at Guardian.ViewModels.ArticleListViewModel.Update()   at Guardian.ViewModels.ArticleListViewModel.OnActivate()   at Caliburn.Micro.Screen.Caliburn.Micro.IActivate.Activate()   at Caliburn.Micro.ScreenExtensions.TryActivate(Object potentialActivatable)   at Caliburn.Micro.ConductorBaseWithActiveItem`1.ChangeActiveItem(IScreen newItem, Boolean closePrevious)   at Guardian.Caliburn.PivotFix`1.ChangeActiveItem(IScreen newItem, Boolean closePrevious, Action`2 changeActiveItemBase)   at Guardian.MainPageViewModel.ChangeActiveItem(IScreen newItem, Boolean closePrevious)   at Caliburn.Micro.Conductor`1.Collection.OneActive.ActivateItem(IScreen item)   at Caliburn.Micro.ConductorBaseWithActiveItem`1.set_ActiveItem(IScreen value)   at System.Reflection.RuntimeMethodInfo.InternalInvoke(RuntimeMethodInfo rtmi, Object obj, BindingFlags invokeAttr, Binder binder, Object parameters, CultureInfo culture, Boolean isBinderDefault, Assembly caller, Boolean verifyAccess, StackCrawlMark& stackMark)   at System.Reflection.RuntimeMethodInfo.InternalInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, StackCrawlMark& stackMark)   at System.Reflection.RuntimePropertyInfo.InternalSetValue(PropertyInfo thisProperty, Object obj, Object value, Object[] index, StackCrawlMark& stackMark)   at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)   at System.Windows.CLRPropertyListener.set_Value(Object value)   at System.Windows.PropertyAccessPathStep.set_Value(Object value)   at System.Windows.PropertyPathListener.set_LeafValue(Object value)   at System.Windows.Data.BindingExpression.UpdateValue()   at System.Windows.Data.BindingExpression.UpdateValueIfNecessary()   at System.Windows.Data.BindingExpression.TargetPropertyChanged(DependencyObject sender, DependencyProperty dp)   at System.Windows.DependencyObject.OnPropertyChanged(DependencyProperty dp)   at System.Windows.FrameworkElement.OnPropertyChanged(DependencyProperty dp)   at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value, Boolean allowReadOnlySet, Boolean isSetByStyle, Boolean isSetByBuiltInStyle, PropertyInvalidationReason reason)   at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value)   at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)   at Microsoft.Phone.Controls.Panorama.set_SelectedItem(Object value)   at Microsoft.Phone.Controls.Panorama.ProcessFlick()   at Microsoft.Phone.Controls.Panorama.GestureEnd()   at Microsoft.Phone.Controls.Panorama.<.ctor>b__3(Object sender, EventArgs args)   at Microsoft.Phone.Controls.SafeRaise.Raise[T](EventHandler`1 eventToRaise, Object sender, EventArgs args)   at Microsoft.Phone.Gestures.GestureHelper.RaiseGestureEnd(EventArgs args)   at Microsoft.Phone.Gestures.GestureHelper.NotifyUp(InputCompletedArgs args)   at Microsoft.Phone.Gestures.ManipulationGestureHelper.Target_ManipulationCompleted(Object sender, ManipulationCompletedEventArgs e)   at System.Windows.CoreInvokeHandler.InvokeEventHandler(Int32 typeIndex, Delegate handlerDelegate, Object sender, Object args)   at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, String eventName)

 

Any thoughts on how I might work around this, other then stripping out all the generic code (not really an option).  Would really like to use AgFx though as it will save me loads of time.

Thanks

Kamran

Coordinator
Apr 12, 2011 at 6:24 PM

Hmmm, you're actually getting a NotSupportedException from the runtime itself, I believe.  I the nested class case I'm not 100% sure why this is but in the attribute case it's because the attribute declaration is completely separate from the class it's attached to, so there's no way for it pick up the <TModel> part.

So you want to be able to do this right?

this.DataContext = DataManager.Current.Load<QueryResult<int>>(1234);

What are you passing into Load exactly?

Apr 12, 2011 at 7:06 PM
Edited Apr 12, 2011 at 7:06 PM

Hmmm, you're actually getting a NotSupportedException from the runtime itself, I believe.  I the nested class case I'm not 100% sure why this is but in the attribute case it's because the attribute declaration is completely separate from the class it's attached to, so there's no way for it pick up the <TModel> part.

So you want to be able to do this right?

Yes, but I'm not entirely certain how to do this.  I did try the following attribute declaration in a vain attempt to get it working:

[DataLoader(typeof(QueryResultLoader<>))]

This compiles but I get the following runtime error - Could not load type 'GuardianApi.Guardian.AgFx.QueryResultLoader`1.QueryResultLoader`1' from assembly 'mscorlib'. (FormatException)  - no surprises there.

What are you passing into Load exactly?

var result = DataManager.Current.Load<QueryResult<Content>>(query); 

where query is of type QueryLoadContext<Content>

Does that sound right to you, or have I missed something?

Coordinator
Apr 13, 2011 at 7:11 PM

This:

[DataLoader(typeof(QueryResultLoader<>))]

Will definitely not work.  It take a while to get, generic types aren't like base types.  You can sort of do this with variance/covariance in .NET 4.0, but not on the phone with the current CLR version.

Oh, I think I understand the problem. 

Basically when DataManager inspects the type, it finds the nested type and tries to do a create instance on it.  Which normally is fine, but this is a generic type.  And the problem is that creating the outer concrete type doesn't automatically create the nested concrete type.  I'm not 100% clear why this is the case and I'm even less clear how to fix it at the present moment, and I think I'd need to spend some time fooling with a repro and a debugger in order to figure out a way to deal with it.

In the meantime, could you do this maybe:

Both of the methods on IDataLoader take an "objectType" parameter which is the type of the model object (e.g. QueryResult<Content>).  See if you can follow this:

1) Make your loader type generic (e.g. IDataLoader<LoadContext>)

2) In both it's GetLoadRequest and DeserializeMethod, do something like this (this is off the top of my head pseudo code)

// figure out the right parameter

Type parameter = objectType.GetGenericParameters[0];

Debug.Assert(typeof(BaseModel).IsAssignableFrom(parameter); // make sure it's a TModel/BaseItem thing

// Create the loader type

Type loaderType = typeof(QueryResultLoader<>).MakeGenericType(parameter);

// create an instance

object loader = Activator.CreateInstance(loaderType);

// call with reflection...

MethodInfo glr = loaderType.GetMethod("GetLoadRequest");

glr.Invoke(loader, new object[]{loadContext, objectType});

 

So basically you just need to do the work yourself of creating the right loader and then delegating to it.  Of course, you'd want to cache the MethodInfos with a lookup by object type (Dictionary<Type, MethodInfo>) so you can quickly call them on future lookups...