by Mike Dennison.
I'm currently in the process of integrating Moodle Maintenance into an external .NET Web Application and I have set up a single class to perform the integration.
Moodle Version 3.0.6+ (Build: 20161007)
.NET Version .NET Framework 4.5.1
I am passing in a Log4Net Ilog instance, but you could cut this code out.
The integrator class, associated .NET versions of Moodle Objects and UNIT Tests are shown below.
Hope this is useful to other Moodle Integrators
Mike Dennison
Systems Developer
Borders College
Galashiels
Scotland
The Moodle Integrator class
using System;
using System.Collections.Specialized;
using System.Globalization;
using System.Net;
using System.Text;
using BC.Website.Infrastructure.Dependencies.Abstract.Moodle;
using Elmah;
using log4net;
using Newtonsoft.Json;
using Website.Common.Exceptions.MoodleIntegration;
using Website.Common.Objects.BC.Moodle;
namespace BC.Website.Infrastructure.Dependencies.Concrete.Moodle
{
/// <summary>
/// Class providing Moodle Integration via API REST Web Service.
/// </summary>
public class MoodleIntegrator : IMoodleIntegrator
{
#region private fields
private readonly string moodleRestApiUrl;
private readonly string moodleRestApiToken;
private readonly ILog log;
#endregion
#region constructor
/// <summary>
/// Create a new instance - injecting all dependencies
/// </summary>
/// <param name="moodleRestApiUrl">The URL, to which REST Web Service calls are made.</param>
/// <param name="moodleRestApiToken">The Security token to pass with the calls.</param>
/// <param name="log">The logging class to use.</param>
public MoodleIntegrator(string moodleRestApiUrl, string moodleRestApiToken, ILog log)
{
this.moodleRestApiUrl = moodleRestApiUrl;
this.moodleRestApiToken = moodleRestApiToken;
this.log = log;
}
#endregion
#region public methods
/// <summary>
/// Get a Moodle Course by its ID.
/// </summary>
/// <param name="courseID">The ID of the Course sought.</param>
/// <returns>A MoodleCourse instance - null if the course is not found.</returns>
public MoodleCourse GetCourseByID(long courseID)
{
var parameters = new NameValueCollection
{
{"wstoken", moodleRestApiToken},
{"wsfunction", "core_course_get_courses"},
{"moodlewsrestformat", "json"},
{"options[ids][0]", courseID.ToString(CultureInfo.InvariantCulture)}
};
var courses = new MoodleDataHelper<MoodleCourse[]>().GetMoodleData(parameters, moodleRestApiUrl, log);
return courses.Length > 0 ? courses[0] : null;
}
/// <summary>
/// Search for Moodle Courses by Name
/// </summary>
/// <param name="searchString">The search string.</param>
/// <returns>A MoodleCourseSearchResult instance - may contain 0 courses if none found.</returns>
public MoodleCourseSearchResult SearchForCoursesByName(string searchString)
{
var parameters = new NameValueCollection
{
{"wstoken", moodleRestApiToken},
{"wsfunction", "core_course_search_courses"},
{"moodlewsrestformat", "json"},
{"criterianame","search"},
{"criteriavalue", searchString}
};
return new MoodleDataHelper<MoodleCourseSearchResult>().GetMoodleData(parameters, moodleRestApiUrl, log);
}
#endregion
#region helper methods
#endregion
#region nested classes
/// <summary>
/// Generic Class to make the actual Moodle REST Web Service Calls
/// </summary>
/// <typeparam name="T">The result type expected from the web service call.</typeparam>
internal class MoodleDataHelper<T>
{
internal T GetMoodleData(NameValueCollection parameters, string url, ILog log)
{
var responseString = GetServiceCallResponseAsJson(parameters, url, log);
try
{
return JsonConvert.DeserializeObject<T>(responseString);
}
catch (Exception)
{
try
{
var moodelEx = JsonConvert.DeserializeObject<MoodleException>(responseString);
if (log != null)
{
var sb = new StringBuilder();
sb.AppendLine("MoodleIntegrator::GetMoodleData - call returned Moodle Exception");
LogParameters(parameters, log, ref sb);
sb.AppendFormat("errorcode {0}", moodelEx.errorcode);
sb.AppendLine();
sb.AppendFormat("exception {0}", moodelEx.exception);
sb.AppendLine();
sb.AppendFormat("message {0}", moodelEx.message);
sb.AppendLine();
}
throw new MoodleWebServiceException("Moodle Web Service Call Failed");
}
catch (Exception)
{
if (log != null)
{
var sb = new StringBuilder();
sb.AppendLine("MoodleIntegrator::GetMoodleData - couldn't deserialise reponse");
LogParameters(parameters, log, ref sb);
sb.AppendFormat("Response {0}", responseString);
sb.AppendLine();
}
throw new MoodleWebServiceException("Moodle Web Service Call Failed");}
}
}
private static void LogParameters(NameValueCollection parameters, ILog log, ref StringBuilder sb)
{
foreach (var key in parameters.AllKeys)
{
sb.AppendFormat("Parm: {0} Value: {1}", key, parameters[key]);
sb.AppendLine();
}
}
private static string GetServiceCallResponseAsJson(NameValueCollection parameters, string url, ILog log)
{
try
{
using (var client = new WebClient())
{
byte[] response =
client.UploadValues(url, parameters);
return Encoding.UTF8.GetString(response);
}
}
catch (Exception ex)
{
var sb = new StringBuilder();
sb.AppendLine("MoodleIntegrator::GetServiceCallResponseAsJson error");
foreach (var key in parameters.AllKeys)
{
sb.AppendFormat("Parm: {0} Value: {1}", key, parameters[key]);
sb.AppendLine();
}
log.Error(sb.ToString(), ex);
throw new MoodleWebServiceException("Moodle Web Service Call Failed", ex);
}
}
}
#endregion
}
}
Moodle Object Classes
Course
using System.Collections.Generic;
namespace Website.Common.Objects.BC.Moodle
{
/// <summary>
/// Represents a Moodle Course.
/// </summary>
public class MoodleCourse
{
// ReSharper disable InconsistentNaming
public decimal? id { get; set; }
public string shortname { get; set; }
public decimal? categoryid { get; set; }
public decimal? categorysortorder { get; set; }
public string fullname { get; set; }
public string idnumber { get; set; }
public string summary { get; set; }
public decimal? summaryformat { get; set; }
public string format { get; set; }
public decimal? showgrades { get; set; }
public decimal? newsitems { get; set; }
public decimal? startdate { get; set; }
public decimal? numsections { get; set; }
public decimal? maxbytes { get; set; }
public decimal? showreports { get; set; }
public decimal? visible { get; set; }
public decimal? groupmode { get; set; }
public decimal? groupmodeforce { get; set; }
public decimal? defaultgroupingid { get; set; }
public decimal? timecreated { get; set; }
public decimal? timemodified { get; set; }
public decimal? enablecompletion { get; set; }
public decimal? completionnotify { get; set; }
public string lang { get; set; }
public object forcetheme { get; set; }
public List<FormatOption> courseformatoptions { get; set; }
// ReSharper restore InconsistentNaming
}
}
Moodle Course Search Result
using System.Collections.Generic;
namespace Website.Common.Objects.BC.Moodle
{
/// <summary>
/// Represents a Moodel Web API Course Search Result.
/// </summary>
public class MoodleCourseSearchResult
{
// ReSharper disable InconsistentNaming
public int total { get; set; }
public List<MoodleCourse> courses { get; set; }
// ReSharper restore InconsistentNaming
}
}
Moodle Exception
namespace Website.Common.Objects.BC.Moodle
{
/// <summary>
/// Represents a Moodle Web API Exception.
/// </summary>
public class MoodleException
{
// ReSharper disable InconsistentNaming
public string exception { get; set; }
public string errorcode { get; set; }
public string message { get; set; }
// ReSharper restore InconsistentNaming
}
}
Format Option
namespace Website.Common.Objects.BC.Moodle
{
/// <summary>
/// Represents A Moodle Format Option.
/// </summary>
public class FormatOption
{
// ReSharper disable InconsistentNaming
public string name { get; set; }
public object value { get; set; }
// ReSharper restore InconsistentNaming
}
}
Unit Test to Exercise Integrator
using System;
using BC.Website.Infrastructure.Dependencies.Concrete.Moodle;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Website.Common.Exceptions.MoodleIntegration;
namespace Website.Infrastructure.Tests
{
/// <summary>
/// Unit Tests To Prove the Functionality of the Moodle Integrator Class.
/// </summary>
[TestClass]
public class MoodleIntegratorTests
{
private const string MoodleRestApiUrl = "http://YOUR-MOODLE-SERVER/webservice/rest/server.php";
private const string MoodleRestApiToken = "YOUR-MOODLE-SECURITY-TOKEN";
private readonly MoodleIntegrator integrator = new MoodleIntegrator(MoodleRestApiUrl, MoodleRestApiToken, null);
[TestMethod]
public void TestGetCourseByIDMethod()
{
var actual = integrator.GetCourseByID(0);
Assert.IsNull(actual);
actual = integrator.GetCourseByID(1);
Assert.IsNotNull(actual);
Assert.AreEqual(actual.fullname, "Borders College Moodle");
Assert.AreEqual(actual.shortname, "Borders Moodle");
Assert.AreEqual(actual.idnumber, "Borders Moodle");
var integrator1 = new MoodleIntegrator(MoodleRestApiUrl, "rubbish token", null);
try
{
integrator1.GetCourseByID(1);
Assert.IsFalse(true);
}
catch (Exception ex)
{
Assert.IsTrue(ex is MoodleWebServiceException);
}
}
[TestMethod]
public void TestSearchForCoursesByNameMethod()
{
var actual = integrator.SearchForCoursesByName("moodle");
Assert.IsNotNull(actual);
Assert.IsTrue(actual.courses.Count > 0);
actual = integrator.SearchForCoursesByName("A Beginner's Guide to Moodle");
Assert.IsNotNull(actual);
Assert.IsTrue(actual.courses.Count > 0);
actual = integrator.SearchForCoursesByName("I hope that this course does not exists ZZZZZZZZZZZZ");
Assert.IsNotNull(actual);
Assert.IsTrue(actual.courses.Count == 0);
}
}
}