Adding Background Tasks to a Web App using C# and ASP.Net

I could have sworn that I posted something about this before, but apparently not. Anyway, a while back I was working on a project that required a number of tasks to occur that couldn't be accomplished easily in the normal post/response of a web request. Basically they were database and file updates that took about five to ten minutes to complete and needed to occur in 30 minute intervals. Now normally I would create a separate desktop app that would just run on the server that would handle the task but I didn't have rights to do that... So I had to build this as a set of background tasks within a web app. And I made it generic enough that I could package it up and release it. So let's look at some code:

    /// <summary>

    /// Manager for the task scheduler

    /// </summary>

    public class Manager:Singleton<Manager>

    {

        #region Constructor

 

        /// <summary>

        /// Constructor

        /// </summary>

        protected Manager()

            : base()

        {

            try

            {

                Config = Gestalt.Manager.Instance.GetConfigFile<Configuration.Configuration>("TaskScheduler");

                Workers = new List<Worker>();

                for (int x = 0; x < Config.NumberOfThreads; ++x)

                {

                    Worker TempWorker = new Worker("");

                    Workers.Add(TempWorker);

                }

            }

            catch { throw; }

        }

 

        #endregion

 

        #region Public Functions

 

        /// <summary>

        /// Starts the task manager

        /// </summary>

        /// <param name="TaskAssemblyLocation">Location of the task assembly</param>

        public void Start(string TaskAssemblyLocation)

        {

            try

            {

                Assembly TaskAssembly = Assembly.LoadFile(TaskAssemblyLocation);

                AddTasks(TaskAssembly);

                StartWorkers();

            }

            catch { throw; }

        }

 

        /// <summary>

        /// Starts the task manager

        /// </summary>

        /// <param name="TaskAssembly">The task assembly</param>

        public void Start(Assembly TaskAssembly)

        {

            try

            {

                AddTasks(TaskAssembly);

                StartWorkers();

            }

            catch { throw; }

        }

 

        /// <summary>

        /// Starts the task manager

        /// </summary>

        /// <param name="TaskAssemblies">The task assemblies</param>

        public void Start(List<Assembly> TaskAssemblies)

        {

            try

            {

                for (int x = 0; x < TaskAssemblies.Count; ++x)

                {

                    AddTasks(TaskAssemblies[x]);

                }

                StartWorkers();

            }

            catch { throw; }

        }

 

        /// <summary>

        /// Stops the tasks

        /// </summary>

        public void Stop()

        {

            try

            {

                for (int x = 0; x < Config.NumberOfThreads; ++x)

                {

                    Workers[x].Stop();

                }

            }

            catch { throw; }

        }

 

        #endregion

 

        #region Private Functions

 

        private void AddTasks(Assembly TaskAssembly)

        {

            try

            {

                List<Type> TaskTypes = Utilities.Reflection.GetTypes(TaskAssembly, "EchoNet.Task");

                for (int x = 0; x < TaskTypes.Count; )

                {

                    for (int y = 0; y < Workers.Count && x < TaskTypes.Count; ++y, ++x)

                    {

                        Task TempTask = (Task)TaskTypes[x].Assembly.CreateInstance(TaskTypes[x].FullName);

                        TempTask.Setup(TaskTypes[x].Name);

                        Workers[y].AddTask(TempTask);

                    }

                }

            }

            catch { throw; }

        }

 

        private void StartWorkers()

        {

            try

            {

                for (int x = 0; x < Config.NumberOfThreads; ++x)

                {

                    Workers[x].Start();

                }

            }

            catch { throw; }

        }

 

        #endregion

 

        #region Private Properties

 

        private List<Worker> Workers { get; set; }

        private Configuration.Configuration Config { get; set; }

        #endregion

    }

