CSLA 4.x how to implement an Async List "factory"?

CSLA 4.x how to implement an Async List "factory"?

Old forum URL: forums.lhotka.net/forums/t/10950.aspx


JCardina posted on Tuesday, December 06, 2011

I think I'm getting lost in generics and delegate land here somewhere, any pointers greatly appreciated:

I have a scenario where I have  a *lot* of ReadOnlyListBase objects in my app. 

I have a grid to display them in and the user chooses the list to view and the app makes the relevant grid columns and then populates the grid using a string key name for the list.

I accomplish this with a list factory that returns the correct list object based on the grid key name passed in.

All is well in my factory using the synchronous static method to retrieve the list by key name, i.e.:

 public static dynamic Get(string GridKey, string ListCriteria, long Offset, long Rows)
        {
            switch (GridKey)
            {
                case GridKeyNames.ClientList:
                    return ClientList.Get(ListCriteria, Offset, Rows);//Easy peasy

                case etc etc etc

            }
            return null;
        }

 

However, I'm stumped as to how to do this with the Asynch static methods of the lists.  

I need to pass a callback delegate but that normally requires a DataPortalResult which requires a generic parameter of the list info type and the whole point of the factory is to not know the type in advance:

For example: this is a working async getter for the same client list:

public static void Get(string ListCriteria, long Offset, long Rows, EventHandler<DataPortalResult<ClientList>> callback)
        {
            DataPortal.BeginFetch<ClientList>(new Utility.GridListParameters(ListCriteria, Offset, Rows, typeof(ClientListInfo)), callback);
        }

How do I call this async form in a factory method?

 

JonnyBee replied on Wednesday, December 07, 2011

You could easily rewrite the callback to be a non generic callback using your own non-generic MyDataPortlResult that inherits from AsyncEventArgs.

    public class MyDataPortalResult : AsyncCompletedEventArgs
    {
        public object Result { getset; }
 
        public MyDataPortalResult(Exception error, object result, object state) : base(error, false, state)
        {
            Result = result;
        }
    }
 
    public static void BeginExecute(int customerId, EventHandler<MyDataPortalResult> callback, object userState = null)
    {
        var cmd = new LookupCustomerCommand();
        cmd.CustomerId = customerId;
        DataPortal.BeginExecute<LookupCustomerCommand>(cmd, (o, e) => callback(e.Object, new MyDataPortalResult(e.Error, e.Object, e.UserState)), userState);
    }

JCardina replied on Wednesday, December 07, 2011

Thank you Jonny, I appreciate that.  I wasn't thinking of it that way at all.  That looks perfect, I'll try it out.

RockfordLhotka replied on Wednesday, December 07, 2011

You can also look at the PagedList sample app (I think in the Silverlight folder). It uses a recursive async factory to load pages of the list in the background so the user doesn't have to wait for the entire list to arrive before they can start using it.

JCardina replied on Thursday, December 08, 2011

RockfordLhotka

You can also look at the PagedList sample app (I think in the Silverlight folder). It uses a recursive async factory to load pages of the list in the background so the user doesn't have to wait for the entire list to arrive before they can start using it.

That example appears to be for retrieving just one type of list, my scenario is different in that I need to retrieve different read only list objects based on a passed in string key.  I was hoping to do that without modifying the CSLA code at all but so far no luck.

 

JCardina replied on Thursday, December 08, 2011

Hi Jonny, just took a detailed look at your suggestions I misunderstood it the first time I looked, I thought you were referring to changes in the business object itself but I see now that I think you're proposing I change the CSLA dataportal code itself?  I'm really trying to avoid making any changes to the stock CSLA implementation and BeginFetch isn't a virtual method so I can't replace it without modifying the source code.

Any other way of accomplishing a factory style async retrieval of Read only list objects without modifying the CSLA source?

JonnyBee replied on Friday, December 09, 2011

Hi,

I'm suggesting changes to the BO itself - NOT to CSLA DataPortal code.
My sample has BeginExecute method on a CommandObject - no intention to suggest a change to CSLA itself.

Generics is great to get strong typed code - but can be a hassle when you make common code like your sample.

After digging into the CSLA code you can actually do this without any modification to your business objects as you can use a non-generic callback:

      public void AsyncPortalCompleted(object sender, IDataPortalResult e)
      {
          if (e.Error == null)
          {
              // show the error 
          }
          else
          {
              // databind e.Object to the UI
          }
      }

JonnyBee replied on Friday, December 09, 2011

IDataPortalResult should also provide access to.UserState.

http://www.lhotka.net/cslabugs/edit_bug.aspx?id=999 

JCardina replied on Friday, December 09, 2011

Thanks again Jonny, I'll take another look!

JCardina replied on Friday, December 09, 2011

JonnyBee
After digging into the CSLA code you can actually do this without any modification to your business objects as you can use a non-generic callback

Sorry Jonny but I'm just not seeing what you're seeing.  There are 4 method signatures for BeginFetch:

public static void BeginFetch<T>(EventHandler<DataPortalResult<T>> callback) where T : Csla.Serialization.Mobile.IMobileObject;           

public static void BeginFetch<T>(EventHandler<DataPortalResult<T>> callback, object userState) where T : Csla.Serialization.Mobile.IMobileObject;

