yeasir007

Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Wednesday, October 24, 2018

Singleton instance for WPF application

Singleton instance for WPF application

 Why Singleton Instance:




A single Instance is a must for desktop-based applications. Assume your application has been opened and then the user clicks again in the desktop shortcut then, running the application should be focused if it is minimized in the taskbar or hide in the task tray instead of opening a new instance. There are several ways to do that. Most of the cases developers use Mutex. There are lots of code related to this Singleton instance over the web. Today I’ll talk about Microsoft’s SingleInstance.cs class and its uses, which are very secured and safe to use. You don’t have to dispose of anything manually, all of the tasks will be done by this class. Here below is the process.

 Github Source 

   Ã¨ Add this class in the project

 Ã¨ Add a reference to System.Runtime.Remoting

//-----------------------------------------------------------------------
// <copyright file="SingleInstance.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//     This class checks to make sure that only one instance of 
//     this application is running at a time.
// </summary>
//-----------------------------------------------------------------------

namespace Microsoft.Shell 
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Serialization.Formatters;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using System.Xml.Serialization;
using System.Security;
using System.Runtime.InteropServices;
using System.ComponentModel;

internal enum WM
{
    NULL = 0x0000,
    CREATE = 0x0001,
    DESTROY = 0x0002,
    MOVE = 0x0003,
    SIZE = 0x0005,
    ACTIVATE = 0x0006,
    SETFOCUS = 0x0007,
    KILLFOCUS = 0x0008,
    ENABLE = 0x000A,
    SETREDRAW = 0x000B,
    SETTEXT = 0x000C,
    GETTEXT = 0x000D,
    GETTEXTLENGTH = 0x000E,
    PAINT = 0x000F,
    CLOSE = 0x0010,
    QUERYENDSESSION = 0x0011,
    QUIT = 0x0012,
    QUERYOPEN = 0x0013,
    ERASEBKGND = 0x0014,
    SYSCOLORCHANGE = 0x0015,
    SHOWWINDOW = 0x0018,
    ACTIVATEAPP = 0x001C,
    SETCURSOR = 0x0020,
    MOUSEACTIVATE = 0x0021,
    CHILDACTIVATE = 0x0022,
    QUEUESYNC = 0x0023,
    GETMINMAXINFO = 0x0024,

    WINDOWPOSCHANGING = 0x0046,
    WINDOWPOSCHANGED = 0x0047,

    CONTEXTMENU = 0x007B,
    STYLECHANGING = 0x007C,
    STYLECHANGED = 0x007D,
    DISPLAYCHANGE = 0x007E,
    GETICON = 0x007F,
    SETICON = 0x0080,
    NCCREATE = 0x0081,
    NCDESTROY = 0x0082,
    NCCALCSIZE = 0x0083,
    NCHITTEST = 0x0084,
    NCPAINT = 0x0085,
    NCACTIVATE = 0x0086,
    GETDLGCODE = 0x0087,
    SYNCPAINT = 0x0088,
    NCMOUSEMOVE = 0x00A0,
    NCLBUTTONDOWN = 0x00A1,
    NCLBUTTONUP = 0x00A2,
    NCLBUTTONDBLCLK = 0x00A3,
    NCRBUTTONDOWN = 0x00A4,
    NCRBUTTONUP = 0x00A5,
    NCRBUTTONDBLCLK = 0x00A6,
    NCMBUTTONDOWN = 0x00A7,
    NCMBUTTONUP = 0x00A8,
    NCMBUTTONDBLCLK = 0x00A9,

    SYSKEYDOWN = 0x0104,
    SYSKEYUP = 0x0105,
    SYSCHAR = 0x0106,
    SYSDEADCHAR = 0x0107,
    COMMAND = 0x0111,
    SYSCOMMAND = 0x0112,

    MOUSEMOVE = 0x0200,
    LBUTTONDOWN = 0x0201,
    LBUTTONUP = 0x0202,
    LBUTTONDBLCLK = 0x0203,
    RBUTTONDOWN = 0x0204,
    RBUTTONUP = 0x0205,
    RBUTTONDBLCLK = 0x0206,
    MBUTTONDOWN = 0x0207,
    MBUTTONUP = 0x0208,
    MBUTTONDBLCLK = 0x0209,
    MOUSEWHEEL = 0x020A,
    XBUTTONDOWN = 0x020B,
    XBUTTONUP = 0x020C,
    XBUTTONDBLCLK = 0x020D,
    MOUSEHWHEEL = 0x020E,


    CAPTURECHANGED = 0x0215,

    ENTERSIZEMOVE = 0x0231,
    EXITSIZEMOVE = 0x0232,