The code above is the manager for everything, it handles the basic creation of worker threads, setting up the individual tasks, etc. You'll notice that it uses a Singleton class as a base class. This is just a helper class from my utility library. The other thing to note is it uses Gestalt.Net for configuration, so the Configuration class is nothing but a stub and not that interesting. Well, it does set up the number of threads to set up. But to be honest, that's not that interesting. Other than that the only thing that the manager touches are the Worker class and Task class, so let's look at the Worker class:

    public class Worker : Worker<bool, string>

    {

        #region Constructor

 

        /// <summary>

        /// Constructor

        /// </summary>

        /// <param name="Params">Not used</param>

        public Worker(string Params)

            : base(Params)

        {

            try

            {

                Tasks = new List<Task>();

            }

            catch { throw; }

        }

 

        #endregion

 

        #region Functions

 

        /// <summary>

        /// Adds a task to 

        /// </summary>

        /// <param name="Task">Task to add</param>

        public void AddTask(Task Task)

        {

            try

            {

                lock (Tasks)

                {

                    Tasks.Add(Task);

                }

            }

            catch { throw; }

        }

 

        #endregion

 

        #region Overridden Functions

 

        protected override bool Work(string Params)

        {

            try

            {

                while (true)

                {

                    if (Stopping)

                        return true;

                    lock (Tasks)

                    {

                        for (int x = 0; x < Tasks.Count; ++x)

                        {

                            if (Tasks[x].NextRunTime < DateTime.Now)

                            {

                                Tasks[x].DoWork();

                                Tasks[x].UpdateTime(true);

                            }

                        }

                    }

                    Sleep(1000);

                }

            }

            catch { throw; }

        }

 

        #endregion

 

        #region Private Properties

 

        private List<Task> Tasks { get; set; }

 

        #endregion

    }

The Worker class is the actual background thread class. Once again it uses a base class helper from my utility library. All it does is wraps the threading code (starting a thread, stopping it, etc.). The Worker thread simply holds the individual tasks, sees if they should be called, and if need be runs them. The task class is where the actual interesting code occurs:

    public abstract class Task:ITask

    {

        #region Constructor

 

        /// <summary>

        /// Constructor

        /// </summary>

        public Task()

        {

        }

 

        #endregion

 

        #region Abstract Functions

 

        public abstract void DoWork();

 

        #endregion

 

        #region Internal Functions

 

        internal void Setup(string ClassName)

        {

            try

            {

                Config = Gestalt.Manager.Instance.GetConfigFile<TaskConfiguration>(ClassName);

                NextRunTime = Config.NextRunTime;

                if (Config.Frequency != EchoNet.Enum.RunTime.Once)

                {

                    while (NextRunTime < DateTime.Now || NextRunTime < Config.Start)

                    {

                        UpdateTime(false);

                    }

                }

                else

                {

                    if (NextRunTime < DateTime.Now)

                        NextRunTime = DateTime.Now;

                    if (NextRunTime < Config.Start)

                        NextRunTime = Config.Start;

                }

                if (NextRunTime > Config.End)

                    NextRunTime = DateTime.MaxValue;

            }

            catch { throw; }

        }

 

        internal void UpdateTime(bool Save)

        {

            try

            {

                if (Config.Frequency == EchoNet.Enum.RunTime.Hourly)

                {

                    NextRunTime = NextRunTime.AddHours(1.0d);

                }

                else if (Config.Frequency == EchoNet.Enum.RunTime.Daily)

                {

                    NextRunTime = NextRunTime.AddDays(1.0d);

                }

                else if (Config.Frequency == EchoNet.Enum.RunTime.Monthly)

                {

                    NextRunTime = NextRunTime.AddMonths(1);

                }

                else if (Config.Frequency == EchoNet.Enum.RunTime.Yearly)

                {

                    NextRunTime = NextRunTime.AddYears(1);

                }

                else if (Config.Frequency == EchoNet.Enum.RunTime.Weekly)

                {

                    NextRunTime = NextRunTime.AddDays(7.0d);

                }

                else if (Config.Frequency == EchoNet.Enum.RunTime.Once)

                {

                    NextRunTime = DateTime.MaxValue;

                }

                if (Save)

                {

                    Config.NextRunTime = NextRunTime;

                    Config.Save();

                }

            }

            catch { throw; }

        }

 

        #endregion

 

        #region Properties

 

        protected TaskConfiguration Config { get; set; }

        internal DateTime NextRunTime { get; set; }

 

        #endregion

    }

OK, I lied, it's not that interesting. The Task class is nothing more than a base class and contains only a function for setup (figuring out the next time to run and getting the config info), an abstract DoWork function, and an UpdateTime function (that figures out the next time to run). And the interface that uses, just sets up the DoWork function. Now I did mention that the task loads config information. This, once again, uses Gestalt.Net to load/save our info. The base class for it looks like this:

    public class TaskConfiguration:Gestalt.Config<TaskConfiguration>

    {

        #region Properties

 

        /// <summary>

        /// Frequency the task is run

        /// </summary>

        public virtual RunTime Frequency { get; set; }

 

        /// <summary>

        /// Start date

        /// </summary>

        public virtual DateTime Start { get; set; }

 

        /// <summary>

        /// End date

        /// </summary>

        public virtual DateTime End { get; set; }

 

        /// <summary>

        /// Next run time

        /// </summary>

        public virtual DateTime NextRunTime { get; set; }

 

        #endregion

    }

