.NET C# Moodle Integration Class

.NET C# Moodle Integration Class

by Mike Dennison -
Number of replies: 1

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);
        }

    }
}



Average of ratings: -
In reply to Mike Dennison

Re: .NET C# Moodle Integration Class

by Steve Bergen -

Hi Mark,


Thank you for your code.  This will be very useful for an integration project I am working on.


There seem to be a couple of dependencies missing and I'm hoping you could provide them:

  • Website.Common.Exceptions.MoodleIntegration
  • Website.Common.Objects.BC.Moodle


It seems that Elmah and BC.Website.Infrastructure.Dependencies.Abstract.Moodle are not required in this implementation.


Thanks.