ServerProxy
Server static class is the main class for the client application to communicate with the server. Internally it's just a holder of your Server WCF TransparentProxy.
It's your responsibility to plug a WPF TransparentProxy that implements IBaseServer into Server static class using SetNewServerCallBack as we seen in
SettingUpClientConnectionUnfortunately, because WPF Transparent Proxies go to a corrupt state when a communication exception happens and they become unusable, the Server needs a way to restore communication. Two kind of exceptions could happend:
- Session Expired: In this case the most convenient is to reconnect and retry the operation that causes the error. Maybe showing a log-in windows if necessary.
- Any Unexpected Exception: Abort and show to the user.
The ability to auto-retry when the session has expired (only!), and to handle the corruption of the communication channel was left to the user in previous versions of the framework, while in the new one is already implemented.
In order to implement this features we require it to provide two lambdas:
- GetServer: Globally set at the beginning of your application, tells Server class what are the necessary steps to connect to the server when necessary.
- An action that performs the operation (Execute) or returns the values (Return) on the server side, like this:
Server.Return((IBaseServer s)=>s.Retrieve(typeof(PersonDN), id));
Server.Execute((MyServers)=>s.DeletePerson(id));
By using this syntax we tell Server what to do in a lambda, so he is free to retry in the case of a session expiration. Let's see how it is implemented in the server.
public static class Server
{
static Func<IBaseServer> getServer;
static IBaseServer current;
public static void SetNewServerCallback(Func<IBaseServer> server)
{
getServer = server;
}
public static void Connect()
{
if (current == null || (current is ICommunicationObject) && ((ICommunicationObject)current).State == CommunicationState.Faulted)
current = getServer();
if (current == null)
throw new InvalidOperationException(Properties.Resources.AConnectionWithTheServerIsNecessaryToContinue);
}
static void HandleSessionException(MessageSecurityException e)
{
MessageBox.Show(Properties.Resources.SessionExpired, Properties.Resources.SessionExpired, MessageBoxButton.OK, MessageBoxImage.Hand);
}
public static R Return<S, R>(Func<S, R> function)
where S : class
{
retry:
Connect();
S server = current as S;
if (server == null)
throw new InvalidOperationException(Properties.Resources.Server0DoesNotImplement1.Formato(current.GetType(), typeof(S)));
try
{
return function(server);
}
catch (MessageSecurityException e)
{
HandleSessionException(e);
current = null;
goto retry;
}
}
public static void Execute<S>(Action<S> action);
(...)
}
IBaseServer overloads
Also, since implementing IBaseServer is mandatory, it contains some convenient generic overloads (some of them extension methods) that already implement the lambda trick to make them repeatable, making server class the equivalent to Database class when you are in the Client side.
public static class Server
{
(...)
public static T Save<T>(this T entidad) where T : IdentifiableEntity
{
return (T)Return((IBaseServer s)=>s.Save(entidad));
}
public static IdentifiableEntity Save(IdentifiableEntity entidad)
public static T Retrieve<T>(int id) where T : IdentifiableEntity
public static IdentifiableEntity Retrieve(Type type, int id)
public static IdentifiableEntity RetrieveFromLazyAndRemember(Lazy lazy)
public static T RetrieveFromLazyAndRemember<T>(this Lazy<T> lazy) where T : class, IIdentifiable
public static IdentifiableEntity RetrieveFromLazyAndForget(Lazy lazy)
public static T RetrieveFromLazyAndForget<T>(this Lazy<T> lazy) where T : class, IIdentifiable
public static List<T> RetrieveAll<T>()
public static List<IdentifiableEntity> RetrieveAll(Type type)
public static List<Lazy<T>> RetrieveAllLazy<T>() where T : class, IIdentifiable
public static List<Lazy> RetriveAllLazy(Type type)
public static List<Lazy> FindLazyLike(Type type, string subString, int count)
public static List<T> SaveList<T>(List<T> list) where T: IdentifiableEntity
(...)
}
Note the different overloads of RetrieveFromLazy: RetrieveFromLazyAndRemember stores the retrieved entity in the lazy entity reference (making the lazy fat), while RetrieveFromLazyAndForget returns the entity without actually storing it. It's client behaviour, there's just one server implementation
Convert
Finally, there's a pair of methods,
Convert and
CanConvert, that simplify using lazy objects and real entities indistinctly, building a lazy around our entity or retrieving the real entity from server when needed.
public static class Server
{
(...)
public static object Convert(object obj, Type type)
public static bool CanConvert(object obj, Type type)
}