SharpDevelop: Adding a component to the designer

As I’m using SharpDevelop for some of my C# development I thought I’d just mention how you add a component to the designer toolbox if you ever need to. It’s not completely clear from the start (and it isn’t really in Visual Studio either) and there is little documentation to find (if you have it, please add it in a comment!).

I needed to add the ObjectListViewer to one of my objects and I wanted it in the toolbox so here is what I did:

1. Right-click in the Tools window:

 

2. Select Configure Sidebar

3.  Add a new category by pressing “New” at the bottom left in the Configure Sidebar window

4. Select the new category and press “Add Components”

5. Select the DLL either from the GAC or by browsing for it in the Add Component window

6. Select the components from the DLL that you want added

7. Press OK 😛

JIRA: Basic C#/Jira searching for issues

(This article is a follow up to http://maffelu.net/jira-basic-cjira-fetching-and-displaying-projects/)

In the previous article, and the article before that, we talked about creating a connection to JIRA with C# and using that connection to get all projects. In this article I will explain how to get all issues for a project.

First of all we’ll be using the code from previous articles, so we’ll build a class called JiraManager which will, for brevity, be handling all our communication. It has one protected method called RunQuery which runs a query against your JIRA instance. It handles the encryption of credentials and sending/fetching of data. All other methods (such as GetProjects and GetIssues) will be calling this method with different parameters:

 

 

public enum JiraResource
{
    project,
    search
}
 
public class JiraManager
{
    private const string m_BaseUrl = "http://localhost.:8080/rest/api/latest/";
    private string m_Username;
    private string m_Password;
 
    public JiraManager(string username, string password)
    {
        m_Username = username;
        m_Password = password;
    }
 
    /// <summary>
    /// Runs a query towards the JIRA REST api
    /// </summary>
    /// <param name="resource">The kind of resource to ask for</param>
    /// <param name="argument">Any argument that needs to be passed, such as a project key</param>
    /// <param name="data">More advanced data sent in POST requests</param>
    /// <param name="method">Either GET or POST</param>
    /// <returns></returns>
    protected string RunQuery(
        JiraResource resource, 
        string argument = null, 
        string data = null,
        string method = "GET")
    {
        string url = string.Format("{0}{1}/", m_BaseUrl, resource.ToString());
 
        if (argument != null)
        {
            url = string.Format("{0}{1}/", url, argument);
        }
 
        HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
        request.ContentType = "application/json";
        request.Method = method;
 
        if (data != null)
        {
            using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
            {
                writer.Write(data);
            }
        }
 
        string base64Credentials = GetEncodedCredentials();
        request.Headers.Add("Authorization", "Basic " + base64Credentials);
 
        HttpWebResponse response = request.GetResponse() as HttpWebResponse;
 
        string result = string.Empty;
        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
        {
            result = reader.ReadToEnd();
        }
 
        return result;
    }
 
    public List<ProjectDescription> GetProjects()
    {
        List<ProjectDescription> projects = new List<ProjectDescription>();
        string projectsString = RunQuery(JiraResource.project);
 
        return JsonConvert.DeserializeObject<List<ProjectDescription>>(projectsString);
    }
 
    public List<Issue> GetIssues(
        string jql, 
        List<string> fields = null,
        int startAt = 0, 
        int maxResult = 50)
    {
        fields = fields ?? new List<string>{"summary", "status", "assignee"};
 
        SearchRequest request = new SearchRequest();
        request.Fields = fields;
        request.JQL = jql;
        request.MaxResults = maxResult;
        request.StartAt = startAt;
 
        string data = JsonConvert.SerializeObject(request);
        string result = RunQuery(JiraResource.search, data: data, method: "POST");
 
        SearchResponse response = JsonConvert.DeserializeObject<SearchResponse>(result);
 
        return response.IssueDescriptions;
    }
 
    private string GetEncodedCredentials()
    {
        string mergedCredentials = string.Format("{0}:{1}", m_Username, m_Password);
        byte[] byteCredentials = UTF8Encoding.UTF8.GetBytes(mergedCredentials);
        return Convert.ToBase64String(byteCredentials);
    }
}

We’ve added a resource to the JiraResource enumeration. This is the ‘search’ resource. The rest call to search for issues looks like this:
/rest/api/latest/search

So a call might look like this:
http://localhost:8080/rest/api/latest/search

In order to search we need to pass along a search request with the post data to the rest service. The data could look like this:

{
    "jql": "project = JSHARP",
    "startAt": 0,
    "maxResults": 15,
    "fields": [
        "summary",
        "status",
        "assignee"
    ]
}

So, we’ll create a class to represent this search request (as can be seen used in the GetIssues method in the code above):

public class SearchRequest
{
    [JsonProperty("jql")]
    public string JQL { get; set; }
 
    [JsonProperty("startAt")]
    public int StartAt { get; set; }
 
    [JsonProperty("maxResults")]
    public int MaxResults { get; set; }
 
    [JsonProperty("fields")]
    public List<string> Fields { get; set; }
 
    public SearchRequest()
    {
        Fields = new List<string>();
    }
}

This allows us to create the search request and later on serialize it to JSON and pass it on in the request. The return result can look like this:

{
	"expand" : "schema,names",
	"startAt" : 0,
	"maxResults" : 50,
	"total" : 1,
	"issues" : [{
			"expand" : "editmeta,renderedFields,transitions,changelog,operations",
			"id" : "10004",
			"self" : "http://localhost.:8080/rest/api/latest/issue/10004",
			"key" : "JSHARP-3",
			"fields" : {
				"summary" : "Install visual studio 2010 express",
				"status" : {
					"self" : "http://localhost.:8080/rest/api/2/status/5",
					"description" : "A resolution has been taken, and it is awaiting verification by reporter. From here issues are either reopened, or are closed.",
					"iconUrl" : "http://localhost.:8080/images/icons/status_resolved.gif",
					"name" : "Resolved",
					"id" : "5"
				},
				"assignee" : {
					"self" : "http://localhost.:8080/rest/api/2/user?username=adm",
					"name" : "adm",
					"emailAddress" : "foo@bar.com",
					"avatarUrls" : {
						"16x16" : "http://localhost.:8080/secure/useravatar?size=small&avatarId=10122",
						"48x48" : "http://localhost.:8080/secure/useravatar?avatarId=10122"
					},
					"displayName" : "Magnus Ferm",
					"active" : true
				}
			}
		}
	]
}

This result can in turn be represented by C# classes as well so that we can deserialize it and use it natively:

public class SearchResponse
{
    [JsonProperty("expand")]
    public string Expand { get; set; }
 
    [JsonProperty("startAt")]
    public int StartAt { get; set; }
 
    [JsonProperty("maxResults")]
    public int MaxResults { get; set; }
 
    [JsonProperty("total")]
    public int Total { get; set; }
 
    [JsonProperty("issues")]
    public List<Issue> IssueDescriptions { get; set; }
}

This response contains a list of issues, they are built up like this:

public class Issue : BaseEntity
{
    private string m_KeyString;
 
    [JsonProperty("expand")]
    public string Expand { get; set; }
 
    [JsonProperty("id")]
    public int Id { get; set; }
 
    #region Special key solution
    [JsonProperty("key")]
    public string ProxyKey
    {
        get
        {
            return Key.ToString();
        }
        set
        {
            m_KeyString = value;
        }
    }
 
    [JsonIgnore]
    public IssueKey Key
    {
        get
        {
            return IssueKey.Parse(m_KeyString);
        }
    }
    #endregion Special key solution
 
    [JsonProperty("fields")]
    public Fields Fields { get; set; }
}
 
/// <summary>
/// A class representing a JIRA issue key [PROJECT KEY]-[ISSUE ID]
/// </summary>
public class IssueKey
{
    public string ProjectKey { get; set; }
 
    public int IssueId { get; set; }
 
    public IssueKey() { }
    public IssueKey(string projectKey, int issueId)
    {
        ProjectKey = projectKey;
        IssueId = issueId;
    }
 
    public static IssueKey Parse(string issueKeyString)
    {
        if (issueKeyString == null)
        {
            throw new ArgumentNullException("IssueKeyString is null!");
        }
 
        string[] split = issueKeyString.Split('-');
 
        if (split.Length != 2)
        {
            throw new ArgumentException("The string entered is not a JIRA key!");
        }
 
        int issueId = 0;
        if (!int.TryParse(split[1], out issueId))
        {
            throw new ArgumentException("The string entered could not be parsed, issue id is non-integer!");
        }
 
        return new IssueKey(split[0], issueId);
    }
 
    public override string ToString()
    {
        return string.Format("{0}-{1}", ProjectKey, IssueId);
    }
}
 
public class Fields
{
    [JsonProperty("summary")]
    public string Summary { get; set; }
 
    [JsonProperty("status")] 
    public Status Status { get; set; }
 
    [JsonProperty("assignee")]
    public Assignee Assignee { get; set; }
}
 
public class Status : BaseEntity
{
    [JsonProperty("description")]
    public string Description { get; set; }
 
    [JsonProperty("iconUrl")]
    public string IconUrl { get; set; }
 
    [JsonProperty("name")]
    public string Name { get; set; }
 
    [JsonProperty("id")]
    public int Id { get; set; }
}
 
public class Assignee : BaseEntity
{
    [JsonProperty("name")]
    public string Name { get; set; }
 
    [JsonProperty("emailAddress")]
    public string EmailAddress { get; set; }
 
    [JsonProperty("avatarUrls")]
    public AvatarUrls AvatarUrls { get; set; }
 
    [JsonProperty("displayName")]
    public string DisplayName { get; set; }
 
    [JsonProperty("active")]
    public bool Active { get; set; }
}
 
public class AvatarUrls
{
    [JsonProperty("16x16")]
    public string Size16 { get; set; }
 
    [JsonProperty("48x48")]
    public string Size48 { get; set; }
}

This is how we represent the return result as objects in C#. This became a pretty huge example so I’ve decided to upload the source code for this example:
JiraExample.zip

If you have any questions just put them in the comments for this article 🙂

JIRA: Basic C#/JIRA fetching and displaying projects

(This is a follow-up to the initial JIRA/C# article: “JIRA: Basic C#/JIRA connection using REST“)

In the previous article about how to work with JIRA in C# utilizing the REST api we made a connection between a console application and JIRA. In this article I will show you how to query for projects and deserialize them using the JSON.NET library.

First off, download the JSON.NET library here.
We will continue on our initial project so I will only explain the new sections (and only if they need explanation). I apologize in advance for the none-refactored code but this is just an example.

Now, the JIRA REST api always replies with data in JSON format. This means we can create C# classes and serialize the data into usable objects. If we look at the result from the first article when we asked for all articles:

[{"self":"http://localhost.:8080/rest/api/2/project/JSHARP","id":"10001","key":"JSHARP","name":"Jira#","avatarUrls":{"16x16":"http://localhost.:8080/secure/projectavatar?size=small&pid=10001&avatarId=10002","48x48":"http://localhost.:8080/secure/projectavatar?pid=10001&avatarId=10002"}},{"self":"http://localhost.:8080/rest/api/2/project/TP","id":"10000","key":"TP","name":"Test Project","avatarUrls":{"16x16":"http://localhost.:8080/secure/projectavatar?size=small&pid=10000&avatarId=10011","48x48":"http://localhost.:8080/secure/projectavatar?pid=10000&avatarId=10011"}}]

I have two projects, Jira# and Test Project. If we copy this into Notepad++ and use the JSMin plugin we’ll see the following:

[{
		"self" : "http://localhost.:8080/rest/api/2/project/JSHARP",
		"id" : "10001",
		"key" : "JSHARP",
		"name" : "Jira#",
		"avatarUrls" : {
			"16x16" : "http://localhost.:8080/secure/projectavatar?size=small&pid=10001&avatarId=10002",
			"48x48" : "http://localhost.:8080/secure/projectavatar?pid=10001&avatarId=10002"
		}
	}, {
		"self" : "http://localhost.:8080/rest/api/2/project/TP",
		"id" : "10000",
		"key" : "TP",
		"name" : "Test Project",
		"avatarUrls" : {
			"16x16" : "http://localhost.:8080/secure/projectavatar?size=small&pid=10000&avatarId=10011",
			"48x48" : "http://localhost.:8080/secure/projectavatar?pid=10000&avatarId=10011"
		}
	}
]

(If you don’t have the JSMin plugin in Notepad++ just go to Plugins => Plugin Manager => Show Plugin Manager => Available and scroll down to JSMin and press install. To format then select Plugins => JSMin => JSFormat)

We’ll create two classes (one for each project and one for the property AvatarUrls):

ProjectDescription

public class ProjectDescription : BaseEntity
{
    [JsonProperty("id")]
    public int Id { get; set; }
 
    [JsonProperty("key")]
    public string Key { get; set; }
 
    [JsonProperty("name")]
    public string Name { get; set; }
 
    [JsonProperty("avatarUrls")]
    public AvatarUrls AvatarUrls { get; set; }
}

AvatarUrls

public class AvatarUrls
{
    [JsonProperty("16x16")]
    public string Size16 { get; set; }
 
    [JsonProperty("48x48")]
    public string Size48 { get; set; }
}

As you can see we use the JsonPropertyAttribute to decorate each property with their real name. Some of the properties are poorly named so we’ll want to change the names in our classes. Now for the deserialization process. In the previous article we created a class called JiraManager which handled all the communication, we’ll continue on that one and rebuild it a bit:

public enum JiraResource
{
    project
}
 
public class JiraManager
{
    private const string m_BaseUrl = "http://localhost.:8080/rest/api/latest/";
    private string m_Username;
    private string m_Password;
 
    public JiraManager(string username, string password)
    {
        m_Username = username;
        m_Password = password;
    }
 
    /// <summary>
    /// Runs a query towards the JIRA REST api
    /// </summary>
    /// <param name="resource">The kind of resource to ask for</param>
    /// <param name="argument">Any argument that needs to be passed, such as a project key</param>
    /// <param name="data">More advanced data sent in POST requests</param>
    /// <param name="method">Either GET or POST</param>
    /// <returns></returns>
    protected string RunQuery(
        JiraResource resource, 
        string argument = null, 
        string data = null,
        string method = "GET")
    {
        string url = string.Format("{0}{1}/", m_BaseUrl, resource.ToString());
 
        if (argument != null)
        {
            url = string.Format("{0}{1}/", url, argument);
        }
 
        HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
        request.ContentType = "application/json";
        request.Method = method;
 
        if (data != null)
        {
            using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
            {
                writer.Write(data);
            }
        }
 
        string base64Credentials = GetEncodedCredentials();
        request.Headers.Add("Authorization", "Basic " + base64Credentials);
 
        HttpWebResponse response = request.GetResponse() as HttpWebResponse;
 
        string result = string.Empty;
        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
        {
            result = reader.ReadToEnd();
        }
 
        return result;
    }
 
    public List<ProjectDescription> GetProjects()
    {
        List<ProjectDescription> projects = new List<ProjectDescription>();
        string projectsString = RunQuery(JiraResource.project);
 
        return JsonConvert.DeserializeObject<List<ProjectDescription>>(projectsString);
    }
 
 
 
    private string GetEncodedCredentials()
    {
        string mergedCredentials = string.Format("{0}:{1}", m_Username, m_Password);
        byte[] byteCredentials = UTF8Encoding.UTF8.GetBytes(mergedCredentials);
        return Convert.ToBase64String(byteCredentials);
    }
}

So what we simply do is that we deserialize the project using the JsonConvert class utilizing the classes we build for the incoming data. Use it like this:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello and welcome to a Jira Example application!");
 
        #region Create manager
        Console.Write("Username: ");
        string username = Console.ReadLine();
 
        Console.Write("Password: ");
        string password = Console.ReadLine();
 
        JiraManager manager = new JiraManager(username, password);
        #endregion
 
        Console.Clear();
 
        List<ProjectDescription> projects = manager.GetProjects();
        Console.WriteLine("Select a project: ");
        for (int i = 0; i < projects.Count; i++)
        {
            Console.WriteLine("{0}: {1}", i, projects[i].Name);
        }
 
        Console.Write("Project to open: ");
        string projectStringIndex = Console.ReadLine();
        int projectIndex = 0;
        if (!int.TryParse(projectStringIndex, out projectIndex))
        {
            Console.WriteLine("You failed to select a project...");
            Environment.Exit(0);
        }
 
        ProjectDescription selectedProject = projects[projectIndex];
        Console.WriteLine("You selected {0}", selectedProject.Name);
 
        Console.Read();
    }
}

The output should look like this:

Fetching projects from Jira and using them in C#
Fetching projects from Jira and using them in C#

In the next article we’ll look at how to search for issues: http://maffelu.net/jira-basic-cjira-searching-for-issues/.

JIRA: Basic C#/JIRA connection using REST

As I’m working a lot through JIRA I thought I’d try to communicate with it using C# and the REST api it’s exposing. This was not completely simple and it’s obvious they prefer people using java. There is a library available if you are a java user which simplifies a lot but there is none for C#. I will probably write more than a few articles about the subject as I’ve just installed JIRA on my computer.

The first thing you need to know about communicating with the REST api is that the full api documentation is found here. This will be your bestest friend ever when developing against it.
The second thing you need to know is that they used to have a SOAP api but since 4.something that’s deprecated in favor of the new REST api.

A standard JIRA REST call looks like this:
http://host:port/context/rest/api-name/api-version/resource-name

If you want to try it out you can use an open project on atlassian:
https://jira.atlassian.com/rest/api/latest/issue/JRA-9

The response is given in JSON format which is short and nice and easy to parse using the JSON.NET library. In this first article however we’ll just ensure that we can connect to JIRA and get some data in return.
When we use the open atlassian project in the link above we view the issue as an anonymous user. That takes us only so far so one of the most important parts of the connection is to send the proper credentials. We’ll create a console project which asks the user for a username and password which it uses to fetch all projects available to that user. That’s enough to get one started:

public enum JiraResource
{
    project
}
 
public class JiraManager
{
    private const string m_BaseUrl = "http://localhost.:8080/rest/api/latest/";
    private string m_Username;
    private string m_Password;
 
    public JiraManager(string username, string password)
    {
        m_Username = username;
        m_Password = password;
    }
 
    public void RunQuery(
        JiraResource resource, 
        string argument = null, 
        string data = null,
        string method = "GET")
    {
        string url = string.Format("{0}{1}/", m_BaseUrl, resource.ToString());
 
        if (argument != null)
        {
            url = string.Format("{0}{1}/", url, argument);
        }
 
        HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
        request.ContentType = "application/json";
        request.Method = method;
 
        if (data != null)
        {
            using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
            {
                writer.Write(data);
            }
        }
 
        string base64Credentials = GetEncodedCredentials();
        request.Headers.Add("Authorization", "Basic " + base64Credentials);
 
        HttpWebResponse response = request.GetResponse() as HttpWebResponse;
 
        string result = string.Empty;
        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
        {
            result = reader.ReadToEnd();
        }
 
        Console.WriteLine(result);
    }
 
    private string GetEncodedCredentials()
    {
        string mergedCredentials = string.Format("{0}:{1}", m_Username, m_Password);
        byte[] byteCredentials = UTF8Encoding.UTF8.GetBytes(mergedCredentials);
        return Convert.ToBase64String(byteCredentials);
    }
}

And then we use it in our programs.cs file:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello and welcome to a Jira Example application!");
 
        Console.Write("Username: ");
        string username = Console.ReadLine();
 
        Console.Write("Password: ");
        string password = Console.ReadLine();
 
        JiraManager manager = new JiraManager(username, password);
        manager.RunQuery(JiraResource.project);
 
        Console.Read();
    }
}

The code could do with some refactoring but this is just a simple example. This, if run, should generate something like this (if you log in correctly and have some projects):

A finished connection to Jira
A finished connection to Jira with all Projects for ‘testuser’ showing.

Things to notice in the code:

  • The Authorization header that we use to send our credentials look like this: “Authorization” : “Basic [BASE64 ENCODED CREDENTIALS]”
  • Content-type must be application/json as we send and receive data using the json format
  • The RunQuery method is more generic than required but I’m going to re-use it in future articles. It allows for you to attach data and extra parameters which is sometimes needed
  • The URL goes to localhost. (notice the dot ‘.’ after localhost). This is to make the transaction visible in Fiddler when I monitor the communication

If you need to monitor the communication with Fiddler you have to, as mentioned in the last line above, add a dot after ‘localhost’ in your URL (if you are running on your local computer, otherwise never mind). Open Fiddler2 and monitor the communication and you’ll see something like this:

