I launched my first app yesterday to the store. The app has a trial version which simply restricts full access to the game.
I downloaded the trial and played for a while, no problems - good.
I then purchased the full version by from the store and started the app again only to find that it didnt release it's restrited areas - NOT good.
I have implemented the following code in the app to implement the restrictions:
/// <summary>
/// The TrialExperienceHelper class can serve as a convenient building-block in your app's trial experience implementation. It queries
/// the license as infrequently as possible, and caches the results, for maximum performance. When built in debug configuration, the class simulates the purchase
/// experience when the Buy method is called. To customize what happens in debug when Buy is called, initialize the simulatedLicMode and/or
/// simulatedLicModeOnPurchase fields to different values. A release build of this class will have access to a license only when the app is
/// published to the Windows Phone Store. For a release build not published to the Store, the value of the LicenseMode property will be
/// LicenseModes.MissingOrRevoked.
/// </summary>
public static class TrialExperienceHelper
{
#region enums
/// <summary>
/// The LicenseModes enumeration describes the mode of a license.
/// </summary>
public enum LicenseModes
{
Full,
MissingOrRevoked,
Trial
}
#endregion enums
#region fields
#if DEBUG
// Determines how a debug build behaves on launch. This field is set to LicenseModes.Full after simulating a purchase.
// Calling the Buy method (or navigating away from the app and back) will simulate a purchase.
internal static LicenseModes simulatedLicMode = LicenseModes.Trial;
#endif // DEBUG
private static bool isActiveCache;
private static bool isTrialCache;
#endregion fields
#region constructors
// The static constructor effectively initializes the cache of the state of the license when the app is launched. It also attaches
// a handler so that we can refresh the cache whenever the license has (potentially) changed.
static TrialExperienceHelper()
{
TrialExperienceHelper.RefreshCache();
PhoneApplicationService.Current.Activated += (object sender, ActivatedEventArgs e) => TrialExperienceHelper.
#if DEBUG
// In debug configuration, when the user returns to the application we will simulate a purchase.
OnSimulatedPurchase();
#else // DEBUG
// In release configuration, when the user returns to the application we will refresh the cache.
RefreshCache();
#endif // DEBUG
}
#endregion constructors
#region properties
/// <summary>
/// The LicenseMode property combines the active and trial states of the license into a single
/// enumerated value. In debug configuration, the simulated value is returned. In release configuration,
/// if the license is active then it is either trial or full. If the license is not active then
/// it is either missing or revoked.
/// </summary>
public static LicenseModes LicenseMode
{
get
{
#if DEBUG
return simulatedLicMode;
#else // DEBUG
if (TrialExperienceHelper.isActiveCache)
{
return TrialExperienceHelper.isTrialCache ? LicenseModes.Trial : LicenseModes.Full;
}
else // License is inactive.
{
return LicenseModes.MissingOrRevoked;
}
#endif // DEBUG
}
}
/// <summary>
/// The IsFull property provides a convenient way of checking whether the license is full or not.
/// </summary>
public static bool IsFull
{
get
{
return (TrialExperienceHelper.LicenseMode == LicenseModes.Full);
}
}
#endregion properties
#region methods
/// <summary>
/// The Buy method can be called when the license state is trial. the user is given the opportunity
/// to buy the app after which, in all configurations, the Activated event is raised, which we handle.
/// </summary>
public static void Buy()
{
MarketplaceDetailTask marketplaceDetailTask = new MarketplaceDetailTask();
marketplaceDetailTask.ContentType = MarketplaceContentType.Applications;
marketplaceDetailTask.Show();
}
/// <summary>
/// This method can be called at any time to refresh the values stored in the cache. We re-query the application object
/// for the current state of the license and cache the fresh values. We also raise the LicenseChanged event.
/// </summary>
public static void RefreshCache()
{
TrialExperienceHelper.isActiveCache = CurrentApp.LicenseInformation.IsActive;
TrialExperienceHelper.isTrialCache = CurrentApp.LicenseInformation.IsTrial;
TrialExperienceHelper.RaiseLicenseChanged();
}
private static void RaiseLicenseChanged()
{
if (TrialExperienceHelper.LicenseChanged != null)
{
TrialExperienceHelper.LicenseChanged();
}
}
#if DEBUG
private static void OnSimulatedPurchase()
{
TrialExperienceHelper.simulatedLicMode = LicenseModes.Full;
TrialExperienceHelper.RaiseLicenseChanged();
}
#endif // DEBUG
#endregion methods
#region events
/// <summary>
/// The static LicenseChanged event is raised whenever the value of the LicenseMode property has (potentially) changed.
/// </summary>
public static event LicenseChangedEventHandler LicenseChanged;
#endregion events
}
Then in my code I'm using if's to determine what to allow with this:
if ((TrialExperienceHelper.LicenseMode == TrialExperienceHelper.LicenseModes.Trial)||
(TrialExperienceHelper.LicenseMode == TrialExperienceHelper.LicenseModes.MissingOrRevoked))
{
//Then add ad' etc
}
This call must be returning TRUE...!
For the life of me I can't see the mistake, maybe someone here more experienced can help? What have I done wrong here?
Related
I need to be able to temporarily disable logging for some scope. In my case there is a background task which periodically attempts to instantiate some device API for every available COM port in the system and sees if it fails. That API writes a lot of information to log in case of failure (exceptions, internal components Dispose calls, etc). As a result, the log is flooded with such unsuccessful attempts errors every second.
I came up with the solution that uses LogContext.PushProperty to identify the supressed log events. However, the following code won't log anything:
internal static class Program
{
public static void Main(String[] args)
{
void StartListeningSomething()
{
Task.Factory.StartNew(() =>
{
while (true)
{
Log.Information("Listening");
Thread.Sleep(500);
}
}, TaskCreationOptions.LongRunning);
}
Log.Logger = new LoggerConfiguration()
.Enrich.WithThreadId()
.Filter.ByExcluding(logEvent => logEvent.Properties.ContainsKey("SuppressLogging"))
.Enrich.FromLogContext()
.WriteTo.Console(new JsonFormatter())
.CreateLogger();
using (LogContext.PushProperty("SuppressLogging", true))
{
StartListeningSomething();
Console.ReadKey(); // Will ignore background thread log messages until key enter
}
// We want to start logging events after exiting using block
// But they won't be logged for listener thread at all
Console.ReadKey();
}
}
All log events inside listener task will be enriched with "SupressLogging" property even after popping it from the scope.
The only workaround I found (except the tedious passing customized ILogger throughout the entire API) consists of the following steps:
Assign some unique value to "SupressLogging" property
Add this value to internal static storage
When exiting scope, remove this value from storage (invalidate)
In Filter section of the logger configuration, check for property being attached and it's value being valid (contained in storage).
The following code uses custom IDisposable token to make it seem like usual PushProperty
internal static class Program
{
public static void Main(String[] args)
{
void StartListeningSomething()
{
Task.Factory.StartNew(() =>
{
while (true)
{
Log.Information("Listening");
Thread.Sleep(500);
}
}, TaskCreationOptions.LongRunning);
}
Log.Logger = new LoggerConfiguration()
.Enrich.WithThreadId()
.Filter.ByExcluding(logEvent => logEvent.IsSuppressed()) // Check if log event marked with supression property
.Enrich.FromLogContext()
.WriteTo.Console(new JsonFormatter())
.CreateLogger();
using (SerilogExtensions.SuppressLogging())
{
StartListeningSomething();
Console.ReadKey(); // Will ignore background thread log messages until some key is entered
}
// Will start logging events after exiting the using block
Console.ReadKey();
}
}
And the actual SerilogExtensions:
/// <summary>
/// Provides helper extensions to Serilog logging.
/// </summary>
public static class SerilogExtensions
{
private const String SuppressLoggingProperty = "SuppressLogging";
private static readonly HashSet<Guid> ActiveSuppressions = new HashSet<Guid>();
/// <summary>
/// Get disposable token to supress logging for context.
/// </summary>
/// <remarks>
/// Pushes "SuppressLogging" property with unique value to SerilogContext.
/// When disposed, disposes Serilog property push token and invalidates stored value so new log messages are no longer
/// supressed.
/// </remarks>
public static IDisposable SuppressLogging()
{
return new SuppressLoggingDisposableToken();
}
/// <summary>
/// Determines whether the given log event suppressed.
/// </summary>
/// <remarks>
/// Also removes "SuppressLogging" property if present.
/// </remarks>
public static Boolean IsSuppressed(this LogEvent logEvent)
{
Boolean containsProperty = logEvent.Properties.TryGetValue(SuppressLoggingProperty, out var val);
if (!containsProperty)
return false;
logEvent.RemovePropertyIfPresent(SuppressLoggingProperty); //No need for that in logs
if (val is ScalarValue scalar && scalar.Value is Guid id)
return ActiveSuppressions.Contains(id);
return false;
}
/// <summary>
/// Disposable wrapper around logging supression property push/pop and value generation/invalidation.
/// </summary>
private class SuppressLoggingDisposableToken : IDisposable
{
private readonly IDisposable _pushPropertyDisposable;
private readonly Guid _guid;
public SuppressLoggingDisposableToken()
{
_guid = Guid.NewGuid();
_pushPropertyDisposable = LogContext.PushProperty(SuppressLoggingProperty, _guid);
ActiveSuppressions.Add(_guid);
}
public void Dispose()
{
ActiveSuppressions.Remove(_guid);
_pushPropertyDisposable.Dispose();
}
}
}
The complete example project can be found on github.
I would like to leave this self-answered question here and also to ask more experienced Serilog users what their opinion of this problem is. May be there is some common approach to logging suppression I didn't find?
I would like to add to ArXen42 answer.
The proposed Hashset to keep track of the the activesuppression keys is not threadsafe and will create issues when using multiple threads.
A solution would be to use the ConcurrentDictionary<T,T2> instead of the HashSet<T> or the solution as stated below without keeping track of GUIDs to supress the logs.
/// Provides helper extensions to Serilog logging.
/// </summary>
public static class SerilogExtensions
{
private const string SuppressLoggingProperty
= "SuppressLogging";
/// <summary>
/// Get disposable token to supress logging for context.
/// </summary>
public static IDisposable SuppressLogging()
{
return LogContext.PushProperty(SuppressLoggingProperty, true);
}
/// <summary>
/// Determines whether the given log event suppressed.
/// </summary>
/// <remarks>
/// Also removes "SuppressLogging" property if present.
/// </remarks>
public static bool IsSuppressed(this LogEvent logEvent)
{
var containsProperty = logEvent.Properties
.TryGetValue(SuppressLoggingProperty, out var val);
if (!containsProperty)
return false;
// remove suppression property from logs
logEvent.RemovePropertyIfPresent(SuppressLoggingProperty);
if (val is ScalarValue scalar && scalar.Value is bool isSuppressed)
return isSuppressed;
return false;
}
}
I have a problem using external libraries for my visual studio extension. Currently I have my own NuGet server on which I host my libraries, now since I dont want functionality twice I extracted some functions out of my extension into an existing library.
The problem is however that whenever I want to use anything from that assembly I can not do so, since visual studio does not include the .dlls in the .vsix package.
So far I have tried:
using Assets to include the libraries from their package location. This creates two items in the solution which also have their "Include in VSIX" property set to true, this did not work
including the projects and then adding the BuiltProjectOutputGroup;BuiltProjectOutputGroupDependencies;GetCopyToOutputDirectoryItems;SatelliteDllsProjectOutputGroup; to the "Other Groups included in VSIX" property of the reference, which did not work
using Assets to include the libraries as projects, which did not work
So now I am at my end since there is nothing that seems to work....
The solutions I found are already suggesting all the steps I tried, but at this point
I also found these two post which are the same as my question but those answerts did not work for me like I said.
this
this
Okay I found a way to do it, its a combonation of two answers I found here on stack overflow.
And eventhough its a bit hacky I suppose its the only way possible.
So I simply used the existing ManualAssemblyResolver and adjusted it to my needs, the Result being this:
public class ManualAssemblyResolver : IDisposable
{
#region Attributes
/// <summary>
/// list of the known assemblies by this resolver
/// </summary>
private readonly List<Assembly> _assemblies;
#endregion
#region Properties
/// <summary>
/// function to be called when an unknown assembly is requested that is not yet kown
/// </summary>
public Func<ResolveEventArgs, Assembly> OnUnknowAssemblyRequested { get; set; }
#endregion
#region Constructor
public ManualAssemblyResolver(params Assembly[] assemblies)
{
_assemblies = new List<Assembly>();
if (assemblies != null)
_assemblies.AddRange(assemblies);
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
}
#endregion
#region Implement IDisposeable
public void Dispose()
{
AppDomain.CurrentDomain.AssemblyResolve -= OnAssemblyResolve;
}
#endregion
#region Private
/// <summary>
/// will be called when an unknown assembly should be resolved
/// </summary>
/// <param name="sender">sender of the event</param>
/// <param name="args">event that has been sent</param>
/// <returns>the assembly that is needed or null</returns>
private Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
foreach (Assembly assembly in _assemblies)
if (args.Name == assembly.FullName)
return assembly;
if (OnUnknowAssemblyRequested != null)
{
Assembly assembly = OnUnknowAssemblyRequested(args);
if (assembly != null)
_assemblies.Add(assembly);
return assembly;
}
return null;
}
#endregion
}
After that I used an Addition ExtensionManager to get the installation path of the extension. Which looks like this
public class ExtensionManager : Singleton<ExtensionManager>
{
#region Constructor
/// <summary>
/// private constructor to satisfy the singleton base class
/// </summary>
private ExtensionManager()
{
ExtensionHomePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Definitions.Constants.FolderName);
if (!Directory.Exists(ExtensionHomePath))
Directory.CreateDirectory(ExtensionHomePath);
SettingsFileFullname = Path.Combine(ExtensionHomePath, Definitions.Constants.SettingsFileName);
InstallationPath = Path.GetDirectoryName(GetType().Assembly.Location);
}
#endregion
#region Properties
/// <summary>
/// returns the installationPath
/// </summary>
public string InstallationPath { get; private set; }
/// <summary>
/// the path to the directory where the settings file is located as well as the log file
/// </summary>
public string ExtensionHomePath { get; private set; }
/// <summary>
/// the fullpath to the settingsfile
/// </summary>
public string SettingsFileFullname { get; private set; }
#endregion
}
Then in the Initialize() method of the Package you will need to create an instance of the ManualAssemblyResolver and provide the Path to the assemblies you need like this:
#region Attributes
private ManualAssemblyResolver _resolver;
#endregion
#region Override Microsoft.VisualStudio.Shell.Package
/// <summary>
/// Initialization of the package; this method is called right after the package is sited, so this is the place
/// where you can put all the initialization code that rely on services provided by VisualStudio.
/// </summary>
protected override void Initialize()
{
_resolver = new ManualAssemblyResolver(
Assembly.LoadFrom(Path.Combine(ExtensionManager.Instance.InstallationPath, Definitions.Constants.NameOfAssemblyA)),
Assembly.LoadFrom(Path.Combine(ExtensionManager.Instance.InstallationPath, Definitions.Constants.NameOfAssemblyB))
);
Note that you will need to call this before anythingelse that even touches anything from the referenced assemblies, otherwise a FileNotFoundException will be thrown.
In any case this seems to work for me now but I wish there was a cleaner way to do it. So if anybody has a better way (a way that actually includes and lloks up the assemblies from the .vsix package) then please post an answer.
EDIT:
Okay now I found the real issue, it was simply the fact that the dlls were satellite dll (the had their assembly culture set) so they were not visible....
However the above fix worked when they were still satillite dlls.
I have a OperationHelper class which is as follows:
public class OperationHelper
{
/// <summary>
/// Gets or sets the Add Operation value
/// </summary>
public static bool AddOperation { get; set; }
/// <summary>
/// Gets or sets the Edit Operation value
/// </summary>
public static bool EditOperation { get; set; }
/// <summary>
/// Gets or sets the Delete Operation value
/// </summary>
public static bool DeleteOperation { get; set; }
/// <summary>
/// Gets or sets the Select Operation value
/// </summary>
public static bool SelectOperation { get; set; }
}
on each request this values are re-assigned. when i run this locally it works properly.
but when i published the code some values are not get assigned or not working as it should work.
So want to know the behavior of the static variable in Asp.Net with C#.
are static variable equal to global variable which is accessible to all user? If it is set by user A to true can user B get that value as True or it has different instance of the variable.
The behavior of static variables is that they are being created as soon as the code they belong to is reached. To solve your problem, consider a static constructor for your class to properly initialize all values to your desire
public class OperationHelper
{
/// <summary>
/// Gets or sets the Add Operation value
/// </summary>
public static bool AddOperation { get; set; }
/// <summary>
/// Gets or sets the Edit Operation value
/// </summary>
public static bool EditOperation { get; set; }
/// <summary>
/// Gets or sets the Delete Operation value
/// </summary>
public static bool DeleteOperation { get; set; }
/// <summary>
/// Gets or sets the Select Operation value
/// </summary>
public static bool SelectOperation { get; set; }
static OperationHelper() {
//initialize your static variables here
}
}
See here for a reference on static constructors.
So want to know the behavior of the static variable in Asp.Net with
C#.
are static variable equal to global variable which is accessible to
all user? If it is set by user A to true can user B get that value as
True or it has different instance of the variable.
The behavior is like that only if your run your site under one working process on your pool.
If your pool have more than one working process, then each process have their static values, and is unknown to you what process is given to each request, to each user. And process together they are not communicate.
So let say that you have a pool with 4 working process.
UserA ask for a page, Process 1 is replay and set a static value to A.
UserB ask for a page, Process 1 is replay and the static value is A.
UserA ask for a page, Process 2 is replay and the static value is not set.
and so on.
More on the subject:
Lifetime of ASP.NET Static Variable
Where are static variables stored in asp.net aspx page
Using static variables instead of Application state in ASP.NET
Static methods on ASP.NET web sites
Asp.net static object appears sometimes as non global
static variables are only created once. so userB will get same instance of the variable to answer your question.
More on this have been discussed here.
you need to consider session that will give you different value for each user accessing the site
I have a NativeActivity derived activity that I wrote that is to use bookmarks as a trigger for a pick branch. Using something I found on MSDN I tried writing this to trigger the branch. The branch contains activities that fire service callbacks to remote clients via send activities. If I set a delay for the trigger, callbacks fire to the clients successfully. If I use my code activity, the pick branch activities don't fire.
public sealed class UpdateListener : NativeActivity<ClientUpdate>
{
[RequiredArgument]
public InArgument<string> BookmarkName { get; set; }
protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark(BookmarkName.Get(context),
new BookmarkCallback(this.OnResumeBookmark));
}
protected override bool CanInduceIdle
{
get { return true; }
}
public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj )
{
Result.Set(context, (ClientUpdate)obj);
}
}
So it takes an arg to set the bookmark name for future bookmark references to execute the trigger. OnResumeBoookmark() takes in a ClientUpdate object that is passed by my application that is hosting the workflowapp. The activity is to return the object so the ClientUpdate can be passed to the workflow and have it sent to the remote clients via the send activity in the pick branch. In theory anyways.
For some reason it seems to be correct but feels wrong. I'm not sure if I should write the Activity in a different way to take care of what I need for my WF service.
I think your intentions would be a bit clearer if you created an extension (that implements IWorkflowInstanceExtension) to perform your action here.
For example:
public sealed class AsyncWorkExtension
: IWorkflowInstanceExtension
{
// only one extension per workflow
private WorkflowInstanceProxy _proxy;
private Bookmark _lastBookmark;
/// <summary>
/// Request the extension does some work for an activity
/// during which the activity will idle the workflow
/// </summary>
/// <param name="toResumeMe"></param>
public void DoWork(Bookmark toResumeMe)
{
_lastBookmark = toResumeMe;
// imagine I kick off some async op here
// when complete system calls WorkCompleted below
// NOTE: you CANNOT block here or you block the WF!
}
/// <summary>
/// Called by the system when long-running work is complete
/// </summary>
/// <param name="result"></param>
internal void WorkCompleted(object result)
{
//NOT good practice! example only
//this leaks resources search APM for details
_proxy.BeginResumeBookmark(_lastBookmark, result, null, null);
}
/// <summary>
/// When implemented, returns any additional extensions
/// the implementing class requires.
/// </summary>
/// <returns>
/// A collection of additional workflow extensions.
/// </returns>
IEnumerable<object> IWorkflowInstanceExtension
.GetAdditionalExtensions()
{
return new object[0];
}
/// <summary>
/// Sets the specified target
/// <see cref="WorkflowInstanceProxy"/>.
/// </summary>
/// <param name="instance">The target workflow instance to set.</param>
void IWorkflowInstanceExtension
.SetInstance(WorkflowInstanceProxy instance)
{
_proxy = instance;
}
}
Within the Activity, you'd use this thusly:
var ext = context.GetExtension<AsyncWorkExtension>();
var bookmark = context.CreateBookmark(BookmarkCallback);
ext.DoWork(bookmark);
return;
This way is much more explicit (instead of using the bookmark name to convey meaning to the "outside" world) and is much easier to extend if, say, you require to send out more information than a bookmark name.
Is there something actually resuming the bookmark here? If not the workflow will wait very patiently and nothing will happen.
Ok,
this question is for people with either a deep knowledge of PRISM or some magic skills I just lack (yet). The Background is simple: Prism allows the declaration of events to which the user can subscribe or publish. In code this looks like this:
_eventAggregator.GetEvent<LayoutChangedEvent>().Subscribe(UpdateUi, true);
_eventAggregator.GetEvent<LayoutChangedEvent>().Publish("Some argument");
Now this is nice, especially because these events are strongly typed, and the declaration is a piece of cake:
public class LayoutChangedEvent : CompositePresentationEvent<string>
{
}
But now comes the hard part: I want to trace events in some way. I had the idea to subscribe using a lambda expression calling a simple log message. Worked perfectly in WPF, but in Silverlight there is some method access error (took me some time to figure out the reason).. If you want to see for yourself, try this in Silverlight:
eA.GetEvent<VideoStartedEvent>().Subscribe(obj => TraceEvent(obj, "vSe", log));
If this would be possible, I would be happy, because I could easily trace all events using a single line to subscribe. But it does not... The alternative approach is writing a different functions for each event, and assign this function to the events. Why different functions? Well, I need to know WHICH event was published. If I use the same function for two different events I only get the payload as argument. I have now way to figure out which event caused the tracing message.
I tried:
using Reflection to get the causing event (not working)
using a constructor in the event to enable each event to trace itself (not allowed)
Any other ideas?
Chris
PS: Writing this text took me most likely longer than writing 20 functions for my 20 events, but I refuse to give up :-) I just had the idea to use postsharp, that would most likely work (although I am not sure, perhaps I end up having only information about the base class).. Tricky and so unimportant topic...
Probably the easiest thing would be to subclass CompositePresentationEvent and override the behavior of the Publish event. Here's the source for CompositePresentationEvent:
http://compositewpf.codeplex.com/SourceControl/changeset/view/26112#496659
Here's the current Publish behavior:
public virtual void Publish(TPayload payload)
{
base.InternalPublish(payload);
}
So you could just add a little to this:
public virtual override void Publish(TPayload payload)
{
ILoggerFacade logger = ServiceLocator.Current.GetInstance<ILoggerFacade>();
logger.Log("Publishing " + payload.ToString(), Category.Debug, Priority.Low);
base.InternalPublish(payload);
}
Here I'm using the logger facility built into Prism, but feel free to substitute your own (or better, just implement ILoggerFacade!).
I was surprised that there were any default messages being published or places to plug in tracing in this system... as much as EventAggregator is abused by people, you'd think this would be a big request!
A little late but better late than never! I recently had the same problem and this is how I solved it.
First, I didn't like the Prism method of publishing/subscribing to events, so I used a method like this instead:
http://neverindoubtnet.blogspot.com/2009/07/simplify-prism-event-aggregator.html
This post above suggests using Extension methods on Event Aggregator to simplify the call to publish/subscribe. As a result your client code looks like this:
IEventAggregator ev;
ev.Publish<MyCustomMessage>();
//or
ev.Publish(new MyCustomMessage(someData));
//and similarly subscription
ev.Subscribe<MyCustomMessage(this.OnCustomMessageReceived);
// ...
private void OnCustomMessageReceived(MyCustomMessage message)
{
// ...
}
// With a BaseMessageEvent class as follows (see the blog post above for where this comes from)
/// <summary>
/// Base class for all messages (events)
/// </summary>
/// <typeparam name="TMessage">The message type (payload delivered to subscribers)</typeparam>
public class BaseEventMessage<TMessage> : CompositePresentationEvent<TMessage>
{
}
Ok this is great, but rather than hacky extension methods I implemented my own event service as follows:
/// <summary>
/// The EventService instance
/// </summary>
public class EventService : IEventService
{
private readonly IEventAggregator eventAggregator;
private readonly ILoggerFacade logger;
/// <summary>
/// Initializes a new instance of the <see cref="EventService"/> class.
/// </summary>
/// <param name="logger">The logger instance.</param>
/// <param name="eventAggregator">The event aggregator instance.</param>
public EventService(IEventAggregator eventAggregator, ILoggerFacade logger)
{
this.logger = logger;
this.eventAggregator = eventAggregator;
}
#region IEventService Members
/// <summary>
/// Publishes the event of type TMessageType to all subscribers
/// </summary>
/// <typeparam name="TMessageType">The message type (Payload), must inherit CompositeEvent</typeparam>
public void Publish<TMessageType>() where TMessageType : BaseEventMessage<TMessageType>, new()
{
TMessageType message = Activator.CreateInstance<TMessageType>();
this.Publish(message);
}
/// <summary>
/// Publishes the event of type TMessageType to all subscribers
/// </summary>
/// <typeparam name="TMessageType">The message type (Payload), must inherit CompositeEvent</typeparam>
/// <param name="message">The message to publish</param>
public void Publish<TMessageType>(TMessageType message) where TMessageType : BaseEventMessage<TMessageType>, new()
{
// Here we can log our message publications
if (this.logger != null)
{
// logger.log etc..
}
this.eventAggregator.GetEvent<TMessageType>().Publish(message);
}
/// <summary>
/// Subscribes to the event of type TMessage
/// </summary>
/// <typeparam name="TMessageType">The message type (Payload), must inherit CompositeEvent</typeparam>
/// <param name="action">The action to execute when the event is raised</param>
public void Subscribe<TMessageType>(Action<TMessageType> action) where TMessageType : BaseEventMessage<TMessageType>, new()
{
// Here we can log our message publications
if (this.logger != null)
{
// logger.log etc..
}
this.eventAggregator.GetEvent<TMessageType>().Subscribe(action);
}
#endregion
}
Then I register IEventService/EventService as a singleton in the bootstrapper and forget about using the IEventAggregator, just use this (however if someone uses the IEventAggregator, its the same instance as that used by the EventService so will still work).
Finally, another trick to add is to use the Stack Frame to tell me where publications and subscriptions are coming from. Note this is a slow process (unwinding the stack frame) so use it sparingly. If you are
raising an event regularly then perhaps put a flag in your BaseEventMessage and check that to see whether to log publications for certain event types.
// Inside Publish method ... Log the subscription
if (this.logger != null)
{
Type messageType = typeof(TMessageType);
Type callingType = GetCallingType();
string methodName = GetCallingMethod().Name;
// Log the publication of this event
this.logger.Log(
string.Format("Event {0} was published by {1}.{2}()",
messageType.Name,
callingType.Name,
methodName),
Category.Debug,
Priority.Low));
}
// Additional methods to add to EventService to get the calling type/class
//
/// <summary>
/// Gets the Type that called the method or property where GetCallingType is called
/// </summary>
/// <returns>The class type that called</returns>
[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static Type GetCallingType()
{
int skip = 2;
MethodBase method = new StackFrame(skip, false).GetMethod();
return method.DeclaringType;
}
/// <summary>
/// Gets the Method that called the method or property where GetCallingMethod is called
/// </summary>
/// <returns>The method type that was called</returns>
public static MethodBase GetCallingMethod()
{
return new StackFrame(2, false).GetMethod();
}
Note the above won't work in Silverlight (the use of the StackFrame), but the rest does. I've found this invaluable when debugging the multitude of events flying around a Prism app!