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); } }
[SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")] // parameters are validated protected void AppendContentFrameDetails(LearningSession session, StringBuilder sb) { FramesetUtil.ValidateNonNullParameter("sb", sb); FramesetUtil.ValidateNonNullParameter("session", session); // The URL for attempt-based content frames is: // http://<...basicWebApp>/Content.aspx/<AssignmentView>/<LearnerAssignmentId>/otherdata/ // the otherdata depends on the view sb.Append(String.Format(CultureInfo.InvariantCulture, "/{0}", Convert.ToInt32(AssignmentView, NumberFormatInfo.InvariantInfo))); StoredLearningSession slsSession = session as StoredLearningSession; if (slsSession == null) { // Not attempt-based view } else { sb.AppendFormat("/{0}", LearnerAssignmentGuidId.ToString()); // In review & ra views, append the current activity id if ((slsSession.View == SessionView.Review) || (slsSession.View == SessionView.RandomAccess)) { sb.AppendFormat("/{0}", FramesetUtil.GetStringInvariant(slsSession.CurrentActivityId)); } } }
void CompleteAssignment(StoredLearningSession newSession, AssignmentSaver saver) { LearnerAssignmentState newStatus = LearnerAssignmentState.Completed; bool?isFinal = false; if (Assignment.AutoReturn) { newStatus = LearnerAssignmentState.Final; isFinal = true; } if (newSession == null) { float?finalPoints = null; if (Status == LearnerAssignmentState.Active && Assignment.IsELearning) { finalPoints = FinishSession(); } Save(newStatus, isFinal, NonELearningStatus(AttemptStatus.Completed), finalPoints, saver); } else { Save(newStatus, isFinal, NonELearningStatus(AttemptStatus.Completed), newSession.TotalPoints, saver); } Status = newStatus; }
/// <summary> /// Allows the app to take action when the session is ending. /// </summary> public void ProcessSessionEnd(LearningSession session, ref string messageTitle, ref string message) { // Session ending results in message shown to the user. if (session.View == SessionView.Execute) { StoredLearningSession slsSession = session as StoredLearningSession; if (slsSession != null) { // 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. switch (slsSession.AttemptStatus) { case AttemptStatus.Abandoned: messageTitle = IUDICO.TestingSystem.Localization.getMessage("HID_SessionAbandonedTitle"); message = IUDICO.TestingSystem.Localization.getMessage("FRM_ExecuteViewAbandonedSessionMsg"); break; case AttemptStatus.Completed: messageTitle = IUDICO.TestingSystem.Localization.getMessage("HID_SessionCompletedTitle"); message = IUDICO.TestingSystem.Localization.getMessage("FRM_ExecuteViewCompletedSessionMsg"); break; case AttemptStatus.Suspended: messageTitle = IUDICO.TestingSystem.Localization.getMessage("HID_SessionSuspendedTitle"); message = IUDICO.TestingSystem.Localization.getMessage("FRM_ExecuteViewSuspendedSessionMsg"); break; } } } }
/// <summary> /// Creates attempt on given organization and returns attempt identifier. /// </summary> /// <param name="orgID">Long integer value represents organization identifier to create attempt on.</param> /// <returns>Long integer value, representing attempt identifier of created attempt.</returns> public long CreateAttempt(long orgID) { ActivityPackageItemIdentifier organizationID = new ActivityPackageItemIdentifier(orgID); StoredLearningSession session = StoredLearningSession.CreateAttempt(this.PStore, this.CurrentUserIdentifier, organizationID, LoggingOptions.LogAll); long attemptID = session.AttemptId.GetKey(); return(attemptID); }
public static void AppendContentFrameDetails(LearningSession session, StringBuilder sb) { // The URL for attempt-based content frames is: // http://<...basicWebApp>/Content.aspx/<view>/<attemptId>/otherdata/ // the otherdata depends on the view sb.Append(string.Format(CultureInfo.CurrentCulture, "/{0}", Convert.ToInt32(session.View))); StoredLearningSession slsSession = session as StoredLearningSession; sb.AppendFormat("/{0}", slsSession.AttemptId.GetKey().ToString()); }
/// <summary>Returns the assignment.</summary> public void Return(AssignmentSaver saver) { CheckUserIsInstructor(); StoredLearningSession session = null; // Check the status switch (Status) { case LearnerAssignmentState.NotStarted: // Force collection & return session = CreateAttemptIfRequired(false); break; case LearnerAssignmentState.Active: // Force collection & return break; case LearnerAssignmentState.Completed: break; case LearnerAssignmentState.Final: // No need to return return; default: // New status added break; } LearnerAssignmentState newStatus = LearnerAssignmentState.Final; if (session == null) { Save(newStatus, true, NonELearningStatus(AttemptStatus.Completed), null, saver); } else { Save(newStatus, true, NonELearningStatus(AttemptStatus.Completed), session.TotalPoints, saver); } Status = newStatus; if (Assignment.EmailChanges) { saver.SendReturnEmail(User, this); } if (Assignment.IsNonELearning) { saver.UpdateDropBoxPermissions(newStatus, User); } }
protected void CreateAttemptButton_Click(object sender, EventArgs e) { // the hidden "Create Attempt" button was auto-clicked by script on page load... // prevent script from clicking "Create Attempt" again AutoPostScript.Visible = false; // hide the "please wait" panel PleaseWait.Visible = false; // the OrganizationId hidden form element contains the ID of the organization to attempt -- // try to create a new attempt based on that ID; an organization is a root-level activity, // so OrganizationId is actually an ActivityPackageItemIdentifier try { // set <currentUser> to information about the current user; we // need the current user's UserItemIdentifier LStoreUserInfo currentUser = GetCurrentUserInfo(); // set <organizationId> from the OrganizationId hidden form element as described above ActivityPackageItemIdentifier organizationId = new ActivityPackageItemIdentifier( Convert.ToInt64(OrganizationId.Value, CultureInfo.InvariantCulture)); // create an attempt on <organizationId> StoredLearningSession session = StoredLearningSession.CreateAttempt(PStore, currentUser.Id, organizationId, LoggingOptions.LogAll); // the operation was successful, and there are no messages to display to the user, so // update the AttemptId hidden form element with the ID of the newly-created attempt, // update the parent page, and close the dialog AttemptId.Value = Convert.ToString(session.AttemptId.GetKey(), CultureInfo.InvariantCulture); UpdateParentPageScript.Visible = true; CloseDialogScript.Visible = true; } catch (Exception ex) { // an unexpected error occurred -- display a generic message that // doesn't include the exception message (since that message may // include sensitive information), and write the exception message // to the event log ErrorIntro.Visible = true; ErrorMessage.Visible = true; ErrorMessage.Controls.Add(new System.Web.UI.LiteralControl( Server.HtmlEncode("A serious error occurred. Please contact your system administrator. More information has been written to the server event log."))); LogEvent(System.Diagnostics.EventLogEntryType.Error, "An exception occurred while creating an attempt:\n\n{0}\n\n", ex.ToString()); Buttons.Visible = true; } }
/// <summary> /// Creates attempt on given organization and returns attempt identifier. /// </summary> /// <param name="orgID">Long integer value represents organization identifier to create attempt on.</param> /// <returns>Long integer value, representing attempt identifier of created attempt.</returns> protected AttemptItemIdentifier CreateAttempt(long orgID, int topicId) { ActivityPackageItemIdentifier organizationID = new ActivityPackageItemIdentifier(orgID); StoredLearningSession session = StoredLearningSession.CreateAttempt(this.PStore, this.GetCurrentUserIdentifier(), organizationID, LoggingOptions.LogAll); // TODO: add IudicoTopicRef LearningStoreJob job = LStore.CreateJob(); Dictionary <string, object> dic = new Dictionary <string, object>(); dic.Add(Schema.AttemptItem.IudicoThemeRef, topicId); job.UpdateItem(session.AttemptId, dic); job.Execute(); return(session.AttemptId); }
/// <summary> /// Delegate implementation to allow the frameset to take action on a session view request. This allows SLK and /// BWP to have different behavior and messages about which requests are not valid. /// </summary> public bool ProcessViewRequest(SessionView view, LearningSession session) { this.Completed = false; switch (view) { case SessionView.Execute: { StoredLearningSession slsSession = session as StoredLearningSession; if (slsSession != null) { if (slsSession.AttemptStatus == AttemptStatus.Completed) { this.RegisterError( ResHelper.GetMessage(Localization.GetMessage("FRM_InvalidAttemptStatusForViewTitle")), ResHelper.GetMessage(Localization.GetMessage("FRM_ExecuteViewCompletedSessionMsg")), false); this.Completed = true; return(false); } else if (slsSession.AttemptStatus == AttemptStatus.Abandoned) { this.RegisterError( ResHelper.GetMessage(Localization.GetMessage("FRM_InvalidAttemptStatusForViewTitle")), ResHelper.GetMessage(Localization.GetMessage("FRM_ExecuteViewAbandonedSessionMsg")), false); return(false); } } } break; case SessionView.Review: // BWP does not provide review view this.RegisterError( ResHelper.GetMessage(Localization.GetMessage("FRM_ViewNotSupportedTitle")), ResHelper.GetMessage(Localization.GetMessage("FRM_ReviewViewNotSupportedMsg")), false); break; case SessionView.RandomAccess: this.RegisterError( ResHelper.GetMessage(Localization.GetMessage("FRM_ViewNotSupportedTitle")), ResHelper.GetMessage(Localization.GetMessage("FRM_RAViewNotSupportedMsg")), false); break; } return(true); }
/// <summary> /// Deletes pacakge and related attempts from database. /// </summary> /// <param name="packId">Long integer value represents package identifier.</param> protected void DeletePackage(long packId) { // set <packageId> to the ID of this package PackageItemIdentifier packageId = new PackageItemIdentifier(packId); // before we delete the package, we need to delete all attempts on the package -- // the following query looks for those attempts LearningStoreJob job = LStore.CreateJob(); LearningStoreQuery query = LStore.CreateQuery( Schema.MyAttempts.ViewName); query.AddCondition(Schema.MyAttempts.PackageId, LearningStoreConditionOperator.Equal, packageId); query.AddCondition(Schema.MyAttempts.AttemptId, LearningStoreConditionOperator.NotEqual, null); query.AddColumn(Schema.MyAttempts.AttemptId); query.AddSort(Schema.MyAttempts.AttemptId, LearningStoreSortDirection.Ascending); job.PerformQuery(query); DataTable dataTable = job.Execute <DataTable>(); AttemptItemIdentifier previousAttemptId = null; // loop once for each attempt on this package foreach (DataRow dataRow in dataTable.Rows) { // set <attemptId> to the ID of this attempt AttemptItemIdentifier attemptId; LStoreHelper.CastNonNull(dataRow["AttemptId"], out attemptId); // if <attemptId> is a duplicate attempt ID, skip it; note that the query // results are sorted by attempt ID (see above) if ((previousAttemptId != null) && (previousAttemptId.GetKey() == attemptId.GetKey())) { continue; } // delete this attempt StoredLearningSession.DeleteAttempt(LStore, attemptId); // continue to the next attempt previousAttemptId = attemptId; } // delete the package PStore.DeletePackage(packageId); }
/// <summary> /// Creates attempt on given organization and returns attempt identifier. /// </summary> /// <param name="orgId">Long integer value represents organization identifier to create attempt on.</param> /// <param name="curriculumChapterTopicId">Int32 value representing id of curriculum chapter topic.</param> /// <param name="topicType"><see cref="TopicTypeEnum"/> value defines part of topic.</param> /// <returns>Long integer value, representing attempt identifier of created attempt.</returns> protected AttemptItemIdentifier CreateAttempt(long orgId, int curriculumChapterTopicId, TopicTypeEnum topicType) { var organizationId = new ActivityPackageItemIdentifier(orgId); StoredLearningSession session = StoredLearningSession.CreateAttempt( this.PStore, this.GetCurrentUserIdentifier(), organizationId, LoggingOptions.LogAll); LearningStoreJob job = this.LStore.CreateJob(); var dic = new Dictionary <string, object> { { Schema.AttemptItem.IudicoCurriculumChapterTopicRef, curriculumChapterTopicId }, { Schema.AttemptItem.IudicoTopicType, topicType } }; job.UpdateItem(session.AttemptId, dic); job.Execute(); return(session.AttemptId); }
void ReactivateSession() { if (AttemptId == null) { throw new InternalErrorException("SLK1010"); } StoredLearningSession learningSession = new StoredLearningSession(SessionView.RandomAccess, AttemptId, Assignment.Store.PackageStore); // reactivate the attempt learningSession.Reactivate(ReactivateSettings.ResetEvaluationPoints); learningSession.CommitChanges(); // restart the attempt learningSession = new StoredLearningSession(SessionView.Execute, AttemptId, Assignment.Store.PackageStore); learningSession.Start(true); learningSession.CommitChanges(); // NOTE: if (learningSession.AttemptStatus != AttemptStatus.Active) then the // restart process failed -- but there's not much we can do about it, and throwing // an exception may make matters worse }
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); }
/// <summary>Collects the assignment.</summary> public void Collect(AssignmentSaver saver) { CheckUserIsInstructor(); StoredLearningSession session = null; // Check the status switch (Status) { case LearnerAssignmentState.NotStarted: session = CreateAttemptIfRequired(true); break; case LearnerAssignmentState.Active: break; case LearnerAssignmentState.Completed: // No need to collect return; case LearnerAssignmentState.Final: // No need to collect return; default: // New status added break; } CompleteAssignment(session, saver); if (Assignment.EmailChanges) { saver.SendCollectEmail(User); } if (Assignment.IsNonELearning) { saver.UpdateDropBoxPermissions(LearnerAssignmentState.Completed, User); } }
/// <summary>Starts the assignment.</summary> public void Start() { CheckUserIsLearner(); // Check the status switch (Status) { case LearnerAssignmentState.NotStarted: break; case LearnerAssignmentState.Active: return; case LearnerAssignmentState.Completed: throw InvalidTransitionException(LearnerAssignmentState.Completed, LearnerAssignmentState.Active); case LearnerAssignmentState.Final: throw InvalidTransitionException(LearnerAssignmentState.Final, LearnerAssignmentState.Active); default: // New status added break; } StoredLearningSession session = CreateAttemptIfRequired(false); LearnerAssignmentState newStatus = LearnerAssignmentState.Active; if (session == null) { Save(newStatus, null, NonELearningStatus(AttemptStatus.Active), null, null); } else { Save(newStatus, null, NonELearningStatus(AttemptStatus.Active), null, null); } Status = newStatus; }
[SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")] // parameters are validated public void ProcessPageLoad(PackageStore packageStore, TryGetViewInfo TryGetViewInfo, TryGetAttemptInfo TryGetAttemptInfo, ProcessViewRequest ProcessViewRequest) { // These should never be a problem, however fxcop complains about them. FramesetUtil.ValidateNonNullParameter("TryGetViewInfo", TryGetViewInfo); FramesetUtil.ValidateNonNullParameter("TryGetAttemptInfo", TryGetAttemptInfo); FramesetUtil.ValidateNonNullParameter("ProcessViewRequest", ProcessViewRequest); FramesetUtil.ValidateNonNullParameter("packageStore", packageStore); // Session information that may be required SessionView view; AttemptItemIdentifier attemptId; // not required for all views // Get View information. It determines what else to look for. if (!TryGetViewInfo(true, out view)) { return; } // Based on View, request other information switch (view) { case SessionView.Execute: { // AttemptId is required if (!TryGetAttemptInfo(true, out attemptId)) { return; } // Create the session m_session = new StoredLearningSession(view, attemptId, packageStore); StoredLearningSession slsSession = m_session as StoredLearningSession; if (!ProcessViewRequest(SessionView.Execute, slsSession)) { return; } // If the attempt id appeared valid (that is, it was numeric), but does not represent a valid // attempt, the call to access AttemptStatus on the session will trigger an InvalidOperationException // containing a message for the user that the attempt id was not valid. switch (slsSession.AttemptStatus) { case AttemptStatus.Abandoned: { // Can't do execute view on abandoned sessions. The application should have handled this // in the ProcessViewRequest. return; } case AttemptStatus.Active: { // Check if it's started. If not, try starting it and forcing selection of a current activity. if (!slsSession.HasCurrentActivity) { try { slsSession.Start(false); slsSession.CommitChanges(); } catch (SequencingException) { // Intentionally ignored. This means it was either already started or could not // select an activity. In either case, just let the content frame ask the user to // deal with selecting an activity. } } else { // If the current activity is not active, then it's possible the frameset was removed from the // user and the content suspended the current activity. In that case, we do this little trick // and try suspending all the activities and then resuming them. The resume will simply resume // all the activities between the current activity and the root. Other suspended activities // will not be affected. if (!slsSession.CurrentActivityIsActive) { slsSession.Suspend(); slsSession.Resume(); slsSession.CommitChanges(); } } } break; case AttemptStatus.Completed: { // Can't do execute view on completed sessions. The application should have handled this in the // ProcessViewRequest. return; } case AttemptStatus.Suspended: { // Resume it slsSession.Resume(); slsSession.CommitChanges(); } break; default: break; } } break; case SessionView.RandomAccess: { // AttemptId is required if (!TryGetAttemptInfo(true, out attemptId)) { return; } StoredLearningSession slsSession = new StoredLearningSession(SessionView.RandomAccess, attemptId, packageStore); m_session = slsSession; if (!ProcessViewRequest(SessionView.RandomAccess, slsSession)) { return; } // Move to the first activity with a resource. PostableFrameHelper.MoveToNextActivity(m_session); } break; case SessionView.Review: { // AttemptId is required if (!TryGetAttemptInfo(true, out attemptId)) { return; } // Create the session StoredLearningSession slsSession = new StoredLearningSession(view, attemptId, packageStore); m_session = slsSession; if (!ProcessViewRequest(SessionView.Review, m_session)) { return; } // Access a property. If the user doesn't have permission, this will throw exception if (m_session.HasCurrentActivity) { // This is good. The 'if' statement is here to make compiler happy. } } break; } }
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] // it's not worth changing this now public void ProcessPageLoad(PackageStore packageStore, bool firstRendering, // frameset is being initialized bool isPostBack, // page was posted TryGetViewInfo TryGetViewInfo, TryGetAttemptInfo TryGetAttemptInfo, TryGetActivityInfo TryGetActivityInfo, GetResourcePath GetResourcePath, AppendContentFrameDetails appendContentFrameDetails, UpdateRenderContext updateRenderContext, ProcessPostedData ProcessPostedData, ProcessViewRequest ProcessViewRequest, ProcessPostedDataComplete ProcessPostedDataComplete, RegisterError registerError, GetErrorInfo getErrorInfo, GetFramesetMsg getFramesetMsg) { RegisterError = registerError; UpdateRenderContext = updateRenderContext; GetErrorInfo = getErrorInfo; AppendContentFrameDetails = appendContentFrameDetails; GetFramesetMsg = getFramesetMsg; m_isPostedPage = isPostBack; // If this is the first time the page is being rendered, just show 'please wait' and return. if (firstRendering) { // Initial page rendering, show "please wait" WritePleaseWaitToResponse(Response); Response.End(); return; } SessionView view; // Get View information. It determines what else to look for. if (!TryGetViewInfo(true, out view)) { return; } // There is something to display, so process the request... AttemptItemIdentifier attemptId; if (!TryGetAttemptInfo(true, out attemptId)) { return; } StoredLearningSession slsSession = new StoredLearningSession(view, attemptId, packageStore); if (!ProcessViewRequest(view, slsSession)) { return; } Session = slsSession; if (slsSession.View == SessionView.Execute) { // Check if the session can be executed... if (slsSession.AttemptStatus != AttemptStatus.Active) { RegisterError(GetFramesetMsg(FramesetStringId.CannotDisplayContentTitle), GetFramesetMsg(FramesetStringId.SessionIsNotActiveMsg), false); return; } if (!slsSession.CurrentActivityIsActive) { RegisterError(GetFramesetMsg(FramesetStringId.SelectActivityTitleHtml), GetFramesetMsg(FramesetStringId.SelectActivityMessageHtml), true); return; } } else if (slsSession.View == SessionView.Review) { // Get information about which activity to review, then make that the current activity. long activityId = -1; // make compiler happy if (!TryGetActivityInfo(true, out activityId)) { return; } // Move to the requested activity. Under normal conditions, this should always succeed, since the frameset should be // giving this page a valid activity id. MoveToActivity(slsSession, activityId); } else if (slsSession.View == SessionView.RandomAccess) { // Get information about which activity to edit, then make that the current activity. long activityId = -1; // make compiler happy if (!TryGetActivityInfo(true, out activityId)) { return; } // Move to the requested activity. Under normal conditions, this should always succeed, since the frameset should be // giving this page a valid activity id. MoveToActivity(slsSession, activityId); } if (isPostBack /* && !SessionEnded */) { //Process information from posted content if (!this.SessionIsReadOnly) { HttpFileCollection files = Request.Files; int numFiles = files.Count; Dictionary <string, HttpPostedFile> newFileCollection = new Dictionary <string, HttpPostedFile>(numFiles); // Allow the application to process the form data if (!ProcessPostedData(slsSession, Request, newFileCollection)) { return; } // Allow MLC to process the form data Session.ProcessFormData(Request.Form, newFileCollection); // Allow application to do final processing after all posted data is processed. ProcessPostedDataComplete(Session); // If there was an error in processing form data, end the processing. This allows, for instance, to // save the data, display an error and not have commands (such as 'move to next activity') executed. if (HasError) { Session.CommitChanges(); return; } } // Issue with Review view: where to get activity id? From URL or posted data? // Find out what the commands are and do them. ICollection <CommandInfo> commands = GetCommands(); foreach (CommandInfo cmdInfo in commands) { switch (cmdInfo.Command) { case Commands.DoNext: { // When leaving a current activity, we must allow navigation requests the SCO has made to be // executed. If that results in changing the current activity, then do not also ask for another // move. if (!ProcessNavigationRequests(Session)) { if (Session.IsMoveToNextValid()) { MoveToNextActivity(Session); ActivityHasChanged = true; } } else { ActivityHasChanged = true; } if (!ActivityHasChanged) { // Moving to the next activity is not valid. WriteError(ResHelper.Format(GetFramesetMsg(FramesetStringId.MoveToNextFailedHtml), ThemeFolderPath), true); } } break; case Commands.DoPrevious: { if (!ProcessNavigationRequests(Session)) { if (Session.IsMoveToPreviousValid()) { MoveToPreviousActivity(Session); ActivityHasChanged = true; } } else { ActivityHasChanged = true; } if (!ActivityHasChanged) { // Moving to the previous activity is not valid. WriteError(ResHelper.Format(GetFramesetMsg(FramesetStringId.MoveToPreviousFailedHtml), ThemeFolderPath), true); } } break; case Commands.DoChoice: case Commands.DoTocChoice: { // These commands are used to navigate to activities, and to navigate to the final 'submit' page. // In SCORM content, these commands do different things. In LRM content (which is what we are // in, since this is the posted page), they have identical effects. // First check whether this is a request for the submit page or an 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; 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 { 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. 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; } else { if (!ProcessNavigationRequests(Session)) { if (Session.IsMoveToActivityValid(activityId)) { MoveToActivity(Session, activityId); ActivityHasChanged = true; } } else { ActivityHasChanged = true; } } } } if (!ActivityHasChanged) { // Moving to the selected activity is not valid. WriteError(ResHelper.Format(GetFramesetMsg(FramesetStringId.MoveToActivityFailedHtml), ThemeFolderPath), true); } } break; case Commands.DoSave: { // Do nothing. The information will be saved since the page was posted. } break; case Commands.DoSubmit: { if (Session.View == SessionView.Execute) { ProcessNavigationRequests(Session); Session.Exit(); } ActivityHasChanged = true; } break; } } } // If an error has been registered (and it's not the submit // page rendering), don't attempt to render the content. if (HasError && !SubmitPageDisplayed) { if (!SessionIsReadOnly) { Session.CommitChanges(); } return; } // There was no error, so go ahead and render the content from the package. // If the current activity has changed in the processing of this page, then render the current activity's resource. // Otherwise, ask the application which resource to read. if (ActivityHasChanged) { // If the activity has changed, we render the content page without rendering the content. The page will then // reload the content frame with the new activity. if (!SessionIsReadOnly) { Session.CommitChanges(); } } else { // Render the requested file in the current activity. RenderPackageContent(GetResourcePath()); // If there was no error, end the response. Otherwise, we wait and render the error on the page. if (!HasError) { Response.End(); } } }
SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")] // parameter is validated public void ProcessSessionEnd(LearningSession session, ref string messageTitle, ref string message) { FramesetUtil.ValidateNonNullParameter("session", session); // If we have already been here, then there is nothing more to do. if (SessionEnded) { return; } LearnerAssignmentProperties la = GetLearnerAssignment(); // Session ending results in message shown to the user. if (session.View == SessionView.Execute) { StoredLearningSession slsSession = session as StoredLearningSession; if (slsSession != null) { // 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. switch (slsSession.AttemptStatus) { case AttemptStatus.Abandoned: messageTitle = SlkFrameset.HID_SessionAbandonedTitle; message = SlkFrameset.HID_ExecuteViewAbandonedSessionMsg; SessionEnded = true; break; case AttemptStatus.Completed: messageTitle = SlkFrameset.HID_SessionCompletedTitle; message = SlkFrameset.HID_ExecuteViewCompletedSessionMsg; SessionEnded = true; break; case AttemptStatus.Suspended: messageTitle = SlkFrameset.HID_SessionSuspendedTitle; message = SlkFrameset.HID_ExecuteViewSuspendedSessionMsg; // Do not set SessionEnded -- the session being suspended does not warrant ending the learner assignment break; } } if (SessionEnded) { // Call FinishLearnerAssignment since the attempt has already been completed. SlkStore.FinishLearnerAssignment(LearnerAssignmentGuidId); } } else if (session.View == SessionView.RandomAccess) { messageTitle = SlkFrameset.HID_GradingFinishedTitle; message = SlkFrameset.HID_GradingFinishedMessage; StringBuilder sb = new StringBuilder(1000); sb.Append(message); sb.Append("<br><script>"); // Write the assignment status to slkFrameMgr WriteSlkMgrInit(sb); sb.AppendLine("slkMgr = Slk_GetSlkManager();"); sb.AppendFormat("slkMgr.LearnerAssignmentId = {0};\r\n", JScriptString.QuoteString(FramesetUtil.GetStringInvariant(la.LearnerAssignmentId.GetKey()), false)); sb.AppendFormat("slkMgr.Status = {0};\r\n", JScriptString.QuoteString(SlkUtilities.GetLearnerAssignmentState(la.Status), false)); if (AssignmentView == AssignmentView.Grading) { string finalPointsValue = "null"; float? finalPoints = la.FinalPoints; if (finalPoints != null) { finalPointsValue = Convert.ToString(finalPoints.Value, CultureInfo.InvariantCulture.NumberFormat); } sb.AppendFormat("slkMgr.FinalPoints = {0};\r\n", finalPointsValue); } // Send information about total points (ie, computed points on the client). if (session != null) { if (session.TotalPoints != null) { sb.AppendFormat("slkMgr.ComputedPoints = {0};\r\n", JScriptString.QuoteString(Convert.ToString(session.TotalPoints, CultureInfo.CurrentCulture.NumberFormat), false)); } else { sb.AppendFormat("slkMgr.ComputedPoints = \"\";\r\n"); } if (session.SuccessStatus != SuccessStatus.Unknown) { sb.AppendFormat("slkMgr.PassFail = {0};\r\n", JScriptString.QuoteString(((session.SuccessStatus == SuccessStatus.Passed) ? "passed" : "failed"), false)); } } } }
// it's not worth changing this now public void ProcessPageLoad( PackageStore packageStore, bool firstRendering, // frameset is being initialized bool isPostBack, // page was posted TryGetViewInfo tryGetViewInfo, TryGetAttemptInfo tryGetAttemptInfo, TryGetActivityInfo tryGetActivityInfo, GetResourcePath getResourcePath, AppendContentFrameDetails appendContentFrameDetails, UpdateRenderContext updateRenderContext, ProcessPostedData processPostedData, ProcessViewRequest processViewRequest, ProcessPostedDataComplete processPostedDataComplete, RegisterError registerError, GetErrorInfo getErrorInfo, GetFramesetMsg getFramesetMsg) { this.RegisterError = registerError; this.UpdateRenderContext = updateRenderContext; this.GetErrorInfo = getErrorInfo; this.AppendContentFrameDetails = appendContentFrameDetails; this.GetFramesetMsg = getFramesetMsg; this.mIsPostedPage = isPostBack; // If this is the first time the page is being rendered, just show 'please wait' and return. if (firstRendering) { // Initial page rendering, show "please wait" WritePleaseWaitToResponse(this.Response); this.Response.End(); return; } SessionView view; // Get View information. It determines what else to look for. if (!tryGetViewInfo(true, out view)) { return; } // There is something to display, so process the request... AttemptItemIdentifier attemptId; if (!tryGetAttemptInfo(true, out attemptId)) { return; } StoredLearningSession slsSession = new StoredLearningSession(view, attemptId, packageStore); if (!processViewRequest(view, slsSession)) { return; } this.Session = slsSession; if (slsSession.View == SessionView.Execute) { // Check if the session can be executed... if (slsSession.AttemptStatus != AttemptStatus.Active) { this.RegisterError( this.GetFramesetMsg(FramesetStringId.CannotDisplayContentTitle), this.GetFramesetMsg(FramesetStringId.SessionIsNotActiveMsg), false); return; } if (!slsSession.CurrentActivityIsActive) { this.RegisterError( this.GetFramesetMsg(FramesetStringId.SelectActivityTitleHtml), this.GetFramesetMsg(FramesetStringId.SelectActivityMessageHtml), true); return; } } else if (slsSession.View == SessionView.Review) { // Get information about which activity to review, then make that the current activity. long activityId = -1; // make compiler happy if (!tryGetActivityInfo(true, out activityId)) { return; } // Move to the requested activity. Under normal conditions, this should always succeed, since the frameset should be // giving this page a valid activity id. MoveToActivity(slsSession, activityId); } else if (slsSession.View == SessionView.RandomAccess) { // Get information about which activity to edit, then make that the current activity. long activityId = -1; // make compiler happy if (!tryGetActivityInfo(true, out activityId)) { return; } // Move to the requested activity. Under normal conditions, this should always succeed, since the frameset should be // giving this page a valid activity id. MoveToActivity(slsSession, activityId); } if (isPostBack /* && !SessionEnded */) { // Process information from posted content if (!this.SessionIsReadOnly) { HttpFileCollection files = this.Request.Files; int numFiles = files.Count; Dictionary<string, HttpPostedFile> newFileCollection = new Dictionary<string, HttpPostedFile>(numFiles); // Allow the application to process the form data if (!processPostedData(slsSession, this.Request, newFileCollection)) { return; } // Allow MLC to process the form data this.Session.ProcessFormData(this.Request.Form, newFileCollection); // Allow application to do final processing after all posted data is processed. processPostedDataComplete(this.Session); // If there was an error in processing form data, end the processing. This allows, for instance, to // save the data, display an error and not have commands (such as 'move to next activity') executed. if (this.HasError) { this.Session.CommitChanges(); return; } } // Issue with Review view: where to get activity id? From URL or posted data? // Find out what the commands are and do them. ICollection<CommandInfo> commands = this.GetCommands(); foreach (CommandInfo cmdInfo in commands) { switch (cmdInfo.Command) { case Commands.DoNext: { // When leaving a current activity, we must allow navigation requests the SCO has made to be // executed. If that results in changing the current activity, then do not also ask for another // move. if (!ProcessNavigationRequests(this.Session)) { if (this.Session.IsMoveToNextValid()) { MoveToNextActivity(this.Session); this.ActivityHasChanged = true; } } else { this.ActivityHasChanged = true; } if (!this.ActivityHasChanged) { // Moving to the next activity is not valid. this.WriteError( ResHelper.Format( this.GetFramesetMsg(FramesetStringId.MoveToNextFailedHtml), this.ThemeFolderPath), true); } } break; case Commands.DoPrevious: { if (!ProcessNavigationRequests(this.Session)) { if (this.Session.IsMoveToPreviousValid()) { MoveToPreviousActivity(this.Session); this.ActivityHasChanged = true; } } else { this.ActivityHasChanged = true; } if (!this.ActivityHasChanged) { // Moving to the previous activity is not valid. this.WriteError( ResHelper.Format( this.GetFramesetMsg(FramesetStringId.MoveToPreviousFailedHtml), this.ThemeFolderPath), true); } } break; case Commands.DoChoice: case Commands.DoTocChoice: { // These commands are used to navigate to activities, and to navigate to the final 'submit' page. // In SCORM content, these commands do different things. In LRM content (which is what we are // in, since this is the posted page), they have identical effects. // First check whether this is a request for the submit page or an 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. this.ActivityHasChanged = true; string title = this.GetFramesetMsg(FramesetStringId.SubmitPageTitleHtml); string message; string saveBtn = this.GetFramesetMsg(FramesetStringId.SubmitPageSaveButtonHtml); if (this.Session.HasCurrentActivity) { message = this.GetFramesetMsg(FramesetStringId.SubmitPageMessageHtml); } else { message = this.GetFramesetMsg(FramesetStringId.SubmitPageMessageNoCurrentActivityHtml); } this.WriteSubmitPage(title, message, saveBtn); } else { 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. 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 (this.IsCurrentActiveActivity(activityId)) { this.ActivityHasChanged = true; } else { if (!ProcessNavigationRequests(this.Session)) { if (this.Session.IsMoveToActivityValid(activityId)) { MoveToActivity(this.Session, activityId); this.ActivityHasChanged = true; } } else { this.ActivityHasChanged = true; } } } } if (!this.ActivityHasChanged) { // Moving to the selected activity is not valid. this.WriteError( ResHelper.Format( this.GetFramesetMsg(FramesetStringId.MoveToActivityFailedHtml), this.ThemeFolderPath), true); } } break; case Commands.DoSave: { // Do nothing. The information will be saved since the page was posted. } break; case Commands.DoSubmit: { if (this.Session.View == SessionView.Execute) { ProcessNavigationRequests(this.Session); this.Session.Exit(); } this.ActivityHasChanged = true; } break; } } } // If an error has been registered (and it's not the submit // page rendering), don't attempt to render the content. if (this.HasError && !this.SubmitPageDisplayed) { if (!this.SessionIsReadOnly) { this.Session.CommitChanges(); } return; } // There was no error, so go ahead and render the content from the package. // If the current activity has changed in the processing of this page, then render the current activity's resource. // Otherwise, ask the application which resource to read. if (this.ActivityHasChanged) { // If the activity has changed, we render the content page without rendering the content. The page will then // reload the content frame with the new activity. if (!this.SessionIsReadOnly) { this.Session.CommitChanges(); } } else { // Render the requested file in the current activity. this.RenderPackageContent(getResourcePath()); // If there was no error, end the response. Otherwise, we wait and render the error on the page. if (!this.HasError) { this.Response.End(); } } }
[SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")] // parameters are validated public void ProcessPageLoad(PackageStore packageStore, TryGetViewInfo TryGetViewInfo, TryGetAttemptInfo TryGetAttemptInfo, ProcessViewRequest ProcessViewRequest) { // These should never be a problem, however fxcop complains about them. FramesetUtil.ValidateNonNullParameter("TryGetViewInfo", TryGetViewInfo); FramesetUtil.ValidateNonNullParameter("TryGetAttemptInfo", TryGetAttemptInfo); FramesetUtil.ValidateNonNullParameter("ProcessViewRequest", ProcessViewRequest); FramesetUtil.ValidateNonNullParameter("packageStore", packageStore); // Session information that may be required SessionView view; AttemptItemIdentifier attemptId; // not required for all views // Get View information. It determines what else to look for. if (!TryGetViewInfo(true, out view)) return; // Based on View, request other information switch (view) { case SessionView.Execute: { // AttemptId is required if (!TryGetAttemptInfo(true, out attemptId)) return; // Create the session m_session = new StoredLearningSession(view, attemptId, packageStore); StoredLearningSession slsSession = m_session as StoredLearningSession; if (!ProcessViewRequest(SessionView.Execute, slsSession)) { if (slsSession.AttemptStatus == AttemptStatus.Completed) { } return; } // If the attempt id appeared valid (that is, it was numeric), but does not represent a valid // attempt, the call to access AttemptStatus on the session will trigger an InvalidOperationException // containing a message for the user that the attempt id was not valid. switch (slsSession.AttemptStatus) { case AttemptStatus.Abandoned: { // Can't do execute view on abandoned sessions. The application should have handled this // in the ProcessViewRequest. return; } case AttemptStatus.Active: { // Check if it's started. If not, try starting it and forcing selection of a current activity. if (!slsSession.HasCurrentActivity) { try { slsSession.Start(false); slsSession.CommitChanges(); } catch (SequencingException) { // Intentionally ignored. This means it was either already started or could not // select an activity. In either case, just let the content frame ask the user to // deal with selecting an activity. } } else { // If the current activity is not active, then it's possible the frameset was removed from the // user and the content suspended the current activity. In that case, we do this little trick // and try suspending all the activities and then resuming them. The resume will simply resume // all the activities between the current activity and the root. Other suspended activities // will not be affected. if (!slsSession.CurrentActivityIsActive) { slsSession.Suspend(); slsSession.Resume(); slsSession.CommitChanges(); } } } break; case AttemptStatus.Completed: { // Can't do execute view on completed sessions. The application should have handled this in the // ProcessViewRequest. return; } case AttemptStatus.Suspended: { // Resume it slsSession.Resume(); slsSession.CommitChanges(); } break; default: break; } } break; case SessionView.RandomAccess: { // AttemptId is required if (!TryGetAttemptInfo(true, out attemptId)) return; StoredLearningSession slsSession = new StoredLearningSession(SessionView.RandomAccess, attemptId, packageStore); m_session = slsSession; if (!ProcessViewRequest(SessionView.RandomAccess, slsSession )) return; // Move to the first activity with a resource. PostableFrameHelper.MoveToNextActivity(m_session); } break; case SessionView.Review: { // AttemptId is required if (!TryGetAttemptInfo(true, out attemptId)) return; // Create the session StoredLearningSession slsSession = new StoredLearningSession(view, attemptId, packageStore); m_session = slsSession; if (!ProcessViewRequest(SessionView.Review, m_session)) return; // Access a property. If the user doesn't have permission, this will throw exception if (m_session.HasCurrentActivity) { // This is good. The 'if' statement is here to make compiler happy. } } break; } }
protected void DeletePackagesButton_Click(object sender, EventArgs e) { // the user clicked "Upload"... // hide the confirmation panel ConfirmMessage.Visible = false; // the PackagesToDelete hidden form element contains a comma-delimited list of IDs of // packages to delete (copied from <dialogArguments> on the client) -- attempt to delete // those packages, and set <deleted> to the IDs of packages successfully deleted List <string> deleted = new List <string>(); try { // loop once for each package to delete foreach (string id in PackagesToDelete.Value.Split(',')) { // set <packageId> to the ID of this package PackageItemIdentifier packageId = new PackageItemIdentifier( Convert.ToInt64(id, CultureInfo.InvariantCulture)); // before we delete the package, we need to delete all attempts on the package -- // the following query looks for those attempts LearningStoreJob job = LStore.CreateJob(); LearningStoreQuery query = LStore.CreateQuery( Schema.MyAttemptsAndPackages.ViewName); query.AddCondition(Schema.MyAttemptsAndPackages.PackageId, LearningStoreConditionOperator.Equal, packageId); query.AddCondition(Schema.MyAttemptsAndPackages.AttemptId, LearningStoreConditionOperator.NotEqual, null); query.AddColumn(Schema.MyAttemptsAndPackages.AttemptId); query.AddSort(Schema.MyAttemptsAndPackages.AttemptId, LearningStoreSortDirection.Ascending); job.PerformQuery(query); DataTable dataTable = job.Execute <DataTable>(); AttemptItemIdentifier previousAttemptId = null; // loop once for each attempt on this package foreach (DataRow dataRow in dataTable.Rows) { // set <attemptId> to the ID of this attempt AttemptItemIdentifier attemptId; LStoreHelper.CastNonNull(dataRow["AttemptId"], out attemptId); // if <attemptId> is a duplicate attempt ID, skip it; note that the query // results are sorted by attempt ID (see above) if ((previousAttemptId != null) && (previousAttemptId.GetKey() == attemptId.GetKey())) { continue; } // delete this attempt StoredLearningSession.DeleteAttempt(LStore, attemptId); // continue to the next attempt previousAttemptId = attemptId; } // delete the package PStore.DeletePackage(packageId); // add the package ID to the list of deleted packages deleted.Add(id); } // the operation was successful, and there are no messages to // display to the user, so close the dialog CloseDialogScript.Visible = true; } catch (Exception ex) { // an unexpected error occurred -- display a generic message that // doesn't include the exception message (since that message may // include sensitive information), and write the exception message // to the event log ErrorIntro.Visible = true; ErrorMessage.Visible = true; ErrorMessage.Controls.Add(new System.Web.UI.LiteralControl( Server.HtmlEncode("A serious error occurred. Please contact your system administrator. More information has been written to the server event log."))); LogEvent(System.Diagnostics.EventLogEntryType.Error, "An exception occurred while deleting a package:\n\n{0}\n\n", ex.ToString()); } // update the buttons DeletePackagesButton.Visible = false; CloseButton.Text = "OK"; // set the hidden form element PackagesSuccessfullyDeleted to a // comma-separated list of IDs of packages that were successfully // deleted, and enable the client-side script that communicates this // information to the parent page PackagesSuccessfullyDeleted.Value = String.Join(",", deleted.ToArray()); UpdateParentPageScript.Visible = true; }
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; } }