Browse files

initial api commit

all get method implemented. Heavy refactoring to come soon.
  • Loading branch information...
1 parent e361fed commit 08d94b1c62b6a0333fcfa6d3538352a3037e4e0e @cbarbara committed Jun 17, 2012
Showing with 39,242 additions and 10 deletions.
  1. +31 −6 .gitignore
  2. +32 −0 Asana-API.sln
  3. +14 −0 AsanaApi.Tests/App.config
  4. +89 −0 AsanaApi.Tests/AsanaApi.Tests.csproj
  5. +27 −0 AsanaApi.Tests/BaseTest.cs
  6. +31 −0 AsanaApi.Tests/Models/TargetTests.cs
  7. +36 −0 AsanaApi.Tests/Properties/AssemblyInfo.cs
  8. +45 −0 AsanaApi.Tests/Service/ProjectsTests.cs
  9. +52 −0 AsanaApi.Tests/Service/StoriesTests.cs
  10. +79 −0 AsanaApi.Tests/Service/TasksTests.cs
  11. +105 −0 AsanaApi.Tests/Service/UserTests.cs
  12. +41 −0 AsanaApi.Tests/Service/WorkspaceTests.cs
  13. +4 −0 AsanaApi.Tests/packages.config
  14. +79 −0 AsanaApi/AsanaApi.csproj
  15. +12 −0 AsanaApi/Models/AsanaApiBase.cs
  16. +12 −0 AsanaApi/Models/Assignee.cs
  17. +24 −0 AsanaApi/Models/ITarget.cs
  18. +74 −0 AsanaApi/Models/OptionalFields.cs
  19. +12 −0 AsanaApi/Models/Project.cs
  20. +27 −0 AsanaApi/Models/SchedulingStatus.cs
  21. +22 −0 AsanaApi/Models/Story.cs
  22. +31 −0 AsanaApi/Models/StorySource.cs
  23. +19 −0 AsanaApi/Models/StoryType.cs
  24. +22 −0 AsanaApi/Models/TargetBase.cs
  25. +22 −0 AsanaApi/Models/Task.cs
  26. +14 −0 AsanaApi/Models/User.cs
  27. +12 −0 AsanaApi/Models/Workspace.cs
  28. +38 −0 AsanaApi/Properties/AssemblyInfo.cs
  29. +77 −0 AsanaApi/Service/AsanaApiRequest.cs
  30. +89 −0 AsanaApi/Service/AsanaApiResponse.cs
  31. +128 −0 AsanaApi/Service/DynamicJsonConverter.cs
  32. +338 −0 AsanaApi/Service/ObjectConversions.cs
  33. +53 −0 AsanaApi/Service/ProjectsService.cs
  34. +41 −0 AsanaApi/Service/StoriesService.cs
  35. +57 −0 AsanaApi/Service/TasksQuery.cs
  36. +62 −0 AsanaApi/Service/TasksService.cs
  37. +54 −0 AsanaApi/Service/UsersService.cs
  38. +31 −0 AsanaApi/Service/WorkspacesService.cs
  39. +10 −4 README.md
  40. +637 −0 Website/Content/Site.css
  41. +18 −0 Website/Controllers/HomeController.cs
  42. +36 −0 Website/Controllers/ProjectsController.cs
  43. +25 −0 Website/Controllers/StoriesController.cs
  44. +34 −0 Website/Controllers/TasksController.cs
  45. +32 −0 Website/Controllers/UsersController.cs
  46. +24 −0 Website/Controllers/WorkspacesController.cs
  47. +1 −0 Website/Global.asax
  48. +47 −0 Website/Global.asax.cs
  49. +15 −0 Website/Models/Projects/ProjectsListModel.cs
  50. +15 −0 Website/Models/Stories/TaskStoriesListModel.cs
  51. +19 −0 Website/Models/Tasks/TasksListModel.cs
  52. +15 −0 Website/Models/Users/UsersListModel.cs
  53. +14 −0 Website/Models/Workspaces/WorkspacesListModel.cs
  54. +35 −0 Website/Properties/AssemblyInfo.cs
  55. +110 −0 Website/Scripts/AjaxLogin.js
  56. +5 −0 Website/Scripts/_references.js
  57. +9,134 −0 Website/Scripts/jquery-1.6.2-vsdoc.js
  58. +9,007 −0 Website/Scripts/jquery-1.6.2.js
  59. +45 −0 Website/Scripts/jquery-1.6.2.min.js
  60. +12,515 −0 Website/Scripts/jquery-ui-1.8.11.js
  61. +1,682 −0 Website/Scripts/jquery-ui-1.8.11.min.js
  62. +163 −0 Website/Scripts/jquery.unobtrusive-ajax.js
  63. +5 −0 Website/Scripts/jquery.unobtrusive-ajax.min.js
  64. +1,323 −0 Website/Scripts/jquery.validate-vsdoc.js
  65. +1,194 −0 Website/Scripts/jquery.validate.js
  66. +79 −0 Website/Scripts/jquery.validate.min.js
  67. +345 −0 Website/Scripts/jquery.validate.unobtrusive.js
  68. +5 −0 Website/Scripts/jquery.validate.unobtrusive.min.js
  69. +19 −0 Website/Shared.cs
  70. +35 −0 Website/Views/Home/Index.cshtml
  71. +40 −0 Website/Views/Projects/Index.cshtml
  72. +10 −0 Website/Views/Shared/Error.cshtml
  73. +41 −0 Website/Views/Shared/_Layout.cshtml
  74. +32 −0 Website/Views/Stories/TaskStories.cshtml
  75. +55 −0 Website/Views/Tasks/Index.cshtml
  76. +58 −0 Website/Views/Users/Index.cshtml
  77. +58 −0 Website/Views/Web.config
  78. +24 −0 Website/Views/Workspaces/Index.cshtml
  79. +3 −0 Website/Views/_ViewStart.cshtml
  80. +30 −0 Website/Web.Debug.config
  81. +31 −0 Website/Web.Release.config
  82. +49 −0 Website/Web.config
  83. +215 −0 Website/Website.csproj
  84. +17 −0 Website/packages.config
