Подтвердить что ты не робот

Пользовательский путь для user.config

Я управляю своими настройками приложения, используя конструктор параметров в VS2008.

"Точный путь для user.config файлы выглядят примерно так:"

<Profile Directory>\<Company Name>\
 <App Name>_<Evidence Type>_<Evidence Hash>\<Version>\user.config

Есть ли способ настроить этот путь? Я бы предпочел что-то вроде этого:

<Profile Directory>\<Company Name>\
 <App Name>\<Version>\user.config

Я заметил, что белые пробелы были заменены символами подчеркивания в "Название компании" в новой созданной папке ( "Test Company" → "Test_Company" ). Я действительно хочу отключить это поведение.

Знаешь, я мог бы написать новый обработчик привязки на основе XML, но я бы хотел использовать конструктор настроек.

4b9b3361

Ответ 1

Невозможно найти хорошую информацию о реализации поставщика настраиваемых параметров, поэтому я включаю в себя полную реализацию ниже (внизу). Формат файла user.config сохраняется, а также функциональность в пределах .settings designer. Я уверен, что есть части, которые можно немного очистить, поэтому не стесняйтесь меня:)

Как и другие, я хотел изменить местоположение файла user.config и по-прежнему получать удовольствие и привязанность к работе с файлами .settings в дизайнере, включая создание значений по умолчанию для новых установок. Важно отметить, что в нашем приложении уже есть другие сохраненные объекты настроек по пути (appData\local\etc), в котором мы уже решили, и мы не хотели, чтобы артефакты находились в нескольких местах.

Код намного длиннее, чем я хотел бы, но ответа SHORT нет. Хотя кажется несколько болезненным, чтобы иметь возможность контролировать путь, создание собственного поставщика настроек по-прежнему остается довольно мощным. Можно было бы изменить реализацию follwing для хранения данных практически в любом месте, включая пользовательский зашифрованный файл, базу данных или взаимодействие с веб-serivice.

Из того, что я прочитал, Microsoft не намерена настраивать путь к конфигурационному файлу. Я возьму их слово, когда они скажут, что это будет страшно. См. (этот). Увы, если вы хотите сделать это самостоятельно, вы должны реализовать свой собственный SettingsProvider.

Здесь идет..

Добавьте ссылку в свой проект в System.Configuration, вам понадобится его для реализации ConfigurationProvider.

Легкий бит. Создайте класс, который реализует SettingsProvider, используйте ctrl +., чтобы помочь вам.

class CustomSettingsProvider : SettingsProvider

Еще один простой бит. Перейдите к коду вашего файла .settings(есть кнопка в конструкторе) и украсьте класс, чтобы указать его на вашей реализации. Это должно быть сделано для переопределения встроенных функций, но это не меняет работу дизайнера (извините, что форматирование здесь странно)

[System.Configuration.SettingsProvider(typeof(YourCompany.YourProduct.CustomSettingsProvider))]

public sealed partial class Settings
{
    //bla bla bla
}

Получение пути: Существует свойство, называемое "SettingsKey" (например, Properties.Settings.Default.SettingsKey). Я использовал это для хранения пути. Я сделал следующее свойство.

/// <summary>
/// The key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
/// </summary>
private string UserConfigPath
{
    get
    {
        return Properties.Settings.Default.SettingsKey;
    }

}

Сохранение значений настроек. Я решил использовать словарь. Это будет активно использоваться немного. Я создал структуру как помощника.

/// <summary>
/// In memory storage of the settings values
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }

/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
    internal string name;
    internal string serializeAs;
    internal string value;
}

Магия. Вы должны переопределить 2 метода, GetPropertyValues ​​ и SetPropertyValues ​​. GetPropertyValues ​​получает в качестве параметра то, что вы видите в дизайнере, вы должны иметь возможность обновлять значения и возвращать новую коллекцию. SetPropertyValues ​​вызывается, когда пользователь сохраняет изменения в значениях, сделанных во время выполнения, именно там я обновляю словарь и выписываю файл.

