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

Enabling JSONP in WCF

Before this week I never really bothered much with WCF. My reasons were two fold, one it always seemed like a pain to configure and the second reason was the fact that it was overkill for the projects that I was working on. That being said, I finally hit a project where it made sense to use it. I needed things like the ability to easily do JSON and XML requests, access through more than just a web request, etc. Anyway, I set myself up with a new project space, followed a couple tutorials, figured out how to host the services in a web app seperate from my other apps so that the services would be centralized, etc. Basically everything was going fine... Well until I tried to make my requests from web app 1 (the project that needed access to the service) to web app 2 (the app actually holding the service). It kept coming back with a failure message.

How could this be? I set up my service contract correctly, my function even had the necessary WebGet attribute:

[WebGet(ResponseFormat=WebMessageFormat.Json)]

That one attribute attached to my function should set it up for JSON. I had my service reference set up in my script manager. I even had the WCF Service file set up correctly using the WebScriptServiceHostFactory:

<%@ ServiceHost Service="Services.MyService" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" %>

According to the documentation I wouldn't even need to bother with configuring the config file. I could strip out the system.serviceModel portion and that factory should handle things for me. Hell if I called it in the browser, it passed back the JSON correctly so what the hell was going on? JSONP... That's right, in all of my haste I had completely forgotten about JSONP (JSON with Padding). JSONP, in a very basic description, is a usage pattern that allows a secondary server (not the primary server) to use JSON in a meaningful way. In otherwords think cross site scripting, which is exactly what I was trying to do. My calls from Sys.Net.WebServiceProxy.invoke (long story as to why I'm not able to simply use jQuery) were requesting JSONP to be sent back and were failing when it didn't receive it. So I went searching online as to how to enable it in WCF.

Apparently I'm the only person out there who has decided to work with .Net 4.0 on anything and especially WCF. Well either that or no one wanted to talk about it. Anyway, after about 2 hours of digging, I finally found some semblence of help on this subject. You see prior to 4.0, the way you were suppose to set up JSONP was that you had to define a number of classes including a message encoder, a message encoder factory, an operationbehaviorattribute, etc. It was a pain and every post about it said just wait until 4 comes out as it will have it by default... Well it does:

<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="false" targetFramework="4.0" />
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="webHttpBehavior">
          <webHttp />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <bindings>
      <webHttpBinding>
        <binding name="webHttpBindingJsonP" crossDomainScriptAccessEnabled="true"></binding>
      </webHttpBinding>
    </bindings>
    <services>
      <service name="Services.MyService">
        <clear />
        <endpoint address="" binding="webHttpBinding" bindingConfiguration="webHttpBindingJsonP"
                  contract="Services.Interfaces.IMyService" behaviorConfiguration="webHttpBehavior" />
        <host>
          <baseAddresses>
            <add baseAddress="http://SERVICELOCATION" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

The bit above is the web.config file for the service web app. It turns out the whole Factory attribute in the svc file is kind of lie, you do indeed need the system.serviceModel section. Anyway, in it we define a endpoint behavior for webHttpBehavior and a binding that uses webHttpBinding called webHttpBindingWithJsonP. The special thing about the binding is the crossDomainScriptAccessEnabled attribute is set to true. This one item enables it. Past that we just set up our service and an endpoint using the binding that we've created and that's it (and of course set up the base address). Two hours and that's all I needed to do. Anyway, I hope it helps someone out there so you don't have to deal with the headache that I've had today. So give it a try, 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: 4/23/2010 at 2:51 PM
Tags: , ,
Categories: AJAX | ASP.Net
Post Information: Permalink | Comments (3) | Post RSSRSS comment feed

Programming for the Cisco Phone Using .Net

Between family visiting, having people move, and "disasters" at work, working on anything of my own has taken a back seat. That being said, I did have enough time to write some code to help with a project I was working on though. At work we ended up getting these Cisco phones. And since you can program for them, I was told I had a new project (I believe the exact quote from my boss was "Figure out what the hell we can do with it."). Anyway, I downloaded Cisco's SDK and looked through their examples... Not a single ASP.Net app... Some ASP and Java apps, but nothing in the .Net realm. So I created my own set of classes to help me out:

/*
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.Linq;
using System.Text;
using Utilities.Cisco.Interfaces;
#endregion

namespace Utilities.Cisco
{
    /// <summary>
    /// Text class
    /// </summary>
    public class Text:IDisplay
    {
        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        public Text()
        {
            SoftKeys = new List<SoftKeyItem>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Title
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// Prompt
        /// </summary>
        public string Prompt { get; set; }

        /// <summary>
        /// Text
        /// </summary>
        public string Content { get; set; }

        /// <summary>
        /// Softkey list
        /// </summary>
        public List<SoftKeyItem> SoftKeys { get; set; }

        #endregion

        #region Public Overridden Functions

        public override string ToString()
        {
            StringBuilder Builder = new StringBuilder();
            Builder.Append("<CiscoIPPhoneText><Title>").Append(Title).Append("</Title><Prompt>")
                .Append(Prompt).Append("</Prompt><Text>").Append(Content).Append("</Text>");
            foreach (SoftKeyItem Item in SoftKeys)
            {
                Builder.Append(Item.ToString());
            }
            Builder.Append("</CiscoIPPhoneText>");
            return Builder.ToString();
        }

        #endregion
    }
}

It turns out that all the Cisco phone is looking for is an XML file. Each type of display item has it's own format (a listing for each of them can be found here). That being said, the code above creates text, which can be displayed on the screen. It inherits from the IDisplay interface, which simply holds a list of soft keys. Soft keys is basically an input from the user:

/*
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.Linq;
using System.Text;
#endregion

namespace Utilities.Cisco
{
    /// <summary>
    /// Softkey class
    /// </summary>
    public class SoftKeyItem
    {
        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        public SoftKeyItem()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Name of the softkey
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// URL action for release event
        /// </summary>
        public string URL { get; set; }

        /// <summary>
        /// URL action for press event
        /// </summary>
        public string URLDown { get; set; }

        /// <summary>
        /// position of softkey
        /// </summary>
        public int Position { get; set; }

        #endregion

        #region Overridden Functions

        public override string ToString()
        {
            StringBuilder Builder = new StringBuilder();
            Builder.Append("<SoftKeyItem><Name>").Append(Name).Append("</Name><URL>").Append(URL).Append("</URL><URLDown>")
                .Append(URLDown).Append("</URLDown><Position>").Append(Position).Append("</Position></SoftKeyItem>");
            return Builder.ToString();
        }

        #endregion
    }
}

Basically a soft key has a name, an action URL when pressed or an action URL when released/clicked, and a position within the list of softkeys. And they're able to be used in conjunction with any display item. Speaking of display items, there are a number of them and depending on the model of your phone, different ones are useable. For instance, I have a 7965 at work. As such I have access to pretty much everything, but earlier phones may have less features available. Anyway, after learning all of this I decided the best thing to do was to create a basic weather app. It's simple enough but for some reason people get excited about things like that. So I created a new web app, created a new webpage, and sstarted working:

namespace Cisco
{
    public partial class _Default : System.Web.UI.Page
    {
        private static string ServerImage="ServerImagePath";
        private static string ServerIP = "ServerPath";
        protected void Page_Load(object sender, EventArgs e)
        {
                if (!Utilities.FileManager.FileExists(Server.MapPath("~/Weather/Images/Image2.png"))
                    || File.GetCreationTime(Server.MapPath("~/Weather/Images/Image2.png")) < DateTime.Now.AddHours(-1))
                {
                    string Content = Utilities.FileManager.GetFileContents(new Uri("http://rss.weather.com/weather/rss/local/"));  //Note that you need to get the RSS Feed for your area and plug it in here
                    XmlDocument XmlDocument = new XmlDocument();
                    XmlDocument.LoadXml(Content);
                    Utilities.FileFormats.RSSHelper.Document Document = new Utilities.FileFormats.RSSHelper.Document(XmlDocument);
                    string Description = Document.Channels[0].Items[0].Description;
                    Regex TempReg = new Regex("<img src=\"(?<URL>[^\"]*)\"(.*)/>(?<TEXT>(.*))");
                    MatchCollection Matches = TempReg.Matches(Description);
                    string Text = "";
                    foreach (Match Match in Matches)
                    {
                        Text = Match.Groups["TEXT"].Value.Replace("anddeg;", "").Replace("For more details?", "").Replace(", and", "\n").Replace(".", "");
                    }

                    Utilities.Media.Image.Image.DrawText(Server.MapPath("~/Weather/Images/Image.png"), Server.MapPath("~/Weather/Images/Image2.png"), Text, new Font("Arial", 16f), Brushes.White, new RectangleF(179, 45, 90, 80));
                }
                Response.ContentType = "text/xml";
                Utilities.Cisco.Image Image = new Utilities.Cisco.Image();
                Image.Title = "Weather";
                Image.Prompt = "Weather";
                Image.URL = "http://" + ServerImage + "/Image2.png";
                Image.X = 0;
                Image.Y = 0;
                Response.Write(Image.ToString());
                Response.End();
            }
    }
}

The code above is the actual web page's code. All we're doing is double checking the last time we updated the image. If it has been more than an hour, we pull our local weather feed from weather.com (or whomever you want as you'll need to replace that location with your own), getting the first item from the RSS feed, and pulling out the text saying what temperature it is and whether it is sunny, partly cloudy, raining, etc. From there we take a default image that we choose and write the text on it, saving it in a new location. We then use the Image class (which I'll show you in a second) to export that as an XML doc that the phone will recognize. And that's it. The RSSHelper, FileManager, and Image classes are all found in my utility library. The Cisco Image class on the other hand can be found here:

/*
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.Linq;
using System.Text;
using Utilities.Cisco.Interfaces;
#endregion

namespace Utilities.Cisco
{
    /// <summary>
    /// Image class
    /// </summary>
    public class Image:IDisplay
    {
        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        public Image()
        {
            SoftKeys = new List<SoftKeyItem>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Title
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// Prompt
        /// </summary>
        public string Prompt { get; set; }

        /// <summary>
        /// X location
        /// </summary>
        public int X { get; set; }

        /// <summary>
        /// Y location
        /// </summary>
        public int Y { get; set; }

        /// <summary>
        /// URL to the image
        /// </summary>
        public string URL { get; set; }

        /// <summary>
        /// Softkeys list
        /// </summary>
        public List<SoftKeyItem> SoftKeys { get; set; }

        #endregion

        #region Overridden Functions

        public override string ToString()
        {
            StringBuilder Builder = new StringBuilder();
            Builder.Append("<CiscoIPPhoneImageFile><Title>").Append(Title).Append("</Title><Prompt>").Append(Prompt).Append("</Prompt><LocationX>")
                .Append(X.ToString()).Append("</LocationX><LocationY>").Append(Y.ToString()).Append("</LocationY><URL>")
                .Append(URL).Append("</URL>");
            foreach (SoftKeyItem Item in SoftKeys)
            {
                Builder.Append(Item.ToString());
            }
            Builder.Append("</CiscoIPPhoneImageFile>");
            return Builder.ToString();
        }

        #endregion
    }
}

It simply wants to know the title, prompt, location of the image, and the URL pointing to it and in return spits out a bit of XML for the phone. Also note that you have to use png files. The phone doesn't work with any other format really, so make sure to convert them over. It's incredibly simple once you jump into it. But that's all I had to do in order to get weather on my phone. If you want the rest of the helpers that I created, you can go here and download them from the utility library's source code. Note that certain elements I didn't include as I didn't need them (I would rather deal with images than individual pixels personally). That being said, you can actually create some nice apps rather quickly for it using the code and you can create items rather quickly to fill in the gaps. So give it a try, 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: 4/16/2010 at 3:05 PM
Tags: , ,
Categories: ASP.Net | C#
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed