Straight forward, easy even, Silverlight applications with ECO that saves, loads and discovers changed objects on the server

This is the beta of the Silverlight persistence – I want feedback on how it works and how hard/easy it is to get along with.

Yes, I have promised this for a long time but we got caught up in STUFF and then there was more STUFF and you know how it goes…

Anyway I have spent the last couple of days wrapping the final things of the Silverlight persistence up.

The first thing that got me stumped was that Silverlight does not accept WCF-interfaces that does not follow the Async pattern (with BeginOp/EndOp). The WCF interface we had for server persistence did not follow that so it had to be changed.

The second thing that got me stumped was that even when having the Async-pattern-WCF-interface, all Silverlight applications will hang if any communication is done over a WCF channel on the Main thread.

So I had to figure out a practical way to call things on a non-MainThread-thread without getting into the total chaos of an ecospace that is read and written simultaneously from multiple threads.

I am not saying that I have “solved” every thread issue in the book with this beta – but I think that I have the strategy laid out.

The biggest challenge is Lazy Load: Lazy Load is a super function of ECO that works really well and removes a ton of work for the developer – it can be dangerous too of course – creating too many server round trips – but ECO has strategies to handle that. The problem with Lazy Load in Silverlight is that the loading cannot be done on the main thread – because the app will hang. And UI-binding cannot be done in a background thread because UI is main thread.

So Lazy Load has to go? Well yes – in Silverlight it has to go, at least on the main thread. We cannot stall a main thread operation that discovers the need for loading – if we do the app will hang for ever.

Introducing IAsyncSupportService

It is really easy to get things to execute on a background thread:

ThreadPool.QueueUserWorkItem(new WaitCallback(

(obj) =>

{

    GetInToTroubleQuick;    

}));

The code above gets a free thread from the thread pool – the thing is that we want to avoid doing stuff out of sequence so we do not want just any thread. For example we do not want a multilink to become fetched AFTER we get the result of Count – we want things to happen in sequence because they depend on each other.

Threading is best used on things that does not have dependencies – in an ecospace objects have dependencies – that is 90% of the point – sure you can have some group of objects that does not depend on some other group of objects but that will not be the normal case – and if so consider 2 ecospaces.

The Service I added to handle things in the background but still in sequence is called IAsyncSupportService. It is used like this:

Code Snippet
  1. EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(
  2. () =>
  3. {
  4.     _ecoSpace.UpdateDatabase();
  5. });

The IAsyncSupportService does a couple of things:
1. Ensures that tasks are executed in the sequence they are added
2. Delays the Subscriber events that a Task will emit until the Task is done then it emits them on the Main thread

The IAsyncSupportService also has a way to sync something from a Task back to the MainThread using a method called DispatchTaskToMainThread:

Code Snippet
  1. ass.PerformTaskAsync(
  2. () =>
  3. {
  4.     c.VMClassDescriptor.ViewModelClass.ViewModel.EnsureSpanFetch();
  5.     ass.DispatchTaskToMainThread(() =>
  6.      {
  7.          c.PauseDisplay = false;
  8.      });
  9. });

Make sure your ecospace objects are fully fetched from server

The thing is that you will want to ensure that you are “fully fetched” while in the Async Task and that you leave nothing to lazy load when you are back on the main thread as your UI pulls the data for the bindings – if you do not, WCF will be used on the mainthread as soon as lazy load kicks in and the app will hang… To ensure that everything is fetched can be a chore… You can do this by calling Evaluate on the expressions you know will be fetched:

IOclService ocl = EcoServiceHelper.GetOclService(EcoSpace);

result=ocl.Evaluate(elem, Expression, vars);

Or you can do it by navigating the associations, and attributes that your UI use with Linq or code… Still a chore…

But if you use ViewModels – Code generated – or not, with the ViewModelSLUserControl or not we will do that for you automatically – go ViewModels!

The ViewModel has a new Method called EnsureSpanFetch – you can call it yourself but it is called when you assign a Root object to a ViewModel. The one time you want to call it is when you call Refresh – there is no telling what other clients have changed so we do not know if there is new data that we will be forced to load after a Refresh:

Code Snippet
  1. EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(
  2. () =>
  3. {
  4.     EcoServiceHelper.GetPersistenceService(_ecoSpace).Refresh(true);
  5.     ViewModelUserControl.ViewModel.EnsureSpanFetch();
  6. });

The example

A simple enough model:

image

And a ViewModel:

image

Add it to the silverlight form:

