536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
2159 lines
81 KiB
C#
2159 lines
81 KiB
C#
|
||
//--------------------------------------------------------------------------------------------------------------------------
|
||
// <copyright company=’Microsoft Corporation’>
|
||
// Copyright © Microsoft Corporation. All Rights Reserved.
|
||
// </copyright>
|
||
//--------------------------------------------------------------------------------------------------------------------------
|
||
// @owner=alexgor, deliant
|
||
//==========================================================================================================================
|
||
// File: ChartHttpHandler.cs
|
||
//
|
||
// Namespace: Microsoft.Reporting.Chart.WebForms
|
||
//
|
||
// Classes: ChartHttpHandler
|
||
//
|
||
// Purpose: ChartHttpHandler is a static class which is responsible to handle with
|
||
// chart images, interactive images, scripts and other resources.
|
||
//
|
||
//
|
||
// Reviewed: DT
|
||
// Reviewed: deliant on 4/14/2011
|
||
// MSRC#10470, VSTS#941768 http://vstfdevdiv:8080/web/wi.aspx?id=941768
|
||
// Please review information associated with MSRC#10470 before making any changes to this file.
|
||
// - Fixes:
|
||
// - Fixed Directory Traversal/Arbitrary File Read, Delete with malformed image key.
|
||
// - Honor HttpContext.Current.Trace.IsEnabled when generate and deliver chart trace info.
|
||
// - Handle empty guid parameter ("?g=") as invalid when enforcing privacy.
|
||
// - Replaced the privacy byte array comparison with custom check (otherwise posible EOS marker can return 0 length string).
|
||
// - Added fixed string to session key to avoid direct session access.
|
||
//
|
||
// Added: deliant on 4/48/2011 fix for VSTS: 3593 - ASP.Net chart under web farm exhibit fast performace degradation
|
||
// Summary: Under large web farm setup ( ~16 processes and up) chart control image handler
|
||
// soon starts to show performace degradation up to denial of service, when a file system is used as storage.
|
||
// Issues:
|
||
// - The image files in count over 2000 in one single folder causes exponentially growing slow response,
|
||
// especially on the remote server. The fix places the Image files in separate subfolders for each process.
|
||
// - Private protection seeks and read several times in the image file istead reading the image at once
|
||
// and then check for privacy marker. Separate small network reads are expensive.
|
||
// - Due missing lock in initialization stage the chart lock files number can grow more that process max
|
||
// number which can create abandon chart image files
|
||
//==========================================================================================================================
|
||
|
||
#region Namespaces
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Text;
|
||
using System.Web;
|
||
using System.Web.UI;
|
||
using System.IO;
|
||
using System.Web.Caching;
|
||
using System.Collections;
|
||
using System.Web.Configuration;
|
||
using System.Resources;
|
||
using System.Reflection;
|
||
using System.Drawing;
|
||
using System.Drawing.Imaging;
|
||
using System.Threading;
|
||
using System.Collections.Specialized;
|
||
using System.Diagnostics;
|
||
using System.Web.Hosting;
|
||
using System.Web.SessionState;
|
||
using System.Drawing.Drawing2D;
|
||
using System.Runtime.InteropServices;
|
||
using System.Globalization;
|
||
using System.Diagnostics.CodeAnalysis;
|
||
using System.Security.Permissions;
|
||
using System.Security;
|
||
using System.Security.Cryptography;
|
||
using System.Collections.ObjectModel;
|
||
using System.Web.UI.WebControls;
|
||
|
||
#endregion //Namespaces
|
||
|
||
namespace System.Web.UI.DataVisualization.Charting
|
||
{
|
||
/// <summary>
|
||
/// ChartHttpHandler processes HTTP Web requests using, handles chart images, scripts and other resources.
|
||
/// </summary>
|
||
#if ASPPERM_35
|
||
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
|
||
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
|
||
#endif
|
||
public class ChartHttpHandler : Page, IRequiresSessionState, IHttpHandler
|
||
{
|
||
|
||
#region Fields
|
||
|
||
// flag that indicates whether this chart handler is installed
|
||
private static bool _installed = false;
|
||
|
||
// flag that indicates whether this chart handler is installed
|
||
private static bool _installChecked = false;
|
||
|
||
// storage settings
|
||
private static ChartHttpHandlerSettings _parameters = null;
|
||
|
||
|
||
// machine hash key which is part in chart image file name
|
||
private static string _machineHash = "_" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_";
|
||
|
||
// web gadren controller file. stays locked diring process lifetime.
|
||
private static FileStream _controllerFileStream = null;
|
||
private static string _controllerDirectory = null;
|
||
private static object _initHandlerLock = new object();
|
||
// used for storing Guid key in context;
|
||
internal static string ContextGuidKey = "{89FA5660-BD13-4f1b-8C7C-355CEC92CC7E}";
|
||
|
||
// web gadren controller file. stays locked diring process lifetime.
|
||
private const string handlerCheckQry = "check";
|
||
|
||
#endregion //Fields
|
||
|
||
#region Consts
|
||
|
||
internal const string ChartHttpHandlerName = "ChartImg.axd";
|
||
internal const string ChartHttpHandlerAppSection = "ChartImageHandler";
|
||
internal const string DefaultConfigSettings = @"storage=file;timeout=20;dir=c:\TempImageFiles\;";
|
||
internal const string WebDevServerUseConfigSettings = "WebDevServerUseConfigSettings";
|
||
#endregion //Consts
|
||
|
||
#region Constructors
|
||
|
||
/// <summary>
|
||
/// Ensures that the handler is initialized.
|
||
/// </summary>
|
||
/// <param name="hardCheck">if set to <c>true</c> then will be thrown all excepitons.</param>
|
||
private static void EnsureInitialized(bool hardCheck)
|
||
{
|
||
if (_installChecked)
|
||
{
|
||
return;
|
||
}
|
||
lock (_initHandlerLock)
|
||
{
|
||
if (_installChecked)
|
||
{
|
||
return;
|
||
}
|
||
if (HttpContext.Current != null)
|
||
{
|
||
try
|
||
{
|
||
using (TextWriter w = new StringWriter(CultureInfo.InvariantCulture))
|
||
{
|
||
HttpContext.Current.Server.Execute(ChartHttpHandlerName + "?" + handlerCheckQry + "=0", w);
|
||
}
|
||
_installed = true;
|
||
}
|
||
catch (HttpException)
|
||
{
|
||
if (hardCheck) throw;
|
||
}
|
||
catch (SecurityException)
|
||
{
|
||
// under minimal configuration we assume that the hanlder is installed if app settings are present.
|
||
_installed = !String.IsNullOrEmpty(WebConfigurationManager.AppSettings[ChartHttpHandlerAppSection]);
|
||
}
|
||
}
|
||
if (_installed || hardCheck)
|
||
{
|
||
InitializeControllerFile();
|
||
}
|
||
_installChecked = true;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Initializes the storage settings
|
||
/// </summary>
|
||
//static ChartHttpHandler()
|
||
private static ChartHttpHandlerSettings InitializeParameters()
|
||
{
|
||
|
||
ChartHttpHandlerSettings result = new ChartHttpHandlerSettings();
|
||
if (HttpContext.Current != null)
|
||
{
|
||
// Read settings from config; use DefaultConfigSettings in case when setting is not found
|
||
string configSettings = WebConfigurationManager.AppSettings[ChartHttpHandlerAppSection];
|
||
if (String.IsNullOrEmpty(configSettings))
|
||
configSettings = DefaultConfigSettings;
|
||
|
||
result = new ChartHttpHandlerSettings(configSettings);
|
||
}
|
||
else
|
||
{
|
||
result.PrepareDesignTime();
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
private static void ResetControllerStream()
|
||
{
|
||
if (_controllerFileStream != null)
|
||
_controllerFileStream.Dispose();
|
||
_controllerFileStream = null;
|
||
_controllerDirectory = null;
|
||
}
|
||
|
||
private static void InitializeControllerFile()
|
||
{
|
||
if (Settings.StorageType == ChartHttpHandlerStorageType.File && _controllerFileStream == null)
|
||
{
|
||
byte[] data = System.Text.Encoding.UTF8.GetBytes("chart io controller file");
|
||
// 2048 processes max.
|
||
for (Int32 i = 0; i < 2048; i++)
|
||
{
|
||
try
|
||
{
|
||
ResetControllerStream();
|
||
string controllerFileName = String.Format(CultureInfo.InvariantCulture, "{0}msc_cntr_{1}.txt", Settings.Directory, i);
|
||
_controllerDirectory = String.Format(CultureInfo.InvariantCulture, "charts_{0}", i);
|
||
_controllerFileStream = new System.IO.FileStream(controllerFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||
_controllerFileStream.Lock(0, data.Length);
|
||
_controllerFileStream.Write(data, 0, data.Length);
|
||
_machineHash = "_" + i + "_";
|
||
if (!Directory.Exists(Settings.Directory + _controllerDirectory))
|
||
{
|
||
Directory.CreateDirectory(Settings.Directory + _controllerDirectory);
|
||
}
|
||
else
|
||
{
|
||
TimeSpan lastWrite = DateTime.Now - Directory.GetLastWriteTime(Settings.Directory + _controllerDirectory);
|
||
if (lastWrite.Seconds < Settings.Timeout.Seconds)
|
||
{
|
||
continue;
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
catch (IOException)
|
||
{
|
||
continue;
|
||
}
|
||
catch (Exception)
|
||
{
|
||
ResetControllerStream();
|
||
throw;
|
||
}
|
||
}
|
||
ResetControllerStream();
|
||
throw new UnauthorizedAccessException(SR.ExceptionHttpHandlerTempDirectoryUnaccesible(Settings.Directory));
|
||
}
|
||
}
|
||
|
||
#endregion //Constructors
|
||
|
||
#region Methods
|
||
|
||
#region ChartImage
|
||
|
||
/// <summary>
|
||
/// Processes the saved image.
|
||
/// </summary>
|
||
/// <param name="context">The context.</param>
|
||
/// <returns>false if the image cannot be processed</returns>
|
||
private static bool ProcessSavedChartImage(HttpContext context)
|
||
{
|
||
// image delivery doesn't depend if handler is intitilzed or not.
|
||
String key = context.Request["i"];
|
||
CurrentGuidKey = context.Request["g"];
|
||
IChartStorageHandler handler = GetHandler();
|
||
try
|
||
{
|
||
Byte[] data = handler.Load(KeyToUnc(key));
|
||
if (data != null && data.Length > 0)
|
||
{
|
||
context.Response.Charset = "";
|
||
context.Response.ContentType = GetMime(key);
|
||
context.Response.BinaryWrite(data);
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServed(key), null);
|
||
if (Settings.StorageType == ChartHttpHandlerStorageType.Session || Settings.DeleteAfterServicing)
|
||
{
|
||
handler.Delete(key);
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageDeleted(key), null);
|
||
}
|
||
return true;
|
||
}
|
||
if (!(handler is DefaultImageHandler))
|
||
{
|
||
// the default handler will write more detailed message
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailNotFound), null);
|
||
}
|
||
}
|
||
catch (NullReferenceException nre)
|
||
{
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, String.Empty), nre);
|
||
throw;
|
||
}
|
||
catch (IOException ioe)
|
||
{
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, String.Empty), ioe);
|
||
throw;
|
||
}
|
||
catch (SecurityException se)
|
||
{
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, String.Empty), se);
|
||
throw;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
#endregion //ChartImage
|
||
|
||
#region Utilities
|
||
|
||
|
||
/// <summary>
|
||
/// Gets or sets the current GUID key.
|
||
/// </summary>
|
||
/// <value>The current GUID key.</value>
|
||
internal static string CurrentGuidKey
|
||
{
|
||
get
|
||
{
|
||
if (HttpContext.Current != null)
|
||
{
|
||
return (string)HttpContext.Current.Items[ContextGuidKey];
|
||
}
|
||
return String.Empty;
|
||
}
|
||
set
|
||
{
|
||
if (HttpContext.Current != null)
|
||
{
|
||
if (String.IsNullOrEmpty(value))
|
||
{
|
||
HttpContext.Current.Items.Remove(ContextGuidKey);
|
||
}
|
||
else
|
||
{
|
||
HttpContext.Current.Items[ContextGuidKey] = value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the chart image handler interface reference.
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private static IChartStorageHandler GetHandler()
|
||
{
|
||
return ChartHttpHandler.Settings.GetHandler();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines whether this instance is installed.
|
||
/// </summary>
|
||
internal static void EnsureInstalled()
|
||
{
|
||
EnsureInitialized(true);
|
||
EnsureSessionIsClean();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the handler URL.
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private static String GetHandlerUrl()
|
||
{
|
||
// the handler have to be executed in current cxecution path in order to get proper user identity
|
||
String appDir = Path.GetDirectoryName(HttpContext.Current.Request.CurrentExecutionFilePath ?? "").Replace("\\","/");
|
||
if (!appDir.EndsWith("/", StringComparison.Ordinal))
|
||
{
|
||
appDir += "/";
|
||
}
|
||
return appDir + ChartHttpHandlerName + "?";
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Gets the MIME type by resource url.
|
||
/// </summary>
|
||
/// <param name="resourceUrl">The resource URL.</param>
|
||
/// <returns></returns>
|
||
[SuppressMessage("Microsoft.Globalization", "CA1308",
|
||
Justification = "No security decision is being made on the ToLowerInvariant() call. It is being used to ensure the file extension is lowercase")]
|
||
private static String GetMime(String resourceUrl)
|
||
{
|
||
String ext = Path.GetExtension(resourceUrl);
|
||
|
||
ext = ext.ToLowerInvariant();
|
||
|
||
if (ext == ".js")
|
||
{
|
||
return "text/javascript";
|
||
}
|
||
else if (ext == ".htm")
|
||
{
|
||
return "text/html";
|
||
}
|
||
else if (".css,.html,.xml".IndexOf(ext, StringComparison.Ordinal) != -1)
|
||
{
|
||
return "text/" + ext.Substring(1);
|
||
}
|
||
else if (".jpg;.jpeg;.gif;.png;.emf".IndexOf(ext, StringComparison.Ordinal) != -1)
|
||
{
|
||
string fmt = ext.Substring(1).Replace("jpg", "jpeg");
|
||
return "image/" + fmt;
|
||
}
|
||
return "text/plain";
|
||
}
|
||
|
||
/// <summary>
|
||
/// Generates the chart image file name (key).
|
||
/// </summary>
|
||
/// <param name="ext">The ext.</param>
|
||
/// <param name="fileName">Name of the file.</param>
|
||
/// <returns></returns>
|
||
private static String GenerateKey(String ext)
|
||
{
|
||
String fmtKey = "chart" + _machineHash + "{0}." + ext;
|
||
RingTimeTracker rt = RingTimeTrackerFactory.GetRingTracker(fmtKey);
|
||
if (!String.IsNullOrEmpty(_controllerDirectory) && String.IsNullOrEmpty(Settings.FolderName))
|
||
{
|
||
return _controllerDirectory + @"\" + rt.GetNextKey();
|
||
}
|
||
return Settings.FolderName + rt.GetNextKey();
|
||
}
|
||
|
||
private static String KeyToUnc(String key)
|
||
{
|
||
if (!String.IsNullOrEmpty(key))
|
||
{
|
||
return key.Replace("/", @"\");
|
||
}
|
||
return key;
|
||
}
|
||
|
||
private static String KeyFromUnc(String key)
|
||
{
|
||
if (!String.IsNullOrEmpty(key))
|
||
{
|
||
return key.Replace(@"\", "/");
|
||
}
|
||
return key;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets a URL by specified request query, file key.
|
||
/// </summary>
|
||
/// <param name="query">The query.</param>
|
||
/// <param name="fileKey">The file key.</param>
|
||
/// <param name="currentGuid">The current GUID.</param>
|
||
/// <returns></returns>
|
||
private static String GetUrl(String query, String fileKey, string currentGuid)
|
||
{
|
||
return GetHandlerUrl() + query + "=" + KeyFromUnc(fileKey) + "&g=" + currentGuid;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the image url.
|
||
/// </summary>
|
||
/// <param name="stream">The stream.</param>
|
||
/// <param name="imageExt">The image extention.</param>
|
||
/// <returns>Generated the image source URL</returns>
|
||
[SuppressMessage("Microsoft.Globalization", "CA1308",
|
||
Justification="No security decision is being made on the ToLowerInvariant() call. It is being used to ensure the file extension is lowercase")]
|
||
internal static String GetChartImageUrl(MemoryStream stream, String imageExt)
|
||
{
|
||
EnsureInitialized(true);
|
||
// generates new guid
|
||
string guidKey = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||
// set new guid in context
|
||
CurrentGuidKey = guidKey;
|
||
|
||
Int32 tryCounts = 10;
|
||
while (tryCounts > 0)
|
||
{
|
||
tryCounts--;
|
||
try
|
||
{
|
||
String key = GenerateKey(imageExt.ToLowerInvariant());
|
||
IChartStorageHandler handler = Settings.GetHandler();
|
||
handler.Save(key, stream.ToArray());
|
||
if (!(handler is DefaultImageHandler))
|
||
{
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageSaved(key), null);
|
||
}
|
||
Settings.FolderName = String.Empty;
|
||
// clear guid so is not accessable out of the scope;
|
||
CurrentGuidKey = String.Empty;
|
||
return ChartHttpHandler.GetUrl("i", key, guidKey);
|
||
}
|
||
catch (IOException) { }
|
||
catch { throw;}
|
||
}
|
||
throw new IOException(SR.ExceptionHttpHandlerCanNotSave);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Ensures the session is clean.
|
||
/// </summary>
|
||
private static void EnsureSessionIsClean()
|
||
{
|
||
if (!_installed) return;
|
||
if (Settings.StorageType == ChartHttpHandlerStorageType.Session)
|
||
{
|
||
IChartStorageHandler handler = ChartHttpHandler.Settings.GetHandler();
|
||
foreach (RingTimeTracker tracker in RingTimeTrackerFactory.OpenedRingTimeTrackers())
|
||
{
|
||
tracker.ForEach(true, delegate(RingItem item)
|
||
{
|
||
if (item.InUse && String.CompareOrdinal(Settings.ReadSessionKey(), item.SessionID) == 0)
|
||
{
|
||
handler.Delete(tracker.GetKey(item));
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageDeleted(tracker.GetKey(item)), null);
|
||
item.InUse = false;
|
||
}
|
||
}
|
||
);
|
||
}
|
||
}
|
||
}
|
||
#endregion //Utilities
|
||
|
||
#region Diagnostics
|
||
|
||
private static void DiagnosticWriteAll(HttpContext context)
|
||
{
|
||
HtmlTextWriter writer;
|
||
using (TextWriter w = new StringWriter(CultureInfo.CurrentCulture))
|
||
{
|
||
|
||
if (context.Request.Browser != null)
|
||
writer = context.Request.Browser.CreateHtmlTextWriter(w);
|
||
else
|
||
writer = new Html32TextWriter(w);
|
||
writer.Write("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\r<html xmlns=\"http://www.w3.org/1999/xhtml\" >\n\r");
|
||
writer.Write("<head>\r\n");
|
||
writer.Write("<style type=\"text/css\">\r\n body, span, table, td, th, div, caption {font-family: Tahoma, Arial, Helvetica, sans-serif;font-size: 10pt;} caption {background-color:Black; color: White; font-weight:bold; padding: 4px; text-align:left; } \r\n</style>\r\n");
|
||
writer.Write("</head>\r\n<body style=\"width:978px\">\r\n");
|
||
writer.Write("<h2>" + SR.DiagnosticHeader + "</h2>\r\n<hr/><br/>\n\r");
|
||
DiagnosticWriteSettings(writer);
|
||
writer.Write("<hr/>");
|
||
DiagnosticWriteActivity(writer);
|
||
writer.Write("<br/><hr/>\n\r<span>");
|
||
try
|
||
{
|
||
writer.Write(typeof(Chart).AssemblyQualifiedName);
|
||
}
|
||
catch ( SecurityException ) {}
|
||
writer.Write("</span></body>\r\n</html>\r\n");
|
||
context.Response.Write(w.ToString());
|
||
}
|
||
}
|
||
|
||
private static void DiagnosticWriteSettings(HtmlTextWriter writer)
|
||
{
|
||
writer.Write("<h4>" + SR.DiagnosticSettingsConfig(WebConfigurationManager.AppSettings[ChartHttpHandlerAppSection]) + "</h4>");
|
||
GridView grid = CreateGridView( true);
|
||
grid.Caption = SR.DiagnosticSettingsHeader;
|
||
BoundField field = new BoundField();
|
||
field.DataField = "Key";
|
||
field.HeaderText = SR.DiagnosticSettingsKey;
|
||
field.HeaderStyle.HorizontalAlign = HorizontalAlign.Left;
|
||
grid.Columns.Add(field);
|
||
|
||
field = new BoundField();
|
||
field.DataField = "Value";
|
||
field.HeaderText = SR.DiagnosticSettingsValue;
|
||
field.HeaderStyle.HorizontalAlign = HorizontalAlign.Left;
|
||
|
||
grid.Columns.Add(field);
|
||
Dictionary<String, String> settings = new Dictionary<String, String>();
|
||
|
||
settings.Add("StorageType", Settings.StorageType.ToString());
|
||
settings.Add("TimeOut", Settings.Timeout.ToString());
|
||
if (Settings.StorageType == ChartHttpHandlerStorageType.File)
|
||
{
|
||
settings.Add("Directory", Settings.Directory);
|
||
}
|
||
settings.Add("DeleteAfterServicing", Settings.DeleteAfterServicing.ToString());
|
||
settings.Add("PrivateImages", Settings.PrivateImages.ToString());
|
||
settings.Add("ImageOwnerKey", Settings.ImageOwnerKey.ToString());
|
||
settings.Add("CustomHandlerName", Settings.CustomHandlerName);
|
||
settings.Add(ChartHttpHandler.WebDevServerUseConfigSettings, String.Equals(Settings[ChartHttpHandler.WebDevServerUseConfigSettings], "true", StringComparison.OrdinalIgnoreCase).ToString());
|
||
|
||
grid.DataSource = settings;
|
||
grid.DataBind();
|
||
|
||
grid.RenderControl(writer);
|
||
|
||
}
|
||
|
||
private static void DiagnosticWriteActivity(HtmlTextWriter writer)
|
||
{
|
||
GridView grid = CreateGridView( true);
|
||
grid.Caption = SR.DiagnosticActivityHeader;
|
||
BoundField field = new BoundField();
|
||
field.DataField = "DateStamp";
|
||
field.ItemStyle.VerticalAlign = VerticalAlign.Top;
|
||
field.HeaderText = SR.DiagnosticActivityTime;
|
||
field.HeaderStyle.HorizontalAlign = HorizontalAlign.Left;
|
||
field.HeaderStyle.Width = 150;
|
||
grid.Columns.Add(field);
|
||
|
||
field = new BoundField();
|
||
field.DataField = "Url";
|
||
field.HeaderText = SR.DiagnosticActivityMessage;
|
||
field.HeaderStyle.HorizontalAlign = HorizontalAlign.Left;
|
||
|
||
grid.Columns.Add(field);
|
||
|
||
grid.RowDataBound += new GridViewRowEventHandler(DiagnosticActivityGrid_RowDataBound);
|
||
|
||
grid.DataSource = Diagnostics.Messages;
|
||
grid.DataBind();
|
||
grid.RenderControl(writer);
|
||
|
||
}
|
||
|
||
static void DiagnosticActivityGrid_RowDataBound(object sender, GridViewRowEventArgs e)
|
||
{
|
||
if (e.Row.RowType == DataControlRowType.DataRow)
|
||
{
|
||
Diagnostics.HandlerPageTraceInfo currentInfo = (Diagnostics.HandlerPageTraceInfo)e.Row.DataItem;
|
||
TableCell cell = e.Row.Cells[1];
|
||
|
||
cell.Controls.Add(new Label() { Text = currentInfo.Verb + "," + currentInfo.Url });
|
||
|
||
GridView grid = CreateGridView(false);
|
||
grid.Style[HtmlTextWriterStyle.MarginLeft] = "20px";
|
||
|
||
grid.ShowHeader = false;
|
||
|
||
BoundField field = new BoundField();
|
||
field.DataField = "Text";
|
||
field.HeaderStyle.HorizontalAlign = HorizontalAlign.Left;
|
||
grid.Columns.Add(field);
|
||
|
||
grid.DataSource = currentInfo.Events;
|
||
grid.DataBind();
|
||
cell.Controls.Add(grid);
|
||
}
|
||
}
|
||
|
||
private static GridView CreateGridView(bool withAlternateStyle)
|
||
{
|
||
GridView result = new GridView();
|
||
|
||
result.AutoGenerateColumns = false;
|
||
result.CellPadding = 4;
|
||
result.Font.Names = new string[] { "Tahoma", "Ariel" };
|
||
result.Font.Size = new FontUnit(10, UnitType.Point);
|
||
result.BorderWidth = 0;
|
||
result.GridLines = GridLines.None;
|
||
result.Width = new Unit(100, UnitType.Percentage);
|
||
|
||
if (withAlternateStyle)
|
||
{
|
||
result.AlternatingRowStyle.BackColor = Color.White;
|
||
result.RowStyle.BackColor = ColorTranslator.FromHtml("#efefef");
|
||
result.RowStyle.ForeColor = Color.Black;
|
||
result.AlternatingRowStyle.ForeColor = Color.Black;
|
||
}
|
||
|
||
result.HeaderStyle.BackColor = Color.Gray;
|
||
result.HeaderStyle.ForeColor = Color.White;
|
||
result.HeaderStyle.Font.Bold = true;
|
||
return result;
|
||
}
|
||
|
||
#endregion //Diagnostics
|
||
|
||
#endregion //Methods
|
||
|
||
#region Properties
|
||
|
||
/// <summary>
|
||
/// Gets the chart image storage settings registred in web.config file under ChartHttpHandler key.
|
||
/// </summary>
|
||
/// <value>The settings.</value>
|
||
public static ChartHttpHandlerSettings Settings
|
||
{
|
||
get
|
||
{
|
||
if (_parameters == null)
|
||
{
|
||
_parameters = InitializeParameters();
|
||
}
|
||
return _parameters;
|
||
}
|
||
}
|
||
|
||
#endregion //Properties
|
||
|
||
#region IHttpHandler Members
|
||
|
||
/// <summary>
|
||
/// Gets a value indicating whether the <see cref="T:System.Web.UI.Page"/> object can be reused.
|
||
/// </summary>
|
||
/// <value></value>
|
||
/// <returns>false in all cases. </returns>
|
||
bool IHttpHandler.IsReusable
|
||
{
|
||
get { return true; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"></see> interface.
|
||
/// </summary>
|
||
/// <param name="context">An <see cref="T:System.Web.HttpContext"></see> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
|
||
void IHttpHandler.ProcessRequest(HttpContext context)
|
||
{
|
||
if (context.Request["i"] != null && ProcessSavedChartImage(context))
|
||
{
|
||
return;
|
||
}
|
||
else if (context.Request["trace"] != null && Diagnostics.IsTraceEnabled)
|
||
{
|
||
DiagnosticWriteAll(context);
|
||
return;
|
||
}
|
||
else if (context.Request[handlerCheckQry] != null)
|
||
{
|
||
// handler execute test - returns no errors.
|
||
return;
|
||
}
|
||
context.Response.StatusCode = 404;
|
||
context.Response.StatusDescription = SR.ExceptionHttpHandlerImageNotFound;
|
||
}
|
||
|
||
#endregion
|
||
|
||
}
|
||
|
||
#region Enumerations
|
||
|
||
/// <summary>
|
||
/// Determines chart image storage medium
|
||
/// </summary>
|
||
public enum ChartHttpHandlerStorageType
|
||
{
|
||
/// <summary>
|
||
/// Static into application memory
|
||
/// </summary>
|
||
InProcess,
|
||
|
||
/// <summary>
|
||
/// File system
|
||
/// </summary>
|
||
File,
|
||
/// <summary>
|
||
/// Using session as storage
|
||
/// </summary>
|
||
Session
|
||
|
||
}
|
||
/// <summary>
|
||
/// Determines the image owner key for privacy protection.
|
||
/// </summary>
|
||
internal enum ImageOwnerKeyType
|
||
{
|
||
/// <summary>
|
||
/// No privacy protection.
|
||
/// </summary>
|
||
None,
|
||
/// <summary>
|
||
/// The key will be automatically determined.
|
||
/// </summary>
|
||
Auto,
|
||
/// <summary>
|
||
/// The user name will be used as key.
|
||
/// </summary>
|
||
UserID,
|
||
/// <summary>
|
||
/// The AnonymousID will be used as key.
|
||
/// </summary>
|
||
AnonymousID,
|
||
/// <summary>
|
||
/// The SessionID will be used as key.
|
||
/// </summary>
|
||
SessionID
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region IChartStorageHandler interface
|
||
|
||
/// <summary>
|
||
/// Defines methods to manage rendered chart images in a storage.
|
||
/// </summary>
|
||
public interface IChartStorageHandler
|
||
{
|
||
/// <summary>
|
||
/// Saves the data into external medium.
|
||
/// </summary>
|
||
/// <param name="key">Index key.</param>
|
||
/// <param name="data">Image data.</param>
|
||
void Save(String key, Byte[] data);
|
||
|
||
|
||
/// <summary>
|
||
/// Loads the data from external medium.
|
||
/// </summary>
|
||
/// <param name="key">Index key.</param>
|
||
/// <returns>A byte array with image data</returns>
|
||
Byte[] Load(String key);
|
||
|
||
|
||
/// <summary>
|
||
/// Deletes the data from external medium.
|
||
/// </summary>
|
||
/// <param name="key">Index key.</param>
|
||
void Delete(String key);
|
||
|
||
/// <summary>
|
||
/// Checks for existence of data under specified key.
|
||
/// </summary>
|
||
/// <param name="key">Index key.</param>
|
||
/// <returns>True if data exists under specified key</returns>
|
||
bool Exists(String key);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region ChartHttpHandlerSettings Class
|
||
|
||
/// <summary>
|
||
/// Enables access to the chart image storage settings.
|
||
/// </summary>
|
||
#if ASPPERM_35
|
||
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
|
||
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
|
||
#endif
|
||
public class ChartHttpHandlerSettings
|
||
{
|
||
#region Fields
|
||
|
||
private StorageSettingsCollection _ssCollection = new StorageSettingsCollection();
|
||
|
||
private string _sesionKey = "chartKey-" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||
|
||
#endregion //Fields
|
||
|
||
#region Properties
|
||
|
||
private ChartHttpHandlerStorageType _chartImageStorage = ChartHttpHandlerStorageType.File;
|
||
|
||
/// <summary>
|
||
/// Gets or sets the chart image storage type.
|
||
/// </summary>
|
||
/// <value>The chart image storage.</value>
|
||
public ChartHttpHandlerStorageType StorageType
|
||
{
|
||
get { return _chartImageStorage; }
|
||
set { _chartImageStorage = value; }
|
||
}
|
||
|
||
private TimeSpan _timeout = TimeSpan.FromSeconds(30);
|
||
|
||
/// <summary>
|
||
/// Gets or sets the timeout.
|
||
/// </summary>
|
||
/// <value>The timeout.</value>
|
||
public TimeSpan Timeout
|
||
{
|
||
get { return _timeout; }
|
||
set { _timeout = value; }
|
||
}
|
||
|
||
private String _url = "~/";
|
||
/// <summary>
|
||
/// Gets or sets the URL.
|
||
/// </summary>
|
||
/// <value>The URL.</value>
|
||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
|
||
public String Url
|
||
{
|
||
get { return _url; }
|
||
set { _url = value; }
|
||
}
|
||
|
||
private String _directory = String.Empty;
|
||
/// <summary>
|
||
/// Gets or sets the directory.
|
||
/// </summary>
|
||
/// <value>The directory.</value>
|
||
public String Directory
|
||
{
|
||
get { return _directory; }
|
||
set { _directory = value; }
|
||
}
|
||
|
||
private const String _folderKeyName = "{5FF3B636-70BA-4180-B7C5-FDD77D8FA525}";
|
||
/// <summary>
|
||
/// Gets or sets the folder which will be used for storing images under <see cref="Directory"/>.
|
||
/// </summary>
|
||
/// <value>The folder name.</value>
|
||
public String FolderName
|
||
{
|
||
get
|
||
{
|
||
if (HttpContext.Current != null && HttpContext.Current.Items.Contains(_folderKeyName))
|
||
{
|
||
return (string)HttpContext.Current.Items[_folderKeyName];
|
||
}
|
||
return String.Empty;
|
||
}
|
||
set
|
||
{
|
||
if (!String.IsNullOrEmpty(value))
|
||
{
|
||
if (!(value.EndsWith("/", StringComparison.Ordinal) || value.EndsWith("\\", StringComparison.Ordinal)))
|
||
{
|
||
value += "\\";
|
||
}
|
||
this.ValidateUri(value);
|
||
}
|
||
if (HttpContext.Current != null)
|
||
{
|
||
HttpContext.Current.Items[_folderKeyName] = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
internal void ValidateUri(string key)
|
||
{
|
||
if (this.StorageType == ChartHttpHandlerStorageType.File)
|
||
{
|
||
FileInfo fi = new FileInfo(this.Directory + key);
|
||
Uri directory = new Uri(this.Directory);
|
||
Uri combinedDirectory = new Uri(fi.FullName);
|
||
if (directory.IsBaseOf(combinedDirectory))
|
||
{
|
||
// it is fine.
|
||
return;
|
||
}
|
||
throw new UnauthorizedAccessException(SR.ExceptionHttpHandlerInvalidLocation);
|
||
}
|
||
}
|
||
|
||
private String _customHandlerName = typeof(DefaultImageHandler).FullName;
|
||
/// <summary>
|
||
/// Gets or sets the name of the custom handler.
|
||
/// </summary>
|
||
/// <value>The name of the custom handler.</value>
|
||
public String CustomHandlerName
|
||
{
|
||
get { return _customHandlerName; }
|
||
set { _customHandlerName = value; }
|
||
}
|
||
|
||
|
||
private Type _customHandlerType = null;
|
||
/// <summary>
|
||
/// Gets the type of the custom handler.
|
||
/// </summary>
|
||
/// <value>The type of the custom handler.</value>
|
||
public Type HandlerType
|
||
{
|
||
get
|
||
{
|
||
if (this._customHandlerType == null)
|
||
{
|
||
this._customHandlerType = Type.GetType(this.CustomHandlerName, true);
|
||
}
|
||
return this._customHandlerType;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets a value indicating whether the handler will utilize private images.
|
||
/// </summary>
|
||
/// <value><c>true</c> if the handler will utilize private images; otherwise, <c>false</c>.</value>
|
||
/// <remarks>
|
||
/// When PrivateImages is set the handler will not return images out of session scope and
|
||
/// the client will not be able to download somebody else's images. This is default behavoiur.
|
||
/// </remarks>
|
||
public bool PrivateImages
|
||
{
|
||
get
|
||
{
|
||
return ImageOwnerKey != ImageOwnerKeyType.None;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// Gets a settings parameter with the specified name registred in web.config file under ChartHttpHandler key.
|
||
/// </summary>
|
||
/// <value></value>
|
||
public string this[string name]
|
||
{
|
||
get
|
||
{
|
||
return this._ssCollection[name];
|
||
}
|
||
}
|
||
|
||
#endregion //Properties
|
||
|
||
#region Constructors
|
||
/// <summary>
|
||
/// Initializes a new instance of the <see cref="T:StorageSettings"/> class.
|
||
/// </summary>
|
||
internal ChartHttpHandlerSettings()
|
||
{
|
||
ImageOwnerKey = ImageOwnerKeyType.Auto;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Initializes a new instance of the <see cref="T:ChartHttpHandlerParameters"/> class.
|
||
/// </summary>
|
||
/// <param name="parameters">The parameters.</param>
|
||
internal ChartHttpHandlerSettings(String parameters) : this()
|
||
{
|
||
this.ParseParams(parameters);
|
||
this._ssCollection.SetReadOnly(true);
|
||
}
|
||
|
||
#endregion //Constructors
|
||
|
||
#region Methods
|
||
|
||
private ConstructorInfo _handlerConstructor = null;
|
||
IChartStorageHandler _storageHandler = null;
|
||
/// <summary>
|
||
/// Creates the handler instance.
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
internal IChartStorageHandler GetHandler()
|
||
{
|
||
if (_storageHandler == null)
|
||
{
|
||
if (this._handlerConstructor == null)
|
||
{
|
||
this.InspectHandlerLoader();
|
||
}
|
||
_storageHandler = this._handlerConstructor.Invoke(new object[0]) as IChartStorageHandler;
|
||
}
|
||
return _storageHandler;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Inspects the handler if it is valid.
|
||
/// </summary>
|
||
private void InspectHandlerLoader()
|
||
{
|
||
this._handlerConstructor = this.HandlerType.GetConstructor(
|
||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
|
||
null,
|
||
new Type[0],
|
||
new ParameterModifier[0]);
|
||
if (this._handlerConstructor == null)
|
||
{
|
||
throw new InvalidOperationException( SR.ExceptionHttpHandlerCanNotLoadType( this.HandlerType.FullName ));
|
||
}
|
||
if (this.GetHandler() == null)
|
||
{
|
||
throw new InvalidOperationException(SR.ExceptionHttpHandlerImageHandlerInterfaceUnsupported(ChartHttpHandler.Settings.HandlerType.FullName));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Parses the params from web.config file key.
|
||
/// </summary>
|
||
/// <param name="parameters">The parameters.</param>
|
||
private void ParseParams(String parameters)
|
||
{
|
||
if (!String.IsNullOrEmpty(parameters))
|
||
{
|
||
|
||
String[] pairs = parameters.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||
for (int index = 0; index < pairs.Length; index++)
|
||
{
|
||
String item = pairs[index].Trim();
|
||
int eqPositon = item.IndexOf('=');
|
||
if (eqPositon != -1)
|
||
{
|
||
String name = item.Substring(0, eqPositon).Trim();
|
||
String value = item.Substring(eqPositon + 1).Trim();
|
||
this._ssCollection.Add(name, value);
|
||
if (name.StartsWith("stor", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (value.StartsWith("inproc", StringComparison.OrdinalIgnoreCase) || value.StartsWith("memory", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
this.StorageType = ChartHttpHandlerStorageType.InProcess;
|
||
}
|
||
else if (value.StartsWith("file", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
this.StorageType = ChartHttpHandlerStorageType.File;
|
||
}
|
||
else if (value.StartsWith("session", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
this.StorageType = ChartHttpHandlerStorageType.Session;
|
||
}
|
||
else
|
||
{
|
||
throw new System.Configuration.SettingsPropertyWrongTypeException(SR.ExceptionHttpHandlerParameterUnknown(name, value));
|
||
}
|
||
}
|
||
else if (name.StartsWith("url", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (!value.EndsWith("/", StringComparison.Ordinal))
|
||
{
|
||
value += "/";
|
||
}
|
||
this.Url = value;
|
||
}
|
||
else if (name.StartsWith("dir", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
this.Directory = value;
|
||
}
|
||
else if (name.StartsWith("time", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
try
|
||
{
|
||
int seconds = Int32.Parse(value, CultureInfo.InvariantCulture);
|
||
if (seconds < -1)
|
||
{
|
||
throw new System.Configuration.SettingsPropertyWrongTypeException(SR.ExceptionHttpHandlerValueInvalid);
|
||
}
|
||
if (seconds == -1)
|
||
{
|
||
this.Timeout = TimeSpan.MaxValue;
|
||
}
|
||
else
|
||
{
|
||
this.Timeout = TimeSpan.FromSeconds(seconds);
|
||
}
|
||
}
|
||
catch (Exception exception)
|
||
{
|
||
throw new System.Configuration.SettingsPropertyWrongTypeException(SR.ExceptionHttpHandlerTimeoutParameterInvalid, exception);
|
||
}
|
||
}
|
||
else if (name.StartsWith("handler", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
this.CustomHandlerName = value;
|
||
}
|
||
else if (name.StartsWith("privateImages", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
bool privateImg = true;
|
||
if (Boolean.TryParse(value, out privateImg) && !privateImg)
|
||
{
|
||
ImageOwnerKey = ImageOwnerKeyType.None;
|
||
}
|
||
}
|
||
else if (name.StartsWith("imageOwnerKey", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
try
|
||
{
|
||
ImageOwnerKey = (ImageOwnerKeyType)Enum.Parse(typeof(ImageOwnerKeyType), value, true);
|
||
}
|
||
catch (ArgumentException)
|
||
{
|
||
throw new System.Configuration.SettingsPropertyWrongTypeException(SR.ExceptionHttpHandlerParameterInvalid(name, value));
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
this.Inspect();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Determines whether web dev server is active.
|
||
/// </summary>
|
||
/// <returns>
|
||
/// <c>true</c> if web dev server active; otherwise, <c>false</c>.
|
||
/// </returns>
|
||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "GetCurrentProcess will fail if there is no access. This is by design. ")]
|
||
// VSTS: 5176 Security annotation violations in System.Web.DataVisualization.dll
|
||
[SecuritySafeCritical]
|
||
private static bool IsWebDevActive()
|
||
{
|
||
try
|
||
{
|
||
Process process = Process.GetCurrentProcess();
|
||
if (process.ProcessName.StartsWith("WebDev.WebServer", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return true;
|
||
}
|
||
if (process.ProcessName.StartsWith("ii----press", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
catch (SecurityException)
|
||
{
|
||
}
|
||
return false;
|
||
}
|
||
/// <summary>
|
||
/// Inspects and validates this instance after loading params.
|
||
/// </summary>
|
||
internal void Inspect()
|
||
{
|
||
switch (this.StorageType)
|
||
{
|
||
case ChartHttpHandlerStorageType.InProcess:
|
||
|
||
break;
|
||
|
||
case ChartHttpHandlerStorageType.File:
|
||
|
||
if (IsWebDevActive() && !( String.Compare(this[ChartHttpHandler.WebDevServerUseConfigSettings], "true", StringComparison.OrdinalIgnoreCase) == 0))
|
||
{
|
||
this.StorageType = ChartHttpHandlerStorageType.InProcess;
|
||
break;
|
||
}
|
||
|
||
if (String.IsNullOrEmpty(this.Url))
|
||
{
|
||
throw new ArgumentException(SR.ExceptionHttpHandlerUrlMissing);
|
||
}
|
||
|
||
String fileDirectory = this.Directory;
|
||
if (String.IsNullOrEmpty(fileDirectory))
|
||
{
|
||
try
|
||
{
|
||
fileDirectory = HttpContext.Current.Server.MapPath(this.Url);
|
||
}
|
||
catch (Exception exception)
|
||
{
|
||
throw new InvalidOperationException(SR.ExceptionHttpHandlerUrlInvalid, exception);
|
||
}
|
||
}
|
||
fileDirectory = fileDirectory.Replace("/", "\\");
|
||
if (!fileDirectory.EndsWith("\\", StringComparison.Ordinal))
|
||
{
|
||
fileDirectory += "\\";
|
||
}
|
||
|
||
if (!System.IO.Directory.Exists(fileDirectory))
|
||
{
|
||
throw new DirectoryNotFoundException(SR.ExceptionHttpHandlerTempDirectoryInvalid(fileDirectory));
|
||
}
|
||
Exception thrown = null;
|
||
try
|
||
{
|
||
String testFileName = fileDirectory + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||
using (FileStream fileStream = File.Create(testFileName)) { }
|
||
File.Delete(testFileName);
|
||
|
||
}
|
||
catch (DirectoryNotFoundException exception)
|
||
{
|
||
thrown = exception;
|
||
}
|
||
catch (NotSupportedException exception)
|
||
{
|
||
thrown = exception;
|
||
}
|
||
catch (PathTooLongException exception)
|
||
{
|
||
thrown = exception;
|
||
}
|
||
catch (UnauthorizedAccessException exception)
|
||
{
|
||
thrown = exception;
|
||
}
|
||
|
||
if (thrown != null)
|
||
{
|
||
throw new UnauthorizedAccessException(SR.ExceptionHttpHandlerTempDirectoryUnaccesible(fileDirectory));
|
||
}
|
||
|
||
this.Directory = fileDirectory;
|
||
break;
|
||
|
||
|
||
}
|
||
if (!String.IsNullOrEmpty(this.CustomHandlerName))
|
||
{
|
||
this.InspectHandlerLoader();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Prepares the design time params.
|
||
/// </summary>
|
||
internal void PrepareDesignTime()
|
||
{
|
||
this.StorageType = ChartHttpHandlerStorageType.File;
|
||
this.Timeout = TimeSpan.FromSeconds(3); ;
|
||
this.Url = Path.GetTempPath();
|
||
this.Directory = Path.GetTempPath();
|
||
}
|
||
|
||
internal string ReadSessionKey()
|
||
{
|
||
if (HttpContext.Current.Session != null)
|
||
{
|
||
// initialize session (if is empty any postsequent request will have different id);
|
||
if (HttpContext.Current.Session.IsNewSession)
|
||
{
|
||
if (HttpContext.Current.Session.IsReadOnly)
|
||
{
|
||
return string.Empty;
|
||
}
|
||
HttpContext.Current.Session[this._sesionKey] = 0;
|
||
}
|
||
return HttpContext.Current.Session.SessionID;
|
||
}
|
||
return String.Empty;
|
||
}
|
||
|
||
internal string GetPrivacyKey( out ImageOwnerKeyType keyType )
|
||
{
|
||
if (ImageOwnerKey == ImageOwnerKeyType.None)
|
||
{
|
||
keyType = ImageOwnerKeyType.None;
|
||
return String.Empty;
|
||
}
|
||
if (HttpContext.Current != null)
|
||
{
|
||
switch (ImageOwnerKey)
|
||
{
|
||
case ImageOwnerKeyType.Auto:
|
||
if (HttpContext.Current.User.Identity.IsAuthenticated)
|
||
{
|
||
keyType = ImageOwnerKeyType.UserID;
|
||
return HttpContext.Current.User.Identity.Name;
|
||
}
|
||
if (!String.IsNullOrEmpty(HttpContext.Current.Request.AnonymousID))
|
||
{
|
||
keyType = ImageOwnerKeyType.AnonymousID;
|
||
return HttpContext.Current.Request.AnonymousID;
|
||
}
|
||
string sessionId = ReadSessionKey();
|
||
keyType = String.IsNullOrEmpty(sessionId) ? ImageOwnerKeyType.None : ImageOwnerKeyType.SessionID;
|
||
return sessionId;
|
||
|
||
case ImageOwnerKeyType.UserID:
|
||
if (!HttpContext.Current.User.Identity.IsAuthenticated)
|
||
{
|
||
throw new InvalidOperationException(SR.ExceptionHttpHandlerPrivacyKeyInvalid("ImageOwnerKey", ImageOwnerKey.ToString()));
|
||
}
|
||
keyType = ImageOwnerKeyType.UserID;
|
||
return HttpContext.Current.User.Identity.Name;
|
||
|
||
case ImageOwnerKeyType.AnonymousID:
|
||
if (String.IsNullOrEmpty(HttpContext.Current.Request.AnonymousID))
|
||
{
|
||
throw new InvalidOperationException(SR.ExceptionHttpHandlerPrivacyKeyInvalid("ImageOwnerKey", ImageOwnerKey.ToString()));
|
||
}
|
||
keyType = ImageOwnerKeyType.AnonymousID;
|
||
return HttpContext.Current.Request.AnonymousID;
|
||
|
||
case ImageOwnerKeyType.SessionID:
|
||
if (HttpContext.Current.Session == null)
|
||
{
|
||
throw new InvalidOperationException(SR.ExceptionHttpHandlerPrivacyKeyInvalid("ImageOwnerKey", ImageOwnerKey.ToString()));
|
||
}
|
||
keyType = ImageOwnerKeyType.SessionID;
|
||
return ReadSessionKey();
|
||
|
||
default:
|
||
Debug.Fail("Unknown ImageOwnerKeyType.");
|
||
break;
|
||
}
|
||
}
|
||
keyType = ImageOwnerKeyType.None;
|
||
return string.Empty;
|
||
}
|
||
|
||
internal string PrivacyKey
|
||
{
|
||
get
|
||
{
|
||
ImageOwnerKeyType keyType;
|
||
return GetPrivacyKey(out keyType);
|
||
}
|
||
}
|
||
|
||
internal bool DeleteAfterServicing
|
||
{
|
||
get
|
||
{
|
||
// default, if is missing in config, is true.
|
||
return !(String.Compare(this["DeleteAfterServicing"], "false", StringComparison.OrdinalIgnoreCase) == 0);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the image owner key type.
|
||
/// </summary>
|
||
/// <value>The image owner key.</value>
|
||
internal ImageOwnerKeyType ImageOwnerKey { get; set; }
|
||
|
||
#endregion //Methods
|
||
|
||
#region SettingsCollection Class
|
||
|
||
private class StorageSettingsCollection : NameValueCollection
|
||
{
|
||
public StorageSettingsCollection()
|
||
: base(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
}
|
||
internal void SetReadOnly(bool flag)
|
||
{
|
||
this.IsReadOnly = flag;
|
||
}
|
||
}
|
||
|
||
#endregion //SettingsCollection Class
|
||
}
|
||
|
||
#endregion ChartHttpHandlerParameters
|
||
|
||
#region DefaultImageHandler Class
|
||
|
||
/// <summary>
|
||
/// Default implementation of ChartHttpHandler.IImageHandler interface
|
||
/// </summary>
|
||
internal class DefaultImageHandler : IChartStorageHandler
|
||
{
|
||
|
||
#region Fields
|
||
// Hashtable for storage
|
||
private static Hashtable _storageData = new Hashtable();
|
||
// lock object
|
||
private static ReaderWriterLock _rwl = new ReaderWriterLock();
|
||
// max access timeout
|
||
private const int accessTimeout = 10000;
|
||
|
||
static string _privacyKeyName = "_pk";
|
||
static byte[] _privacyMarker = (new Guid("332E3AB032904bceA82B249C25E65CB6")).ToByteArray();
|
||
static string _sessionKeyPrefix = "chart-3ece47b3-9481-4b22-ab45-ab669972eb79";
|
||
|
||
#endregion //Fields
|
||
|
||
#region Constructors
|
||
|
||
/// <summary>
|
||
/// Initializes a new instance of the <see cref="T:DefaultImageHandler"/> class.
|
||
/// </summary>
|
||
internal DefaultImageHandler()
|
||
{
|
||
}
|
||
#endregion //Constructors
|
||
|
||
#region Members
|
||
/// <summary>
|
||
/// Nots the type of the supported storage.
|
||
/// </summary>
|
||
/// <param name="settings">The settings.</param>
|
||
private void NotSupportedStorageType(ChartHttpHandlerSettings settings)
|
||
{
|
||
throw new NotSupportedException( SR.ExceptionHttpHandlerStorageTypeUnsupported( settings.StorageType.ToString() ));
|
||
}
|
||
|
||
#endregion //Members
|
||
|
||
#region Methods
|
||
|
||
/// <summary>
|
||
/// Returns privacy hash which will be save in the file.
|
||
/// </summary>
|
||
/// <returns>A byte array of hash data</returns>
|
||
private static byte[] GetHashData()
|
||
{
|
||
string currentGuid = ChartHttpHandler.CurrentGuidKey;
|
||
string sessionID = ChartHttpHandler.Settings.PrivacyKey;
|
||
|
||
if (String.IsNullOrEmpty(sessionID))
|
||
{
|
||
return new byte[0];
|
||
}
|
||
|
||
byte[] data = Encoding.UTF8.GetBytes(sessionID + "/" + currentGuid);
|
||
|
||
using (SHA1 sha = new SHA1CryptoServiceProvider())
|
||
{
|
||
return sha.ComputeHash(data);
|
||
}
|
||
|
||
}
|
||
|
||
private static bool CompareBytes(byte[] a, byte[] b)
|
||
{
|
||
if (a.Length != b.Length) return false;
|
||
for (int i = 0; i < a.Length; i++)
|
||
{
|
||
if (a[i] != b[i]) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private static string GetSessionImageKey(string key)
|
||
{
|
||
// all session variables starts with _sessionKeyPrefix to avoid direct access to session by passing image key in Url query.
|
||
return _sessionKeyPrefix + key;
|
||
}
|
||
|
||
#endregion //Methods
|
||
|
||
#region ImageHandler Members
|
||
|
||
/// <summary>
|
||
/// Stores the data into external medium.
|
||
/// </summary>
|
||
/// <param name="key">The key.</param>
|
||
/// <param name="data">The data.</param>
|
||
void IChartStorageHandler.Save(String key, Byte[] data)
|
||
{
|
||
ChartHttpHandlerSettings settings = ChartHttpHandler.Settings;
|
||
ImageOwnerKeyType imageOwnerKeyType = ImageOwnerKeyType.None;
|
||
string privacyKey = settings.GetPrivacyKey(out imageOwnerKeyType);
|
||
if (settings.StorageType == ChartHttpHandlerStorageType.InProcess)
|
||
{
|
||
_rwl.AcquireWriterLock(accessTimeout);
|
||
try
|
||
{
|
||
_storageData[key] = data;
|
||
if (settings.PrivateImages && !String.IsNullOrEmpty(privacyKey))
|
||
{
|
||
_storageData[key + _privacyKeyName] = privacyKey;
|
||
Diagnostics.TraceWrite( SR.DiagnosticChartImageSavedPrivate(key, imageOwnerKeyType.ToString()), null);
|
||
}
|
||
else
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageSaved(key), null);
|
||
}
|
||
finally
|
||
{
|
||
_rwl.ReleaseWriterLock();
|
||
}
|
||
}
|
||
else if (settings.StorageType == ChartHttpHandlerStorageType.File)
|
||
{
|
||
using (FileStream stream = File.Create(settings.Directory + key))
|
||
{
|
||
stream.Write(data, 0, data.Length);
|
||
if (settings.PrivateImages && !String.IsNullOrEmpty(privacyKey))
|
||
{
|
||
byte[] privacyData = GetHashData();
|
||
stream.Write(privacyData, 0, privacyData.Length);
|
||
// we will put a marker at the end of the file;
|
||
stream.Write(_privacyMarker, 0, _privacyMarker.Length);
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageSavedPrivate(key, imageOwnerKeyType.ToString()), null);
|
||
}
|
||
else
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageSaved(key), null);
|
||
}
|
||
}
|
||
else if (settings.StorageType == ChartHttpHandlerStorageType.Session)
|
||
{
|
||
HttpContext.Current.Session[GetSessionImageKey(key)] = data;
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageSaved(key), null);
|
||
}
|
||
else this.NotSupportedStorageType(settings);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Retrieves the data from external medium.
|
||
/// </summary>
|
||
/// <param name="key">The key.</param>
|
||
Byte[] IChartStorageHandler.Load( String key)
|
||
{
|
||
ChartHttpHandlerSettings settings = ChartHttpHandler.Settings;
|
||
ImageOwnerKeyType imageOwnerKeyType = ImageOwnerKeyType.None;
|
||
string privacyKey = settings.GetPrivacyKey(out imageOwnerKeyType);
|
||
Byte[] data = new Byte[0];
|
||
if (settings.StorageType == ChartHttpHandlerStorageType.InProcess)
|
||
{
|
||
_rwl.AcquireReaderLock(accessTimeout);
|
||
try
|
||
{
|
||
if (settings.PrivateImages)
|
||
{
|
||
if (!String.IsNullOrEmpty(privacyKey))
|
||
{
|
||
if (!String.Equals((string)_storageData[key + _privacyKeyName], privacyKey, StringComparison.Ordinal))
|
||
{
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailPrivacyFail(imageOwnerKeyType.ToString())), null);
|
||
return data;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!String.IsNullOrEmpty((string)_storageData[key + _privacyKeyName]))
|
||
{
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailPrivacyFail(imageOwnerKeyType.ToString())), null);
|
||
return data;
|
||
}
|
||
}
|
||
}
|
||
data = (Byte[])_storageData[key];
|
||
if (data == null)
|
||
{
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailNotFound), null);
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
_rwl.ReleaseReaderLock();
|
||
}
|
||
}
|
||
else if (settings.StorageType == ChartHttpHandlerStorageType.File)
|
||
{
|
||
settings.ValidateUri(key);
|
||
if (File.Exists(settings.Directory + key))
|
||
{
|
||
using (FileStream fileStream = File.OpenRead(settings.Directory + key))
|
||
{
|
||
byte[] fileData = new byte[fileStream.Length];
|
||
fileStream.Read(fileData, 0, fileData.Length);
|
||
using (MemoryStream stream = new MemoryStream(fileData))
|
||
{
|
||
int streamCut = 0;
|
||
if (settings.PrivateImages)
|
||
{
|
||
// read the marker first
|
||
byte[] privacyMarkerStream = new Byte[_privacyMarker.Length];
|
||
|
||
streamCut += _privacyMarker.Length;
|
||
stream.Seek(stream.Length - streamCut, SeekOrigin.Begin);
|
||
stream.Read(privacyMarkerStream, 0, privacyMarkerStream.Length);
|
||
|
||
if (!String.IsNullOrEmpty(privacyKey))
|
||
{
|
||
byte[] privacyData = GetHashData();
|
||
streamCut += privacyData.Length;
|
||
byte[] privacyDataFromStream = new Byte[privacyData.Length];
|
||
stream.Seek(stream.Length - streamCut, SeekOrigin.Begin);
|
||
stream.Read(privacyDataFromStream, 0, privacyDataFromStream.Length);
|
||
|
||
if (!CompareBytes(privacyDataFromStream, privacyData))
|
||
{
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailPrivacyFail(imageOwnerKeyType.ToString())), null);
|
||
return data;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// this image is marked as private - check end return null if fails
|
||
if (String.Equals(
|
||
Encoding.Unicode.GetString(privacyMarkerStream),
|
||
Encoding.Unicode.GetString(_privacyMarker),
|
||
StringComparison.Ordinal))
|
||
{
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailPrivacyFail(imageOwnerKeyType.ToString())), null);
|
||
return data;
|
||
}
|
||
// its fine ( no user is stored )
|
||
streamCut = 0;
|
||
}
|
||
}
|
||
stream.Seek(0, SeekOrigin.Begin);
|
||
data = new Byte[(int)stream.Length - streamCut];
|
||
stream.Read(data, 0, (int)data.Length);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
Diagnostics.TraceWrite(SR.DiagnosticChartImageServedFail(key, SR.DiagnosticChartImageServedFailNotFound), null);
|
||
}
|
||
else if (settings.StorageType == ChartHttpHandlerStorageType.Session)
|
||
{
|
||
data = (Byte[])HttpContext.Current.Session[GetSessionImageKey(key)];
|
||
}
|
||
else this.NotSupportedStorageType(settings);
|
||
return data;
|
||
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Removes the data from external medium.
|
||
/// </summary>
|
||
/// <param name="key">The key.</param>
|
||
void IChartStorageHandler.Delete(String key)
|
||
{
|
||
ChartHttpHandlerSettings settings = ChartHttpHandler.Settings;
|
||
if (settings.StorageType == ChartHttpHandlerStorageType.InProcess)
|
||
{
|
||
|
||
_rwl.AcquireWriterLock(accessTimeout);
|
||
try
|
||
{
|
||
_storageData.Remove(key);
|
||
_storageData.Remove(key + _privacyKeyName);
|
||
}
|
||
finally
|
||
{
|
||
_rwl.ReleaseWriterLock();
|
||
}
|
||
}
|
||
else if (settings.StorageType == ChartHttpHandlerStorageType.File)
|
||
{
|
||
File.Delete(settings.Directory + key);
|
||
}
|
||
else if (settings.StorageType == ChartHttpHandlerStorageType.Session)
|
||
{
|
||
HttpContext.Current.Session.Remove(GetSessionImageKey(key));
|
||
}
|
||
else this.NotSupportedStorageType(settings);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks for existence the specified key.
|
||
/// </summary>
|
||
/// <param name="key">The key.</param>
|
||
/// <returns></returns>
|
||
bool IChartStorageHandler.Exists(String key)
|
||
{
|
||
ChartHttpHandlerSettings settings = ChartHttpHandler.Settings;
|
||
if (settings.StorageType == ChartHttpHandlerStorageType.InProcess)
|
||
{
|
||
_rwl.AcquireReaderLock(accessTimeout);
|
||
try
|
||
{
|
||
return _storageData.Contains(key);
|
||
}
|
||
finally
|
||
{
|
||
_rwl.ReleaseReaderLock();
|
||
}
|
||
}
|
||
else if (settings.StorageType == ChartHttpHandlerStorageType.File)
|
||
{
|
||
return File.Exists(settings.Directory + key);
|
||
}
|
||
else if (settings.StorageType == ChartHttpHandlerStorageType.Session)
|
||
{
|
||
return HttpContext.Current.Session[GetSessionImageKey(key)] is Byte[];
|
||
}
|
||
else this.NotSupportedStorageType(settings);
|
||
return false;
|
||
}
|
||
|
||
#endregion
|
||
|
||
}
|
||
|
||
#endregion //DefaultImageHandler Class
|
||
|
||
#region RingTimeTracker class
|
||
|
||
/// <summary>
|
||
/// RingItem contains time span of creation timedate and index for key generation.
|
||
/// </summary>
|
||
internal class RingItem
|
||
{
|
||
internal Int32 Index;
|
||
internal DateTime Created = DateTime.Now;
|
||
internal string SessionID = String.Empty;
|
||
internal bool InUse;
|
||
/// <summary>
|
||
/// Initializes a new instance of the <see cref="T:RingItem"/> class.
|
||
/// </summary>
|
||
/// <param name="index">The index.</param>
|
||
internal RingItem( int index)
|
||
{
|
||
this.Index = index;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// RingTimeTracker is a helper class for generating keys and tracking RingItem.
|
||
/// Contains linked list queue and tracks exprired items.
|
||
/// </summary>
|
||
internal class RingTimeTracker
|
||
{
|
||
#region Fields
|
||
// the item life span
|
||
private TimeSpan _itemLifeTime = TimeSpan.FromSeconds(360);
|
||
// last requested RingItem
|
||
private LinkedListNode<RingItem> _current;
|
||
// default key format to format names
|
||
private String _keyFormat = String.Empty;
|
||
// LinkedList with ring items
|
||
private LinkedList<RingItem> _list = new LinkedList<RingItem>();
|
||
// Record session ID
|
||
private bool _recordSessionID = false;
|
||
|
||
#endregion //Fields
|
||
|
||
#region Constructors
|
||
|
||
/// <summary>
|
||
/// Initializes a new instance of the <see cref="T:RingTimeTracker"/> class.
|
||
/// </summary>
|
||
/// <param name="itemLifeTime">The item life time.</param>
|
||
/// <param name="keyFormat">The key format.</param>
|
||
/// <param name="recordSessionID">if set to <c>true</c> the session ID will be recorded.</param>
|
||
internal RingTimeTracker(TimeSpan itemLifeTime, String keyFormat, bool recordSessionID)
|
||
{
|
||
System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(keyFormat));
|
||
this._itemLifeTime = itemLifeTime;
|
||
this._keyFormat = keyFormat;
|
||
this._list.AddLast(new RingItem(_list.Count));
|
||
this._current = this._list.First;
|
||
this._current.Value.Created = DateTime.Now - this._itemLifeTime - TimeSpan.FromSeconds(1);
|
||
this._recordSessionID = recordSessionID;
|
||
}
|
||
|
||
#endregion //Constructors
|
||
|
||
#region Methods
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// Determines whether the specified item is expired.
|
||
/// </summary>
|
||
/// <param name="item">The item.</param>
|
||
/// <param name="now">The now.</param>
|
||
/// <returns>
|
||
/// <c>true</c> if the specified item is expired; otherwise, <c>false</c>.
|
||
/// </returns>
|
||
internal bool IsExpired(RingItem item, DateTime now)
|
||
{
|
||
TimeSpan elapsed = (now - item.Created);
|
||
return elapsed > this._itemLifeTime;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Gets the next key.
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
internal String GetNextKey()
|
||
{
|
||
DateTime now = DateTime.Now;
|
||
lock (this)
|
||
{
|
||
if ( !this.IsExpired(this._current.Value, now))
|
||
{
|
||
if (this._current.Next == null)
|
||
{
|
||
if (!this.IsExpired(this._list.First.Value, now))
|
||
{
|
||
this._list.AddLast(new RingItem(_list.Count));
|
||
this._current = this._list.Last;
|
||
}
|
||
else
|
||
{
|
||
this._current = this._list.First;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!this.IsExpired(this._current.Next.Value, now))
|
||
{
|
||
this._list.AddAfter(this._current, new RingItem(_list.Count));
|
||
}
|
||
this._current = this._current.Next;
|
||
}
|
||
}
|
||
this._current.Value.Created = now;
|
||
if (this._recordSessionID)
|
||
{
|
||
this._current.Value.SessionID = ChartHttpHandler.Settings.ReadSessionKey();
|
||
this._current.Value.InUse = true;
|
||
}
|
||
return this.GetCurrentKey();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the current key.
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
internal String GetCurrentKey()
|
||
{
|
||
return String.Format( CultureInfo.InvariantCulture, this._keyFormat, this._current.Value.Index);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the key.
|
||
/// </summary>
|
||
/// <param name="ringItem">The ring item.</param>
|
||
/// <returns></returns>
|
||
internal String GetKey(RingItem ringItem)
|
||
{
|
||
return String.Format(CultureInfo.InvariantCulture, this._keyFormat, ringItem.Index);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Do Action for each item.
|
||
/// </summary>
|
||
/// <param name="onlyExpired">if set to <c>true</c> do action for only expired items.</param>
|
||
/// <param name="action">The action.</param>
|
||
public void ForEach(bool onlyExpired, Action<RingItem> action)
|
||
{
|
||
if (action == null)
|
||
{
|
||
throw new ArgumentNullException("action");
|
||
}
|
||
DateTime now = DateTime.Now;
|
||
lock (this)
|
||
{
|
||
foreach (RingItem item in this._list)
|
||
{
|
||
if (onlyExpired)
|
||
{
|
||
if (this.IsExpired(item, now))
|
||
{
|
||
action(item);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
action(item);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion //Methods
|
||
|
||
}
|
||
#endregion //RingTracker class
|
||
|
||
#region RingTimeTrackerFactory Class
|
||
|
||
/// <summary>
|
||
/// RingTimeTrackerFactory contains static list of RingTimeTracker for each key formats
|
||
/// </summary>
|
||
internal static class RingTimeTrackerFactory
|
||
{
|
||
|
||
private static ListDictionary _ringTrackers = new ListDictionary();
|
||
private static Object _lockObject = new Object();
|
||
|
||
/// <summary>
|
||
/// Gets the ring tracker by specified key format.
|
||
/// </summary>
|
||
/// <param name="keyFormat">The key format.</param>
|
||
/// <returns></returns>
|
||
internal static RingTimeTracker GetRingTracker(String keyFormat)
|
||
{
|
||
if (_ringTrackers.Contains(keyFormat))
|
||
{
|
||
return (RingTimeTracker)_ringTrackers[keyFormat];
|
||
}
|
||
lock (_lockObject)
|
||
{
|
||
if (_ringTrackers.Contains(keyFormat))
|
||
{
|
||
return (RingTimeTracker)_ringTrackers[keyFormat];
|
||
}
|
||
RingTimeTracker result = new RingTimeTracker(ChartHttpHandler.Settings.Timeout, keyFormat,ChartHttpHandler.Settings.StorageType == ChartHttpHandlerStorageType.Session);
|
||
_ringTrackers.Add(keyFormat, result);
|
||
return result;
|
||
}
|
||
}
|
||
|
||
internal static IList OpenedRingTimeTrackers()
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
return new ArrayList(_ringTrackers.Values);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
#endregion //RingTimeTrackerFactory Class
|
||
|
||
#region Diagnostics class
|
||
|
||
/// <summary>
|
||
/// Contains helpres methods for diagnostics.
|
||
/// </summary>
|
||
internal static class Diagnostics
|
||
{
|
||
/// <summary>
|
||
/// Trace category
|
||
/// </summary>
|
||
const string ChartCategory = "chart.handler";
|
||
/// <summary>
|
||
/// Name of context item which contain the current trace item
|
||
/// </summary>
|
||
const string ContextID = "Trace-{89FA5660-BD13-4f1b-8C7C-355CEC92CC7E}";
|
||
/// <summary>
|
||
/// Used for syncronizing.
|
||
/// </summary>
|
||
static object _lockObject = new object();
|
||
/// <summary>
|
||
/// Limit of trace messages in the history.
|
||
/// </summary>
|
||
const int MessageLimit = 20;
|
||
/// <summary>
|
||
/// Collection of request messages.
|
||
/// </summary>
|
||
static List<HandlerPageTraceInfo> _messages = new List<HandlerPageTraceInfo>(MessageLimit);
|
||
|
||
/// <summary>
|
||
/// Contains request info
|
||
/// </summary>
|
||
public class HandlerPageTraceInfo
|
||
{
|
||
/// <summary>
|
||
/// Events collection in this request.
|
||
/// </summary>
|
||
private List<ChartHandlerEvents> _events = new List<ChartHandlerEvents>();
|
||
/// <summary>
|
||
/// Initializes a new instance of the <see cref="HandlerPageTraceInfo"/> class.
|
||
/// </summary>
|
||
public HandlerPageTraceInfo()
|
||
{
|
||
if (HttpContext.Current != null)
|
||
{
|
||
DateStamp = DateTime.Now;
|
||
if (HttpContext.Current.Request != null)
|
||
{
|
||
Url = HttpContext.Current.Request.Url.ToString();
|
||
Verb = HttpContext.Current.Request.HttpMethod;
|
||
}
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Gets or sets the date stamp.
|
||
/// </summary>
|
||
/// <value>The date stamp.</value>
|
||
public DateTime DateStamp { get; private set; }
|
||
/// <summary>
|
||
/// Gets or sets the URL.
|
||
/// </summary>
|
||
/// <value>The URL.</value>
|
||
public string Url { get; private set; }
|
||
/// <summary>
|
||
/// Gets or sets the verb.
|
||
/// </summary>
|
||
/// <value>The verb.</value>
|
||
public string Verb { get; private set; }
|
||
/// <summary>
|
||
/// Gets the events.
|
||
/// </summary>
|
||
/// <value>The events.</value>
|
||
public IList<ChartHandlerEvents> Events
|
||
{
|
||
get
|
||
{
|
||
return _events.AsReadOnly();
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Adds a trace info item.
|
||
/// </summary>
|
||
/// <param name="message">The message.</param>
|
||
/// <param name="errorInfo">The error info.</param>
|
||
internal void AddTraceInfo(string message, string errorInfo)
|
||
{
|
||
lock (_events)
|
||
{
|
||
_events.Add(new ChartHandlerEvents()
|
||
{
|
||
Message = message,
|
||
ErrorInfo = errorInfo
|
||
}
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Contains an event in particural request.
|
||
/// </summary>
|
||
public class ChartHandlerEvents
|
||
{
|
||
/// <summary>
|
||
/// Gets or sets the message.
|
||
/// </summary>
|
||
/// <value>The message.</value>
|
||
public string Message { get; set; }
|
||
/// <summary>
|
||
/// Gets or sets the error info.
|
||
/// </summary>
|
||
/// <value>The error info.</value>
|
||
public string ErrorInfo { get; set; }
|
||
/// <summary>
|
||
/// Gets the text.
|
||
/// </summary>
|
||
/// <value>The text.</value>
|
||
public string Text { get { return Message + ErrorInfo; } }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Writes message in the trace.
|
||
/// </summary>
|
||
/// <param name="message">The message.</param>
|
||
/// <param name="errorInfo">The error info.</param>
|
||
internal static void TraceWrite( string message, Exception errorInfo)
|
||
{
|
||
if (IsTraceEnabled)
|
||
{
|
||
HttpContext.Current.Trace.Write(ChartCategory, message, errorInfo);
|
||
if (CurrentTraceInfo != null)
|
||
{
|
||
CurrentTraceInfo.AddTraceInfo(message, errorInfo != null ? errorInfo.ToString() : String.Empty);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the current trace info.
|
||
/// </summary>
|
||
/// <value>The current trace info.</value>
|
||
private static HandlerPageTraceInfo CurrentTraceInfo
|
||
{
|
||
get
|
||
{
|
||
lock (_lockObject)
|
||
{
|
||
if (HttpContext.Current != null)
|
||
{
|
||
if (HttpContext.Current.Items[Diagnostics.ContextID] == null)
|
||
{
|
||
HandlerPageTraceInfo pageTrace = new HandlerPageTraceInfo();
|
||
_messages.Add(pageTrace);
|
||
if (_messages.Count > MessageLimit)
|
||
{
|
||
_messages.RemoveRange(0, _messages.Count - MessageLimit);
|
||
}
|
||
HttpContext.Current.Items[Diagnostics.ContextID] = pageTrace;
|
||
}
|
||
return (HandlerPageTraceInfo)HttpContext.Current.Items[Diagnostics.ContextID];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets a value indicating whether this instance is trace enabled.
|
||
/// </summary>
|
||
/// <value>
|
||
/// <c>true</c> if this instance is trace enabled; otherwise, <c>false</c>.
|
||
/// </value>
|
||
internal static bool IsTraceEnabled
|
||
{
|
||
get
|
||
{
|
||
return HttpContext.Current != null && HttpContext.Current.Trace.IsEnabled;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the messages collection.
|
||
/// </summary>
|
||
/// <value>The messages.</value>
|
||
internal static ReadOnlyCollection<HandlerPageTraceInfo> Messages
|
||
{
|
||
get
|
||
{
|
||
List<HandlerPageTraceInfo> result;
|
||
lock (_lockObject)
|
||
{
|
||
result = new List<HandlerPageTraceInfo>(_messages);
|
||
}
|
||
return result.AsReadOnly();
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion //Diagnostics class
|
||
}
|