    IME_SETCONTEXT = 0x0281,
    IME_NOTIFY = 0x0282,
    IME_CONTROL = 0x0283,
    IME_COMPOSITIONFULL = 0x0284,
    IME_SELECT = 0x0285,
    IME_CHAR = 0x0286,
    IME_REQUEST = 0x0288,
    IME_KEYDOWN = 0x0290,
    IME_KEYUP = 0x0291,

    NCMOUSELEAVE = 0x02A2,

    DWMCOMPOSITIONCHANGED = 0x031E,
    DWMNCRENDERINGCHANGED = 0x031F,
    DWMCOLORIZATIONCOLORCHANGED = 0x0320,
    DWMWINDOWMAXIMIZEDCHANGE = 0x0321,

    #region Windows 7
    DWMSENDICONICTHUMBNAIL = 0x0323,
    DWMSENDICONICLIVEPREVIEWBITMAP = 0x0326,
    #endregion

    USER = 0x0400,

    // This is the hard-coded message value used by WinForms for Shell_NotifyIcon.
    // It's relatively safe to reuse.
    TRAYMOUSEMESSAGE = 0x800, //WM_USER + 1024
    APP = 0x8000,
}

[SuppressUnmanagedCodeSecurity]
internal static class NativeMethods
{
    /// <summary>
    /// Delegate declaration that matches WndProc signatures.
    /// </summary>
    public delegate IntPtr MessageHandler(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled);

    [DllImport("shell32.dll", EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
    private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);


    [DllImport("kernel32.dll", EntryPoint = "LocalFree", SetLastError = true)]
    private static extern IntPtr _LocalFree(IntPtr hMem);


    public static string[] CommandLineToArgvW(string cmdLine)
    {
        IntPtr argv = IntPtr.Zero;
        try
        {
            int numArgs = 0;

            argv = _CommandLineToArgvW(cmdLine, out numArgs);
            if (argv == IntPtr.Zero)
            {
                throw new Win32Exception();
            }
            var result = new string[numArgs];

            for (int i = 0; i < numArgs; i++)
            {
                IntPtr currArg = Marshal.ReadIntPtr(argv, i * Marshal.SizeOf(typeof(IntPtr)));
                result[i] = Marshal.PtrToStringUni(currArg);
            }

            return result;
        }
        finally
        {

            IntPtr p = _LocalFree(argv);
            // Otherwise LocalFree failed.
            // Assert.AreEqual(IntPtr.Zero, p);
        }
    }

} 

public interface ISingleInstanceApp 
{ 
     bool SignalExternalCommandLineArgs(IList<string> args); 
} 

/// <summary>
/// This class checks to make sure that only one instance of 
/// this application is running at a time.
/// </summary>
/// <remarks>
/// Note: this class should be used with some caution because it does no
/// security checking. For example, if one instance of an app that uses this class
/// is running as Administrator, any other instance, even if it is not
/// running as Administrator can activate it with command line arguments.
/// For most apps, this will not be much of an issue.
/// </remarks>
public static class SingleInstance<TApplication>  
            where   TApplication: Application ,  ISingleInstanceApp 

{
    #region Private Fields

    /// <summary>
    /// String delimiter used in channel names.
    /// </summary>
    private const string Delimiter = ":";

    /// <summary>
    /// Suffix to the channel name.
    /// </summary>
    private const string ChannelNameSuffix = "SingeInstanceIPCChannel";

    /// <summary>
    /// Remote service name.
    /// </summary>
    private const string RemoteServiceName = "SingleInstanceApplicationService";

    /// <summary>
    /// IPC protocol used (string).
    /// </summary>
    private const string IpcProtocol = "ipc://";

    /// <summary>
    /// Application mutex.
    /// </summary>
    private static Mutex singleInstanceMutex;

    /// <summary>
    /// IPC channel for communications.
    /// </summary>
    private static IpcServerChannel channel;

    /// <summary>
    /// List of command line arguments for the application.
    /// </summary>
    private static IList<string> commandLineArgs;

    #endregion

    #region Public Properties

    /// <summary>
    /// Gets list of command line arguments for the application.
    /// </summary>
    public static IList<string> CommandLineArgs
    {
        get { return commandLineArgs; }
    }

    #endregion

    #region Public Methods

    /// <summary>
    /// Checks if the instance of the application attempting to start is the first instance. 
    /// If not, activates the first instance.
    /// </summary>
    /// <returns>True if this is the first instance of the application.</returns>
    public static bool InitializeAsFirstInstance( string uniqueName )
    {
        commandLineArgs = GetCommandLineArgs(uniqueName);

        // Build unique application Id and the IPC channel name.
        string applicationIdentifier = uniqueName + Environment.UserName;

        string channelName = String.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);

        // Create mutex based on unique application Id to check if this is the first instance of the application. 
        bool firstInstance;
        singleInstanceMutex = new Mutex(true, applicationIdentifier, out firstInstance);
        if (firstInstance)
        {
            CreateRemoteService(channelName);
        }
        else
        {
            SignalFirstInstance(channelName, commandLineArgs);
        }

        return firstInstance;
    }

