A common task for developers is - you guessed it – to search for a job! This post will show you how to build a standard ASP.NET App that uses the Indeed API to make searching for jobs easy. I’ll show the basics of how I constructed the app and included will be a download with the complete Visual Studio 2015 source code. The only thing you’ll need is a new Indeed publisher API key to insert into the AppSettings section of your web.config, and you should start out by getting that here:
http://www.indeed.com/publisher
The Indeed API is a very simple, REST – based API that returns XML. So we’ll have two classes for our search interface – the result class, and the QueryParameters class, which holds the various query parameters that can be passed on the querystring of the API URL.
Here’s the QueryParameters class first:
public class IndeedQueryParameters
{
/// <summary>
/// Searches Job Title.
/// </summary>
public string JobQuery { get; set; }
/// <summary>
/// Searches for jobs near a particular location.
/// </summary>
public string Location { get; set; }
/// <summary>
/// Sort by relevance or date. Default is relevance.
/// </summary>
public string Sort { get; set; }
/// <summary>
/// Distance from search location ("as the crow flies"). Default is 25.
/// </summary>
public string SearchRadius { get; set; }
/// <summary>
/// Site type. To show only jobs from job boards use 'jobsite'. For jobs from direct employer websites use 'employer'.
/// </summary>
public string St { get; set; }
/// <summary>
/// Job type. Allowed values: "fulltime", "parttime", "contract", "internship", "temporary".
/// </summary>
public string Jt { get; set; }
/// <summary>
/// Start results at this result number, beginning with 0. Default is 0.
/// </summary>
public string Start { get; set; }
/// <summary>
/// Maximum number of results returned per query. Default is 10
/// </summary>
public string Limit { get; set; }
/// <summary>
/// Number of days back to search.
/// </summary>
public string FromAge { get; set; }
/// <summary>
/// Filter duplicate results. 0 turns off duplicate job filtering. Default is 1.
/// </summary>
public string Filter { get; set; }
/// <summary>
/// If latlong=1, returns latitude and longitude information for each job result. Default is 0.
/// </summary>
public string LatitudeLongitude { get; set; }
/// <summary>
/// Search within country specified. Default is us. See below for a complete list of supported countries.
/// </summary>
public string Country { get; set; }
/// <summary>
/// Channel Name: Group API requests to a specific channel.
/// </summary>
public string Channel { get; set; }
/// <summary>
/// The IP number of the end-user to whom the job results will be displayed. This field is required.
/// </summary>
public string UserIP { get; set; }
/// <summary>
/// The User-Agent (browser) of the end-user to
/// whom the job results will be displayed.
/// This can be obtained from the "User-Agent"
/// HTTP request header from the end-user.
/// This field is required.
/// </summary>
public string UserAgent { get; set; }
}
And the IndeedSearchResult class which maps to the returned XML document:
public class IndeedSearchResult
{
/// <summary>
/// The time the job listing was posted formatted
/// for display.
/// </summary>
/// <example>6 days ago</example>
public string FormattedRelativeTime { get; set; }
/// <summary>
/// Has this job listing expired?
/// </summary>
/// <example>false</example>
public string Expired { get; set; }
/// <summary>
/// Is this an Indeed sponsored job?
/// </summary>
/// <example>false</example>
public string Sponsored { get; set; }
/// <summary>
/// The indeed job key.
/// </summary>
/// <example>0123456789abcdef</example>
public string JobKey { get; set; }
/// <summary>
/// Latitude of the job's location.
/// </summary>
/// <example>41.057693</example>
public string Latitude { get; set; }
/// <summary>
/// Longitude of the job's location.
/// </summary>
/// <example>-73.54395</example>
public string Longitude { get; set; }
/// <summary>
/// Code to be placed on the onMouseDown event of the title link.
/// </summary>
/// <example>indeed_clk(this, '');</example>
public string OnMouseDown { get; set; }
/// <summary>
/// A short description of the summary of the job.
/// </summary>
/// <example>Indeed is looking for strategic Account Executives to help in the expansion of our Stamford, CT location. We are
/// in the process of interviewing sales candidates who have 2-5 years of sales experience and who have experience
/// generating new business and growing existing accounts. A strong candidate will have excellent communication
/// skills, consistent work ethic and a desire to be a part of the...</example>
public string Snippet { get; set; }
/// <summary>
/// The date that the job was posted.
/// </summary>
/// <example>Tue, 03 Aug 2010 14:00:47 GMT</example>
public string Date { get; set; }
/// <summary>
/// The country where the job is located. This
/// will be in the format of a country code.
/// </summary>
/// <example>US</example>
public string Country { get; set; }
/// <summary>
/// The state where the job is located.
/// </summary>
/// <example>CT</example>
public string State { get; set; }
/// <summary>
/// The source of the job posting.
/// </summary>
/// <example>Indeed</example>
public string Source { get; set; }
/// <summary>
/// The city where the job is located.
/// </summary>
/// <example>Stamford</example>
public string City { get; set; }
/// <summary>
/// The job title.
/// </summary>
/// <example>Sales Account Executive</example>
public string JobTitle { get; set; }
/// <summary>
/// The company the job is for.
/// </summary>
/// <example>Indeed</example>
public string Company { get; set; }
/// <summary>
/// Full location of the job listing that is
/// formatted for display.
/// </summary>
/// <example>Stamford, CT 06902</example>
public string FormattedLocationFull { get; set; }
/// <summary>
/// The URL of the job posting on Indeed.com
/// </summary>
/// <example>http://www.indeed.com/rc/clk?jk=0123456789abcdef</example>
/// <remarks>Notice that the job key is part of the URL.</remarks>
public string URL { get; set; }
}
With these items complete, all we need is our search method so that we can bind results to a standard ASP.NET DataGrid:
public static List<IndeedSearchResult> GetSearchResults(IndeedQueryParameters parameters, string apiPublisherKey)
{
Contract.Requires(null != apiPublisherKey);
Contract.Requires("" != apiPublisherKey.Trim());
// To get your own Publisher ID: http://www.indeed.com/publisher
string requestUrl = "http://api.indeed.com/ads/apisearch" +
String.Format("?publisher={0}", apiPublisherKey) +
String.Format("&q={0}", parameters.JobQuery) +
String.Format("&l={0}", parameters.Location) +
String.Format("&sort={0}", parameters.Sort) +
String.Format("&radius={0}", parameters.SearchRadius) +
String.Format("&st={0}", parameters.St) +
String.Format("&jt={0}", parameters.Jt) +
String.Format("&start={0}", parameters.Start) +
String.Format("&limit={0}", parameters.Limit) +
String.Format("&fromage={0}", parameters.FromAge) +
String.Format("&filter={0}", parameters.Filter) +
String.Format("&latlong={0}", parameters.LatitudeLongitude) +
String.Format("&co={0}", parameters.Country) +
String.Format("&chnl={0}", parameters.Channel) +
String.Format("&userip={0}", parameters.UserIP) +
String.Format("&useragent={0}", parameters.UserAgent) +
"&v=2";
XmlDocument doc = new XmlDocument();
doc.Load(requestUrl);
XmlElement root = doc.DocumentElement;
XmlNodeList nodes = root.SelectNodes("//results//result");
List<IndeedSearchResult> results = new List<IndeedSearchResult>();
foreach (XmlNode node in nodes)
{
IndeedSearchResult result = new IndeedSearchResult();
result.JobTitle = node["jobtitle"].InnerText;
result.Company = node["company"].InnerText;
result.City = node["city"].InnerText;
result.State = node["state"].InnerText;
result.Country = node["country"].InnerText;
result.FormattedLocationFull = node["formattedLocation"].InnerText;
result.Source = node["source"].InnerText;
result.Date =DateTime.Parse( node["date"].InnerText).ToShortDateString();
result.Snippet = node["snippet"].InnerText;
result.URL = node["url"].InnerText;
result.OnMouseDown = node["onmousedown"].InnerText;
if ( node["latitude"] !=null) // prevent blow-ups if null
result.Latitude = node["latitude"].InnerText;
result.JobKey = node["jobkey"].InnerText;
result.Sponsored = node["sponsored"].InnerText;
result.Expired = node["expired"].InnerText;
result.FormattedLocationFull = node["formattedLocationFull"].InnerText;
result.FormattedRelativeTime = node["formattedRelativeTime"].InnerText;
results.Add(result);
}
results = results.OrderByDescending(x => x.Date).ToList();
return results;
}
The final step is to perform a search based on the minimum number of QueryParameter inputs, namely the job search term e.g. “.NET Developer” and the location, e.g. “Orlando FL” and we’re done:
protected void DoSearch(int pagestart = 0)
{
Indeed.IndeedSearch srch = new IndeedSearch();
IndeedQueryParameters parms = new IndeedQueryParameters();
parms.Start = pagestart.ToString();
parms.JobQuery = txtSearch.Text;
parms.Location = txtLocation.Text;
parms.Limit = 1000.ToString();
parms.UserIP = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList[1].ToString();
List<IndeedSearchResult> results = srch.GetSearchResults(parms);
GridView1.PageIndex = pagestart;
GridView1.DataSource = results;
GridView1.DataBind();
}
I’ve added paging and sorting by job date. You can download the complete source code here:
IndeedWeb.zip (10.95 mb)