A week ago, I applied for a 'Software Engineering Internship' at one of the company. Since I know C++, C#, Java well and have worked on couple of ASP.NET MVC projects with MS SQL server before, I knew I was well qualified for the job. Plus, I recently graduated from my Bachelor of Computer Science with High Distinction average so this opportunity was exactly what I was looking for.
I sent out my application and was shortlisted for a phone interview. I was really excited and couple of days after I got a phone call from HR and they told me that my skill sets were perfect and they want me to do a practical test before moving on to face to face interview. The lady told me that she was very confident that I would be able to smash the practical test and they would ask some database related questions on the test.
Before the practical test, I started studying SQL a lot, I did all the questions I could find on the internet and was fairly confident. On the day of the practical test, she sent me an email with PDF with the questions.
They told me I had 4 hours to complete those tasks. When I first looked at the questions, I was fairly surprised since I didn't expect the questions to be on this subject. I prepared for SQL. haha I had never programmed Web API before but I had done ASP.NET MVC web applications and knew that we can create API in the controller that can return JSON data which can be consumed by client using AJAX calls.
So, for the first task, I was thinking of using Entity Framework connecting to database file and mapping it to objects but in the second task they mentioned not to use Entity Framework so I had to use something else. At the end, I used XML format for data store because it was easy to hand craft an XML file for debugging purposes.
My Category
class looks like this:
[Serializable]
public class Category
{
public string Name { get; set; }
public List<Category> Childs { get; set; }
public Category()
{
Childs = new List<Category>();
}
public Category(string name)
{
Name = name;
Childs = new List<Category>();
}
}
The whole idea was that I would create category data structure using that class and use XmlSerializer
to serialise and deserialise it to and from a file.
After that, I moved on to task 2, which was to create Web API. I had 2 options here, either use Web API project in Visual Studios to create those APIs or use the MVC Controller class to create them. I have never used Web API before and since I only had limited amount of time to complete the task, I decided to go with the second option. Learning to use WebAPI might have taken another hour and I didn't have that much time, but if I was working without the time limit, I would have definitely gone the WebAPI route.
Here are my API functions that client can consume. I kept it all in HomeContoller.cs file:
public class HomeController : Controller
{
//Index view, pretty simple one
public ActionResult Index()
{
return View();
}
//API that refreshes the category structure, created this just for debugging
public void RefreshStructure()
{
Category category = new Category("root");
category.Childs.Add(new Category("English"));
category.Childs.Add(new Category("History"));
category.Childs.Add(new Category("Science"));
category.Childs.ElementAt(0).Childs.Add(new Category("Literature"));
category.Childs.ElementAt(0).Childs.Add(new Category("Language"));
category.Childs.ElementAt(2).Childs.Add(new Category("Biology"));
category.Childs.ElementAt(2).Childs.Add(new Category("Chemistry"));
category.Childs.ElementAt(2).Childs.Add(new Category("Physics"));
saveAllCategories(category);
}
//Creates root category from XML file
private Category getAllCategories()
{
XmlSerializer xmlS = new XmlSerializer(typeof(Category));
FileStream readFileStream = new FileStream(Server.MapPath("~/datamodel/model.xml"), FileMode.Open, FileAccess.Read, FileShare.Read);
Category category = (Category)xmlS.Deserialize(readFileStream);
readFileStream.Close();
return category;
}
//Save Categories to XML file
private void saveAllCategories(Category categories)
{
XmlSerializer xmlS = new XmlSerializer(typeof(Category));
TextWriter textWriter = new StreamWriter(Server.MapPath("~/datamodel/model.xml"));
xmlS.Serialize(textWriter, categories);
textWriter.Close();
}
//Navigates to particulate category
private Category navigateToCategory(IEnumerable<string> path, Category root)
{
Category destination = root;
if (path.Count() == 1) //It has got to be the root
{
return root;
}
else
{
for (int i = 1; i < path.Count(); ++i)
{
destination = destination.Childs.FirstOrDefault(c => c.Name == path.ElementAt(i));
if (destination == null) //Opps that is a problem, path doesn't exist
{
return null;
}
}
}
return destination;
}
//Creates ouput that can be sent as Json. Categories name separated by ~
private string getDelimiterSeparatedCategories(Category category)
{
string retVal = category.Name;
foreach (var c in category.Childs)
{
retVal += "~" + c.Name;
}
return retVal;
}
//API to create Category
[HttpPost]
public JsonResult CreateCategory(IEnumerable<string> path, string name)
{
Category root = getAllCategories();
Category destination = navigateToCategory(path, root);
if (destination == null)
{
return Json(new { Result = "ERROR" });
}
destination.Childs.Add(new Category(name));
string retVal = getDelimiterSeparatedCategories(destination);
saveAllCategories(root);
return Json(new { Result = "OK", Data = retVal});
}
//API to retrieve category
[HttpPost]
public JsonResult RetrieveCategories(IEnumerable<string> path)
{
Category root = getAllCategories();
Category destination = navigateToCategory(path, root);
if (destination == null)
{
return Json(new { Result = "ERROR" });
}
string retVal = getDelimiterSeparatedCategories(destination);
return Json(new { Result = "OK", Data = retVal });
}
//API to update category
[HttpPost]
public JsonResult UpdateCategory(IEnumerable<string> path, string name)
{
if (path.Count() == 1)
{
return Json(new { Result = "ERROR" });
}
Category root = getAllCategories();
Category destination = navigateToCategory(path, root);
if (destination == null)
{
return Json(new { Result = "ERROR" });
}
destination.Name = name;
string retVal = getDelimiterSeparatedCategories(destination);
saveAllCategories(root);
return Json(new { Result = "OK", Data = retVal });
}
//API to delete category
[HttpPost]
public JsonResult DeleteCategory(IEnumerable<string> path)
{
if (path.Count() == 1)
{
return Json(new { Result = "ERROR" });
}
List<string> pathParent = new List<string>();
for (int i = 0; i < path.Count() - 1; ++i)
{
pathParent.Add(path.ElementAt(i));
}
Category root = getAllCategories();
Category parent = navigateToCategory(pathParent.AsEnumerable(), root);
Category destination = navigateToCategory(path, root);
if (destination == null)
{
return Json(new { Result = "ERROR" });
}
parent.Childs.Remove(destination);
saveAllCategories(root);
string retVal = getDelimiterSeparatedCategories(parent);
return Json(new { Result = "OK", Data = retVal });
}
}
As you can see, I have 4 API exposed for client to consume:
CreateCategory
RetrieveCategories
UpdateCategory
DeleteCategory
After finishing those, I went on to create the client that could consume those APIs:
@{
ViewBag.Title = "Home Page";
}
<h1 id="CurrentCategory"></h1>
<div id="CategoriesList"></div>
<input type="text" placeholder="Add Category" id="CategoryName" /><button onclick="addCategory()">Add Category</button><br />
<input type="text" placeholder="Update Category" id="NewName" /><button onclick="updateCategory()">Update Category</button><br />
<button onclick="deleteCategory()">Delete Category</button>
@section scripts {
<script type="text/javascript">
var cList = document.getElementById("CategoriesList");
var cCategory = document.getElementById("CurrentCategory")
var pArray = new Array();
pArray[0] = "root";
retrieveCategories();
function retrieveCategories() {
$.ajax({
url: '@Url.Action("RetrieveCategories", "Home")',
type: 'POST',
datatype: 'json',
data: { path: pArray },
success: function (data) {
var categories = getCategoriesList(data.Data);
var list = "<ul>";
cCategory.innerHTML = categories[0];
for (var i = 1; i < categories.length; ++i) {
list += "<li><a href=\"javascript:gotoParticularCategories('" + categories[i] + "')\">" + categories[i] + "</a></li>";
}
list += "</ul>";
cList.innerHTML = list;
},
error: function () {
cList.innerHTML = "There was an error! Please refresh the page";
}
});
}
function getCategoriesList(input) {
var retVal = input.split("~");
return retVal;
}
function gotoParticularCategories(category) {
pArray[pArray.length] = category;
retrieveCategories();
}
function deleteCategory() {
$.ajax({
url: '@Url.Action("DeleteCategory", "Home")',
type: 'POST',
datatype: 'json',
data: { path: pArray },
success: function (data) {
if (data.Result != "ERROR") {
pArray.splice(pArray.length - 1, 1);
}
var categories = getCategoriesList(data.Data);
var list = "<ul>";
cCategory.innerHTML = categories[0];
for (var i = 1; i < categories.length; ++i) {
list += "<li><a href=\"javascript:gotoParticularCategories('" + categories[i] + "')\">" + categories[i] + "</a></li>";
}
list += "</ul>";
cList.innerHTML = list;
},
error: function () {
cList.innerHTML = "There was an error! Please refresh the page";
}
});
}
function addCategory() {
var categoryName = document.getElementById("CategoryName").value;
$.ajax({
url: '@Url.Action("CreateCategory", "Home")',
type: 'POST',
datatype: 'json',
data: { path: pArray, name: categoryName },
success: function (data) {
var categories = getCategoriesList(data.Data);
var list = "<ul>";
cCategory.innerHTML = categories[0];
for (var i = 1; i < categories.length; ++i) {
list += "<li><a href=\"javascript:gotoParticularCategories('" + categories[i] + "')\">" + categories[i] + "</a></li>";
}
list += "</ul>";
cList.innerHTML = list;
},
error: function () {
cList.innerHTML = "There was an error! Please refresh the page";
}
});
}
function updateCategory() {
var categoryName = document.getElementById("NewName").value;
$.ajax({
url: '@Url.Action("UpdateCategory", "Home")',
type: 'POST',
datatype: 'json',
data: { path: pArray, name: categoryName },
success: function (data) {
var categories = getCategoriesList(data.Data);
var list = "<ul>";
cCategory.innerHTML = categories[0];
for (var i = 1; i < categories.length; ++i) {
list += "<li><a href=\"javascript:gotoParticularCategories('" + categories[i] + "')\">" + categories[i] + "</a></li>";
}
list += "</ul>";
cList.innerHTML = list;
},
error: function () {
cList.innerHTML = "There was an error! Please refresh the page";
}
});
}
</script>
}
I know my code could have been structured so much better but I was really trying to finish this in time and if I had time I would have refactored it so it looked much better. For the client I am using AJAX calls to call the APIs I had created before.
At the end, I had a functional web application that did everything they asked for and I sent it to them on time, but I am concerned that it might not have been enough. I feel like I could have done a lot better and I am really passionate about programming and really want this internship. It's been 2 days already and they haven't contacted me and I am really stressed out.
I just wanted to ask if you think I did enough to pass through it or not.
If you want to download the project and play around with it, it's here.