public static void BeginFetch<T>(object criteria, EventHandler<DataPortalResult<T>> callback) where T : Csla.Serialization.Mobile.IMobileObject;

public static void BeginFetch<T>(object criteria, EventHandler<DataPortalResult<T>> callback, object userState) where T : Csla.Serialization.Mobile.IMobileObject;

None of them appear to support a non generic callback.

RockfordLhotka replied on Friday, December 09, 2011

The Silverlight CslaDataProvider is an example of a 'factory' that understands how to create/retrieve an instance of any type of business object. If I recall, it uses some reflection to invoke the appropriate generic factory method on the business class, allowing each business class to properly encapsulate its own creation process.

JonnyBee replied on Friday, December 09, 2011

I believe this is the Liskov substitution in practice:

Take this CommandObject for LookupCustomer:

  [Serializable]
  public class LookupCustomerCommand : CommandBase<LookupCustomerCommand>
  {
    public static readonly PropertyInfo<int> CustomerIdProperty = RegisterProperty<int>(c => c.CustomerId);
    public int CustomerId
    {
      get { return ReadProperty(CustomerIdProperty); }
      set { LoadProperty(CustomerIdProperty, value); }
    }
 
    public static readonly PropertyInfo<string> NameProperty = RegisterProperty<string>(c => c.Name);
 
    public string Name
    {
      get { return ReadProperty(NameProperty); }
      set { LoadProperty(NameProperty, value); }
    }
 
    public static LookupCustomerCommand Execute(int customerId)
    {
      var cmd = new LookupCustomerCommand();
      cmd.CustomerId = customerId;
      cmd = DataPortal.Execute<LookupCustomerCommand>(cmd);
      return cmd;
    }
 
    public static void BeginExecute(int customerId, EventHandler<DataPortalResult<LookupCustomerCommand>> callback)
    {
      var cmd = new LookupCustomerCommand();
      cmd.CustomerId = customerId;
      DataPortal.BeginExecute<LookupCustomerCommand>(cmd, callback);
    }
 
    /// <summary>
    /// The data portal_ execute.
    /// </summary>
    protected override void DataPortal_Execute()
    {
      // wait for 500 ms
      System.Threading.Thread.Sleep(500);
 
      // simulate llokup and set customer name  
      this.Name = string.Format("Name ({0})"this.CustomerId);
    }
  }

And used in a Rule
    protected override void Execute(RuleContext context)     {       var id = (int) context.InputPropertyValues[PrimaryProperty];       // uses the async methods in DataPortal to perform data access on a background thread.       LookupCustomerCommand.BeginExecute(id, AsyncPortalCompleted);     }     public void AsyncPortalCompleted(object sender, IDataPortalResult e)     {       if (e.Error == null)       {         // show the error        }       else       {         // do something with the object returned       }     }

This will compile and run just fine.

And the reason it works (Liskovs substitution) is that DataPortalResult<T> implements IDataPortalResult:
  public class DataPortalResult<T> : EventArgsIDataPortalResult


JCardina replied on Friday, December 09, 2011

Aha!  Thanks Jonny for being persistent.  I see what you mean now, I went back to your first idea of a custom DataPortalResult and it works perfectly.
I thought I'd post the important bits for anyone else in future who needs to do this with a read only list

My replacement for the stock CSLA DataPortalResult

public class MyDataPortalResult : System.ComponentModel.AsyncCompletedEventArgs
    {
        public object Result { get; set; }

        public MyDataPortalResult(Exception error, object result, object state)
            : base(error, false, state)
        {
            Result = result;
        }
    }



Async static get method in my CSLA ReadOnlyListBase based object

 public static void Get(string ListCriteria, EventHandler<MyDataPortalResult> callback)
        {
            DataPortal.BeginFetch<TListClass>(ListCriteria, (o, e) => callback(e.Object, new MyDataPortalResult(e.Error, e.Object, e.UserState)));
        }




Fragment from list factory get method showing it in action

 public static void Get(string GridKey, string ListCriteria, EventHandler<MyDataPortalResult> callback)
        {
            switch (GridKey)
            {
                case GridKeyNames.ClientList:
                    ClientList.Get(ListCriteria, callback);
                    break;
        ... etc etc etc ...



My unit test to ensure it works and showing how to call it

 [TestMethod()]
        public void AsyncGetListFromFactoryWorks()
        {
            TestUtil.LogOnAdmin();
            dynamic d = null;
            ListFactory.Get("ClientList", "", (o, e) =>
            {
                if (e.Error != null)
                    throw e.Error;
                else
                    d = e.Result;
            });
            System.DateTime StartOfTest = DateTime.Now;
            while (d == null)
            {
                TimeSpan ts = DateTime.Now - StartOfTest;
                if (ts.Seconds > 5)
                {
                    Assert.Inconclusive("Async method test taking longer than 5 seconds");
                }
            };

            Assert.IsTrue(d is ClientList, "Incorrect list retrieved");
            Assert.IsTrue(d.TotalRecords > 0, "Empty list retrieved, should have had more than zero records");

            TestUtil.LogOff();
        }

Copyright (c) Marimer LLC