ASP.NET MVC: Сохраняем настройки сайта в свою секцию файла конфигурации web.config
Сайтостроение | создано: 22.09.2012 | опубликовано: 22.09.2012 | обновлено: 13.01.2024 | просмотров: 10410
Мне много раз приходилось сохранять настройки сайта в файле конфигурации. Раздел appSettings предоставляет возможность хранить настройки по принципу "ключ" = "значение" (Dictionary). Я же хочу показать как можно создать свою секцию в файле конфигурации, как сохранять новые и обновленные значения.
Что будет в статье?
В предыдущей части были созданы сами настройки и реализовано чтение настроек. В этой предстоит создать форму управления настройками, ajax-сервис, ViewModel на javascript и всё что ещё потребуется.
Сохранение настроек в web.config
Для начал доработаем немного Config-помощник, дописав в него один новый метод, которой будет сохранять настройки:
internal static Configuring Save(SiteSettings settings) {
Configuring success = new Configuring();
try {
Configuration cfg = WebConfigurationManager.OpenWebConfiguration("~");
var group = cfg.SectionGroups[CONFIGGROUPNAME];
SiteSettings section = (SiteSettings)group.Sections[CONFIGSECTIONNAME];
if (section != null) {
section.Lenta.AllowPostFromShare = settings.Lenta.AllowPostFromShare;
section.Lenta.DeleteAfterDays = settings.Lenta.DeleteAfterDays;
section.PagerSize.Clear();
for (int i = 0; i < settings.PagerSize.Count; i++) {
section.PagerSize.Add(settings.PagerSize[i]);
}
cfg.Save();
success.Success = true;
}
}
catch (ConfigurationErrorsException error) {
success.ConfigException = error;
}
return success;
}
Рисуем форму редактирования
В панели управления администратора я сделал ссылку на представление Settings.cshtml. И вот так, не сложно, наполнил это представление html-разметкой:
<div class="clear">
<div class="left" style="width: 49%">
<h4>Лента</h4>
<div class="editor-label">
<label for="DeleteAfterDays">Очищать старее чем, дни:</label>
</div>
<div class="editor-field">
<input type="text" id="DeleteAfterDays" value="" />
</div>
<div class="editor-label">
<input type="checkbox" id="AllowPostFromShare" />
Разрешить публикацию через ускоритель
</div>
</div>
<div class="left" style="width: 49%">
<h4>Пользовательский интерфейс</h4>
<h6>Настройки пейджера для сущностей</h6>
<div></div>
<button>+</button>
</div>
</div>
<div class="clear"></div>
<p>
<button>Сохранить все</button>
</p>
И раз уж я решил реализовать задуманное с использованием фреймворка Knockout.js, то далее это представление наполнится атрибутами привязки (data-bind). А пока надо подключить требуемые скрипты к этому представлению. Хочу заметить, что это пока не полный список.
@section scripts{
<script src="@Url.Content("~/scripts/knockout-2.1.0.js")"></script>
<script src="@Url.Content("~/scripts/knockout.validation.js")"></script>
}
Лирическое отступление
Я набросал небольшой Nuget-пакет, который при установки создает папку Js и помещает туда пока один единственный файл site.core.js (читателям моего блога название должно быть знакомо). Далее я постараюсь наполнить контролами и различными полезными штуками этот пакет, по мере написания сайтов. Это хороший старт для вашего фреймворка на JavaScript для всего сайта. Файл содержит обертку на jQuery методы getJSON и postJSON. Я установил себе в проект этот пакет:
PM> Install-Package JsSite Successfully installed 'JsSite 0.1.0'. Successfully added 'JsSite 0.1.0' to Calabonga.Mvc.Humor. PM>
Пакеты и скрипты установлены. Давай подготовим серверную часть.
Создаем новый контролер
Название для контролера AjaxController говорит само за себя. Этот контролер будет обслуживать весь сайт. Но пока в нем будет только пара методов. Вот первый, он читает данные из конфигурации:
public JsonResult LoadSettings() {
SiteSettings config = Config.Get();
var jsonConfig = new SiteSettingsJson();
jsonConfig.Lenta.AllowPostFromShare = config.Lenta.AllowPostFromShare;
jsonConfig.Lenta.DeleteAfterDays = config.Lenta.DeleteAfterDays;
for (int i = 0; i < config.PagerSize.Count; i++) {
jsonConfig.PagerSize.Add(new PagerSizeItem {
Name = config.PagerSize[i].Name,
Size = config.PagerSize[i].Size
});
}
return Json(jsonConfig, JsonRequestBehavior.AllowGet);
}
Следует остановиться на минутку и описать казус, который у меня приключился. В строке 2 я получаю объект и при попытке его отправить через JsonResult в строке 12 выдавалась ошибка сериализации объекта SiteSettings. Сам по себе объект простой, но его предки (вспомните от чего он унаследован) упорно не хотели проходить сериализацию. Пришлось сделать простой прокси-класс SiteSettingsJson, и, наполняя его данными передавать на форму. Класс настолько прост, что я даже его приводить не буду, да?
Время для Js-сервиса
Новый файл в папке Js я назвал site.services.js. Вот его содержимое:
/// <reference path="site.core.js" />
/// <reference path="../Scripts/jquery-1.8.1.js" />
/// <reference path="../Scripts/knockout-2.1.0.debug.js" />
(function (site) {
"use strict";
site.services.settings = {
load: function (callback) {
site.fw.ajaxService.getJson("LoadSettings", {}, callback);
},
save: function (jsonData, callback) {
site.fw.ajaxService.postJson("SaveSettings", jsonData, callback);
}
}
})(site);
Этот сервис использует обертку на ajax, которую я получил вместе с JsSite-пакетом. Как вы видите есть уже и второй метод, который будет сохранять данные, но его я пока не писал в AjaxController’e, займусь им позже.
Основной ViewModel на JavaScript? Легко!
Для начала нужно прочитать настройки и отобразить их при открытии страницы. Для этого я создал ViewModel страницы Settings.cshtml, который “умеет” загружать настройки. Файл я назвал site.vm.settings.js положил его в папку Js:
/// <reference path="site.core.js" />
/// <reference path="site.services.js" />
/// <reference path="../Scripts/jquery-1.8.1.js" />
/// <reference path="../Scripts/knockout.mapping-latest.debug.js" />
/// <reference path="../Scripts/knockout-2.1.0.debug.js" />
(function (site) {
"use strict";
site.vm.settings = function () {
var
//статус работы сервиса
isbusy = ko.observable(false),
// конфигурация
config = ko.observable({
"Lenta": ko.observable({
"DeleteAfterDays": ko.observable(),
"AllowPostFromShare": ko.observable(true)
}),
"PagerSize": ko.observableArray()
}),
// PagerSize: название
newName = ko.observable("Entity"),
// PagerSize: размер страниц
newSize = ko.observable(0),
// метод загрузки конфигурации
load = function () {
isbusy(true);
site.services.settings.load(function (json) {
isbusy(false);
ko.mapping.fromJS(json.Config, {}, config);
});
},
// сохранение настроек
save = function () {
isbusy(true);
var jsonData = ko.toJSON(config);
site.services.settings.save(jsonData, function (json) {
isbusy(false);
alert(json)
})
},
// добавдение объекта в список PagerSize
add = function () {
config().PagerSize.push(new PagerSize(newName(), newSize()));
newName("Entity"); newSize(10);
},
// удаление объекта из списка PagerSize
remove = function (item) {
config().PagerSize.remove(item);
};
load();
return {
config: config,
isbusy: isbusy,
remove: remove,
add: add,
newName: newName,
newSize: newSize,
save:save
}
}();
})(site);
Не думаю, что нужно подробно останавливаться на распечатке. Тем более, что я постарался с комментариями.
Представление Settings.cshtml
Теперь надо показать html-код разметки этого самого представления Settings.cshtml, потому что я кое-что добавил и “нашпиговал” атрибутами привязки. Приведу этот код тоже целиком:
@{
ViewBag.Title = "Настройки системы";
Layout = "~/Views/Shared/_LayoutMain.cshtml";
}
<h2>Настройки системы</h2>
<div data-bind="ifnot: isbusy">
<div class="clear">
<div class="left" style="width: 49%">
<h4>Лента</h4>
<div class="editor-label">
<label for="DeleteAfterDays">Очищать старее чем, дни:</label>
</div>
<div class="editor-field">
<input type="text" id="DeleteAfterDays"
data-bind="value: config().Lenta().DeleteAfterDays" />
</div>
<div class="editor-label">
<input type="checkbox" id="AllowPostFromShare"
data-bind="checked: config().Lenta().AllowPostFromShare" />
Разрешить публикацию через ускоритель
</div>
</div>
<div class="left" style="width: 49%">
<h4>Пользовательский интерфейс</h4>
<h6>Настройки пейджера для сущностей</h6>
<div data-bind="foreach: config().PagerSize">
<p>
<b><span data-bind="text: Name"></span></b> на одной странице <b>
<span data-bind="text: Size"></span></b>
<button data-bind="click: $parent.remove">х</button>
</p>
</div>
Название класса сущности:<br />
<input type="text" data-bind="value: newName" /><br />
Количество объектов на странице:<br />
<input type="text" data-bind="value: newSize" /><br />
<button data-bind="click: add">Добавить новую</button>
</div>
</div>
<div class="clear"></div>
<p>
<button data-bind="click: save">Сохранить все</button>
</p>
</div>
@section scripts{
<script src="@Url.Content("~/scripts/knockout-2.1.0.js")"></script>
<script src="@Url.Content("~/scripts/knockout.validation.js")"></script>
<script src="@Url.Content("~/scripts/knockout.mapping-latest.js")"></script>
<script src="@Url.Content("~/js/site.core.js")"></script>
<script src="@Url.Content("~/js/site.services.js")"></script>
<script src="@Url.Content("~/js/site.vm.settings.js")"></script>
<script>
$(function () {
ko.applyBindings(site.vm.settings);
});
</script>
}
Ну, и как это выглядит, чтобы уж совсем всё было наглядно:

