|
//-----------------------------------------------------------------------------
// <copyright file="TrackingValidationObjectDicionary.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------
namespace System.Net
{
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Diagnostics;
// TrackingValidationObjectDictionary uses an internal collection of objects to store
// only those objects which are not strings. It still places a copy of the string
// value of these objects into the base StringDictionary so that the public methods
// of StringDictionary still function correctly.
// NOTE: all keys are converted to lowercase prior to adding to ensure consistency of
// values between keys in the StringDictionary and internalObjects because StringDictionary
// automatically does this internally
internal class TrackingValidationObjectDictionary : StringDictionary
{
#region Private Fields
// even though validators may exist, we should not initialize this initially since by default it is empty
// and it may never be populated with values if the user does not set them
private IDictionary<string, object> internalObjects = null;
private readonly IDictionary<string, ValidateAndParseValue> validators;
#endregion
#region Constructors
// it is valid for validators to be null. this means that no validation should be performed
internal TrackingValidationObjectDictionary(IDictionary<string, ValidateAndParseValue> validators)
{
IsChanged = false;
this.validators = validators;
}
#endregion
#region Private Methods
// precondition: key must not be null
// addValue determines if we are doing a set (false) or an add (true)
private void PersistValue(string key, string value, bool addValue)
{
Debug.Assert(key != null, "key was null");
// StringDictionary will automatically store the key as lower case so
// we must convert it so that the validators and internalObjects will
// be consistent
key = key.ToLowerInvariant();
// StringDictionary allows keys with null values however null values for parameters in
// ContentDisposition have no meaning so they must be ignored on add. StringDictionary
// would not throw on null so this can't either since it would be a breaking change.
// in addition, a key with an empty value is not valid so we do not persist those either
if (!string.IsNullOrEmpty(value))
{
if (validators != null && validators.ContainsKey(key))
{
// run the validator for this key; it will throw if the value is invalid
object valueToAdd = validators[key](value);
// now that the value is valid, ensure that internalObjects exists since we have to
// add to it
if (internalObjects == null)
{
internalObjects = new Dictionary<string, object>();
}
if (addValue)
{
// set will do an Add if the key does not exist but if the user
// specifically called Add then we must let it throw
internalObjects.Add(key, valueToAdd);
base.Add(key, valueToAdd.ToString());
}
else
{
internalObjects[key] = valueToAdd;
base[key] = valueToAdd.ToString();
}
}
else
{
if (addValue)
{
base.Add(key, value);
}
else
{
base[key] = value;
}
}
IsChanged = true;
}
}
#endregion
#region Internal Fields
// set to true if any values have been changed by any mutator method
internal bool IsChanged
{
get;
set;
}
// delegate to perform validation and conversion if necessary
// these MUST throw on invalid values. Additionally, each validator
// may be passed a string OR another type of object and so should react
// appropriately
internal delegate object ValidateAndParseValue(object valueToValidate);
#endregion
#region Internal Methods
// public interface only allows strings so this provides a means
// to get the objects when they are not strings
internal object InternalGet(string key)
{
// internalObjects will throw if the key is not found so we must check it
if (internalObjects != null && internalObjects.ContainsKey(key))
{
return internalObjects[key];
}
else
{
// this will return null if the key does not exist so no check needed
return base[key];
}
}
// this method bypasses validation
// preconditions: value MUST have been validated and must not be null
internal void InternalSet(string key, object value)
{
// InternalSet is only used with objects that belong in internalObjects so we must always
// initialize it here
if (internalObjects == null)
{
internalObjects = new Dictionary<string, object>();
}
// always replace the existing value when we set internally
internalObjects[key] = value;
base[key] = value.ToString();
IsChanged = true;
}
#endregion
#region Public Fields
public override string this[string key]
{
get
{
// no need to check internalObjects since the string equivalent in base will
// already have been set correctly when the value was originally passed in
return base[key];
}
set
{
PersistValue(key, value, false);
}
}
#endregion
#region Public Methods
public override void Add(string key, string value)
{
PersistValue(key, value, true);
}
public override void Clear()
{
if (internalObjects != null)
{
internalObjects.Clear();
}
base.Clear();
IsChanged = true;
}
public override void Remove(string key)
{
if (internalObjects != null && internalObjects.ContainsKey(key))
{
internalObjects.Remove(key);
}
base.Remove(key);
IsChanged = true;
}
#endregion
}
}
|