That's it. All the system cares about is if this has a start/end time period, the next time it should run (which it updates itself), and the frequency it should run (hourly, daily, weekly, etc.). And that's all there is to the entire system, It's very basic but surprisingly flexible. As an example, we can set up a task by simply doing the following:

    public class Task1:EchoNet.Task

    {

        public override void DoWork()

        {

            try

            {

                Utilities.FileManager.SaveFile("This is a test", @"C:\MyFiles\File1.txt");

            }

            catch { throw; }

        }

    }

Or at least that's it for the task itself. We still need to set up the config files:

    public class TaskSchedulerConfig:EchoNet.Configuration.Configuration

    {

    }

 

    [Gestalt.Attributes.Config(Name = "Task1")]

    public class Task1Config : EchoNet.Configuration.TaskConfiguration

    {

        public Task1Config()

        {

            NextRunTime = new DateTime(2010, 5, 20, 11, 11, 0);

            Start = new DateTime(1900, 1, 1);

            End = DateTime.MaxValue;

            Frequency = EchoNet.Enum.RunTime.Hourly;

        }

        protected override string ConfigFileLocation { get { return @"C:\TestWebApp\App_Data\Task.config"; } }

    }

You may have noticed that the first item is simply an empty class. It inherits from our main config file for the task manager and just lets the standard 4 threads be the default. In the second class, we're inheriting from the individual task's config object. In this case, because we never named the base class, we have to set the name in the attribute (this associates this config with the Task1 class. We then set some defaults in the constructor and override the ConfigFileLocation property to set a path for the config to be saved (so we can edit it outside of the code if need be). That's it to the configuration, from here all we have to do is actually start it up:

            Gestalt.Manager.Instance.RegisterConfigFile(typeof(Default).Assembly);

            EchoNet.Manager.Instance.Start(typeof(Default).Assembly);

The two function calls are rather simple. First we start Gestalt.Net, telling it where our config classes are and then the same thing with the task manager (EchoNet is the namespace for it). In a web app this would go in the global Application_Start function. And finally we need to stop it:

EchoNet.Manager.Instance.Stop();

And this would go in your Application_Stop function. Anyway, that's it. The start would load up the tasks, start the threads, and let things run. The Stop would simply stop the threads and shut things down. Really simple and rather effective, so take a look, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 5/28/2010 at 8:30 AM
Tags: , , ,
Categories: ASP.Net | C#
Post Information: Permalink | Comments (1) | Post RSSRSS comment feed

Scanning a Network for Computers in C#

I was tasked recently with creating an app that would scan a network for any and all computers in it using C#. Well not exactly... It was more anything in our domain, but for the most part that should be anything on our network. Anyway, it turns out that it's extremely simple and really only took 3 steps:

  • Get the list of computer names from Active Directory
  • Do a DNS lookup on those names
  • And report the results

That's all that I needed to do. It was especially easy because of some helper classes that I had laying around to deal with AD. A while back I posted two classes to help with AD. They're a bit out of date as I've added extra functionality since then, the newer version of the classes looks like this:

/*
Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/


#region Usings
using System;
using System.Collections.Generic;
using System.DirectoryServices;
#endregion

namespace Utilities.LDAP
{
    /// <summary>
    /// Class for helping with AD
    /// </summary>
    public class Directory:IDisposable
    {
        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="UserName">User name used to log in</param>
        /// <param name="Password">Password used to log in</param>
        /// <param name="Path">Path of the LDAP server</param>
        /// <param name="Query">Query to use in the search</param>
        public Directory(string Query,string UserName, string Password, string Path)
        {
            try
            {
                Entry = new DirectoryEntry(Path, UserName, Password, AuthenticationTypes.Secure);
                this.Path = Path;
                this.UserName = UserName;
                this.Password = Password;
                this.Query = Query;
                Searcher = new DirectorySearcher(Entry);
                Searcher.Filter = Query;
                Searcher.PageSize = 1000;
            }
            catch { throw; }
        }
        #endregion

        #region Public Functions
        /// <summary>
        /// Finds a user by his user name
        /// </summary>
        /// <param name="UserName">User name to search by</param>
        /// <returns>The user's entry</returns>
        public Entry FindUserByUserName(string UserName)
        {
            try
            {
                List<Entry> Entries = FindUsers("samAccountName=" + UserName);
                if (Entries.Count > 0)
                {
                    return Entries[0];
                }
                return null;
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all active users
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all active users' entries</returns>
        public List<Entry> FindActiveUsers(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&((userAccountControl:1.2.840.113556.1.4.803:=512)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(cn=*$)))({0}))", Filter);
                return FindUsers(Filter);
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all users
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all users meeting the specified Filter</returns>
        public List<Entry> FindUsers(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&(objectClass=User)(objectCategory=Person)({0}))", Filter);
                Searcher.Filter = Filter;
                return FindAll();
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all computers
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all computers meeting the specified Filter</returns>
        public List<Entry> FindComputers(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&(objectClass=computer)({0}))", Filter);
                Searcher.Filter = Filter;
                return FindAll();
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all active users and groups
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all active groups' entries</returns>
        public List<Entry> FindActiveUsersAndGroups(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&((userAccountControl:1.2.840.113556.1.4.803:=512)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(cn=*$)))({0}))", Filter);
                return FindUsersAndGroups(Filter);
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all users and groups
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all users and groups meeting the specified Filter</returns>
        public List<Entry> FindUsersAndGroups(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&(|(&(objectClass=Group)(objectCategory=Group))(&(objectClass=User)(objectCategory=Person)))({0}))", Filter);
                Searcher.Filter = Filter;
                return FindAll();
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all active groups
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all active groups' entries</returns>
        public List<Entry> FindActiveGroups(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&((userAccountControl:1.2.840.113556.1.4.803:=512)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(cn=*$)))({0}))", Filter);
                return FindGroups(Filter);
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds all groups
        /// </summary>
        /// <param name="Filter">Filter used to modify the query</param>
        /// <param name="args">Additional arguments (used in string formatting</param>
        /// <returns>A list of all groups meeting the specified Filter</returns>
        public List<Entry> FindGroups(string Filter, params object[] args)
        {
            try
            {
                Filter = string.Format(Filter, args);
                Filter = string.Format("(&(objectClass=Group)(objectCategory=Group)({0}))", Filter);
                Searcher.Filter = Filter;
                return FindAll();
            }
            catch { throw; }
        }

        /// <summary>
        /// Returns a group's list of members
        /// </summary>
        /// <param name="GroupName">The group's name</param>
        /// <returns>A list of the members</returns>
        public List<Utilities.LDAP.Entry> FindActiveGroupMembers(string GroupName)
        {
            try
            {
                List<Utilities.LDAP.Entry> Entries = this.FindGroups("cn=" + GroupName);
                if (Entries.Count < 1)
                    return new List<Utilities.LDAP.Entry>();

                return this.FindActiveUsersAndGroups("memberOf=" + Entries[0].DistinguishedName);
            }
            catch
            {
                return new List<Utilities.LDAP.Entry>();
            }
        }

        /// <summary>
        /// Finds all entries that match the query
        /// </summary>
        /// <returns>A list of all entries that match the query</returns>
        public List<Entry> FindAll()
        {
            try
            {
                List<Entry> ReturnedResults = new List<Entry>();
                SearchResultCollection Results = Searcher.FindAll();
                foreach (SearchResult Result in Results)
                {
                    ReturnedResults.Add(new Entry(Result.GetDirectoryEntry()));
                }
                Results.Dispose();
                return ReturnedResults;
            }
            catch { throw; }
        }

        /// <summary>
        /// Finds one entry that matches the query
        /// </summary>
        /// <returns>A single entry matching the query</returns>
        public Entry FindOne()
        {
            try
            {
                SearchResult Result = Searcher.FindOne();
                return new Entry(Result.GetDirectoryEntry());
            }
            catch { throw; }
        }

        /// <summary>
        /// Closes the directory
        /// </summary>
        public void Close()
        {
            try
            {
                Entry.Close();
            }
            catch { throw; }
        }

        /// <summary>
        /// Checks to see if the person was authenticated
        /// </summary>
        /// <returns>true if they were authenticated properly, false otherwise</returns>
        public bool Authenticate()
        {
            try
            {
                if (!Entry.Guid.ToString().ToLower().Trim().Equals(""))
                {
                    return true;
                }
            }
            catch
            {
            }
            return false;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Path of the server
        /// </summary>
        public string Path
        {
            get { return _Path; }
            set
            {
                _Path = value;
                try
                {
                    if (Entry != null)
                    {
                        Entry.Close();
                    }
                    Entry = new DirectoryEntry(_Path, _UserName, _Password, AuthenticationTypes.Secure);
                    Searcher = new DirectorySearcher(Entry);
                    Searcher.Filter = Query;
                    Searcher.PageSize = 1000;
                }
                catch { throw; }
            }
        }

        /// <summary>
        /// User name used to log in
        /// </summary>
        public string UserName
        {
            get { return _UserName; }
            set
            {
                _UserName = value;
                try
                {
                    if (Entry != null)
                    {
                        Entry.Close();
                    }
                    Entry = new DirectoryEntry(_Path, _UserName, _Password, AuthenticationTypes.Secure);
                    Searcher = new DirectorySearcher(Entry);
                    Searcher.Filter = Query;
                    Searcher.PageSize = 1000;
                }
                catch { throw; }
            }
        }

        /// <summary>
        /// Password used to log in
        /// </summary>
        public string Password
        {
            get { return _Password; }
            set
            {
                _Password = value;
                try
                {
                    if (Entry != null)
                    {
                        Entry.Close();
                    }
                    Entry = new DirectoryEntry(_Path, _UserName, _Password, AuthenticationTypes.Secure);
                    Searcher = new DirectorySearcher(Entry);
                    Searcher.Filter = Query;
                    Searcher.PageSize = 1000;
                }
                catch { throw; }
            }
        }

        /// <summary>
        /// The query that is being made
        /// </summary>
        public string Query
        {
            get { return _Query; }
            set
            {
                _Query = value;
                Searcher.Filter = _Query;
            }
        }

        /// <summary>
        /// Decides what to sort the information by
        /// </summary>
        public string SortBy
        {
            get { return _SortBy; }
            set
            {
                _SortBy = value;
                Searcher.Sort.PropertyName = _SortBy;
                Searcher.Sort.Direction = SortDirection.Ascending;
            }
        }
        #endregion

        #region Private Variables
        private string _Path = "";
        private string _UserName = "";
        private string _Password = "";
        private DirectoryEntry Entry = null;
        private string _Query = "";
        private DirectorySearcher Searcher = null;
        private string _SortBy = "";
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (Entry != null)
            {
                Entry.Dispose();
                Entry = null;
            }
            if (Searcher != null)
            {
                Searcher.Dispose();
                Searcher = null;
            }
        }

        #endregion
    }
}

/*
Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/


#region Usings
using System;
using System.Collections.Generic;
using System.DirectoryServices;
#endregion

namespace Utilities.LDAP
{
    /// <summary>
    /// Directory entry class
    /// </summary>
    public class Entry:IDisposable
    {
        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="DirectoryEntry">Directory entry for the item</param>
        public Entry(DirectoryEntry DirectoryEntry)
        {
            this._DirectoryEntry = DirectoryEntry;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Actual base directory entry
        /// </summary>
        public DirectoryEntry DirectoryEntry
        {
            get { return _DirectoryEntry; }
            set { _DirectoryEntry = value; }
        }

        /// <summary>
        /// Email property for this entry
        /// </summary>
        public string Email
        {
            get { return (string)GetValue("mail"); }
            set { SetValue("mail", value); }
        }

        /// <summary>
        /// distinguished name property for this entry
        /// </summary>
        public string DistinguishedName
        {
            get { return (string)GetValue("distinguishedname"); }
            set { SetValue("distinguishedname", value); }
        }

        /// <summary>
        /// country code property for this entry
        /// </summary>
        public string CountryCode
        {
            get { return (string)GetValue("countrycode"); }
            set { SetValue("countrycode", value); }
        }

        /// <summary>
        /// company property for this entry
        /// </summary>
        public string Company
        {
            get { return (string)GetValue("company"); }
            set { SetValue("company", value); }
        }

        /// <summary>
        /// MemberOf property for this entry
        /// </summary>
        public List<string> MemberOf
        {
            get
            {
                List<string> Values = new List<string>();
                PropertyValueCollection Collection = DirectoryEntry.Properties["memberof"];
                foreach (object Item in Collection)
                {
                    Values.Add((string)Item);
                }
                return Values;
            }
        }

        /// <summary>
        /// display name property for this entry
        /// </summary>
        public string DisplayName
        {
            get { return (string)GetValue("displayname"); }
            set { SetValue("displayname", value); }
        }

        /// <summary>
        /// initials property for this entry
        /// </summary>
        public string Initials
        {
            get { return (string)GetValue("initials"); }
            set { SetValue("initials", value); }
        }

        /// <summary>
        /// title property for this entry
        /// </summary>
        public string Title
        {
            get { return (string)GetValue("title"); }
            set { SetValue("title", value); }
        }

        /// <summary>
        /// samaccountname property for this entry
        /// </summary>
        public string SamAccountName
        {
            get { return (string)GetValue("samaccountname"); }
            set { SetValue("samaccountname", value); }
        }

        /// <summary>
        /// givenname property for this entry
        /// </summary>
        public string GivenName
        {
            get { return (string)GetValue("givenname"); }
            set { SetValue("givenname", value); }
        }

        /// <summary>
        /// cn property for this entry
        /// </summary>
        public string CN
        {
            get { return (string)GetValue("cn"); }
            set { SetValue("cn", value); }
        }

        /// <summary>
        /// name property for this entry
        /// </summary>
        public string Name
        {
            get { return (string)GetValue("name"); }
            set { SetValue("name", value); }
        }

        /// <summary>
        /// office property for this entry
        /// </summary>
        public string Office
        {
            get { return (string)GetValue("physicaldeliveryofficename"); }
            set { SetValue("physicaldeliveryofficename", value); }
        }

        /// <summary>
        /// telephone number property for this entry
        /// </summary>
        public string TelephoneNumber
        {
            get { return (string)GetValue("telephonenumber"); }
            set { SetValue("telephonenumber", value); }
        }
        #endregion

        #region Public Functions
        /// <summary>
        /// Saves any changes that have been made
        /// </summary>
        public void Save()
        {
            try
            {
                _DirectoryEntry.CommitChanges();
            }
            catch { throw; }
        }

        /// <summary>
        /// Gets a value from the entry
        /// </summary>
        /// <param name="Property">Property you want the information about</param>
        /// <returns>an object containing the property's information</returns>
        public object GetValue(string Property)
        {
            try
            {
                PropertyValueCollection Collection = DirectoryEntry.Properties[Property];
                if (Collection != null)
                {
                    return Collection.Value;
                }
                return null;
            }
            catch { throw; }
        }

        /// <summary>
        /// Sets a property of the entry to a specific value
        /// </summary>
        /// <param name="Property">Property of the entry to set</param>
        /// <param name="Value">Value to set the property to</param>
        public void SetValue(string Property,object Value)
        {
            try
            {
                PropertyValueCollection Collection = DirectoryEntry.Properties[Property];
                if (Collection != null)
                {
                    Collection.Value = Value;
                }
            }
            catch { throw; }
        }
        #endregion

        #region Private Variables
        private DirectoryEntry _DirectoryEntry;
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (_DirectoryEntry != null)
            {
                _DirectoryEntry.Dispose();
                _DirectoryEntry = null;
            }
        }

        #endregion
    }
}

The two classes are Directory and Entry. Directory is what is used to connect to Active Directory and the Entry class actually holds each entry's info (also note that they are IDisposable, so make sure to dispose of them when done). Anyway, most of the code is of little use in this instance. I mean the directory class has the ability to search for users, groups, etc. There's really only one function of much use: FindComputers. This function allows us to search for any computer that fits specific criteria that we pass into it. Or in our instance, anything in our domain:

    using(Utilities.LDAP.Directory TempDirectory = new Utilities.LDAP.Directory("", "USERNAME", "PASSWORD", "LDAPLOCATION"))
    {
        List<Utilities.LDAP.Entry> Entries = TempDirectory.FindComputers("name=*");
    }

The code above uses the Directory class to find all computers within AD, giving us back a list of those entries. This isn't the most useful code at this point but it gets us pass step one. We have our list of machines. So on to step 2, doing the DNS lookup. One of the great things about .Net is that a lot of functionality is already there for you. In this case all we have to do is take the name of each entry and feed it to Dns.GetHostEntry (which is in the System.Net namespace). It in turn gives us an IPAddress class, which holds every address that the machine is using (IPv4, IPv6, etc.). However, if you've tried it in the past you will notice that if it can't find the machine, it takes forever to run (well 14 seconds anyway). And if you're searching for 5000 machines and any decent number of them are offline, that is going to, well, suck. There are options, you can either modify your registry or you can use multithreading to get the job done. I went with the second option:

                    using(Utilities.LDAP.Directory TempDirectory = new Utilities.LDAP.Directory("", "USERNAME", "PASSWORD", "LDAPLOCATION"))
                    {
                        List<Utilities.LDAP.Entry> Entries = TempDirectory.FindComputers("name=*");
                        for (int x = 0; x < Entries.Count; ++x)
                        {
                            try
                            {
                                using (Utilities.LDAP.Entry Entry = Entries[x])
                                {
                                    DNSSearch TempWorker = new DNSSearch(Entry.Name);
                                    TempWorker.Finished = new EventHandler<Utilities.Events.EventArgs.OnEndEventArgs>(FinishedSearch);
                                    TempWorker.Start();
                                }
                            }
                            catch { }
                        }
                    }

The code above just adds to our AD search, adding a loop, creating a DNSSearch class for each item, passing in the name, setting an eventhandler, and calling start. The DNSSearch class looks like this:

    public class DNSSearch:Worker<Machine,string>
    {
        public DNSSearch(string Params)
            : base(Params)
        {
        }

        protected override Machine Work(string Params)
        {
            Machine TempMachine = new Machine();
            TempMachine.Name = Params;
            TempMachine.Addresses = new List<string>();
            try
            {
                IPHostEntry HostEntry = Dns.GetHostEntry(Params);
                foreach (IPAddress Address in HostEntry.AddressList)
                {
                    TempMachine.Addresses.Add(Address.AddressFamily + ": " + Address.ToString());
                }
            }
            catch { }
            return TempMachine;
        }
    }

The class inherits from a Worker class, which I showed here. Although there are some slight updates to it, so you may want to get the latest version from my utility library here. Anyway, the base class wants to know what we are getting as input and what to expect as output. In our case it's expecting a string for input and a class called Machine for output. The machine class looks like this:

    public class Machine
    {
        public string Name { get; set; }
        public List<string> Addresses { get; set; }

    }

It's really just a holder for the info. So when we call Start on the DNSSearch object, back in the original function, the base class starts the thread and calls Work in DNSSearch. The Work function creates a Machine object, sets the name of the machine and calls Dns.GetHostEntry, filling out the address info for us. When it's done it returns the machine object. This gets sent to our Finished EventHandler. In the original function we set the Finished event to a function called FinishedSearch:

        public static List<Machine> MachineList { get; set; }

        public static void FinishedSearch(object sender, Utilities.Events.EventArgs.OnEndEventArgs e)
        {
            lock (typeof(List<Machine>))
            {
                if (MachineList == null)
                    MachineList = new List<Machine>();
                MachineList.Add((Machine)e.Content);
            }
        }

The function simply locks our list of machines, sets it up if need be and adds our machine object to said list and then unlocks it. So how do we get this info back to our form, or web page, or whatever? Simple:

        public static List<Machine> SearchNetwork()
        {
            try
            {
                if (MachineList == null)
                {
                    using(Utilities.LDAP.Directory TempDirectory = new Utilities.LDAP.Directory("", "USERNAME", "PASSWORD", "LDAPLOCATION"))
                    {
                        List<Utilities.LDAP.Entry> Entries = Temp.FindComputers("name=*");
                        for (int x = 0; x < Entries.Count; ++x)
                        {
                            try
                            {
                                using (Utilities.LDAP.Entry Entry = Entries[x])
                                {
                                    DNSSearch TempWorker = new DNSSearch(Entry.Name);
                                    TempWorker.Finished = new EventHandler<Utilities.Events.EventArgs.OnEndEventArgs>(FinishedSearch);
                                    TempWorker.Start();
                                }
                            }
                            catch { }
                        }
                    }
                }
                return MachineList;
            }
            catch { throw; }
        }

By returning the MachineList, which was static, we have access to it as it's being updated. So we can do something like this in a function:

                List<Machine> Machines = SearchNetwork();
                lock (typeof(List<Machine>))
                {
                    foreach (Machine TempMachine in Machines)
                    {
                        if (TempMachine.Addresses.Count > 0)
                        {
                            //The machine is online
                        }
                        else
                        {
                            //The machine is offline
                        }
                    }
                }

Note that we have to lock it because the threads are going to continue to update our list, but it doesn't stop us from locking it and using it ourselves. An even better idea might be to use a list with events telling you when something is added... Sort of like the code I created here. Using something like that, you could have a list on a form update in real time as items are added to the list. Anyway, I know I threw a bunch of code up here but it's quite simple once you get down to it. Anyway, try it out, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 5/3/2010 at 2:50 PM
Tags: , , , ,
Categories: C#
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

Multithreading in C#

Multithreading seems to be a hot topic of discussion for a lot of programmers. It also seems to be one of the more difficult concepts to grasp (or even to get started with). Luckily in C#/.Net it's pretty simple when compared to some other languages that I've dealt with. In C# all you need to do is make a call to ThreadPool or create a Thread class and you're done... Sort of anyway... However there is a lack of certain functionality that you may want to add. In my case I needed a way of being notified of certain events so I created a helper base class that is reusable.

/// <summary>
    /// Worker class used in multi threading
    /// </summary>
    /// <typeparam name="ResultType">Result type</typeparam>
    /// <typeparam name="InputParams">The input parameter type</typeparam>
    public abstract class Worker<ResultType,InputParams>
    {
        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="Params">Parameters used in the function</param>
        protected Worker(InputParams Params)
        {
            this.Params = Params;
            this.WorkerThread = new Thread(DoWork);
        }

        #endregion

        #region Protected Abstract Functions

        /// <summary>
        /// Function that actually does the work
        /// </summary>
        /// <param name="Params">Parameter used by the function</param>
        /// <returns>The result of the function</returns>
        protected abstract ResultType Work(InputParams Params);

        #endregion

        #region Public Functions

        /// <summary>
        /// Starts the thread
        /// </summary>
        public void Start()
        {
            this.WorkerThread.Start();
        }

        /// <summary>
        /// Stops the thread and waits for it to finish
        /// </summary>
        public void Stop()
        {
            if (WorkerThread != null && WorkerThread.IsAlive)
            {
                this.WorkerThread.Join();
                this.WorkerThread = null;
            }
            OnEndEventArgs EndEvents = new OnEndEventArgs();
            EndEvents.Content = Result;
            EventHelper.Raise<OnEndEventArgs>(Finished, this, EndEvents);
        }

        #endregion

        #region Private Functions

        /// <summary>
        /// Wrapper for the function that actually does the work.
        /// Calls the start and finished events as well as stores
        /// the result for use within the class.
        /// </summary>
        private void DoWork()
        {
            EventHelper.Raise<OnStartEventArgs>(Started, this, new OnStartEventArgs());

            Result = Work(this.Params);

            OnEndEventArgs EndEvents = new OnEndEventArgs();
            EndEvents.Content = Result;
            EventHelper.Raise<OnEndEventArgs>(Finished, this, EndEvents);
        }

        #endregion

        #region Events

        /// <summary>
        /// Called when the thread is finished
        /// </summary>
        public EventHandler<OnEndEventArgs> Finished { get; set; }

        /// <summary>
        /// Called when the thread is started
        /// </summary>
        public EventHandler<OnStartEventArgs> Started { get; set; }

        /// <summary>
        /// Can be used by the worker function to indicate progress
        /// </summary>
        public EventHandler<ChangedEventArgs> Updated { get; set; }

        /// <summary>
        /// Can be used by the worker function to indicate an exception has occurred
        /// </summary>
        public EventHandler<OnErrorEventArgs> Exception { get; set; }

        #endregion

        #region Properties

        /// <summary>
        /// Indicates whether or not the thread is stopped/started
        /// </summary>
        public bool Stopped
        {
            get
            {
                if (WorkerThread != null && WorkerThread.IsAlive)
                    return false;
                return true;
            }
        }

        /// <summary>
        /// The result (can be used by the class that inherits from this base class
        /// </summary>
        protected ResultType Result { get; set; }

        /// <summary>
        /// Parameters used in the function
        /// </summary>
        private InputParams Params = default(InputParams);

        /// <summary>
        /// The thread used
        /// </summary>
        private Thread WorkerThread=null;

        #endregion
    }

The code should be fairly easy to follow. You use this as a base class adding the result and parameter type. There is one function that you need to override. That's it really, well with one exception. The class has events that it uses (which uses EventArgs that are within my utility library). It has Started and Finished which is called when the thread is started and stopped respectively. The Finished event will contain the results from the function within the Content object.There are also events for Updated and Exception which are optional. The Updated event would be used to update the rest of the world on the thread's progress. The Exception event on the other hand would be used when the thread has an exception, letting everyone else know. Anyway, I hope that the code can be of use to someone. So try it out, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 10/7/2009 at 2:02 PM
Tags: , ,
Categories: C#
Post Information: Permalink | Comments (1) | Post RSSRSS comment feed