/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
    //load the file
    if (!_loaded)
    {
         _loaded = true;
         LoadValuesFromFile();
    }

    //collection that will be returned.
    SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();

    //iterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
    foreach (SettingsProperty setting in collection)
    {
        SettingsPropertyValue value = new SettingsPropertyValue(setting);
        value.IsDirty = false;

        //need the type of the value for the strong typing
        var t = Type.GetType(setting.PropertyType.FullName);

        if (SettingsDictionary.ContainsKey(setting.Name))
        {
            value.SerializedValue = SettingsDictionary[setting.Name].value;
            value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
        }
        else //use defaults in the case where there are no settings yet
        {
            value.SerializedValue = setting.DefaultValue;
            value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
        }

            values.Add(value);
    }
    return values;
}

/// <summary>
/// Must override this, this is the bit that does the saving to file.  Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
    //grab the values from the collection parameter and update the values in our dictionary.
    foreach (SettingsPropertyValue value in collection)
    {
        var setting = new SettingStruct()
        {
            value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
            name = value.Name,
            serializeAs = value.Property.SerializeAs.ToString()
        };

        if (!SettingsDictionary.ContainsKey(value.Name))
        {
            SettingsDictionary.Add(value.Name, setting);
        }
        else
        {
            SettingsDictionary[value.Name] = setting;
        }
    }

    //now that our local dictionary is up-to-date, save it to disk.
    SaveValuesToFile();
}

Полное решение. Итак, вот весь класс, который включает в себя методы конструктора, Initialize и helper. Не стесняйтесь вырезать/вставлять срез и кости.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.IO;

namespace YourCompany.YourProduct
{
    class CustomSettingsProvider : SettingsProvider
    {
        const string NAME = "name";
        const string SERIALIZE_AS = "serializeAs";
        const string CONFIG = "configuration";
        const string USER_SETTINGS = "userSettings";
        const string SETTING = "setting";

        /// <summary>
        /// Loads the file into memory.
        /// </summary>
        public CustomSettingsProvider()
        {
            SettingsDictionary = new Dictionary<string, SettingStruct>();

        }

        /// <summary>
        /// Override.
        /// </summary>
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(ApplicationName, config);
        }

        /// <summary>
        /// Override.
        /// </summary>
        public override string ApplicationName
        {
            get
            {
                return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
            }
            set
            {
                //do nothing
            }
        }

        /// <summary>
        /// Must override this, this is the bit that matches up the designer properties to the dictionary values
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        /// <returns></returns>
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            //load the file
            if (!_loaded)
            {
                _loaded = true;
                LoadValuesFromFile();
            }

            //collection that will be returned.
            SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();