Communication monitoring in Fiddler
Communication caught in Fiddler, header highlighted in red

Read more in the next article: JIRA: Basic C#/JIRA fetching and displaying projects

C#: create a basic windows service

The service

Creating services in C# differs a bit from creating a standard console/winform/wpf application. A service is often created to run continuously and it has no graphical interface, something which makes it hard to communicate errors to the user.
This post will show you how to create a very basic windows service which communicates with the user through the event handler!

First up, create a console project and call it something nice, such as TestService:

Add a console project
Add a console project

 

Add references to the project to System.ServiceProcess and System.Configuration.Install. Then add a new item,a windows service, called TestService.cs and let it inherit from ServiceBase:

Windows Service now, Installer for later
Windows Service now, Installer for later
namespace TestService
{
    partial class TestService: ServiceBase
    {
        public TestService()
        {
            InitializeComponent();
        }
 
        protected override void OnStart(string[] args)
        {
            // TODO: Add code here to start your service.
        }
 
        protected override void OnStop()
        {
            // TODO: Add code here to perform any tear-down necessary to stop your service.
        }
    }
}

TestService comes with a few methods you can override. You should at least override OnStart and OnStop as these let you control what happens when the service starts and stops:

namespace MyFirstService
{
    public class MyService : ServiceBase
    {
        public MyService()
        {
            InitializeComponent();
            this.ServiceName = "MyService";
        }
 