    /// <summary>
    /// Cleans up single-instance code, clearing shared resources, mutexes, etc.
    /// </summary>
    public static void Cleanup()
    {
        if (singleInstanceMutex != null)
        {
            singleInstanceMutex.Close();
            singleInstanceMutex = null;
        }

        if (channel != null)
        {
            ChannelServices.UnregisterChannel(channel);
            channel = null;
        }
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Gets command line args - for ClickOnce deployed applications, command line args may not be passed directly, they have to be retrieved.
    /// </summary>
    /// <returns>List of command line arg strings.</returns>
    private static IList<string> GetCommandLineArgs( string uniqueApplicationName )
    {
        string[] args = null;
        if (AppDomain.CurrentDomain.ActivationContext == null)
        {
            // The application was not clickonce deployed, get args from standard API's
            args = Environment.GetCommandLineArgs();
        }
        else
        {
            // The application was clickonce deployed
            // Clickonce deployed apps cannot recieve traditional commandline arguments
            // As a workaround commandline arguments can be written to a shared location before 
            // the app is launched and the app can obtain its commandline arguments from the 
            // shared location               
            string appFolderPath = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), uniqueApplicationName);

            string cmdLinePath = Path.Combine(appFolderPath, "cmdline.txt");
            if (File.Exists(cmdLinePath))
            {
                try
                {
                    using (TextReader reader = new StreamReader(cmdLinePath, System.Text.Encoding.Unicode))
                    {
                        args = NativeMethods.CommandLineToArgvW(reader.ReadToEnd());
                    }

                    File.Delete(cmdLinePath);
                }
                catch (IOException)
                {
                }
            }
        }

        if (args == null)
        {
            args = new string[] { };
        }

        return new List<string>(args);
    }

    /// <summary>
    /// Creates a remote service for communication.
    /// </summary>
    /// <param name="channelName">Application's IPC channel name.</param>
    private static void CreateRemoteService(string channelName)
    {
        BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
        serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
        IDictionary props = new Dictionary<string, string>();

        props["name"] = channelName;
        props["portName"] = channelName;
        props["exclusiveAddressUse"] = "false";

        // Create the IPC Server channel with the channel properties
        channel = new IpcServerChannel(props, serverProvider);

        // Register the channel with the channel services
        ChannelServices.RegisterChannel(channel, true);

        // Expose the remote service with the REMOTE_SERVICE_NAME
        IPCRemoteService remoteService = new IPCRemoteService();
        RemotingServices.Marshal(remoteService, RemoteServiceName);
    }

    /// <summary>
    /// Creates a client channel and obtains a reference to the remoting service exposed by the server - 
    /// in this case, the remoting service exposed by the first instance. Calls a function of the remoting service 
    /// class to pass on command line arguments from the second instance to the first and cause it to activate itself.
    /// </summary>
    /// <param name="channelName">Application's IPC channel name.</param>
    /// <param name="args">
    /// Command line arguments for the second instance, passed to the first instance to take appropriate action.
    /// </param>
    private static void SignalFirstInstance(string channelName, IList<string> args)
    {
        IpcClientChannel secondInstanceChannel = new IpcClientChannel();
        ChannelServices.RegisterChannel(secondInstanceChannel, true);

        string remotingServiceUrl = IpcProtocol + channelName + "/" + RemoteServiceName;

        // Obtain a reference to the remoting service exposed by the server i.e the first instance of the application
        IPCRemoteService firstInstanceRemoteServiceReference = (IPCRemoteService)RemotingServices.Connect(typeof(IPCRemoteService), remotingServiceUrl);

        // Check that the remote service exists, in some cases the first instance may not yet have created one, in which case
        // the second instance should just exit
        if (firstInstanceRemoteServiceReference != null)
        {
            // Invoke a method of the remote service exposed by the first instance passing on the command line
            // arguments and causing the first instance to activate itself
            firstInstanceRemoteServiceReference.InvokeFirstInstance(args);
        }
    }

    /// <summary>
    /// Callback for activating first instance of the application.
    /// </summary>
    /// <param name="arg">Callback argument.</param>
    /// <returns>Always null.</returns>
    private static object ActivateFirstInstanceCallback(object arg)
    {
        // Get command line args to be passed to first instance
        IList<string> args = arg as IList<string>;
        ActivateFirstInstance(args);
        return null;
    }

    /// <summary>
    /// Activates the first instance of the application with arguments from a second instance.
    /// </summary>
    /// <param name="args">List of arguments to supply the first instance of the application.</param>
    private static void ActivateFirstInstance(IList<string> args)
    {
        // Set main window state and process command line args
        if (Application.Current == null)
        {
            return;
        }

        ((TApplication)Application.Current).SignalExternalCommandLineArgs(args);
    }

    #endregion

    #region Private Classes

    /// <summary>
    /// Remoting service class which is exposed by the server i.e the first instance and called by the second instance
    /// to pass on the command line arguments to the first instance and cause it to activate itself.
    /// </summary>
    private class IPCRemoteService : MarshalByRefObject
    {
        /// <summary>
        /// Activates the first instance of the application.
        /// </summary>
        /// <param name="args">List of arguments to pass to the first instance.</param>
        public void InvokeFirstInstance(IList<string> args)
        {
            if (Application.Current != null)
            {
                // Do an asynchronous call to ActivateFirstInstance function
                Application.Current.Dispatcher.BeginInvoke(
                    DispatcherPriority.Normal, new DispatcherOperationCallback(SingleInstance<TApplication>.ActivateFirstInstanceCallback), args);
            }
        }

        /// <summary>
        /// Remoting Object's ease expires after every 5 minutes by default. We need to override the InitializeLifetimeService class
        /// to ensure that lease never expires.
        /// </summary>
        /// <returns>Always null.</returns>
        public override object InitializeLifetimeService()
        {
            return null;
        }
    }

    #endregion
  }
 }