            //itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
            foreach (SettingsProperty setting in collection)
            {
                SettingsPropertyValue value = new SettingsPropertyValue(setting);
                value.IsDirty = false;

                //need the type of the value for the strong typing
                var t = Type.GetType(setting.PropertyType.FullName);

                if (SettingsDictionary.ContainsKey(setting.Name))
                {
                    value.SerializedValue = SettingsDictionary[setting.Name].value;
                    value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
                }
                else //use defaults in the case where there are no settings yet
                {
                    value.SerializedValue = setting.DefaultValue;
                    value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
                }

                values.Add(value);
            }
            return values;
        }

        /// <summary>
        /// Must override this, this is the bit that does the saving to file.  Called when Settings.Save() is called
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            //grab the values from the collection parameter and update the values in our dictionary.
            foreach (SettingsPropertyValue value in collection)
            {
                var setting = new SettingStruct()
                {
                    value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
                    name = value.Name,
                    serializeAs = value.Property.SerializeAs.ToString()
                };

                if (!SettingsDictionary.ContainsKey(value.Name))
                {
                    SettingsDictionary.Add(value.Name, setting);
                }
                else
                {
                    SettingsDictionary[value.Name] = setting;
                }
            }

            //now that our local dictionary is up-to-date, save it to disk.
            SaveValuesToFile();
        }

        /// <summary>
        /// Loads the values of the file into memory.
        /// </summary>
        private void LoadValuesFromFile()
        {
            if (!File.Exists(UserConfigPath))
            {
                //if the config file is not where it supposed to be create a new one.
                CreateEmptyConfig();
            }

            //load the xml
            var configXml = XDocument.Load(UserConfigPath);

            //get all of the <setting name="..." serializeAs="..."> elements.
            var settingElements = configXml.Element(CONFIG).Element(USER_SETTINGS).Element(typeof(Properties.Settings).FullName).Elements(SETTING);

            //iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
            //using "String" as default serializeAs...just in case, no real good reason.
            foreach (var element in settingElements)
            {
                var newSetting = new SettingStruct()
                {
                    name = element.Attribute(NAME) == null ? String.Empty : element.Attribute(NAME).Value,
                    serializeAs = element.Attribute(SERIALIZE_AS) == null ? "String" : element.Attribute(SERIALIZE_AS).Value,
                    value = element.Value ?? String.Empty
                };
                SettingsDictionary.Add(element.Attribute(NAME).Value, newSetting);
            }
        }

        /// <summary>
        /// Creates an empty user.config file...looks like the one MS creates.  
        /// This could be overkill a simple key/value pairing would probably do.
        /// </summary>
        private void CreateEmptyConfig()
        {
            var doc = new XDocument();
            var declaration = new XDeclaration("1.0", "utf-8", "true");
            var config = new XElement(CONFIG);
            var userSettings = new XElement(USER_SETTINGS);
            var group = new XElement(typeof(Properties.Settings).FullName);
            userSettings.Add(group);
            config.Add(userSettings);
            doc.Add(config);
            doc.Declaration = declaration;
            doc.Save(UserConfigPath); 
        }

        /// <summary>
        /// Saves the in memory dictionary to the user config file
        /// </summary>
        private void SaveValuesToFile()
        {
            //load the current xml from the file.
            var import = XDocument.Load(UserConfigPath);

            //get the settings group (e.g. <Company.Project.Desktop.Settings>)
            var settingsSection = import.Element(CONFIG).Element(USER_SETTINGS).Element(typeof(Properties.Settings).FullName);

            //iterate though the dictionary, either updating the value or adding the new setting.
            foreach (var entry in SettingsDictionary)
            {
                var setting = settingsSection.Elements().FirstOrDefault(e => e.Attribute(NAME).Value == entry.Key);
                if (setting == null) //this can happen if a new setting is added via the .settings designer.
                {
                    var newSetting = new XElement(SETTING);
                    newSetting.Add(new XAttribute(NAME, entry.Value.name));
                    newSetting.Add(new XAttribute(SERIALIZE_AS, entry.Value.serializeAs));
                    newSetting.Value = (entry.Value.value ?? String.Empty);
                    settingsSection.Add(newSetting);
                }
                else //update the value if it exists.
                {
                    setting.Value = (entry.Value.value ?? String.Empty);
                }
            }
            import.Save(UserConfigPath);
        }

        /// <summary>
        /// The setting key this is returning must set before the settings are used.
        /// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
        /// </summary>
        private string UserConfigPath
        {
            get
            {
                return Properties.Settings.Default.SettingsKey;
            }

        }

        /// <summary>
        /// In memory storage of the settings values
        /// </summary>
        private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }

        /// <summary>
        /// Helper struct.
        /// </summary>
        internal struct SettingStruct
        {
            internal string name;
            internal string serializeAs;
            internal string value;
        }

        bool _loaded;
    }
}    

Ответ 2

Вам нужно будет реализовать собственный SettingsProvider, чтобы настроить путь.

Смотрите Часто задаваемые вопросы по настройкам клиента