        protected override void OnStart(string[] args)
        {
            base.OnStart(args);
        }
 
        protected override void OnStop()
        {
            base.OnStop();
        }
    }
}

Call the base implementation first and then add whatever you want to do afterwards. To make sure this service in the one that’s run we go to the program.cs class and call for this service on execution:

namespace TestService
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceBase.Run(new ServiceBase[]
            {
                new TestService()
            });
        }
    }
}

The installer

Now, this won’t install as normal and the easiest way to install it is to create an installer class and then add a setup project to handle it. We start by adding a new item, an installer item (see image above), called TestServiceInstaller.cs which we let inherit from the Installer class in System.Configuration.Install. Add the code as shown below:

namespace TestServiceService
{
    [RunInstaller(true)]
    public class TestServiceInstaller : Installer
    {
        public MyServiceInstaller()
        {
            ServiceProcessInstaller serviceProcessInstaller =
                               new ServiceProcessInstaller();
            ServiceInstaller serviceInstaller = new ServiceInstaller();
 
            //Who will run this service?
            serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
            //Set login credentials if needed
            //serviceProcessInstaller.Username = ?;
            //serviceProcessInstaller.Password = ?;
 
            //This is the name that is displayed in the service menu
            serviceInstaller.DisplayName = "My test service";
 
            //How will the service act on startup?
            serviceInstaller.StartType = ServiceStartMode.Manual;
 
            //The actual name of the service, must be the same
            //as in your ServiceBase
            serviceInstaller.ServiceName = "TestService";
 
            this.Installers.Add(serviceProcessInstaller);
            this.Installers.Add(serviceInstaller);
 
            this.Installers.Add(serviceProcessInstaller);
            this.Installers.Add(serviceInstaller);
 
            InitializeComponent();
        }
    }
}

This is just a basic installer class and you need to configure at least the ServiceName and the DisplayName to match your preferences. The ServiceName must match your services name or else it won’t install.

Communicate using the EventLog

