ASP.NET MVC: И снова про форму обратной связи или куда еще втыкнуть Knockout

Сайтостроение | создано: 14.01.2013 | опубликовано: 14.01.2013 | обновлено: 13.01.2024 | просмотров: 15043 | всего комментариев: 6

Уже не раз на страницах блога был материал о форме обратной связи. Но о форме с использованием Knockout еще не было. Про это и будет мой сказ.

Создание и подготовка проекта

У меня студия Visual Studio 2012 Ultimate с лицензией подписчика (не плохо да?), но вы можете воспользоваться и бесплатной версией (VS 2012 Express). Создаю новый проект ASP.NET MVC4. Сразу же, чтобы не терять время запускаю команду обновления всех nuget-пакетов (выполняю команду в Package Manager Console):

PM> Install-Package
… {много букв касаемо обновления пакетов} …

Теперь поставлю несколько дополнительных пакетов, конечно же nuget-пакетов:

PM> Install-Package knockoutjs
'knockoutjs 2.2.0' already installed.
FeedbackWithKo already has a reference to 'knockoutjs 2.2.0'.

PM> Install-Package jssite
Successfully installed 'JsSite 0.2.2'.
Successfully added 'JsSite 0.2.2' to FeedbackWithKo.

PM> Install-Package knockout.Validation
Attempting to resolve dependency 'knockoutjs (≥ 2.0.0)'.
Successfully installed 'Knockout.Validation 1.0.1'.
Successfully added 'Knockout.Validation 1.0.1' to FeedbackWithKo.

PM> Install-Package MvcTools.Mvc4
Attempting to resolve dependency 'XmlExport (≥ 0.2.1)'.
Successfully installed 'XmlExport 0.2.2'.
Successfully installed 'MvcTools.Mvc4 0.3.5'.
Successfully added 'XmlExport 0.2.2' to FeedbackWithKo.
Successfully added 'MvcTools.Mvc4 0.3.5' to FeedbackWithKo.

PM>

Раскидаю всё по папкам, для удобства, привычка, для красоты (нужное подчеркнуть). Принцип простой, мухи и котлеты отдельно:

105-0001

Обратите внимание, что App в папке Scripts появится как результат установки пакета JsSite. Далее следует “прибраться” в шаблоне. Для начала удаляю _Layout.cshtml и вместо него в файле _ViewStart.cshtml ставлю использование _LayoutExtended.cshtml:

@{
    Layout = "~/Views/Shared/_LayoutExtended.cshtml";
}

В самом шаблоне _LayoutExtended.cshtml добавлю еще одну закладку:

<ul id="menu">
    <<li>@Html.ActionLink("Home", "Index", "Home")li>
    <<li>@Html.ActionLink("About", "About", "Home")li>
    <<li>@Html.ActionLink("Contact", "Contact", "Home")li>
    <<li>@Html.ActionLink("Feedback", "Feedback", "Home")li>
ul>

В строке 5 можете наблюдать добавленный пункт меню. А в контроллере HomeController соответственно, нужен метод Feedback:

public ActionResult Feedback() {
     return View();
 }

К методу полагается еще и представление  Feedback.cshtml. Я его тоже создал, но пока оставил его пустым.

А еще я сразу добавил загрузку Bundle для Knockout внизу странице (сразу после регистрации jquery):

@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/ko")
@RenderSection("scripts", required: false)

Создание самого пакета сжатия и минимизации в следующей части.

Разберемся с Bundle

В папке App_Start есть файл BundleConfig.cs, в котором уже есть пакеты (bundles), я добавил еще для knockout и, таким образом, вот полный файл:

public class BundleConfig {
    // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725
    public static void RegisterBundles(BundleCollection bundles) {
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/lib/jquery-{version}.js"));

        bundles.Add(new ScriptBundle("~/bundles/ko").Include(
                    "~/Scripts/lib/knockout-{version}.js",
                    "~/Scripts/lib/knockout.validation.js"));

        bundles.Add(new ScriptBundle("~/bundles/site").Include(
                    "~/Scripts/app/site.core.js",
                    "~/Scripts/app/site.services.js",
                    "~/Scripts/app/site.controls.js",
                        "~/Scripts/app/site.bindingHandlers.js"));

        bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                    "~/Scripts/lib/jquery-ui-{version}.js"));

        bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                    "~/Scripts/lib/jquery.unobtrusive*",
                    "~/Scripts/lib/jquery.validate*"));

        bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                    "~/Scripts/lib/modernizr-*"));

        bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));

        bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
                    "~/Content/themes/base/jquery.ui.core.css",
                    "~/Content/themes/base/jquery.ui.resizable.css",
                    "~/Content/themes/base/jquery.ui.selectable.css",
                    "~/Content/themes/base/jquery.ui.accordion.css",
                    "~/Content/themes/base/jquery.ui.autocomplete.css",
                    "~/Content/themes/base/jquery.ui.button.css",
                    "~/Content/themes/base/jquery.ui.dialog.css",
                    "~/Content/themes/base/jquery.ui.slider.css",
                    "~/Content/themes/base/jquery.ui.tabs.css",
                    "~/Content/themes/base/jquery.ui.datepicker.css",
                    "~/Content/themes/base/jquery.ui.progressbar.css",
                    "~/Content/themes/base/jquery.ui.theme.css"));
    }
}

В строках с 7-15 вы можете наблюдать то, что я добавил для работы knockout и для jssite. Кажется ничего не забыл.

Начнем программировать

Для того чтобы всё получилось сначала создаем FeedbackViewModel класс, для того чтобы на было что получать на стороне сервера:

public class FeedbackViewModel {

    [Required]
    [StringLength(100)]
    [Display(Name = "Тема сообщения")]
    public string Subject { get; set; }

    [Required]
    [StringLength(50)]
    [Display(Name = "Как к Вам обращаться")]
    public string UserName { get; set; }

    [Required]
    [RegularExpression(@"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$", ErrorMessage = "Неверный формат электронной почты")]
    [StringLength(50)]
    [Display(Name = "Email для обратной связи")]
    public string EmailAdrress { get; set; }

    [Required]
    [StringLength(500)]
    [DataType(DataType.MultilineText)]
    [Display(Name = "Текст сообщения")]
    public string Message { get; set; }

}

Теперь создаем контроллер. Я буду использовать простой контроллер, который является наследником от Controller, но вы в праве использовать и ApiController. Вот содержание файла AjaxController.cs:

public class AjaxController : Controller {
    [HttpPost]
    public JsonResult SendFeedback(FeedbackViewModel model) {
        if (ModelState.IsValid) {
            // отправляем сообщение...
            return Json(new { result = "Сообщение отправлено" });
        }
        else {
            return Json(new { error = "ошибка в заполнении формы" });
        }
    }
}

На данном этапе серверная часть завершена. Теперь займемся javascript’ами.

JavaScript не желаете?

В папке Scripts/App есть файл site.services.js, в нем примерный код для обращения к сервисам сайта. Я написал такой код сервиса:

///////////////////////////////////////////////////////////////
//  simple DataService sample
//  автор: calabonga.net
///////////////////////////////////////////////////////////////

(function (site) {

  "use strict";

    site.services.utilits = {
        sendFeedback: function (feedback, callback) {
            if (callback === undefined) {throw new Error(200, "callback is undefined");}
            site.fw.ajaxService.postJson("SendFeedback", feedback, callback);
        }
    };
})(site);

Теперь создаю новый файл site.vm.feedback.js, в котором будет ViewModel (на javascript, естественно), для работы формы обратной связи. Приведу весь код, а потом немного прокомментирую:

$(function () {

    site.vm.Feedback = function () {
        var item = this;
        item.subject = ko.observable()
            .extend({ required: true, maxLength: 100 });
        item.message = ko.observable()
            .extend({ required: true, maxLength: 500 });
        item.emailadrress = ko.observable()
            .extend({ required: true, maxLength: 50, email: true });
        item.username = ko.observable()
            .extend({ required: true, maxLength: 50 });
        return item;
    };

    site.vm.feedbackViewModel = function () {
        var message = ko.observable("заполните форму"),
            feedback = new site.vm.Feedback(),
            isbusy = ko.observable(false),
            issended = ko.observable(false),
            errors = ko.validatedObservable(feedback),
            send = function () {
                isbusy(true);
                var jsonData = ko.toJSON(feedback);
                site.services.utilits.sendFeedback(jsonData, callback);
            },
            callback = function (json) {
                if (!json.error) {
                    alert(json.result);
                    message(json.result);
                    issended(true);
                } else {
                    alert(json.error);
                    message(json.error);
                }
                isbusy(false);
            };

        return {
            issended:issended,
            isbusy:isbusy,
            errors: errors,
            send: send,
            feedback: feedback,
            message: message
        };
    }();

    ko.applyBindings(site.vm.feedbackViewModel);
});

Строки 3-14: Модель (если хотете “класс”) Feedback.

Строки 6,8,10,12: Рассширения класса Feedback, наложенные для валидации объекта на форме. Для этого используется Knockout.Validation.js.

Строки 16-42: ViewModel формы обратной связи.

Строка 17: Свойство используется для отображения сообщения с сервера о результате отправки формы.

Строка 18: Экземпляр класса Feedback.

Строка 21: Инициализируем валидатор ошибок, задав объект для валидации.

Строка 22-26: Функция отправки сообщения.

Строка 27-37: Обработка результатов отправки, полученных с сервера.

Строка 49: Привязка модели к форме (это самая магическая магия под названием knockout).

Представление формы (View)

Давайте я просто покажу саму форму. А если будут вопросы, буду рад ответить на них:

@{
    ViewBag.Title = "Форма обратной связи";
}
<hgroup>
    <h2>Форма обратной связиh2>
    <h3 data-bind="text: message">h3>
hgroup>
<div data-bind="ifnot: isbusy, ifnot: issended">
    <p>
        <span>Тема сообщения:span><br />
        <input type="text" data-bind="value: feedback.subject" /><br />
        <br />

        <span>Ваше имя:span><br />
        <input type="text" data-bind="value: feedback.username" /><br />
        <br />

        <span>Электроящик:span><br />
        <input type="text" data-bind="value: feedback.emailadrress" /><br />
        <br />

        <span>Сообщение:span><br />
        <textarea data-bind="value: feedback.message">textarea><br />
        <br />

        <button data-bind="click:send, enable: errors().isValid()">отправитьbutton>
    p>
    <p>
        <span data-bind="text: JSON.stringify(ko.toJS($data), null, 2)">span>
    p>
div>

@section scripts {
    @Scripts.Render("~/bundles/site")
    <script src="~/Scripts/app/site.vm.feedback.js">script>
}

Это полный текст. Давайте отправим какую-нибудь информацию:

105-0002

Нажем кнопку “отправить”, и…:

105-0003

Скриншот как заключение

В как результат всей статьи:

105-0004

Вот и всё. Demo-проект лежит на github. Пишите комментарии.

Комментарии к статье (6)

Спасибо за полезную статью. Вот только что-то я никак не пойму, куда будет отправлено письмо?

EmailAdrress - это адрес отправителя по имени UserName или получателя?

Где прописыватеся куда будет оправлено письмо?

Есть ли какой-то более простой способ прикрутить к сайту форму обратной связи?

Оксана, EmailAdress - в данном примере используется как адрес отправителя, для того чтобы получатель (в моем примере это администратор сайта) мог написать ответ на этот адрес.

Хорошо бы где-то чтобы вы выкладывали весь рабочий пример. У себя до кучи не могу собрать. Есть ?

Не очень понимаю, о чем вы говорите, Alex, проект не компилируется, который выложен в статье?

Спасибо за статью. У меня остался вопрос где заполняется почтовый адрес администратора сайта ?

Nikita
Вы можете заполнять адрес откуда захотите: из базы, из настроек или намертво прописать в код. Этот вопрос не имеет принципиального значения.