В: Почему путь настолько неясен? Есть ли способ изменить/настроить его?

A: Алгоритм построения пути должен отвечать определенным строгим требованиям с точки зрения безопасности, изоляции и надежности. Несмотря на то, что мы пытались сделать путь максимально легко понятным, используя дружественные строки, поставляемые приложениями, невозможно полностью сохранить путь без проблем с конфликтами с другими приложениями, спуфингами и т.д.

LocalFileSettingsProvider не предоставляет способ изменения файлов, в которых хранятся настройки. Обратите внимание, что сам поставщик не определяет местоположение файла конфигурации в первую очередь - это система конфигурации. Если вам необходимо сохранить настройки в другом месте по какой-либо причине, рекомендуемым способом является создание собственного SettingsProvider. Это довольно просто реализовать, и вы можете найти образцы в SDK.NET 2.0, которые показывают, как это сделать. Имейте в виду, однако, что вы можете столкнуться с теми же проблемами изоляции, о которых говорилось выше.

Ответ 3

Построение патронов превосходный ответ:

Внедрить новый частичный класс на основе Settings.Designer.cs, поэтому Конструктор настроек не уничтожит атрибут при внесении изменений:

namespace Worker.Properties
{
    [System.Configuration.SettingsProvider(
        typeof(SettingsProviders.DllFileSettingsProvider<Settings>))]
    internal sealed partial class Settings
    {

    }
}

Я создал следующий класс, который можно поместить во внешний проект. Он позволяет config быть project.dll.config. Наследуя и переопределяя ConfigPath позволяет любой путь, который вам нравится. Я также добавил поддержку для чтения System.Collections.Specialized.StringCollection. Эта версия предназначена для настроек приложения.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.IO;


//http://stackoverflow.com/questions/2265271/custom-path-of-the-user-config
namespace SettingsProviders
{
    public  class DllFileSettingsProvider<Properties_Settings> : SettingsProvider where Properties_Settings : new() 
    {
        const string NAME = "name";
        const string SERIALIZE_AS = "serializeAs";
        const string CONFIG = "configuration";
        const string APPLICATION_SETTINGS = "applicationSettings";
        const string SETTING = "setting";

        /// <summary>
        /// Loads the file into memory.
        /// </summary>
        public DllFileSettingsProvider()
        {
            SettingsDictionary = new Dictionary<string, SettingStruct>();

        }

        /// <summary>
        /// Override.
        /// </summary>
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(ApplicationName, config);
        }

        /// <summary>
        /// Override.
        /// </summary>
        public override string ApplicationName
        {
            get
            {
                return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
            }
            set
            {
                //do nothing
            }
        }

        /// <summary>
        /// Must override this, this is the bit that matches up the designer properties to the dictionary values
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        /// <returns></returns>
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            //load the file
            if (!_loaded)
            {
                _loaded = true;
                LoadValuesFromFile();
            }

            //collection that will be returned.
            SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();

