/// <summary> /// Writes the file from a byte array directly to the response. The output stream /// extension must be set prior to calling this method. /// If the response is provided and the path extension is not /// in the list of files to use IIS compatibility mode, this is much faster than /// copying the file contents to the output stream and should be used whenever possible. /// If there is no response object, the method reverts to copying the file between streams. /// </summary> /// <param name="fileBytes">The file to render.</param> internal void WriteFileToResponse(byte[] fileBytes) { using (Disposer disposer = new Disposer()) { // Open a stream on the file. For some odd reason fxcop does not acknowledge the ability of disposer // to actually dispose fileStream, so we give it its own using block. using (Stream fileStream = new MemoryStream(fileBytes)) { // If the response was provided (which it will be, in most non-test cases), then // do the TransmitFile or WriteFile. Otherwise, copy the streams to the response output stream. if (Response != null) { // IisCompatibilityMode is not considered here, because in no case do we have a file // path that can be read by TransmitFile. WriteIisCompatibilityModeToResponse(fileStream); } else { DetachableStream outputDS = new DetachableStream(OutputStream); disposer.Push(outputDS); Utilities.CopyStream(fileStream, ImpersonationBehavior.UseImpersonatedIdentity, outputDS, ImpersonationBehavior.UseImpersonatedIdentity); outputDS.Detach(); } } } }
/// <summary> /// Writes the file from the package directly to the response and set the appropriate /// content type in the response. If the response is provided and the path extension is not /// in the list of files to use IIS compatibility mode, this is much faster than /// copying the file contents to the output stream and should be used whenever possible. /// If there is no response object, the method reverts to copying the file between streams. /// </summary> /// <param name="relativePath">The package-relative path to the file to render.</param> internal void WriteFileToResponse(string relativePath) { // Set the mime type on the response string pathExtension = Path.GetExtension(relativePath); SetOutputStreamExtension(pathExtension); using (Disposer disposer = new Disposer()) { PackageReader pkgReader = m_session.GetPackageReader(); disposer.Push(pkgReader); // If the response was provided (which it will be, in most non-test cases), then // do the TransmitFile or WriteFile. Otherwise, copy the streams to the response output stream. if (Response != null) { // If we are to use Compatibility mode, then write the file if (UseCompatibilityMode(pathExtension)) { Stream packageFile = pkgReader.GetFileStream(relativePath); WriteIisCompatibilityModeToResponse(packageFile); } #if DOTNET40 else if (FileIsHtml(pathExtension)) { SendHtmlChangingCssHref(pkgReader.GetFileStream(relativePath)); } else if (FileIsJavascript(pathExtension)) { SendJavascriptChangingCssHref(pkgReader.GetFileStream(relativePath)); } #endif else { pkgReader.TransmitFile(relativePath, m_context.Response); } } else { DetachableStream outputDS = new DetachableStream(OutputStream); disposer.Push(outputDS); Stream packageFile = pkgReader.GetFileStream(relativePath); disposer.Push(packageFile); Utilities.CopyStream(packageFile, ImpersonationBehavior.UseImpersonatedIdentity, outputDS, ImpersonationBehavior.UseImpersonatedIdentity); outputDS.Detach(); } } }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")] // the writer is disposed for all code paths internal static void CopyStream(Stream fromStream, ImpersonationBehavior readImpersonationBehavior, Stream toStream, ImpersonationBehavior writeImpersonationBehavior) { using (Disposer disposer = new Disposer()) { DetachableStream dsToStream = new DetachableStream(toStream); disposer.Push(dsToStream); BinaryWriter writer = new BinaryWriter(dsToStream); disposer.Push(writer); // An addition revert during write operations is required if the setting for reading and writing // is not the same bool requiresWriteRevert = (readImpersonationBehavior != writeImpersonationBehavior); using (ImpersonateIdentity readId = new ImpersonateIdentity(readImpersonationBehavior)) { byte[] bytesIn = new byte[65536]; int bytesRead; while ((bytesRead = fromStream.Read(bytesIn, 0, bytesIn.Length)) != 0) { // If we have to impersonate to write, then do it. Otherwise skip it. if (requiresWriteRevert) { using (ImpersonateIdentity id = new ImpersonateIdentity(writeImpersonationBehavior)) { writer.Write(bytesIn, 0, bytesRead); } } else { writer.Write(bytesIn, 0, bytesRead); } } } dsToStream.Detach(); } }
/// <summary> /// If there is an m_result cached from a call to CreateManifestNavigator, return that manifest. /// Otherwise, create a manifest using a relaxed conversion. /// </summary> /// <returns>Contents of the imsmanifest.xml converted from the index.xml.</returns> internal Stream ConvertFromIndexXml() { ConversionResult result; if (m_result == null || m_result.Manifest == null) { result = ManifestConverter.ConvertFromIndexXml(GetFileStream("index.xml"), GetFilePaths(), true, ValidationBehavior.None); } else { result = m_result; } using(DetachableStream output = new DetachableStream(result.Manifest.OuterXml.Length)) { using (XmlWriter writer = XmlWriter.Create(output)) { result.Manifest.WriteSubtree(writer); writer.Flush(); output.Detach(); } output.Stream.Seek(0, SeekOrigin.Begin); return output.Stream; } }
/// <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); } }
/// <summary> /// Writes the file from the package directly to the response and set the appropriate /// content type in the response. If the response is provided and the path extension is not /// in the list of files to use IIS compatibility mode, this is much faster than /// copying the file contents to the output stream and should be used whenever possible. /// If there is no response object, the method reverts to copying the file between streams. /// </summary> /// <param name="relativePath">The package-relative path to the file to render.</param> internal void WriteFileToResponse(string relativePath) { // Set the mime type on the response string pathExtension = Path.GetExtension(relativePath); SetOutputStreamExtension(pathExtension); using(Disposer disposer = new Disposer()) { PackageReader pkgReader = m_session.GetPackageReader(); disposer.Push(pkgReader); // If the response was provided (which it will be, in most non-test cases), then // do the TransmitFile or WriteFile. Otherwise, copy the streams to the response output stream. if (Response != null) { // If we are to use Compatibility mode, then write the file if (UseCompatibilityMode(pathExtension)) { Stream packageFile = pkgReader.GetFileStream(relativePath); WriteIisCompatibilityModeToResponse(packageFile); } else { pkgReader.TransmitFile(relativePath, m_context.Response); } } else { DetachableStream outputDS = new DetachableStream(OutputStream); disposer.Push(outputDS); Stream packageFile = pkgReader.GetFileStream(relativePath); disposer.Push(packageFile); Utilities.CopyStream(packageFile, ImpersonationBehavior.UseImpersonatedIdentity, outputDS, ImpersonationBehavior.UseImpersonatedIdentity); outputDS.Detach(); } } }
/// <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); } }