/// <summary> /// Requests the RloHandler to do whatever is required to exit from the current activity. /// This request may only be issued when the session is in Execute view and is not active -- it is /// either Completed or Abandoned. /// </summary> /// <param name="context">The context within which the command is processed</param> /// <remarks> /// This method should only be called for the <c>SessionView.Execute</c> view. However, /// no checks are done internally to verify this - if this is called with other views, /// unexpected results will occur. /// </remarks> public override void ProcessSessionEnd(RloDataModelContext context) { LearningDataModel learningDataModel = context.LearningDataModel; // Set ExitMode to suspend so that when a student exits the activity it is left in a suspended state. // This way if the activity is reactivated, the student's previous answers are intact. learningDataModel.NavigationRequest.ExitMode = ExitMode.Suspended; // If the page has never been visited, "visit" it. if (!GetPageHasBeenVisited(learningDataModel)) { AssessmentItemManager.DataModelContext = context; // Get the input stream containing the primary file from the resource associated with the // current activity in the session. using (Stream inputStream = context.GetInputStream()) { // find all the assessment items (<IMG> tags that contain the text "mslamrk" as part of the src attribute.) using (HtmlTextReader reader = new HtmlTextReader(inputStream)) { int srcIndex; while (reader.Read()) { if (IsAITag(reader, out srcIndex)) { try { AssessmentItem ai = AssessmentItem.Parse(reader.GetAttributeValue(srcIndex)); AssessmentItemRenderer renderer = AssessmentItemManager.GetRenderer(ai); renderer.TryAddToDataModel(); } catch (FormatException) { // skip this one. } } } } } SetPageHasBeenVisited(learningDataModel); } // If the page has never been autograded, call ProcessSessionEnd on the form data processors if (!GetPageHasBeenAutograded(learningDataModel)) { AssessmentItemManager.ProcessFormContext = new RloProcessFormDataContext(SessionView.Execute, learningDataModel); float?totalPoints = null; foreach (Interaction interaction in learningDataModel.Interactions) { FormDataProcessor processor = m_assessmentItemMgr.GetFormDataProcessor(interaction); // must check that processor is non null, since GetFormDataProcessor() can return null. // If it is null, any item score associated with this interaction is not totalled into // EvaluationPoints. if (processor != null) { processor.ProcessSessionEnd(context); if (interaction.Evaluation.Points.HasValue) { if (totalPoints.HasValue) { totalPoints += interaction.Evaluation.Points; } else { totalPoints = interaction.Evaluation.Points; } } } } learningDataModel.EvaluationPoints = totalPoints; SetPageHasBeenAutograded(learningDataModel); } }
/// <summary> /// Render the requested view into the output stream. /// </summary> /// <param name="context">The context within which to render the page.</param> /// <remarks> /// When this method returns the <paramref name="context"/> OutputStream will contain /// the rendered file. /// <p> /// The following methods and properties must be return valid values from /// the <paramref name="context"/>: /// <ul> /// <li>EmbeddedUiResourcePath, must be non-null</li> /// <li>FormElementId</li> /// <li>GetInputStream</li> /// <li>OutputStream</li> /// <li>View</li> /// </ul> /// </p> /// <p> /// Additionally, if the following properties are set, they will be used: /// <ul> /// <li>FormElementAction</li> /// <li>HiddenControls</li> /// <li>ScriptToRender</li> /// </ul> /// </p> /// All other properties on <paramref name="context"/> are ignored. /// </remarks> /// <exception cref="FileNotFoundException">The requested file attachment can't be found.</exception> public override void Render(RloRenderContext context) { AIResources.Culture = LocalizationManager.GetCurrentCulture(); // string is the key (which is AssessmentItem.Id_AssessmentItem.Type) // int is the ordinal (0 based) which is the number of times the key has been processed Dictionary <string, int> assessmentItems = new Dictionary <string, int>(); // The most common case is that the file is in the package Stream inputStream = null; AssessmentItemManager.DataModelContext = context; LearningDataModel learningDataModel = context.LearningDataModel; try { int srcIndex; // represents the index of the "src" attribute on an <img> node. // If this is the first time the page is being rendered, parse the page and determine // the interactions on the page. if (context.View == SessionView.Execute) { if (!GetPageHasBeenVisited(learningDataModel)) { using (inputStream = context.GetInputStream()) { // If the file being requested is the default file for the current activity, if (context.IsResourceEntryPoint) { // find all the assessment items (<IMG> tags that contain the text "mslamrk" as part of the src attribute.) using (HtmlTextReader reader = new HtmlTextReader(inputStream)) { while (reader.Read()) { if (IsAITag(reader, out srcIndex)) { try { AssessmentItem ai = AssessmentItem.Parse(reader.GetAttributeValue(srcIndex)); AssessmentItemRenderer renderer = AssessmentItemManager.GetRenderer(ai); renderer.TryAddToDataModel(); } catch (FormatException) { // skip this one. This is mirrored below in the 2nd pass. } } } } } } SetPageHasBeenVisited(learningDataModel); } } // must get the input stream again since it may not be possible to seek back to the beginning using (inputStream = context.GetInputStream()) { if (context.Response != null) { // Clear the output response context.Response.Clear(); } // If the file being requested is the default file for the current activity, if (context.IsResourceEntryPoint) { if (context.View == SessionView.Execute) { // Set ExitMode to suspend so that when a student exits the activity it is left in a suspended state. // This way if the activity is reactivated, the student's previous answers are intact. learningDataModel.NavigationRequest.ExitMode = ExitMode.Suspended; } DetachableStream detachable = new DetachableStream(context.OutputStream); // Parse through the input stream again, this time rendering into the output as we go. using (StreamWriter writer = new StreamWriter(detachable)) { using (HtmlTextReader reader = new HtmlTextReader(inputStream)) { while (reader.Read()) { if (IsAITag(reader, out srcIndex)) { try { AssessmentItem ai = AssessmentItem.Parse(reader.GetAttributeValue(srcIndex)); AssessmentItemRenderer renderer = AssessmentItemManager.GetRenderer(ai); if (assessmentItems.ContainsKey(ai.RenderKey)) { assessmentItems[ai.RenderKey] += 1; } else { assessmentItems.Add(ai.RenderKey, 0); } writer.Write(renderer.Render(assessmentItems[ai.RenderKey]).ToString()); } catch (FormatException) { // skip this one. This is mirrored above in the 1st pass. } } else { HandleNode(reader, writer); } } } // don't allow closing the StreamWriter to close the context.OutputStream. writer.Flush(); detachable.Detach(); } // set the response type context.SetOutputStreamExtension(Path.GetExtension(context.RelativePath)); } else { // for a non-entry-point file, copy the file directly to the output stream context.WriteFileToResponse(context.RelativePath); } } return; } catch (FileNotFoundException) { // This means the requested file is not in the package. That's not necessarily a problem, since it // may be a request for an attachment. } // We got here because the file is not in the package. In that case, render it if it is a file attachment int beginAttachmentInfo = context.RelativePath.IndexOf("/~RLO/", StringComparison.Ordinal); if (beginAttachmentInfo != -1) { // attachmentInfo should be of the form <interactionId>/<attachmentIndex>, so split it into the parts string attachmentInfo = context.RelativePath.Substring(beginAttachmentInfo + 6); RenderFileAttachment(context, attachmentInfo); } else { // This means the requested file is not in the package, nor is it a request for an attachment. throw new FileNotFoundException(AIResources.FileNotFound); } }