            //itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
            foreach (SettingsProperty setting in collection)
            {
                SettingsPropertyValue value = new SettingsPropertyValue(setting);
                value.IsDirty = false;

                //need the type of the value for the strong typing
                var t = Type.GetType(setting.PropertyType.FullName);

                if (setting.PropertyType == typeof(System.Collections.Specialized.StringCollection))
                {
                    var xml = SettingsDictionary[setting.Name].value;
                    var stringReader = new System.IO.StringReader(xml);
                    var xmlreader = System.Xml.XmlReader.Create(stringReader);
                    var ser = new System.Xml.Serialization.XmlSerializer(typeof(System.Collections.Specialized.StringCollection));
                    var obj = ser.Deserialize(xmlreader);
                    var col = (System.Collections.Specialized.StringCollection)obj;
                    value.PropertyValue = col;
                }
                else if (SettingsDictionary.ContainsKey(setting.Name))
                {
                    value.SerializedValue = SettingsDictionary[setting.Name].value;
                    value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
                }
                else //use defaults in the case where there are no settings yet
                {
                    value.SerializedValue = setting.DefaultValue;
                    value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
                }

                values.Add(value);
            }
            return values;
        }

        /// <summary>
        /// Must override this, this is the bit that does the saving to file.  Called when Settings.Save() is called
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            //grab the values from the collection parameter and update the values in our dictionary.
            foreach (SettingsPropertyValue value in collection)
            {
                var setting = new SettingStruct()
                {
                    value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
                    name = value.Name,
                    serializeAs = value.Property.SerializeAs.ToString()
                };

                if (!SettingsDictionary.ContainsKey(value.Name))
                {
                    SettingsDictionary.Add(value.Name, setting);
                }
                else
                {
                    SettingsDictionary[value.Name] = setting;
                }
            }

            //now that our local dictionary is up-to-date, save it to disk.
            SaveValuesToFile();
        }

        /// <summary>
        /// Loads the values of the file into memory.
        /// </summary>
        private void LoadValuesFromFile()
        {
            if (!File.Exists(ConfigPath))
            {
                //if the config file is not where it supposed to be create a new one.
                throw new Exception("Config file not found: " + ConfigPath);
            }

            //load the xml
            var configXml = XDocument.Load(ConfigPath);

            //get all of the <setting name="..." serializeAs="..."> elements.
            var settingElements = configXml.Element(CONFIG).Element(APPLICATION_SETTINGS).Element(typeof(Properties_Settings).FullName).Elements(SETTING);

            //iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
            //using "String" as default serializeAs...just in case, no real good reason.
            foreach (var element in settingElements)
            {

                var newSetting = new SettingStruct()
                {
                    name = element.Attribute(NAME) == null ? String.Empty : element.Attribute(NAME).Value,
                    serializeAs = element.Attribute(SERIALIZE_AS) == null ? "String" : element.Attribute(SERIALIZE_AS).Value ,
                    value = element.Value ?? String.Empty
                };

                if (newSetting.serializeAs == "Xml")
                {
                    var e = (XElement)element.Nodes().First();
                    newSetting.value = e.LastNode.ToString() ?? String.Empty;
                };

                SettingsDictionary.Add(element.Attribute(NAME).Value, newSetting);
            }
        }

        /// <summary>
        /// Creates an empty user.config file...looks like the one MS creates.  
        /// This could be overkill a simple key/value pairing would probably do.
        /// </summary>
        private void CreateEmptyConfig()
        {
            var doc = new XDocument();
            var declaration = new XDeclaration("1.0", "utf-8", "true");
            var config = new XElement(CONFIG);
            var userSettings = new XElement(APPLICATION_SETTINGS);
            var group = new XElement(typeof(Properties_Settings).FullName);
            userSettings.Add(group);
            config.Add(userSettings);
            doc.Add(config);
            doc.Declaration = declaration;
            doc.Save(ConfigPath);
        }

        /// <summary>
        /// Saves the in memory dictionary to the user config file
        /// </summary>
        private void SaveValuesToFile()
        {
            //load the current xml from the file.
            var import = XDocument.Load(ConfigPath);

            //get the settings group (e.g. <Company.Project.Desktop.Settings>)
            var settingsSection = import.Element(CONFIG).Element(APPLICATION_SETTINGS).Element(typeof(Properties_Settings).FullName);

            //iterate though the dictionary, either updating the value or adding the new setting.
            foreach (var entry in SettingsDictionary)
            {
                var setting = settingsSection.Elements().FirstOrDefault(e => e.Attribute(NAME).Value == entry.Key);
                if (setting == null) //this can happen if a new setting is added via the .settings designer.
                {
                    var newSetting = new XElement(SETTING);
                    newSetting.Add(new XAttribute(NAME, entry.Value.name));
                    newSetting.Add(new XAttribute(SERIALIZE_AS, entry.Value.serializeAs));
                    newSetting.Value = (entry.Value.value ?? String.Empty);
                    settingsSection.Add(newSetting);
                }
                else //update the value if it exists.
                {
                    setting.Value = (entry.Value.value ?? String.Empty);
                }
            }
            import.Save(ConfigPath);
        }

        /// <summary>
        /// The setting key this is returning must set before the settings are used.
        /// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
        /// </summary>
        public virtual string ConfigPath
        {
            get
            {
                var name = new Properties_Settings().GetType().Module.Name + ".config";
                if (System.IO.File.Exists(name)==false)
                {
                    System.Diagnostics.Trace.WriteLine("config file NOT found:" + name);
                }
                System.Diagnostics.Trace.WriteLine("config file found:" + name);

                return name;
               // return Properties.Settings.Default.SettingsKey;
            }

        }

        /// <summary>
        /// In memory storage of the settings values
        /// </summary>
        internal Dictionary<string, SettingStruct> SettingsDictionary { get; set; }

        /// <summary>
        /// Helper struct.
        /// </summary>
        internal struct SettingStruct
        {
            internal string name;
            internal string serializeAs;
            internal string value;
        }

        bool _loaded;
    }
}