View
37 .gitignore
@@ -1,6 +1,31 @@
-# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
-bin
-obj
-
-# mstest test results
-TestResults
+#ignore thumbnails created by windows
+Thumbs.db
+#Ignore files build by Visual Studio
+*.obj
+*.pdb
+*.user
+*.aps
+*.pch
+*.vspscc
+*_i.c
+*_p.c
+*.ncb
+*.suo
+*.tlb
+*.tlh
+*.bak
+*.cache
+*.ilk
+*.log
+ClientBin
+[Bb]in
+[Dd]ebug*/
+*.lib
+*.sbr
+obj/
+[Rr]elease*/
+_ReSharper*/
+*.csproj.user
+*.resharper*
+#nuget stuff
+packages
View
32 Asana-API.sln
@@ -0,0 +1,32 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Website", "Website\Website.csproj", "{73B28B91-C49F-4B36-B7CF-C0E6C8994486}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsanaApi", "AsanaApi\AsanaApi.csproj", "{3A261803-E7CA-4F47-B216-F559660869FB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsanaApi.Tests", "AsanaApi.Tests\AsanaApi.Tests.csproj", "{40E31301-EA74-45F4-BBA8-0EFE93B33825}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {73B28B91-C49F-4B36-B7CF-C0E6C8994486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {73B28B91-C49F-4B36-B7CF-C0E6C8994486}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {73B28B91-C49F-4B36-B7CF-C0E6C8994486}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {73B28B91-C49F-4B36-B7CF-C0E6C8994486}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3A261803-E7CA-4F47-B216-F559660869FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3A261803-E7CA-4F47-B216-F559660869FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3A261803-E7CA-4F47-B216-F559660869FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3A261803-E7CA-4F47-B216-F559660869FB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {40E31301-EA74-45F4-BBA8-0EFE93B33825}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {40E31301-EA74-45F4-BBA8-0EFE93B33825}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {40E31301-EA74-45F4-BBA8-0EFE93B33825}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {40E31301-EA74-45F4-BBA8-0EFE93B33825}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
View
14 AsanaApi.Tests/App.config
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <appSettings>
+
+ <!--START Replace Me-->
+ <add key="testApiKey" value="XXXXXXXXXXXXXXXXXXX" />
+ <add key="testUserId" value="123123123123123" />
+ <add key="testUserName" value="User Name" />
+ <add key="testWorkspaceId" value="123123123123123" />
+ <add key="testProjectId" value="123123123123123" />
+ <!--END Replace Me-->
+
+ </appSettings>
+</configuration>
View
89 AsanaApi.Tests/AsanaApi.Tests.csproj
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{40E31301-EA74-45F4-BBA8-0EFE93B33825}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>AsanaApi.Tests</RootNamespace>
+ <AssemblyName>AsanaApi.Tests</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Gallio, Version=3.3.0.0, Culture=neutral, PublicKeyToken=eb9cfa67ee6ab36e, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\mbunit.3.3.454.0\lib\net40\Gallio.dll</HintPath>
+ </Reference>
+ <Reference Include="Gallio40, Version=3.3.0.0, Culture=neutral, PublicKeyToken=eb9cfa67ee6ab36e, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\mbunit.3.3.454.0\lib\net40\Gallio40.dll</HintPath>
+ </Reference>
+ <Reference Include="MbUnit, Version=3.3.0.0, Culture=neutral, PublicKeyToken=eb9cfa67ee6ab36e, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\mbunit.3.3.454.0\lib\net40\MbUnit.dll</HintPath>
+ </Reference>
+ <Reference Include="MbUnit40, Version=3.3.0.0, Culture=neutral, PublicKeyToken=eb9cfa67ee6ab36e, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\mbunit.3.3.454.0\lib\net40\MbUnit40.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Configuration" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="BaseTest.cs" />
+ <Compile Include="Models\TargetTests.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Service\StoriesTests.cs" />
+ <Compile Include="Service\ProjectsTests.cs" />
+ <Compile Include="Service\TasksTests.cs" />
+ <Compile Include="Service\UserTests.cs" />
+ <Compile Include="Service\WorkspaceTests.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\AsanaApi\AsanaApi.csproj">
+ <Project>{3A261803-E7CA-4F47-B216-F559660869FB}</Project>
+ <Name>AsanaApi</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config">
+ <SubType>Designer</SubType>
+ </None>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
View
27 AsanaApi.Tests/BaseTest.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Tests
+{
+ public abstract class BaseTest
+ {
+ protected readonly string API_KEY,
+ TEST_USER_NAME;
+
+ protected readonly long TEST_USER_ID,
+ TEST_WORKSPACE_ID,
+ TEST_PROJECT_ID;
+
+ public BaseTest()
+ {
+ API_KEY = System.Configuration.ConfigurationManager.AppSettings[ "testApiKey" ];
+ TEST_USER_NAME = System.Configuration.ConfigurationManager.AppSettings[ "testUserName" ];
+
+ TEST_USER_ID = long.Parse( System.Configuration.ConfigurationManager.AppSettings[ "testUserId" ] );
+ TEST_WORKSPACE_ID = long.Parse( System.Configuration.ConfigurationManager.AppSettings[ "testWorkspaceId" ] );
+ TEST_PROJECT_ID = long.Parse( System.Configuration.ConfigurationManager.AppSettings[ "testProjectId" ] );
+ }
+ }
+}
View
31 AsanaApi.Tests/Models/TargetTests.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using MbUnit.Framework;
+using AsanaApi.Models;
+
+namespace AsanaApi.Tests.Models
+{
+ [TestFixture]
+ public class TargetTests : BaseTest
+ {
+ [Test]
+ public void PointlessTest()
+ {
+ Project project = new Project() { Id = 100 };
+ Task task = new Task(){ Id = 100 };
+
+ Assert.IsTrue( project is ITarget, "project should be a ITarget" );
+ Assert.IsTrue( task is ITarget, "project should be a ITarget" );
+
+ AssertITargetId( 100, project );
+ AssertITargetId( 100, task );
+ }
+
+ private void AssertITargetId( long id, ITarget iTarget )
+ {
+ Assert.AreEqual( id, iTarget.Id, "Id is incorrect" );
+ }
+ }
+}
View
36 AsanaApi.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle( "AsanaApi.Tests" )]
+[assembly: AssemblyDescription( "" )]
+[assembly: AssemblyConfiguration( "" )]
+[assembly: AssemblyCompany( "Everyone" )]
+[assembly: AssemblyProduct( "AsanaApi.Tests" )]
+[assembly: AssemblyCopyright( "Copyright © Everyone 2012" )]
+[assembly: AssemblyTrademark( "" )]
+[assembly: AssemblyCulture( "" )]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible( false )]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid( "7ddecd40-826a-4a9f-97a1-1e58450baa3d" )]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion( "1.0.0.0" )]
+[assembly: AssemblyFileVersion( "1.0.0.0" )]
View
45 AsanaApi.Tests/Service/ProjectsTests.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using MbUnit.Framework;
+using AsanaApi.Service;
+using AsanaApi.Models;
+
+namespace AsanaApi.Tests.Service
+{
+ [TestFixture]
+ public class ProjectsTests : BaseTest
+ {
+ [Test]
+ public void GetProjects()
+ {
+ ProjectsService service = new ProjectsService( API_KEY );
+ Project[] projects = service.GetProjects();
+ Assert.IsNotNull( projects, "we should have gotten some projects back" );
+ foreach( var item in projects )
+ {
+ Assert.IsNotNull( item.Name, "name should always come back" );
+ Assert.AreEqual( DateTime.MinValue, item.CreatedAt, "CreatedAt isn't populated by default" );
+ Assert.IsFalse( item.ModifiedAt.HasValue, "modified date is null" );
+ Assert.IsNull( item.Followers, "shouldn't be any followers" );
+ }
+ }
+
+ [Test]
+ public void GetProjectsWithFields()
+ {
+ ProjectsService service = new ProjectsService( API_KEY );
+ Project[] projects = service.GetProjects( new OptionalFields[] { OptionalFields.Project_CreatedAt, OptionalFields.Project_ModifiedAt } );
+ Assert.IsNotNull( projects, "we should have gotten some projects back" );
+
+ foreach( var item in projects )
+ {
+ Assert.IsNotNull( item.Name, "name should always come back" );
+ Assert.AreNotEqual( DateTime.MinValue, item.CreatedAt, "CreatedAt should be populated" );
+ Assert.IsTrue( item.ModifiedAt.HasValue, "modified date shouldn't be null" );
+ Assert.IsNull( item.Followers, "shouldn't be any followers" );
+ }
+ }
+ }
+}
View
52 AsanaApi.Tests/Service/StoriesTests.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using MbUnit.Framework;
+using AsanaApi.Service;
+using AsanaApi.Models;
+
+namespace AsanaApi.Tests.Service
+{
+ [TestFixture]
+ public class StoriesTests : BaseTest
+ {
+ [Test]
+ public void GetStories()
+ {
+ TasksService ts = new TasksService( API_KEY );
+ Task t = ts.GetTasksInProject( TEST_PROJECT_ID )[ 0 ];
+
+ StoriesService service = new StoriesService( API_KEY );
+ Story[] stories = service.GetStories( t );
+ Assert.IsNotNull( stories, "we should have gotten some stories back" );
+ Assert.IsTrue( stories.Length > 0, "stories.Length was only " + stories.Length.ToString() );
+ foreach( var item in stories )
+ {
+ Assert.IsNotNull( item.Text, "text should always come back" );
+ Assert.IsTrue( item.Type.HasValue, "type should always be populated" );
+ Assert.IsNotNull( item.CreatedBy, "CreatedBy should be back now" );
+ }
+ }
+
+ [Test]
+ public void GetTotalStory()
+ {
+ TasksService ts = new TasksService( API_KEY );
+ Task t = ts.GetTasksInProject( TEST_PROJECT_ID )[ 0 ];
+
+ StoriesService service = new StoriesService( API_KEY );
+ Story[] stories = service.GetStories( t, StoriesService.AllOptionalFieldsForStories );
+ Assert.IsNotNull( stories, "we should have gotten some stories back" );
+ Assert.IsTrue( stories.Length > 0, "stories.Length was only " + stories.Length.ToString() );
+ foreach( var item in stories )
+ {
+ Assert.IsNotNull( item.Text, "text should always come back" );
+ Assert.IsTrue( item.Type.HasValue, "type should always be populated" );
+ Assert.IsNotNull( item.CreatedBy, "CreatedBy should be back now" );
+ Assert.IsNotNull( item.Target, "Target should be back now" );
+ Assert.IsTrue( item.Source.HasValue, "Source should be back now" );
+ }
+ }
+ }
+}
View
79 AsanaApi.Tests/Service/TasksTests.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using MbUnit.Framework;
+using AsanaApi.Service;
+using AsanaApi.Models;
+
+namespace AsanaApi.Tests.Service
+{
+ [TestFixture]
+ public class TasksTests : BaseTest
+ {
+ [Test]
+ public void GetAllMyTasks()
+ {
+ TasksService taskService = new TasksService( API_KEY );
+ Task[] tasks = taskService.GetTasks( new TasksQuery() { AssigneeId = TEST_USER_ID, WorkspaceId = TEST_WORKSPACE_ID } );
+ Assert.IsNotNull( tasks, "we should have gotten some tasks back" );
+ foreach( var item in tasks )
+ {
+ Assert.AreEqual( DateTime.MinValue, item.CreatedAt, "Since ReturnCompleteTaskRecords is false, CreatedAt shouldn't come back" );
+ }
+ }
+
+ [Test]
+ public void GetAllMyTasksWithDetails()
+ {
+ TasksService taskService = new TasksService( API_KEY );
+ Task[] tasks = taskService.GetTasks( new TasksQuery() { AssigneeId = TEST_USER_ID, WorkspaceId = TEST_WORKSPACE_ID, ReturnCompleteTaskRecords = true } );
+ Assert.IsNotNull( tasks, "we should have gotten some tasks back" );
+ foreach( var item in tasks )
+ {
+ Assert.AreNotEqual( DateTime.MinValue, item.CreatedAt, "Since ReturnCompleteTaskRecords is true, CreatedAt should come back" );
+ }
+ }
+
+ [Test]
+ public void GetTasksInProject()
+ {
+ TasksService taskService = new TasksService( API_KEY );
+ Task[] tasks = taskService.GetTasks( new TasksQuery() { ProjectId = TEST_PROJECT_ID } );
+ Assert.IsNotNull( tasks, "we should have gotten some tasks back" );
+ Assert.IsTrue( tasks.Length > 0, "task.Length should be > 0, but it was " + tasks.Length.ToString() );
+ foreach( var item in tasks )
+ {
+ Assert.AreEqual( DateTime.MinValue, item.CreatedAt, "Since ReturnCompleteTaskRecords is false, CreatedAt shouldn't come back" );
+ }
+
+
+ Task[] tasks2 = taskService.GetTasksInProject( TEST_PROJECT_ID );
+ Assert.IsNotNull( tasks2, "we should have gotten some tasks back" );
+ Assert.IsTrue( tasks2.Length > 0, "task.Length should be > 0, but it was " + tasks2.Length.ToString() );
+ foreach( var item in tasks2 )
+ {
+ Assert.AreEqual( DateTime.MinValue, item.CreatedAt, "Since ReturnCompleteTaskRecords is false, CreatedAt shouldn't come back" );
+ }
+
+ CompareTasks( tasks, tasks2 );
+ }
+
+ private void CompareTasks( Task[] tasks, Task[] tasks2 )
+ {
+ Assert.AreEqual( tasks.Length, tasks2.Length, "we should get the same tasks back" );
+ for( int i = 0; i < tasks.Length; i++ )
+ {
+ CompareTasks( tasks[ i ], tasks2[ i ] );
+ }
+ }
+ private void CompareTasks( Task task, Task task_2 )
+ {
+ Assert.AreEqual( task.Id, task_2.Id, "wrong id" );
+ Assert.AreEqual( task.Name, task_2.Name, "wrong Name" );
+ Assert.AreEqual( task.CreatedAt, task_2.CreatedAt, "wrong CreatedAt" );
+ Assert.AreEqual( task.CompletedAt, task_2.CompletedAt, "wrong CompletedAt" );
+ Assert.AreEqual( task.Notes, task_2.Notes, "wrong Notes" );
+ }
+ }
+}
View
105 AsanaApi.Tests/Service/UserTests.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using MbUnit.Framework;
+using AsanaApi.Service;
+using AsanaApi.Models;
+
+namespace AsanaApi.Tests.Service
+{
+ [TestFixture]
+ public class UserTests : BaseTest
+ {
+ [Test]
+ public void GetMyUser()
+ {
+ UsersService service = new UsersService( API_KEY );
+ User user = service.GetUser();
+ Assert.IsNotNull( user );
+ }
+
+ [Test]
+ public void GetUser()
+ {
+ UsersService service = new UsersService( API_KEY );
+ User user = service.GetUser( TEST_USER_ID );
+ Assert.IsNotNull( user );
+ Assert.AreEqual( TEST_USER_ID, user.Id, "user id is incorrect" );
+ Assert.AreEqual( TEST_USER_NAME, user.Name, "name is incorrect" );
+ Assert.IsNotNull( user.Email, "email shouldn't be null" );
+ Assert.IsNotNull( user.Workspaces, "Workspaces shouldn't be null" );
+ Assert.IsTrue( user.Workspaces.Length > 0, "user.Workspaces.Length == " + user.Workspaces.Length.ToString() + " - it should be > 0 " );
+
+ }
+
+ [Test]
+ public void GetUsers()
+ {
+ UsersService service = new UsersService( API_KEY );
+ User[] users = service.GetUsers();
+ Assert.IsNotNull( users );
+ Assert.IsTrue( users.Length > 0, "users.Length == " + users.Length.ToString() + " - it should be > 0 " );
+ Assert.IsNull( users[ 0 ].Workspaces, "Workspaces should come back null" );
+ Assert.IsNull( users[ 0 ].Email, "Email should come back null" );
+ }
+
+ [Test]
+ public void GetUsersInWorkspace()
+ {
+ UsersService service = new UsersService( API_KEY );
+ User[] users = service.GetUsersInWorkspace( TEST_WORKSPACE_ID );
+ Assert.IsNotNull( users );
+ Assert.IsTrue( users.Length > 0, "users.Length == " + users.Length.ToString() + " - it should be > 0 " );
+ Assert.IsNull( users[ 0 ].Workspaces, "Workspaces should come back null" );
+ Assert.IsNull( users[ 0 ].Email, "Email should come back null" );
+ }
+
+ [Test]
+ public void GetUsersWithEmail()
+ {
+ UsersService service = new UsersService( API_KEY );
+ User[] users = service.GetUsers( new OptionalFields[] { OptionalFields.User_Email } );
+ Assert.IsNotNull( users );
+ Assert.IsTrue( users.Length > 0, "users.Length == " + users.Length.ToString() + " - it should be > 0 " );
+ Assert.IsNull( users[ 0 ].Workspaces, "Workspaces should come back null" );
+ Assert.IsNotNull( users[ 0 ].Email, "Email should come back" );
+ }
+
+ [Test]
+ public void GetUsersWithWorkspaces()
+ {
+ UsersService service = new UsersService( API_KEY );
+ User[] users = service.GetUsers( new OptionalFields[] { OptionalFields.User_Workspaces } );
+ Assert.IsNotNull( users );
+ Assert.IsTrue( users.Length > 0, "users.Length == " + users.Length.ToString() + " - it should be > 0 " );
+ Assert.IsNotNull( users[ 0 ].Workspaces, "Workspaces should come back" );
+ Assert.IsNull( users[ 0 ].Email, "Email should come back null" );
+ }
+
+ [Test]
+ public void GetUsersWithWorkspacesAndEmail()
+ {
+ UsersService service = new UsersService( API_KEY );
+ User[] users = service.GetUsers( new OptionalFields[] { OptionalFields.User_Workspaces, OptionalFields.User_Email } );
+ Assert.IsNotNull( users );
+ Assert.IsTrue( users.Length > 0, "users.Length == " + users.Length.ToString() + " - it should be > 0 " );
+ Assert.IsNotNull( users[ 0 ].Workspaces, "Workspaces should come back" );
+ Assert.IsNotNull( users[ 0 ].Email, "Email should come back" );
+ }
+
+ [Test]
+ public void GetUsersWithWorkspacesAndEmail_DifferentEnumOrdering()
+ {
+ UsersService service = new UsersService( API_KEY );
+ User[] users = service.GetUsers( new OptionalFields[] { OptionalFields.User_Workspaces, OptionalFields.User_Email } );
+ Assert.IsNotNull( users );
+ Assert.IsTrue( users.Length > 0, "users.Length == " + users.Length.ToString() + " - it should be > 0 " );
+
+ Assert.IsNotNull( users[ 0 ].Workspaces, "Workspaces should come back" );
+ Assert.IsNotNull( users[ 0 ].Email, "Email should come back" );
+ Assert.AreEqual( TEST_USER_ID, users[ 0 ].Id, "user id is incorrect" );
+ Assert.AreEqual( TEST_USER_NAME, users[ 0 ].Name, "name is incorrect" );
+ }
+ }
+}
View
41 AsanaApi.Tests/Service/WorkspaceTests.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using MbUnit.Framework;
+using AsanaApi.Service;
+using AsanaApi.Models;
+
+namespace AsanaApi.Tests.Service
+{
+ [TestFixture]
+ public class WorkspaceTests : BaseTest
+ {
+ [Test]
+ public void GetWorkspaces()
+ {
+ WorkspacesService service = new WorkspacesService( API_KEY );
+ Workspace[] workspaces = service.GetWorkspaces();
+ Assert.IsNotNull( workspaces, "we should have gotten some workspaces back" );
+ foreach( var item in workspaces )
+ {
+ Assert.IsNotNull( item.Name, "name should always come back" );
+ }
+ }
+
+ [Test]
+ public void GetProjectsOfWorkspace()
+ {
+ WorkspacesService service = new WorkspacesService( API_KEY );
+ Project[] projects = service.GetProjects( TEST_WORKSPACE_ID );
+ Assert.IsNotNull( projects, "we should have gotten some projects back" );
+ foreach( var item in projects )
+ {
+ Assert.IsNotNull( item.Name, "name should always come back" );
+ Assert.AreEqual( DateTime.MinValue, item.CreatedAt, "CreatedAt isn't populated by default" );
+ Assert.IsFalse( item.ModifiedAt.HasValue, "modified date is null" );
+ Assert.IsNull( item.Followers, "shouldn't be any followers" );
+ }
+ }
+ }
+}
View
4 AsanaApi.Tests/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="mbunit" version="3.3.454.0" />
+</packages>
View
79 AsanaApi/AsanaApi.csproj
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{3A261803-E7CA-4F47-B216-F559660869FB}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>AsanaApi</RootNamespace>
+ <AssemblyName>AsanaApi</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Web.Extensions" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Models\AsanaApiBase.cs" />
+ <Compile Include="Models\Assignee.cs" />
+ <Compile Include="Models\ITarget.cs" />
+ <Compile Include="Models\OptionalFields.cs" />
+ <Compile Include="Models\Project.cs" />
+ <Compile Include="Models\SchedulingStatus.cs" />
+ <Compile Include="Models\Story.cs" />
+ <Compile Include="Models\StorySource.cs" />
+ <Compile Include="Models\StoryType.cs" />
+ <Compile Include="Models\TargetBase.cs" />
+ <Compile Include="Models\Task.cs" />
+ <Compile Include="Models\User.cs" />
+ <Compile Include="Models\Workspace.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Service\AsanaApiRequest.cs" />
+ <Compile Include="Service\AsanaApiResponse.cs" />
+ <Compile Include="Service\StoriesService.cs" />
+ <Compile Include="Service\DynamicJsonConverter.cs" />
+ <Compile Include="Service\ObjectConversions.cs" />
+ <Compile Include="Service\ProjectsService.cs" />
+ <Compile Include="Service\TasksQuery.cs" />
+ <Compile Include="Service\TasksService.cs" />
+ <Compile Include="Service\UsersService.cs" />
+ <Compile Include="Service\WorkspacesService.cs" />
+ </ItemGroup>
+ <ItemGroup />
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
View
12 AsanaApi/Models/AsanaApiBase.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public abstract class AsanaApiBase
+ {
+ public long Id { get; set; }
+ }
+}
View
12 AsanaApi/Models/Assignee.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public class Assignee : AsanaApiBase
+ {
+ public string Name { get; set; }
+ }
+}
View
24 AsanaApi/Models/ITarget.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public interface ITarget
+ {
+ long Id { get; set; }
+
+ DateTime CreatedAt { get; set; }
+
+ Assignee[] Followers { get; set; }
+
+ DateTime? ModifiedAt { get; set; }
+
+ string Name { get; set; }
+
+ string Notes { get; set; }
+
+ Workspace Workspace { get; set; }
+ }
+}
View
74 AsanaApi/Models/OptionalFields.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public enum OptionalFields
+ {
+ User_Email,
+ User_Name,
+ User_Workspaces,
+ User_Workspaces_Name,
+
+ Project_CreatedAt,
+ Project_ModifiedAt,
+
+ Story_Source,
+ Story_Target,
+ Story_Target_Name,
+ }
+
+ internal class OptionFieldsHelper
+ {
+ internal static string ToOptionalFieldNames( OptionalFields[] returnedFields )
+ {
+ StringBuilder sb = new StringBuilder( returnedFields.Length * 10 );
+ for( int i = 0; i < returnedFields.Length; i++ )
+ {
+ if( i > 0 )
+ {
+ sb.Append( "," );
+ }
+ sb.Append( ToOptionalFieldName( returnedFields[ i ] ) );
+ }
+ return sb.ToString();
+ }
+
+ private static string ToOptionalFieldName( OptionalFields optionalField )
+ {
+ switch( optionalField )
+ {
+ case OptionalFields.User_Email:
+ return "email";
+
+ case OptionalFields.User_Name:
+ return "name";
+
+ case OptionalFields.User_Workspaces:
+ return "workspaces";
+
+ case OptionalFields.User_Workspaces_Name:
+ return "workspaces.name";
+
+ case OptionalFields.Project_CreatedAt:
+ return "created_at";
+
+ case OptionalFields.Project_ModifiedAt:
+ return "modified_at";
+
+ case OptionalFields.Story_Source:
+ return "source";
+
+ case OptionalFields.Story_Target:
+ return "target";
+
+ case OptionalFields.Story_Target_Name:
+ return "target.name";
+
+ }
+ return "";
+ }
+ }
+}
View
12 AsanaApi/Models/Project.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public class Project : TargetBase, ITarget
+ {
+ public bool IsArchived { get; set; }
+ }
+}
View
27 AsanaApi/Models/SchedulingStatus.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public enum SchedulingStatus
+ {
+ /// <summary>
+ /// In the inbox.
+ /// </summary>
+ Inbox,
+ /// <summary>
+ /// Scheduled for later.
+ /// </summary>
+ Later,
+ /// <summary>
+ /// Scheduled for today.
+ /// </summary>
+ Today,
+ /// <summary>
+ /// Marked as upcoming.
+ /// </summary>
+ Upcoming
+ }
+}
View
22 AsanaApi/Models/Story.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public class Story : AsanaApiBase
+ {
+ public DateTime CreatedAt { get; set; }
+
+ public Assignee CreatedBy { get; set; }
+
+ public string Text { get; set; }
+
+ public ITarget Target { get; set; }
+
+ public StorySource? Source { get; set; }
+
+ public StoryType? Type { get; set; }
+ }
+}
View
31 AsanaApi/Models/StorySource.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public enum StorySource
+ {
+ /// <summary>
+ /// Via the Asana web app.
+ /// </summary>
+ Web,
+ /// <summary>
+ /// Via email.
+ /// </summary>
+ Email,
+ /// <summary>
+ /// Via the Asana mobile app.
+ /// </summary>
+ Mobile,
+ /// <summary>
+ /// Via the Asana API.
+ /// </summary>
+ Api,
+ /// <summary>
+ /// Unknown or unrecorded.
+ /// </summary>
+ unknown
+ }
+}
View
19 AsanaApi/Models/StoryType.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public enum StoryType
+ {
+ /// <summary>
+ /// A comment from a user. The text will be the message portion of the comment.
+ /// </summary>
+ Comment,
+ /// <summary>
+ /// A system-generated story based on a user action. The text will be a description of the action.
+ /// </summary>
+ System,
+ }
+}
View
22 AsanaApi/Models/TargetBase.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public abstract class TargetBase : AsanaApiBase, ITarget
+ {
+ public DateTime CreatedAt { get; set; }
+
+ public Assignee[] Followers { get; set; }
+
+ public DateTime? ModifiedAt { get; set; }
+
+ public string Name { get; set; }
+
+ public string Notes { get; set; }
+
+ public Workspace Workspace { get; set; }
+ }
+}
View
22 AsanaApi/Models/Task.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public class Task : TargetBase, ITarget
+ {
+ public Assignee Assignee { get; set; }
+
+ public SchedulingStatus? AssigneeStatus { get; set; }
+
+ public bool Completed { get; set; }
+
+ public DateTime? CompletedAt { get; internal set; }
+
+ public DateTime? DueDate { get; set; }
+
+ public Project[] Projects { get; set; }
+ }
+}
View
14 AsanaApi/Models/User.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public class User : Assignee
+ {
+ public string Email { get; set; }
+
+ public Workspace[] Workspaces { get; set; }
+ }
+}
View
12 AsanaApi/Models/Workspace.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Models
+{
+ public class Workspace : AsanaApiBase
+ {
+ public string Name { get; set; }
+ }
+}
View
38 AsanaApi/Properties/AssemblyInfo.cs
@@ -0,0 +1,38 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle( "AsanaApi" )]
+[assembly: AssemblyDescription( "" )]
+[assembly: AssemblyConfiguration( "" )]
+[assembly: AssemblyCompany( "Everyone" )]
+[assembly: AssemblyProduct( "AsanaApi" )]
+[assembly: AssemblyCopyright( "Copyright © Everyone 2012" )]
+[assembly: AssemblyTrademark( "" )]
+[assembly: AssemblyCulture( "" )]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible( false )]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid( "0c258754-2761-4e60-b050-0abc30f99bad" )]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion( "1.0.0.0" )]
+[assembly: AssemblyFileVersion( "1.0.0.0" )]
+
+[assembly: InternalsVisibleTo( "AsanaApi.Tests" )]
View
77 AsanaApi/Service/AsanaApiRequest.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Web.Script.Serialization;
+using System.Net;
+
+namespace AsanaApi.Service
+{
+ public abstract class AsanaApiRequest
+ {
+ const string URL_BASE = @"https://app.asana.com/api/1.0";
+ protected readonly string _apiKey;
+ readonly string _basicAuthenticationString;
+
+ public AsanaApiRequest( string ApiKey )
+ {
+ this._apiKey = ApiKey;
+ this._basicAuthenticationString = Convert.ToBase64String( new UTF8Encoding().GetBytes( _apiKey + ":" ) );
+ }
+
+ HttpWebRequest CreateClient( string urlPath )
+ {
+ HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create( URL_BASE + urlPath );
+ httpRequest.Method = "GET";
+ httpRequest.AllowAutoRedirect = false;
+ httpRequest.UseDefaultCredentials = false;
+ httpRequest.UserAgent = ".NET AsanaApi";
+ httpRequest.Headers[ "Authorization" ] = "Basic " + _basicAuthenticationString;
+ httpRequest.Accept = "application/json, text/json, text/x-json, text/javascript";
+ httpRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
+ httpRequest.KeepAlive = false;
+ return httpRequest;
+ }
+
+ protected dynamic ExecuteRequest( string urlPath )
+ {
+ return ExecuteRequest( urlPath, null, null );
+ }
+ protected dynamic ExecuteRequest( string urlPath, AsanaApi.Models.OptionalFields[] returnedFields, string fieldNamesToAppendToOptionalFieldNames )
+ {
+ HttpWebRequest httpRequest = CreateClient( AppendOptionalFields( urlPath, returnedFields, fieldNamesToAppendToOptionalFieldNames ) );
+ AsanaApiResponse response = new AsanaApiResponse().Execute( httpRequest ).CheckForError();
+
+ return response.GetContentAsDynamicObject( false );
+ }
+
+ private string AppendOptionalFields( string urlPath, Models.OptionalFields[] returnedFields, string fieldNamesToAppendToOptionalFieldNames )
+ {
+ if( returnedFields == null )
+ {
+ return urlPath;
+ }
+
+ string fieldNames = AsanaApi.Models.OptionFieldsHelper.ToOptionalFieldNames( returnedFields );
+ if( string.IsNullOrEmpty( fieldNames ) )
+ {
+ return urlPath;
+ }
+ if( string.IsNullOrEmpty( fieldNamesToAppendToOptionalFieldNames ) == false )
+ {
+ fieldNames += fieldNamesToAppendToOptionalFieldNames;
+ }
+
+ if( urlPath.Contains( "?" ) == false )
+ {
+ urlPath += "?";
+ }
+ else
+ {
+ urlPath += "&";
+ }
+
+ return ( urlPath + "opt_fields=" + fieldNames );
+ }
+ }
+}
View
89 AsanaApi/Service/AsanaApiResponse.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Net;
+using System.IO;
+using System.Web.Script.Serialization;
+
+namespace AsanaApi.Service
+{
+ class AsanaApiResponse
+ {
+ public bool Failed { get; private set; }
+ public string Content { get; private set; }
+ public HttpStatusCode StatusCode { get; private set; }
+
+ internal AsanaApiResponse Execute( HttpWebRequest request )
+ {
+ try
+ {
+ HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+ ReadResponse( response );
+ }
+ catch( WebException wex )
+ {
+ ReadResponse( (HttpWebResponse)wex.Response );
+
+ Failed = true;
+ }
+ catch( Exception )
+ {
+ Failed = true;
+ }
+
+ return this;
+ }
+
+ private void ReadResponse( HttpWebResponse response )
+ {
+ using( StreamReader sr = new StreamReader( response.GetResponseStream() ) )
+ {
+ this.StatusCode = response.StatusCode;
+
+ Content = sr.ReadToEnd();
+ sr.Close();
+ }
+ }
+
+ internal AsanaApiResponse CheckForError()
+ {
+ if( Failed == false )
+ {
+ return this;
+ }
+
+ switch( this.StatusCode )
+ {
+ case System.Net.HttpStatusCode.BadRequest:
+ //Invalid request. This usually occurs because of a missing or malformed parameter. Check the documentation and the syntax of your request and try again.
+ throw new ArgumentException( this.Content );
+
+ case System.Net.HttpStatusCode.Unauthorized:
+ //No authorization. A valid API key was not provided with the request, so the API could not associate a user with the request.
+ throw new UnauthorizedAccessException( "No authorization. A valid API key was not provided with the request, so the API could not associate a user with the request" );
+
+ case System.Net.HttpStatusCode.Forbidden:
+ throw new UnauthorizedAccessException( "Access denied. The API key was valid but the user does not have the access required to complete the request. This can happen if you try to read or write to objects that the user does not have access to." );
+
+ case System.Net.HttpStatusCode.NotFound:
+ throw new Exception( "Not found. Either the request method and path supplied do not specify a known action in the API, or the object specified by the request does not exist." );
+
+ case System.Net.HttpStatusCode.InternalServerError:
+ throw new Exception( "Server error. There was a problem on Asana's end." );
+ }
+
+ throw new Exception( "Unknown client error." );
+ }
+
+ internal dynamic GetContentAsDynamicObject( bool includeDataWrapper )
+ {
+ string data = includeDataWrapper ? Content : ( Content.Substring( 8, Content.Length - 9 ) );
+
+ var serializer = new JavaScriptSerializer();
+ serializer.RegisterConverters( new[] { new DynamicJsonConverter() } );
+
+ return serializer.Deserialize( data, typeof( object ) );
+ }
+ }
+}
View
128 AsanaApi/Service/DynamicJsonConverter.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Dynamic;
+using System.Linq;
+using System.Text;
+using System.Web.Script.Serialization;
+
+namespace AsanaApi.Service
+{
+ sealed class DynamicJsonConverter : JavaScriptConverter
+ {
+ public override object Deserialize( IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer )
+ {
+ if( dictionary == null )
+ throw new ArgumentNullException( "dictionary" );
+
+ return type == typeof( object ) ? new DynamicJsonObject( dictionary ) : null;
+ }
+
+ public override IDictionary<string, object> Serialize( object obj, JavaScriptSerializer serializer )
+ {
+ throw new NotImplementedException();
+ }
+
+ public override IEnumerable<Type> SupportedTypes
+ {
+ get { return new ReadOnlyCollection<Type>( new List<Type>( new[] { typeof( object ) } ) ); }
+ }
+
+ #region Nested type: DynamicJsonObject
+
+ private sealed class DynamicJsonObject : DynamicObject
+ {
+ private readonly IDictionary<string, object> _dictionary;
+
+ public DynamicJsonObject( IDictionary<string, object> dictionary )
+ {
+ if( dictionary == null )
+ throw new ArgumentNullException( "dictionary" );
+ _dictionary = dictionary;
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder( "{" );
+ ToString( sb );
+ return sb.ToString();
+ }
+
+ private void ToString( StringBuilder sb )
+ {
+ var firstInDictionary = true;
+ foreach( var pair in _dictionary )
+ {
+ if( !firstInDictionary )
+ sb.Append( "," );
+ firstInDictionary = false;
+ var value = pair.Value;
+ var name = pair.Key;
+ if( value is string )
+ {
+ sb.AppendFormat( "{0}:\"{1}\"", name, value );
+ }
+ else if( value is IDictionary<string, object> )
+ {
+ new DynamicJsonObject( (IDictionary<string, object>)value ).ToString( sb );
+ }
+ else if( value is ArrayList )
+ {
+ sb.Append( name + ":[" );
+ var firstInArray = true;
+ foreach( var arrayValue in (ArrayList)value )
+ {
+ if( !firstInArray )
+ sb.Append( "," );
+ firstInArray = false;
+ if( arrayValue is IDictionary<string, object> )
+ new DynamicJsonObject( (IDictionary<string, object>)arrayValue ).ToString( sb );
+ else if( arrayValue is string )
+ sb.AppendFormat( "\"{0}\"", arrayValue );
+ else
+ sb.AppendFormat( "{0}", arrayValue );
+
+ }
+ sb.Append( "]" );
+ }
+ else
+ {
+ sb.AppendFormat( "{0}:{1}", name, value );
+ }
+ }
+ sb.Append( "}" );
+ }
+
+ public override bool TryGetMember( GetMemberBinder binder, out object result )
+ {
+ if( !_dictionary.TryGetValue( binder.Name, out result ) )
+ {
+ // return null to avoid exception. caller can check for null this way...
+ result = null;
+ return true;
+ }
+
+ var dictionary = result as IDictionary<string, object>;
+ if( dictionary != null )
+ {
+ result = new DynamicJsonObject( dictionary );
+ return true;
+ }
+
+ var arrayList = result as ArrayList;
+ if( arrayList != null && arrayList.Count > 0 )
+ {
+ if( arrayList[ 0 ] is IDictionary<string, object> )
+ result = new List<object>( arrayList.Cast<IDictionary<string, object>>().Select( x => new DynamicJsonObject( x ) ) );
+ else
+ result = new List<object>( arrayList.Cast<object>() );
+ }
+
+ return true;
+ }
+ }
+
+ #endregion
+ }
+}
View
338 AsanaApi/Service/ObjectConversions.cs
@@ -0,0 +1,338 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using AsanaApi.Models;
+
+namespace AsanaApi.Service
+{
+ class ObjectConversions
+ {
+ internal static Assignee[] ToAssignees( dynamic dUsers )
+ {
+ if( dUsers == null )
+ {
+ return null;
+ }
+
+ List<Assignee> ret = new List<Assignee>();
+ foreach( var item in dUsers )
+ {
+ ret.Add( ToAssignee( item ) );
+ }
+ return ret.ToArray();
+ }
+ internal static Assignee ToAssignee( dynamic dUser )
+ {
+ if( dUser == null )
+ {
+ return null;
+ }
+
+ Assignee user = new Assignee()
+ {
+ Id = long.Parse( dUser.id.ToString() ),
+ Name = dUser.name
+ };
+
+ return user;
+ }
+ internal static User ToUser( dynamic dUser )
+ {
+ if( dUser == null )
+ {
+ return null;
+ }
+
+ User user = new User()
+ {
+ Id = long.Parse( dUser.id.ToString() ),
+ Name = dUser.name,
+ Email = dUser.email
+ };
+
+ user.Workspaces = ToWorkspaces( dUser.workspaces );
+
+ return user;
+ }
+ internal static User[] ToUsers( dynamic dUsers )
+ {
+ if( dUsers == null )
+ {
+ return null;
+ }
+
+ List<User> ret = new List<User>();
+ foreach( var item in dUsers )
+ {
+ ret.Add( ToUser( item ) );
+ }
+ return ret.ToArray();
+ }
+
+ internal static Workspace[] ToWorkspaces( dynamic dWorkspaces )
+ {
+ if( dWorkspaces == null )
+ {
+ return null;
+ }
+
+ List<Workspace> ret = new List<Workspace>();
+ foreach( var item in dWorkspaces )
+ {
+ ret.Add( ToWorkspace( item ) );
+ }
+ return ret.ToArray();
+ }
+ internal static Workspace ToWorkspace( dynamic dWorkspace )
+ {
+ if( dWorkspace == null )
+ {
+ return null;
+ }
+
+ return new Workspace()
+ {
+ Id = long.Parse( dWorkspace.id.ToString() ),
+ Name = dWorkspace.name
+ };
+ }
+
+
+ internal static Task[] ToTasks( dynamic dTasks )
+ {
+ if( dTasks == null )
+ {
+ return null;
+ }
+
+ List<Task> ret = new List<Task>();
+ foreach( var item in dTasks )
+ {
+ ret.Add( ToTask( item ) );
+ }
+ return ret.ToArray();
+
+ }
+ internal static Task ToTask( dynamic dTask )
+ {
+ if( dTask == null )
+ {
+ return null;
+ }
+
+ /*
+ {
+ "id": 752517863351,
+ "created_at": "2012-04-22T03:29:33.284Z",
+ "modified_at": "2012-05-05T14:54:17.789Z",
+ "name": "require a login",
+ "notes": "",
+ "assignee": {
+ "id": 752517793331,
+ "name": "Chris Barbara"
+ },
+ "completed": false,
+ "assignee_status": "later",
+ "due_on": null,
+ "projects": [
+ {
+ "id": 752517863337,
+ "name": "T-1000"
+ }
+ ],
+ "workspace": {
+ "id": 752517862999,
+ "name": "Xignite"
+ },
+ "followers": [
+ {
+ "id": 752517793331,
+ "name": "Chris Barbara"
+ }
+ ],
+ "completed_at": null
+ }
+ */
+ Task t = new Task()
+ {
+ Id = long.Parse( dTask.id.ToString() ),
+ Name = dTask.name
+ };
+
+ //if this is null, then we are just dealing with a list of Ids & names.
+ if( dTask.created_at == null )
+ {
+ return t;
+ }
+
+ t.CreatedAt = DateTime.Parse( dTask.created_at );
+
+ if( dTask.modified_at != null )
+ {
+ t.ModifiedAt = DateTime.Parse( dTask.modified_at );
+ }
+
+ t.Notes = dTask.notes;
+
+ t.Assignee = ToAssignee( dTask.assignee );
+
+ t.Completed = dTask.completed;
+
+ SchedulingStatus ss = SchedulingStatus.Inbox;
+ if( Enum.TryParse<SchedulingStatus>( dTask.assignee_status, true, out ss ) )
+ {
+ t.AssigneeStatus = ss;
+ }
+
+ if( dTask.due_on != null )
+ {
+ t.DueDate = DateTime.Parse( dTask.due_on );
+ }
+
+ t.Projects = ToProjects( dTask.projects );
+
+ t.Workspace = ToWorkspace( dTask.workspace );
+
+ t.Followers = ToAssignees( dTask.followers );
+
+ if( dTask.completed_at != null )
+ {
+ t.CompletedAt = DateTime.Parse( dTask.completed_at );
+ }
+
+ return t;
+ }
+
+ internal static Project[] ToProjects( dynamic dProjects )
+ {
+ if( dProjects == null )
+ {
+ return null;
+ }
+
+ List<Project> ret = new List<Project>();
+ foreach( var item in dProjects )
+ {
+ ret.Add( ToProject( item ) );
+ }
+ return ret.ToArray();
+ }
+
+ internal static Project ToProject( dynamic dProject )
+ {
+ if( dProject == null )
+ {
+ return null;
+ }
+
+ Project p = new Project()
+ {
+ Id = long.Parse( dProject.id.ToString() ),
+ Name = dProject.name
+ };
+
+ //if this is null, then we are just dealing with a list of Ids & names.
+ if( dProject.created_at == null )
+ {
+ return p;
+ }
+
+ p.CreatedAt = DateTime.Parse( dProject.created_at );
+
+ if( dProject.modified_at != null )
+ {
+ p.ModifiedAt = DateTime.Parse( dProject.modified_at );
+ }
+
+ p.Notes = dProject.notes;
+
+ p.Workspace = ToWorkspace( dProject.workspace );
+
+ p.Followers = ToAssignees( dProject.followers );
+
+ return p;
+ }
+
+ internal static Story[] ToStories( dynamic dStories )
+ {
+ if( dStories == null )
+ {
+ return null;
+ }
+
+ List<Story> ret = new List<Story>();
+ foreach( var item in dStories )
+ {
+ ret.Add( ToStory( item ) );
+ }
+ return ret.ToArray();
+ }
+ internal static Story ToStory( dynamic dStory )
+ {
+ if( dStory == null )
+ {
+ return null;
+ }
+ /*
+ {
+ "id": 991498733530,
+ "created_at": "2012-06-02T16:02:28.692Z",
+ "type": "system",
+ "text": "added to Api-Test-Project",
+ "created_by": {
+ "id": 752517793331,
+ "name": "Chris Barbara"
+ }
+ }
+ */
+
+ Story s = new Story()
+ {
+ Id = long.Parse( dStory.id.ToString() ),
+ CreatedAt = DateTime.Parse( dStory.created_at ),
+ Text = dStory.text,
+ CreatedBy = ToAssignee( dStory.created_by ),
+ };
+
+ StoryType st = StoryType.System;
+ if( Enum.TryParse<StoryType>( dStory.type, true, out st ) )
+ {
+ s.Type = st;
+ }
+
+ /*
+ [
+ {
+ "target": {
+ "id": 991498733529,
+ "name": "Make a create c# project"
+ },
+ "source": "web",
+ }
+ ]
+ */
+
+ StorySource ss = StorySource.unknown;
+ if( Enum.TryParse<StorySource>( dStory.source, true, out ss ) )
+ {
+ s.Source = ss;
+ }
+
+ if( dStory.target != null )
+ {
+ //HACK: this is how we can tell if it is a Task or a Project
+ if( dStory.target.completed == null )
+ {
+ s.Target = ToProject( dStory.target );
+ }
+ else
+ {
+ s.Target = ToTask( dStory.target );
+ }
+ }
+
+ return s;
+ }
+ }
+}
View
53 AsanaApi/Service/ProjectsService.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using AsanaApi.Models;
+
+namespace AsanaApi.Service
+{
+ public class ProjectsService : AsanaApiRequest
+ {
+ public ProjectsService( string ApiKey )
+ : base( ApiKey )
+ {
+ }
+
+ public Task[] GetTasks( long projectId )
+ {
+ return new TasksService( _apiKey ).GetTasksInProject( projectId );
+ }
+
+ public Project GetProject( long projectId, OptionalFields[] returnedFields = null )
+ {
+ return GetProject( "/projects/" + projectId.ToString(), returnedFields );
+ }
+
+ public Project[] GetProjects( OptionalFields[] returnedFields = null )
+ {
+ return GetProjects( "/projects", returnedFields );
+ }
+ public Project[] GetProjectsInWorkspace( long workspaceId, OptionalFields[] returnedFields = null )
+ {
+ return GetProjects( "/workspaces/" + workspaceId.ToString() + "/projects", returnedFields );
+ }
+
+ private dynamic MakeRequest( string urlPath, OptionalFields[] returnedFields )
+ {
+ return ExecuteRequest( urlPath, returnedFields, ",name,id" );
+ }
+
+ private Project[] GetProjects( string urlPath, OptionalFields[] returnedFields )
+ {
+ dynamic dProjects = MakeRequest( urlPath, returnedFields );
+
+ return ObjectConversions.ToProjects( dProjects );
+ }
+ private Project GetProject( string urlPath, OptionalFields[] returnedFields )
+ {
+ dynamic dProject = MakeRequest( urlPath, returnedFields );
+
+ return ObjectConversions.ToProject( dProject );
+ }
+ }
+}
View
41 AsanaApi/Service/StoriesService.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using AsanaApi.Models;
+
+namespace AsanaApi.Service
+{
+ public class StoriesService : AsanaApiRequest
+ {
+ public StoriesService( string ApiKey )
+ : base( ApiKey )
+ {
+ }
+
+ static OptionalFields[] _allFields = new OptionalFields[] {OptionalFields.Story_Source, OptionalFields.Story_Target, OptionalFields.Story_Target_Name };
+ public static OptionalFields[] AllOptionalFieldsForStories
+ {
+ get
+ {
+ return _allFields;
+ }
+ }
+
+ public Story[] GetStories( Task target, OptionalFields[] returnedFields = null )
+ {
+ return GetStories( target.Id, returnedFields );
+ }
+ public Story[] GetStories( long taskId, OptionalFields[] returnedFields = null )
+ {
+ return GetStories( "/tasks/" + taskId.ToString() + "/stories", returnedFields );
+ }
+
+ private Story[] GetStories( string urlPath, OptionalFields[] returnedFields = null )
+ {
+ return ObjectConversions.ToStories(
+ ExecuteRequest( urlPath, returnedFields, ",created_at,id,text,source,type,created_by,created_by.name,target.completed" )
+ );
+ }
+ }
+}
View
57 AsanaApi/Service/TasksQuery.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace AsanaApi.Service
+{
+ public class TasksQuery
+ {
+ public TasksQuery()
+ {
+ }
+
+ public long? ProjectId { get; set; }
+ public long? AssigneeId { get; set; }
+ public long? WorkspaceId { get; set; }
+
+ public bool ReturnCompleteTaskRecords { get; set; }
+
+ internal string PopulateRequestQueryString( string urlPath )
+ {
+ bool qsAlready = urlPath.Contains( "?" );
+
+ if( ProjectId.HasValue )
+ {
+ urlPath = AddParameter( qsAlready, urlPath, "project=" + ProjectId.Value.ToString() );
+ qsAlready = true;
+ }
+ if( AssigneeId.HasValue )
+ {
+ urlPath = AddParameter( qsAlready, urlPath, "assignee=" + AssigneeId.Value.ToString() );
+ qsAlready = true;
+ }
+ if( WorkspaceId.HasValue )
+ {
+ urlPath = AddParameter( qsAlready, urlPath, "workspace=" + WorkspaceId.Value.ToString() );
+ qsAlready = true;
+ }
+
+ return urlPath;
+ }
+
+ private string AddParameter( bool qsAlready, string urlPath, string newParameter )
+ {
+ if( qsAlready )
+ {
+ urlPath += "&";
+ }
+ else
+ {
+ urlPath += "?";
+ }
+ urlPath += newParameter;
+ return urlPath;
+ }
+ }
+}
View
62 AsanaApi/Service/TasksService.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace AsanaApi.Service
+{
+ public class TasksService : AsanaApiRequest
+ {
+ public TasksService( string ApiKey )
+ : base( ApiKey )
+ {
+ }
+
+ public Models.Task[] GetTasks( TasksQuery query )
+ {
+ if( query == null )
+ {
+ throw new ArgumentNullException( "query" );
+ }
+
+ return GetTasks( "/tasks", query );
+ }
+
+ public Models.Task[] GetTasksInProject( long projectId )
+ {
+ return GetTasks( "/projects/" + projectId.ToString() + "/tasks", null );
+ }
+
+ private Models.Task[] GetTasks( string urlPath, TasksQuery query )
+ {
+ if( query != null )
+ {
+ urlPath = query.PopulateRequestQueryString( urlPath );
+ }
+
+ Models.Task[] nameAndIdOnly = ObjectConversions.ToTasks( ExecuteRequest( urlPath ) );
+ if( nameAndIdOnly == null )
+ {
+ return null;
+ }
+
+ if( query == null || query.ReturnCompleteTaskRecords == false )
+ {
+ return nameAndIdOnly;
+ }
+
+ Models.Task[] ret = new Models.Task[ nameAndIdOnly.Length ];
+ Parallel.For( 0, nameAndIdOnly.Length, i =>
+ {
+ ret[ i ] = GetTask( nameAndIdOnly[ i ].Id );
+ } );
+ return ret;
+ }
+
+ public Models.Task GetTask( long taskId )
+ {
+ return ObjectConversions.ToTask( ExecuteRequest( "/tasks/" + taskId.ToString() ) );
+ }
+ }
+}
View
54 AsanaApi/Service/UsersService.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using AsanaApi.Models;
+
+namespace AsanaApi.Service
+{
+ public class UsersService : AsanaApiRequest
+ {
+ public UsersService( string ApiKey )
+ : base( ApiKey )
+ {
+ }
+
+ static OptionalFields[] _allFields = new OptionalFields[] { OptionalFields.User_Name, OptionalFields.User_Email, OptionalFields.User_Workspaces, OptionalFields.User_Workspaces_Name };
+ public static OptionalFields[] AllOptionalFieldsForUsers
+ {
+ get
+ {
+ return _allFields;
+ }
+ }
+
+ public User GetUser()
+ {
+ return GetUser( "me" );
+ }
+
+ public User GetUser( long UserId )
+ {
+ return GetUser( UserId.ToString() );
+ }
+
+ private User GetUser( string userId )
+ {
+ return ObjectConversions.ToUser( ExecuteRequest( "/users/" + userId ) );
+ }
+
+ public User[] GetUsers( OptionalFields[] returnedFields = null )
+ {
+ return GetUsers( "/users", returnedFields );
+ }
+ public User[] GetUsersInWorkspace( long workspaceId, OptionalFields[] returnedFields = null )
+ {
+ return GetUsers( "/workspaces/" + workspaceId.ToString() + "/users", returnedFields );
+ }
+
+ private User[] GetUsers( string urlPath, OptionalFields[] returnedFields )
+ {
+ return ObjectConversions.ToUsers( ExecuteRequest( urlPath, returnedFields, ",name,id" ) );
+ }
+ }
+}
View
31 AsanaApi/Service/WorkspacesService.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using AsanaApi.Models;
+
+namespace AsanaApi.Service
+{
+ public class WorkspacesService : AsanaApiRequest
+ {
+ public WorkspacesService( string ApiKey )
+ : base( ApiKey )
+ {
+ }
+
+ public Project[] GetProjects( long workspaceId )
+ {
+ return new ProjectsService( _apiKey ).GetProjectsInWorkspace( workspaceId );
+ }
+
+ public Workspace[] GetWorkspaces()
+ {
+ return GetWorkspaces( "/workspaces" );
+ }
+
+ private Workspace[] GetWorkspaces( string urlPath )
+ {
+ return ObjectConversions.ToWorkspaces( ExecuteRequest( urlPath ) );
+ }
+ }
+}
View
14 README.md
@@ -1,4 +1,10 @@
-AsanaApi
-========
-
-.NET wrapper around the Asana Api
+AsanaApi
+========
+.NET wrapper around the Asana Api
+
+## Install / configure
+1. use NuGet to download all the required packages (MVC4 dependancies + mbunit)
+2. Open Webiste\Web.config - replace the "AsanaApiKey" value with your Api key.
+3. If you want to run the unit tests, open AsanaApi.Tests\App.config - replace all the values with your Api key and your account values.
+4. Go
+
View
637 Website/Content/Site.css
@@ -0,0 +1,637 @@
+html {
+ background-color: #e2e2e2;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ background-color: #fff;
+ border-top: solid 10px #000;
+ color: #333;
+ font-size: .85em;
+ font-family: "Segoe UI", Verdana, Helvetica, Sans-Serif;
+ margin: 0;
+ padding: 0;
+}
+
+a:link, a:visited,
+a:active, a:hover {
+ color: #333;
+ outline: none;
+ padding-left: 3px;
+ padding-right: 3px;
+ text-decoration: underline;
+}
+
+a:hover {
+ background-color: #c7d1d6;
+}
+
+header, footer, hgroup,
+nav, section {
+ display: block;
+}
+
+.float-left {
+ float: left;
+}
+
+.float-right {
+ float: right;
+}
+
+.highlight {
+ background-color: #a6dbed;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+.clear-fix:after {
+ content: ".";
+ clear: both;
+ display: block;
+ height: 0;
+ visibility: hidden;
+}
+
+h1, h2, h3,
+h4, h5, h6 {
+ color: #000;
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+
+h1 {
+ font-size: 2em;
+}
+
+h2 {
+ font-size: 1.75em;
+}
+
+h3 {
+ font-size: 1.2em;
+}
+
+h4 {
+ font-size: 1.1em;
+}
+
+h5, h6 {
+ font-size: 1em;
+}
+
+
+/* main layout
+----------------------------------------------------------*/
+.content-wrapper {
+ margin: 0 auto;
+ max-width: 960px;
+}
+
+#body {
+ background-color: #efeeef;
+ clear: both;
+ padding-bottom: 35px;
+}
+
+ .main-content {
+ background: url("../Images/accent.png") no-repeat;
+ padding-left: 10px;
+ padding-top: 30px;
+ }
+
+ .featured + .main-content {
+ background: url("../Images/heroAccent.png") no-repeat;
+ }
+
+footer {
+ clear: both;
+ background-color: #e2e2e2;
+ font-size: .8em;
+ height: 100px;
+}
+
+
+/* site title
+----------------------------------------------------------*/
+.site-title {
+ color: #c8c8c8;
+ font-family: Rockwell, Consolas, "Courier New", Courier, monospace;
+ font-size: 2.3em;
+ margin: 20px 0;
+}
+
+.site-title a, .site-title a:hover, .site-title a:active {
+ background: none;
+ color: #c8c8c8;
+ outline: none;
+ text-decoration: none;
+}
+
+
+/* login
+----------------------------------------------------------*/
+#login {
+ display: block;
+ font-size: .85em;
+ margin: 20px 0 12px;
+ text-align: right;
+}
+
+ #login a {
+ background-color: #d3dce0;
+ margin-left: 10px;
+ margin-right: 3px;
+ padding: 2px 3px;
+ text-decoration: none;
+ }
+
+ #login a.username {
+ background: none;
+ margin-left: 0px;
+ text-decoration: underline;
+ }
+
+ #login ul {
+ margin: 0;
+ }
+
+ #login li {
+ display: inline;
+ list-style: none;
+ }
+
+
+/* menu
+----------------------------------------------------------*/
+ul#menu {
+ font-size: 1.3em;
+ font-weight: 600;
+ margin: 0;
+ text-align: right;
+}
+
+ ul#menu li {
+ display: inline;
+ list-style: none;
+ padding-left: 15px;
+ }
+
+ ul#menu li a {
+ background: none;
+ color: #999;
+ text-decoration: none;
+ }
+
+ ul#menu li a:hover {
+ color: #333;
+ text-decoration: none;
+ }
+
+
+/* page elements
+----------------------------------------------------------*/
+/* featured */
+.featured {
+ background-color: #fff;
+}
+
+ .featured .content-wrapper {
+ background-color: #7ac0da;
+ background-image: -ms-linear-gradient(left, #7ac0da 0%, #a4d4e6 100%);
+ background-image: -o-linear-gradient(left, #7ac0da 0%, #a4d4e6 100%);
+ background-image: -webkit-gradient(linear, left top, right top, color-stop(0, #7ac0da), color-stop(1, #a4d4e6));
+ background-image: -webkit-linear-gradient(left, #7ac0da 0%, #a4d4e6 100%);
+ background-image: linear-gradient(left, #7ac0da 0%, #a4d4e6 100%);
+ color: #3e5667;
+ padding: 20px 40px 30px 40px;
+ }
+
+ .featured hgroup.title h1, .featured hgroup.title h2 {
+ color: #fff;
+ }
+
+ .featured p {
+ font-size: 1.1em;
+ }
+
+/* page titles */
+hgroup.title {
+ margin-bottom: 10px;
+}
+
+hgroup.title h1, hgroup.title h2 {
+ display: inline;
+}
+
+hgroup.title h2 {