ASP.NET MVC: Сохраняем настройки сайта в свою секцию файла конфигурации web.config
Сайтостроение | создано: 22.09.2012 | опубликовано: 22.09.2012 | обновлено: 13.01.2024 | просмотров: 10083
Мне много раз приходилось сохранять настройки сайта в файле конфигурации. Раздел 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); }
Заключение
Данные читаются, изменяются, сохраняются – значит поставленная цель достигнута и моя миссия завершена. Вас же я прошу писать комментарии.
Да прибудет с вами сила!