è 

      Implement ISingleInstanceApp in the App.xaml.cs by redefining Main functions. This Interface has only one method and will be called for the second instance.

  

public partial class App : Application,ISingleInstanceApp
{
    private const string MyAppUniqueGUID = "MyGitHubProject{YEASIR00-7DOT-BLOG-SPOT-DOTCOM06LOL7}";
    [STAThread]
    public static void Main()
    {
        if (SingleInstance<App>.InitializeAsFirstInstance(MyAppUniqueGUID))
        {
            var application = new App();
            application.InitializeComponent();
            application.Run();
            // Allow single instance code to perform cleanup operations
            SingleInstance<App>.Cleanup();
        }
    }

    #region ISingleInstanceApp Members
    public bool SignalExternalCommandLineArgs(IList<string> args)
    {
        //This args contains commandLine Parameter for the Second instance
        if ((MainWindow.WindowState == WindowState.Minimized))
        {
            MainWindow.WindowState = WindowState.Normal;
        }
        if (MainWindow.Visibility != Visibility.Visible)
        {
            MainWindow.UpdateLayout();
            MainWindow.Visibility = Visibility.Visible;
        }

        MainWindow.Activate();
        MainWindow.Focus();
        MainWindow.Topmost = true;

        return true;
    }
    #endregion
 }

  

    Ã¨ Set new main entry point by selecting Project Properties –> Application and set “Startup object” to your App class name instead of “(Not Set)”.

       



   Ã¨ Cancel the default WPF main function by right click on App.xaml =>Properties => Set Build Action to “Page” instead of “Application Definition”

      



Happy coding. :)

Tuesday, October 23, 2018

How to implement log manager for C# WPF application

How to implement log manager for C# WPF application

Why Logger Needed: 


 

Indexing log data is one of the finest ways of observing the deployed application. It will assist the developer to figure out the potential area when the problem will arise. According to the Twelve-Factor-App Should be treated as an event stream. There are several prevalent logging frameworks for the .NetFramework to assist the developer which also provides configuration options that can be modified in any environment, from development to production without any hassle.

Logging Frameworks:

In this post, I'll implement Log4Net in WPF application.

Steps to follow:

  • è Create your application

·         Go to NuGet package manager

·         Select Manage NuGet Packages for Solution

·         Select log4net by Apache Software

  1. è Add a config file for your application and renamed as you wish.(Like ApplicationPackages.config)

 

Add package id, version, and targetFramework under <packages> a tag like a bellow.


<?xml version="1.0" encoding="utf-8" ?>
<packages>
     <package id="log4net" version="2.0.10" targetFramework="net472" />  
</packages>


Now implement your log file handler like where your log will be saved or the size of the log and other pieces of stuff. For more details check this link


using log4net;
using log4net.Appender;
using log4net.Core;
using log4net.Layout;
using log4net.Repository.Hierarchy;
using System;