Code Snippet
  1. <Button Click="Button_Click">
  2.     <TextBlock Text="Add C1"></TextBlock></Button>
  3. <Button Click="Button_Click_1">
  4.     <TextBlock Text="Add C2"></TextBlock>
  5. </Button>
  6. <Button Click="Button_Click_2">
  7.     <TextBlock Text="UpdateDatabase"></TextBlock>
  8. </Button>
  9. <Button Click="Button_Click_3">
  10.     <TextBlock Text="Refresh"></TextBlock>
  11. </Button>
  12. <StackPanel Margin="20">
  13.     <TextBlock FontSize="24" FontFamily="Georgia"
  14.            Text="Below the viewmodel rendered from UI hints">
  15.     </TextBlock>
  16.     <ecovm:ViewModelSLUserControl
  17.            x:Name="ViewModelUserControl"
  18.            ViewModelName="ViewModel1" >
  19.     </ecovm:ViewModelSLUserControl>
  20. </StackPanel>

image

And some codebehind

Code Snippet
  1. public partial class MainPage : UserControl
  2. {
  3.   private EcoSpaceAndModel.EcoSpace1 _ecoSpace;
  4.   public MainPage()
  5.   {
  6.     InitializeComponent();
  7.     _ecoSpace = new EcoSpaceAndModel.EcoSpace1();
  8.     _ecoSpace.Active = true;
  9.     DequeuerSL.Active = true;
  10.     ViewModelDefinitionsInApplication.Init(_ecoSpace);
  11.     ViewModelUserControl.SetEcoSpace(_ecoSpace);
  12.     (Resources["ViewModel1"] as ViewModel1).SetObject(_ecoSpace, null);
  13.  
  14.   }
  15.  
  16.   private int x = 0;
  17.   private void Button_Click(object sender, RoutedEventArgs e)
  18.   {
  19.     // This code could execute in the main thread (without PerformTaskAsync)
  20.     // but it is safer to run it on the same thread as the persistence
  21.     EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(() =>
  22.     {
  23.       x++;
  24.       new EcoProject1.Class1(_ecoSpace) { Attribute1 = "c1" + x.ToString() };
  25.     });
  26.  
  27.   }
  28.  
  29.   private void Button_Click_1(object sender, RoutedEventArgs e)
  30.   {
  31.     // This code could execute in the main thread (without PerformTaskAsync)
  32.     // but it is safer to run it on the same thread as the persistence
  33.     EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(() =>
  34.     {
  35.       x++;
  36.       new EcoProject1.Class2(_ecoSpace) { Name = "c2" + x.ToString() };
  37.     });
  38.   }
  39.  
  40.   private void Button_Click_2(object sender, RoutedEventArgs e)
  41.   {
  42.     // This code MUST be executed in a TaskAsync since it use the
  43.     // WCF channel
  44.     EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(() =>
  45.     {
  46.       _ecoSpace.UpdateDatabase();
  47.     });
  48.   }
  49.  
  50.   private void Button_Click_3(object sender, RoutedEventArgs e)
  51.   {
  52.     // This code MUST be executed in a TaskAsync since it use the
  53.     // WCF channel
  54.     EcoServiceHelper.GetAsyncSupportService(_ecoSpace).PerformTaskAsync(() =>
  55.     {
  56.       EcoServiceHelper.GetPersistenceService(_ecoSpace).Refresh(true);
  57.       // The refresh could have found new objects – make sure these
  58.       // are fully fetched to avoid lazy load on main thread -> hang
  59.       ViewModelUserControl.ViewModel.EnsureSpanFetch();
  60.     });
  61.   }
  62. }

Deploying to a server

This example has a persistence server hosted in IIS. Getting everything to work properly can be a challenge. To help you smoking issues out I recommend the IE-plugin called Web Development Helper; http://projects.nikhilk.net/WebDevHelper

This tool installs in IE and when activated it shows like this and reveal all kinds of errors like a missing clientaccesspolicy file – it is a life saver :

image

I have published this demo server here THE SERVER

I assign this url to the PersistenceMapperClient in the Silverlight client:

Code Snippet
  1. public EcoSpace1(string theurl)
  2.     : base()
  3. {
  4.     this.persistenceMapperClient1 = new Eco.Wcf.Client.PersistenceMapperWCFClient();
  5.     this.persistenceMapperClient1.Uri = theurl;
  6.     this.PersistenceMapper = this.persistenceMapperClient1;
  7. }

The client I published here

 THE CLIENT ,

now the interesting thing will be when you open two or more of the clients, change data in them, update the database and then refresh the other.

The full sample code is in the latest eoc6 build demos folder SilverlightWCFPersistenceDemo.

(The persistence server uses a MemoryMapper – so it will clear when IIS swaps out my server – could have used any other persistence mapper available to eco of course but that is not what this post is about )

Read this far? Thanks! Now write some comment below:

This entry was posted in Silverlight, ViewModel. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*