I've been asked a few times (e.g.,
here) about how to use the DevForce Debug and Trace facilities to put client-side messages in the server-side log.
There are lots of ways to go about it but I think one of the easiest is to use the DevForce InvokeServerMethod to pass a message to the middle tier server (our BOS) which can post the message to the server-side DebugLog on the client's behalf.
You won't have to do any fancy configuration; there is no new WCF channel to configure, open, or close. You'll be using the same pipeline DevForce established for sending entities back and forth.
This is actually a nifty example of using the DevForce ServerMethod; I hope it will inspire you to think of other uses. You should read up on it in the Developer's Guide.
Before I show you the code, I want to warn you not to be too chatty in production. Every call consumes some server cycles that will not be available for more important needs. The more likely risk is that you blow up the server log with a lot of noise. You'll want to take a measured approach here.
With that advisory out of the way, I proceed.
ServerLogger on the Middle Tier (BOS)
Let's start with the Server where we will create a static class, ServerLogger, and give it a single static method, Log.
We have to think about where to put this. It could go in its own assembly, in which case you have to remember to deploy it to the server and remember to add that assembly to the Probe Assemblies in your server configuration file (typically a web.config).
I'm lazy so I'll put mine in the model assembly (called "ModelExplorer.Model" in my example) because that is sure to be deployed to the server and is already named in the Probe Assemblies of the configuration file.
Our "Log" method must have the signature of a DevForce "IdeaBlade.EntityModel.ServerMethodDelegate". It looks like this:
namespace DomainModel {
public static class ServerLogger {
[IdeaBlade.EntityModel.AllowRpcAttribute]
public static object Log(
System.Security.Principal.IPrincipal principal,
IdeaBlade.EntityModel.EntityManager serverEntityManager,
params object[] args) {
if (args.Length == 0) return null;
var msg = string.Format("Client message: '{0}'", args[0]);
IdeaBlade.Core.TraceFns.WriteLine(msg);
return null;
}
}
}
I highlighted the things I want to talk about in blue.
The namespace could be anything but I have to take note of it because I'll need to know it when I construct my client call.
The class (and its methods) must be static. I don't like statics either but that's the way it works.
Be sure to keep the class stateless. That will forestall threading nightmares and help keep your server light.
The log method must carry the AllowRpc attribute or DevForce won't call it. This is partly a security measure to ensure that methods which happen to have a conforming signature are not callable from the client by accident.
Look at the good stuff we pass into your method. You get the Principal of the client user. Your method can determine by checking principal.IsInRole("xxx") if this client has the right to call your method.
We also give you an empty but logged-in EntityManager in case your method wants to perform some DevForce persistence operations on the server. We won't need it for our logger.
Finally, there are the "args" which hold the information passed to us from the client. In our example, we expect the message to be a string in the first (and only) arg; we dress it up and pass it along to the DevForce TraceFns ... as if we had composed it on the server to begin with.
There is not much type-safety here; you'll have to check that you received args and that they are of the expected type. It is up to you to ensure that all objects passed from the client are serializable and understood on the server.
Notice that we return null. You can return any serializable object that the client understands. We have nothing to send back so we return null.
Invoke the ServerLogger from the Client
In my example, I will log a message in the server DebugLog whenever I attempt a query on the client.
I've jacked into the QueryRepository class in ModelExplorer.Explorer project of my Silverlight PrismExplorer demo where I have a collection of query "key" names and their associated LINQ queries. Just before I ask DevForce to run the query, I call the following method:
LogToServer("Query called: " + queryKey;
That's it. Construct the message from the "queryKey" and we're done! Ok ... here's the guts of LogToServer:
private void LogToServer(string message) {
Manager.InvokeServerMethodAsync(
"DomainModel.ServerLogger,ModelExplorer.Model", // Assembly-qualified name for the server class
"Log", // name of the server method
delegate {}, // callback (here a "do nothing" delegate)
null, // state object to identify this particular call (we don't care)
message);
}
The method does need access to a logged in and connected EntityManager.
We are using the async form of InvokeServerMethod here. On a regular .NET client we could use the synchronous version which would block the client calling thread until the server responded. That's not allowed in Silverlight so we don't bother offering it.
In practice, no matter what the client environment, you may still prefer the async approach for sending a message to the server; why block when you don't really care about the server response?
The trickiest part of this is getting the ServerLogger class name right.
I am assuming that you don't have access to the ServerLogger type on the client (generally true of server method classes) so you can't use the simpler overload in which you just supply the method delegate (e.g., ServerLogger.Log). You'll have to describe the type and method with two strings.
First you specify the class; you have to use the "assembly-qualified" name to identify it. That name is of the form, "namespace.typename,assemblyname", which in our example is "DomainModel.ServerLogger,ModelExplorer.Model".
Remember from our discussion above that we slipped our server class into the same assembly that holds the domain model on the server. You wouldn't have to do it the lazy way as I have here.
We must specify a callback for when the async method returns. We don't really care when it returns or what the server returns so we'll give it a "do nothing" delegate; the expression "delegate {}" is a quick-and-dirty way of writing a delegate that takes any parameters and does nothing.
We don't care to distinguish one invocation from another so we'll give it a null "state" object.
Finally, the meat: we send the message which is simply a string. This (and all arguments) passed to the server must be serializable ... which strings are.
Again, no type safety here. That's why I wrapped the particulars in this type-safe, general purpose method which, in a real application, would be made available everywhere via a client-side "service".
Results
After running the application and my first query, the relevant portion of the
server-side DebugLog looks like this:
I highlighted the "IdeaBladeConfig resoution so you'd know we are looking at the server-side log.
I highlighted the entry for the client message which shows the user name of the client (this came from the Principal), the server logging method, and the message itself.
DevForce offers an extremely simple mechanism for sending serializable material to (and from) your custom server-side method. You don't have to configure anything to use it because you're using the same client/server pipeline that DevForce employs for its own purposes.
Your server method is given the client's Principal so you can impose the authorization rules you deem appropriate. You could use this same basic approach to write a server-side method that routed client traffic to anything your server can reach. The BOS becomes a service hub.
In this example, we've put EntityManager.InvokeServerMethod to use as a simple way to pass messages from the client to the server-side log.
Edited by WardBell - 18-Sep-2009 at 12:15am