namespace MyGitHubProject.LogManagers
{
   public class LogHandler
   {
    private static bool isSetupLogHandler;

    public LogHandler()
    {
        isSetupLogHandler = false;
    }
    ~LogHandler()
    {

    }

    public static void Configure()
    {
        if (isSetupLogHandler)
            return;
        isSetupLogHandler = true;

        Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();

        PatternLayout patternLayout = new PatternLayout();
        patternLayout.ConversionPattern = "%date [%thread] %-5level %logger - %message%newline";
        patternLayout.ActivateOptions();

        RollingFileAppender roller = new RollingFileAppender();
        roller.AppendToFile = true;
        roller.File = Environment.ExpandEnvironmentVariables(@"%AppData%\MyGitHubProject\UseRegistryInWPF\UseRegistryInWPFLog.txt");
        roller.Layout = patternLayout;
        roller.MaxSizeRollBackups = 20;
        roller.MaximumFileSize = "20MB";
        roller.RollingStyle = RollingFileAppender.RollingMode.Size;
        roller.StaticLogFileName = true;
        roller.ActivateOptions();
        hierarchy.Root.AddAppender(roller);

        MemoryAppender memory = new MemoryAppender();
        memory.ActivateOptions();
        hierarchy.Root.AddAppender(memory);

        hierarchy.Root.Level = Level.Info;
        hierarchy.Configured = true;
    }
  }
 }

è

      Now initialize your Log manger in App.xml.cs class and create your customized method for the record the log     files.


    private static readonly ILog Log = LogManager.GetLogger(typeof(App));

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        try
        {
            ConfigureLoggerManager();

            //Checking commandline argument in WPF application
            string commandLineArg = e.Args.Length == 1 ? e.Args[0].ToString() : "";
        }
        catch (Exception ex)
        {
            App.Log.Info( Enum.GetName( typeof( LogLevel ), LogLevel.FATAL ), ex );
            Shutdown();
        }
    }


    private void ConfigureLoggerManager()
    {
        LogHandler.Configure(); //Configure log4net 
        LOG("****************** Start My Application Logging  **********************");

        AppDomain.CurrentDomain.UnhandledException +=
        (sender, args) =>
        {
            LOG(LogLevel.FATAL, $"Exception from all threads in the AppDomain : {args.ExceptionObject}");
        };

        Dispatcher.UnhandledException +=
        (sender, args) =>
        {
            LOG(LogLevel.FATAL, $"Exception from a single specific UI dispatcher thread : {args.Exception}");
        };

        Application.Current.DispatcherUnhandledException +=
        (sender, args) =>
        {
            LOG(LogLevel.FATAL, $"Exception from the main UI dispatcher thread in WPF application : {args.Exception}");
        };

        TaskScheduler.UnobservedTaskException +=
        (sender, args) =>
        {
            LOG(LogLevel.FATAL, $"Exception from within each AppDomain that uses a task scheduler for asynchronous operations. : {args.Exception}");
        };
    }

    /// <summary>
    /// Record app maintaing log
    /// </summary>
    /// <param name="msg"></param>
    /// <param name="lineNumber"></param>
    /// <param name="caller"></param>
    public static void LOG(string msg, [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string caller = null)
    {
        Log.Info($"{caller} {lineNumber} : {msg}");
    }

    /// <summary>
    /// Record app maintaing log
    /// </summary>
    /// <param name="logLevel"></param>
    /// <param name="msg"></param>
    /// <param name="lineNumber"></param>
    /// <param name="caller"></param>
    public static void LOG(LogLevel logLevel, string msg, [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string caller = null)
    {
        string message = String.Empty;

        switch (logLevel)
        {
            case LogLevel.ERROR:
                message = "### ERROR ###";
                break;
            case LogLevel.DEBUG:
                message = "### DEBUG ###";
                break;
            case LogLevel.FATAL:
                message = "### FATAL ###";
                break;
            case LogLevel.INFO:
                message = "### INFO ###";
                break;
            case LogLevel.TRACE:
                message = "### TRACE ###";
                break;
            case LogLevel.WARN:
                message = "### WARN ###";
                break;
            default:
               evel == LogLevel.DEBUG)
        {
            Console.WriteLine(message);
        }                

        Log.Info(message);
    }

    /// <summary>
    /// Override OnExit to dispose other contoller classes if needed
    /// </summary>
    protected override void OnExit(ExitEventArgs e)
    {
        LOG(LogLevel.TRACE, "App exit has been called.");
        base.OnExit(e);
    }

        N.B: For any help please comments below. 

        Happy coding  :)