StoredLearningSession CreateAttemptIfRequired(bool transitionToComplete) { // NotStarted --> Active or Completed or Final if (Assignment.IsELearning) { // create an attempt for this learner assignment StoredLearningSession learningSession = StoredLearningSession.CreateAttempt(Assignment.Store.PackageStore, LearnerId, LearnerAssignmentId, Assignment.RootActivityId, Assignment.Store.Settings.LoggingOptions); // start the assignment, forcing selection of a first activity learningSession.Start(true); // if NotStarted --> Completed or Final, transition to the Completed state if (transitionToComplete) { // transition to Completed learningSession.Exit(); } // save changes to <learningSession> learningSession.CommitChanges(); return(learningSession); } else { return(null); } }
float?FinishSession() { if (AttemptId == null) { throw new InternalErrorException("SLK1007"); } // set <learningSession> to refer to the attempt associated with this learner assignment StoredLearningSession learningSession = new StoredLearningSession(SessionView.Execute, AttemptId, Assignment.Store.PackageStore); // transition the attempt to "Completed" state; note that this will initialize the "content score", i.e. the score computed from the content if (learningSession.HasCurrentActivity) { // make sure that if the content wants to suspend itself, it does learningSession.ProcessNavigationRequests(); } learningSession.Exit(); learningSession.CommitChanges(); return(learningSession.TotalPoints); }
SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] // it's not worth changing this now public void ProcessPageLoad(PackageStore packageStore, GetSessionTitle getSessionTitle, TryGetViewInfo TryGetViewInfo, TryGetAttemptInfo TryGetAttemptInfo, AppendContentFrameDetails appendContentFrameDetails, RegisterError registerError, GetErrorInfo getErrorInfo, ProcessSessionEnd ProcessSessionEnd, ProcessViewRequest ProcessViewRequest, GetFramesetMsg getFramesetMsg, // messages that appear that are unique to different framesets bool isPostBack) { try { RegisterError = registerError; GetErrorInfo = getErrorInfo; AppendContentFrameDetails = appendContentFrameDetails; GetFramesetMsg = getFramesetMsg; m_getSessionTitle = getSessionTitle; m_isPostedPage = isPostBack; AttemptItemIdentifier attemptId; SessionView view; LoadContentFrame = true; ActivityHasChanged = false; if (!TryGetViewInfo(false, out view)) { WriteError(ResHelper.GetMessage(IUDICO.TestingSystem.Localization.getMessage("FRM_ViewNotSupportedMsg"))); return; } switch (view) { case SessionView.Execute: { if (!TryGetAttemptInfo(false, out attemptId)) return; Session = new StoredLearningSession(view, attemptId, packageStore); if (!ProcessViewRequest(view, Session)) return; // If the session has ended, allow the application to deal with it. ProcessSessionEnd(Session, ref m_sessionEndedMsgTitle, ref m_sessionEndedMsg); } break; case SessionView.Review: { if (!TryGetAttemptInfo(false, out attemptId)) return; Session = new StoredLearningSession(view, attemptId, packageStore); // Do not set logging options in review view. if (!ProcessViewRequest(view, Session)) return; } break; case SessionView.RandomAccess: { // Note: RandomAccess is not supported in BWP, however that would have been caught before // displaying this frame. if (!TryGetAttemptInfo(false, out attemptId)) return; Session = new StoredLearningSession(view, attemptId, packageStore); // Do not set logging options in random access view. if (!ProcessViewRequest(view, Session)) return; // Move to the first activity with a resource. MoveToNextActivity(Session); } break; default: WriteError(ResHelper.GetMessage(IUDICO.TestingSystem.Localization.getMessage("FRM_ViewNotSupportedMsg"))); return; } // If the page is posted, process posted data. Remember that all posted data should be considered hostile! // Note that if the session has already ended, then none of the posted data is saved or processed. if (isPostBack && !SessionIsEnded) { // Process any datamodel changes before doing any navigation. This does not save any data. ProcessDataModelValues(Request.Form[HiddenFieldNames.DataModel], Request.Form[HiddenFieldNames.ObjectiveIdMap]); // Assume we do not have to reload the content frame ActivityHasChanged = false; LoadContentFrame = false; // if the view requires more data, get it if ((Session.View == SessionView.Review) || (Session.View == SessionView.RandomAccess)) { // Get the current activity from the posted data and set the session to that activity string strActivityId = Request.Form[HiddenFieldNames.ActivityId]; long activityId; if (String.IsNullOrEmpty(strActivityId) || !long.TryParse(strActivityId, out activityId)) { WriteError(ResHelper.GetMessage(IUDICO.TestingSystem.Localization.getMessage("HID_InvalidActivityId"), strActivityId)); } else { MoveToActivity(Session, activityId); } } // Find out what the commands are and do them. m_saveOnly = true; ICollection<CommandInfo> commands = GetCommands(); foreach (CommandInfo cmdInfo in commands) { switch (cmdInfo.Command) { case Commands.DoNext: { if (!Session.HasCurrentActivity || !ProcessNavigationRequests(Session)) { if (Session.IsMoveToNextValid()) { MoveToNextActivity(Session); ActivityHasChanged = true; LoadContentFrame = true; } } else { ActivityHasChanged = true; LoadContentFrame = true; } if (!ActivityHasChanged) { // Moving to the next activity is not valid. It's possible that when the current // activity was unloaded, it exited or suspended itself. If that's the case, we // try to reactivate it. Note this causes the attempt count on the activity to // increment, so on "poorly" written content, the user may not see their data // anymore. ActivateCurrentActivity(); WriteError(ResHelper.Format(GetFramesetMsg(FramesetStringId.MoveToNextFailedHtml), ThemeFolderPath), true); } m_saveOnly = false; } break; case Commands.DoPrevious: { if (!Session.HasCurrentActivity || !ProcessNavigationRequests(Session)) { if (Session.IsMoveToPreviousValid()) { MoveToPreviousActivity(Session); ActivityHasChanged = true; LoadContentFrame = true; } } else { ActivityHasChanged = true; LoadContentFrame = true; } if (!ActivityHasChanged) { // Moving to the previous activity is not valid. It's possible that when the current // activity was unloaded, it exited or suspended itself. If that's the case, we // try to reactivate it. Note this causes the attempt count on the activity to // increment, so on "poorly" written content, the user may not see their data // anymore. ActivateCurrentActivity(); WriteError(ResHelper.Format(GetFramesetMsg(FramesetStringId.MoveToPreviousFailedHtml), ThemeFolderPath), true); } m_saveOnly = false; } break; case Commands.DoChoice: { // This command is used to navigate to activities, primarily from either within a link in an // error page, or the submit page returning to the current activity. This command does not // create a new attempt if the requested activity is already the current activity. // Requesting to move to a specific activity. The cmdData will include a numeric activity id. string cmdData = cmdInfo.CommandData; long activityId; if (long.TryParse(cmdData, out activityId)) { // If the requested activity is the current activity, then do not do the navigation. // We skip it because moving to the activity basically exits the current attempt and creates // a new one and in this case, that is not the desired behavior. // That new one also increments the attempt count. If we don't do the move, we // pretend it was done. This will force the content frame to be reloaded with the current // activity. if (IsCurrentActiveActivity(activityId)) { ActivityHasChanged = true; LoadContentFrame = true; } else { // If there is no current activity, or if any navigation requests did not // result in a move, then continue with the choice. if (!Session.HasCurrentActivity || !ProcessNavigationRequests(Session)) { if (Session.IsMoveToActivityValid(activityId)) { MoveToActivity(Session, activityId); ActivityHasChanged = true; LoadContentFrame = true; } } else { ActivityHasChanged = true; LoadContentFrame = true; } } } if (!ActivityHasChanged) { // Moving to the selected activity is not valid. It's possible that when the current // activity was unloaded, it exited or suspended itself. If that's the case, we // try to reactivate it. Note this causes the attempt count on the activity to // increment, so on "poorly" written content, the user may not see their data // anymore. ActivateCurrentActivity(); WriteError(ResHelper.Format(GetFramesetMsg(FramesetStringId.MoveToActivityFailedHtml), ThemeFolderPath), true); } m_saveOnly = false; } break; case Commands.DoTocChoice: { // This command is used to navigate to activities in response to a user selecting a node // in the TOC. In this case, even if the current activity is the activity that is requested, // then a MoveToActivity() is requested. This will cause the attempt count on the activity // to be incremented and the RTE to be reinitialized. This may be a surprise to the user, but // is a requirement of the SCORM 2004 conformance tests. // If the selected page is the submit page (either in Execute or RandomAccess views), then // display the message and don't ask the session to move to a new activity. string cmdData = cmdInfo.CommandData; if (String.CompareOrdinal(cmdData, SubmitId) == 0) { // Requesting submit page. Do not change the current activity, but mark it as changed so that // it appears to the user that it has changed. ActivityHasChanged = true; LoadContentFrame = true; string title = GetFramesetMsg(FramesetStringId.SubmitPageTitleHtml); string message; string saveBtn = GetFramesetMsg(FramesetStringId.SubmitPageSaveButtonHtml); if (Session.HasCurrentActivity) message = GetFramesetMsg(FramesetStringId.SubmitPageMessageHtml); else message = GetFramesetMsg(FramesetStringId.SubmitPageMessageNoCurrentActivityHtml); WriteSubmitPage(title, message, saveBtn); } else { // Requesting to move to a specific activity. The cmdData will include a numeric activity id. long activityId; if (long.TryParse(cmdData, out activityId)) { // If there is no current activity, or if any navigation requests did not // result in a move, then continue with the choice. if (!Session.HasCurrentActivity || !ProcessNavigationRequests(Session)) { if (Session.IsMoveToActivityValid(activityId)) { MoveToActivity(Session, activityId); ActivityHasChanged = true; LoadContentFrame = true; } } else { ActivityHasChanged = true; LoadContentFrame = true; } } } if (!ActivityHasChanged) { // Moving to the selected activity is not valid. It's possible that when the current // activity was unloaded, it exited or suspended itself. If that's the case, we // try to reactivate it. Note this causes the attempt count on the activity to // increment, so on "poorly" written content, the user may not see their data // anymore. ActivateCurrentActivity(); WriteError(ResHelper.Format(GetFramesetMsg(FramesetStringId.MoveToActivityFailedHtml), ThemeFolderPath), true); } m_saveOnly = false; } break; case Commands.DoIsChoiceValid: { string activityKey = cmdInfo.CommandData; bool isValid = false; if (!String.IsNullOrEmpty(activityKey)) { isValid = Session.IsMoveToActivityValid(activityKey); } m_isNavValidResponse = new IsNavValidResponseData(Commands.DoIsChoiceValid, activityKey, isValid); m_saveOnly = false; } break; case Commands.DoIsNavigationValid: { string navCommand = cmdInfo.CommandData; if (!String.IsNullOrEmpty(navCommand)) { bool isValid = false; if (navCommand == Commands.DoNext) { isValid = Session.IsMoveToNextValid(); } else if (navCommand == Commands.DoPrevious) { isValid = Session.IsMoveToPreviousValid(); } m_isNavValidResponse = new IsNavValidResponseData(Commands.DoIsNavigationValid, navCommand, isValid); } m_saveOnly = false; } break; case Commands.DoSave: { // Do nothing. The information will be saved since the page was posted. } break; case Commands.DoTerminate: // end the current activity { try { if (Session.View == SessionView.Execute) { // Keep track of state before calling ProcessNavigationRequests so that we can // detect if the call changes it in a way that requires reloading the content frame. long activityBeforeNavigation = Session.CurrentActivityId; int activityAttemptCount = Session.CurrentActivityDataModel.ActivityAttemptCount; LearningSession session = Session; if (session.HasCurrentActivity) session.ProcessNavigationRequests(); // The activity has changed if... // ... the session now does not have a current activity (since it did before we did the ProcessNavRequests call, OR // ... the session's current activity is not activity anymore (since it was before the call), OR // ... the session's activity id has changed // ... the session's activity id has not changed, but the attempt count has if (!session.HasCurrentActivity) { ActivityHasChanged = true; LoadContentFrame = true; } else if ((session.View == SessionView.Execute) && (!Session.CurrentActivityIsActive)) { // In Execute view, it started as active or would have thrown an exception. ActivityHasChanged = true; // do not load content frame, as that causes messages to flash while switching activities LoadContentFrame = false; } else if (activityBeforeNavigation != session.CurrentActivityId) { // The current activity has changed. ActivityHasChanged = true; LoadContentFrame = true; } else if ((activityBeforeNavigation == session.CurrentActivityId) && (activityAttemptCount != session.CurrentActivityDataModel.ActivityAttemptCount)) { // The activity has not changed, but the attempt count has. ActivityHasChanged = true; LoadContentFrame = true; } else { // In all other cases, it has not changed ActivityHasChanged = false; LoadContentFrame = false; } } else if ((Session.View == SessionView.Review) || (Session.View == SessionView.RandomAccess)) { // The activity has changed simply by calling this. This allows the client RTE to reinitialize and behave // as if navigation has happened. ActivityHasChanged = true; LoadContentFrame = true; } } catch (SequencingException ex) { WriteError(ResHelper.GetMessage(IUDICO.TestingSystem.Localization.getMessage("HID_TerminateFailed"), HttpUtility.HtmlEncode(ex.Message))); } m_saveOnly = false; } break; case Commands.DoSubmit: { // Submit the attempt -- meaning, do an ExitAll if (Session.View == SessionView.Execute) { if (Session.HasCurrentActivity) { ProcessNavigationRequests(Session); } Session.Exit(); } else if (Session.View == SessionView.RandomAccess) { // This may also be a request to end grading (for SLK), in which case // we just set a flag, get the right strings to display and call it done SessionIsEnded = true; ProcessSessionEnd(Session, ref m_sessionEndedMsgTitle, ref m_sessionEndedMsg); } ActivityHasChanged = true; LoadContentFrame = true; m_saveOnly = false; } break; } } } else { // Is this the first page load, as part of frameset? (Some hidden values are only rendered in this case.) string param = Request.QueryString[FramesetQueryParameter.Init]; if (!String.IsNullOrEmpty(param)) { m_isFramesetInitialization = true; } } // If this was not simply a save operation and there is no current activity, then display a message // asking the user to select one. if (!m_saveOnly && !Session.HasCurrentActivity) { RegisterError(GetFramesetMsg(FramesetStringId.SelectActivityTitleHtml), GetFramesetMsg(FramesetStringId.SelectActivityMessageHtml), true); } // In Execute view, ProcessSessionEnd may write to the database and change state of data related to the attempt. // Therefore, session changes must be written in the same transation as the session end changes. TransactionOptions transactionOptions = new TransactionOptions(); transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.Serializable; using (LearningStoreTransactionScope scope = new LearningStoreTransactionScope(transactionOptions)) { if (!SessionIsReadOnly) { // Save all changes Session.CommitChanges(); } if (Session.View == SessionView.Execute) { // The rollup and/or sequencing process may have changed the state of the attempt. If so, there are some cases // that cannot continue so show an error message. Allow the application to process this, since the messages are // different. ProcessSessionEnd(Session, ref m_sessionEndedMsgTitle, ref m_sessionEndedMsg); } // finish the transaction scope.Complete(); } InitHiddenControlInfo(); m_pageLoadSuccessful = true; } catch (ThreadAbortException) { // Do nothing -- thread is leaving. throw; } catch (Exception) { m_pageLoadSuccessful = false; throw; } }