The easiest way to communicate with the user is to use the EventLog. If you haven’t seen or used it before you can open it by going to Start => Control Panel => Administrative Tools => EventLog (or Event Viewer in Vista). The EventView is where services and programs can output information to a user and it is very handy.
To configure an EventLog go back to the TestService.cs and change it to this:

namespace TestService
{
    public class TestService: ServiceBase
    {
        private const string Source = "TestServiceLog";
        private const string Log = "Application";
 
        private EventLog m_EventLog;
 
        public TestService()
        {
            this.ServiceName = "TestService";
 
            ConfigureEventLog();
        }
 
        protected override void OnStart(string[] args)
        {
            base.OnStart(args);
 
            m_EventLog.WriteEntry("TestServiceis starting!");
        }
 
        protected override void OnStop()
        {
            base.OnStop();
 
            m_EventLog.WriteEntry("TestServiceis stopping!");
        }
 
        private void ConfigureEventLog()
        {
            if (!EventLog.SourceExists(Source))
            {
                EventLog.CreateEventSource(Source, Log);
            }
 
            m_EventLog.Source = Source;
            m_EventLog.Log = Log;
 
            m_EventLog.WriteEntry("EventLog configured!");
        }
    }
}

The EventLog must be configured before using and it is prudent to check if the source exists. You don’t know if it exists on another computer and the EventLog will crash if it doesn’t.

A Setup-project

Now to the installation and usage of our fine service. Add a Setup project and call it ServiceSetup:

Add setup project
Add setup project

Add the service project by right clicking on the setup project and selecting View => File System and enter the Application Folder. Right click in the application folder and select Add => Project Output :

Configuring the setup project
Configuring the setup project

Select the project as follows:

Adding the project output
Adding the project output

Now, there is one more thing to do in order to get this to work. If you just install normally now the files will be copied to the installation directory complete but the service won’t get registered and it will have been quite pointless. We need to right-click on the project once more and select View => Custom Actions and add our project output there:

Custom actions
Custom actions

Now, if you right-click on the setup project and build it (setup projects generally has to be build individually as they are not included in the building schedule, change it by right-clicking by going to Solution configurations => Configuration manager). After building the project, right-click on it and select install.

Using the installed service

Go to Start => Control Panel => Administrative Tools => Services and find your service (by its display name):

C#: Rename a project in Visual Studio

Renaming projects in Visual Studio is a pain in the @$$ and can be really time consuming if you don’t know what you’re doing. Visual Studio 2008 doesn’t come with a good refactoring tool for project renaming and the solution files can really cause problems if you don’t edit them properly.

I’ll create a somewhat fake project for this example:

Our test project
Our test project

 

When the project is created, right-click on the project and rename it from BadNameProject to GoodNameProject:

Now the project is properly named to GoodNameProject, almost. Right-click on the project and go to properties:

As you can see the Assembly name and Default namespace hasn’t change name so you need to change these as well.

Now, right-click on the project once more and select Open folder in Windows explorer, you need to rename the folder to the project as well and configure the solution file.

Rename the folder
Rename the folder

Right-click on the TestProject.sln file and select to edit it with a proper tool (such as NotePad++), you will see something like this:

Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BadNameProject", "BadNameProject\BadNameProject.csproj", "{9E1B883A-025C-47AD-9350-D4CAD2760517}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Release|Any CPU = Release|Any CPU
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{9E1B883A-025C-47AD-9350-D4CAD2760517}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{9E1B883A-025C-47AD-9350-D4CAD2760517}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{9E1B883A-025C-47AD-9350-D4CAD2760517}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{9E1B883A-025C-47AD-9350-D4CAD2760517}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
EndGlobal

Change references from the old BadNameProject to the new GoodNameProject.

When you return to Visual Studio you will be prompted to take action against the outside changes to the solution file. If you want to save your old project do so, but Discard is what you want to choose here and embrace your new changes!

 

Tadaa, you’re pretty much done. Depending on how much refactoring you need to do of course, don’t forget to change the project namespaces in your files…

Tip: If you have to change a lot of references from the old namespace to the new, use the SHIFT + CTRL + F12 debugging tool (jump to next error) to quickly add new references.