Убедитесь, что зависимые проекты включают файл project.dll.config, добавив событие post-build:

copy $(SolutionDir)Worker\$(OutDir)Worker.dll.config $(TargetDir) /y

Ответ 4

Вот более простая, более кратковременная альтернатива созданию настраиваемого класса настроек: измените доказательство своего приложения, чтобы часть "url" была константой, а не была основана на местоположении исполняемого файла. Для этого вам необходимо изменить стандартную AppDomain при запуске программы. Есть две части: настройка app.config для использования вашего AppDomainManager и создание AppDomainManager и HostSecurityManager для настройки доказательств Url. Звучит сложно, но это намного проще, чем создание настраиваемого класса настроек. Это относится только к неподписанным сборкам. Если у вас подписанная сборка, она будет использовать эти данные вместо Url. Но хорошие новости о вашем пути всегда будут постоянными (пока ключ подписи не изменится).

Вы можете скопировать код ниже и просто заменить бит YourAppName.

DefaultAppDomainManager.cs:

using System;
using System.Security;
using System.Security.Policy;

namespace YourAppName
{
    /// <summary>
    /// A least-evil (?) way of customizing the default location of the application user.config files.
    /// </summary>
    public class CustomEvidenceHostSecurityManager : HostSecurityManager
    {
        public override HostSecurityManagerOptions Flags
        {
            get
            {
                return HostSecurityManagerOptions.HostAssemblyEvidence;
            }
        }

        public override Evidence ProvideAssemblyEvidence(System.Reflection.Assembly loadedAssembly, Evidence inputEvidence)
        {
            if (!loadedAssembly.Location.EndsWith("YourAppName.exe"))
                return base.ProvideAssemblyEvidence(loadedAssembly, inputEvidence);

            // override the full Url used in Evidence to just "YourAppName.exe" so it remains the same no matter where the exe is located
            var zoneEvidence = inputEvidence.GetHostEvidence<Zone>();
            return new Evidence(new EvidenceBase[] { zoneEvidence, new Url("YourAppName.exe") }, null);
        }
    }

    public class DefaultAppDomainManager : AppDomainManager
    {
        private CustomEvidenceHostSecurityManager hostSecurityManager;
        public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
        {
            base.InitializeNewDomain(appDomainInfo);

            hostSecurityManager = new CustomEvidenceHostSecurityManager();
        }

        public override HostSecurityManager HostSecurityManager
        {
            get
            {
                return hostSecurityManager;
            }
        }
    }
}

app.config excerpt:

<runtime>
    <appDomainManagerType value="YourAppName.DefaultAppDomainManager" />
    <appDomainManagerAssembly value="DefaultAppDomainManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</runtime>