Слева я значение 17 поменяю на 15, а правом списке к уже существующим: Logs, Exhibit, Lenta добавил еще одну сущность Comment и теперь в режиме отладки хочу проверить, приходят ли данный в AjaxController. А-а-а-а-а вот они-и!

Осталось только “прикрутить” валидацию", но это пусть будет уже вашим домашним заданием, тем более, что об этом уже был разговор. А еще надо написать метод сохранения данных. Так как у меня появился прокси-класс для настроек, то теперь я могу использовать его как входящие данные для метода сохранения в Config-помощнике. Итак, представлю второй метод Config-помощника:
internal static Configuring Save(SiteSettingsJson settings)
{
Configuring success = new Configuring();
try
{
Configuration cfg = WebConfigurationManager.OpenWebConfiguration("~");
var group = cfg.SectionGroups[CONFIGGROUPNAME];
SiteSettings section = (SiteSettings)group.Sections[CONFIGSECTIONNAME];
if (section != null)
{
section.Lenta.AllowPostFromShare = settings.Lenta.AllowPostFromShare;
section.Lenta.DeleteAfterDays = settings.Lenta.DeleteAfterDays;
section.PagerSize.Clear();
for (int i = 0; i < settings.PagerSize.Count; i++)
{
section.PagerSize.Add(new PageSizeItemsElement()
{
Name = settings.PagerSize[i].Name,
Size = settings.PagerSize[i].Size
});
}
cfg.Save();
success.Success = true;
}
}
catch (ConfigurationErrorsException error)
{
success.ConfigException = error;
}
return success;
}
Вот еще маленький класс упомянутый в предыдущем листинге, для полноты картины:
public class Configuring
{
public bool Success { get; set; }
public ConfigurationErrorsException ConfigException { get; set; }
}
И, наконец, завершим начатое. Вот конечный вариант второго метода AjaxController’а, который сохраняет данные:
[HttpPost]
public JsonResult SaveSettings(SiteSettingsJson config) {
Response.CacheControl = "no-cache";
string message=string.Empty;
if (ModelState.IsValid)
{
Config.Save(config);
message = "Настройки успешно сохранены";
}
else { message = "Неверные данные в конфигурации"; }
return Json(message);
}
Заключение
Данные читаются, изменяются, сохраняются – значит поставленная цель достигнута и моя миссия завершена. Вас же я прошу писать комментарии.
Да прибудет с вами сила!