保存按钮不会触发事件背后的代码

时间:2013-11-22 21:20:00

标签: c# javascript jquery asp.net-mvc-4 knockout.js

这是一个简单的MVC4应用程序,带有单个下拉列表。当我单击Save按钮时,应该触发SaveWorkRequestDetails的事件,在下面看作[HttpPost],但它不是出于某种原因。我看到一个500错误,指出当我点击Save按钮时找不到myMachine:12345 / WorkRequest / SaveWorkRequestDetails。有关Chrome中控制台窗口的图片,请参阅帖子底部。

控制器

public ActionResult Index()
{
    _model = new WorkRequestViewModel()
    {
        WorkSections = GetWorkSections()
    };

    return View(_model);
}

[HttpPost]
public JsonResult SaveWorkRequestDetails(WorkRequestViewModel viewModel)
{
    // TODO: Save logic goes here
    return Json(new {});
}

这是应该触发Save事件的WorkRequest.js文件。这会触发所述事件,如果我在上面的ActionResult Index()返回View时不包含_model,但是,当我这样做时,在BeforeSend的WorkRequest.js文件中看到的警报为'null'。显然,这不是预期的行为,因为我确实希望这些值和Save按钮触发[HttpPost]。

var WorkRequest = {

PrepareKo: function () {
    ko.bindingHandlers.date = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            element.onchange = function () {
                var observable = valueAccessor();
                observable(new Date(element.value));
            }
        },
        update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var observable = valueAccessor();
            var valueUnwrapped = ko.utils.unwrapObservable(observable);
            if ((typeof valueUnwrapped == 'string' || valueUnwrapped instanceof String) &&
                         valueUnwrapped.indexOf('/Date') === 0) {
                var parsedDate = WorkRequest.ParseJsonDate(valueUnwrapped);
                element.value = parsedDate.getMonth() + 1 + "/" +
                  parsedDate.getDate() + "/" + parsedDate.getFullYear();
                observable(parsedDate);
            }
        }
    };
},

ParseJsonDate: function (jsonDate) {
    return new Date(parseInt(jsonDate.substr(6)));
},

BindUIwithViewModel: function (viewModel) {
    ko.applyBindings(viewModel);
},

EvaluateJqueryUI: function () {
    $('.dateInput').datepicker();
},

RegisterUIEventHandlers: function () {

    $('#Save').click(function (e) {

        // Check whether the form is valid. Note: Remove this check, if you are not using HTML5
        if (document.forms[0].checkValidity()) {

            e.preventDefault();

            $.ajax({
                type: "POST",
                url: WorkRequest.SaveUrl,
                data: ko.toJSON(WorkRequest.ViewModel),
                contentType: 'application/json',
                async: true,
                beforeSend: function () {
                    // Display loading image
                    alert(ko.toJSON(WorkRequest.ViewModel));
                },
                success: function (result) {
                    // Handle the response here.
                },
                complete: function () {
                    // Hide loading image.
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    // Handle error.
                }
            });
        }
    });
  },
};

$(document).ready(function () {
  WorkRequest.PrepareKo();
  WorkRequest.BindUIwithViewModel(WorkRequest.ViewModel);
  WorkRequest.EvaluateJqueryUI();
  WorkRequest.RegisterUIEventHandlers();
});

这是我的Index.cshtml

@using Microsoft.Ajax.Utilities
@model WorkRequest.ViewModel.WorkRequestViewModel
@using WorkRequest.Helper

@section styles{
  @Styles.Render("~/Content/themes/base/css")
  <link href="~/Content/WorkRequest.css" rel="stylesheet" />
}

@section scripts{
@Scripts.Render("~/bundles/jqueryui")
<script src="~/Scripts/knockout-2.2.0.js"></script>
<script src="~/Scripts/knockout.mapping-latest.js"></script>
<script src="~/Scripts/Application/WorkRequest.js"></script>
<script type="text/javascript">
    WorkRequest.SaveUrl = '@Url.Action("SaveWorkRequestDetails", "WorkRequest")';
    WorkRequest.ViewModel = ko.mapping.fromJS(@Html.Raw(Json.Encode(Model)));
</script>
}
<form>
  <div class="mainWrapper">
    <table>
        <tr>
            <td>Work Sections: 
            </td>
            <td>
               @Html.DropDownList("WorkSections", Model.WorkSections, new {data_bind="value: WorkRequest.ViewModel.WorkSections"})
            </td>
        </tr>
    </table>
</div>
<br />
<input id="Save" type="submit" value="Save" />
</form>

现在整个示例基于我在此处找到的演示:http://www.codeproject.com/Articles/657981/ASP-NET-MVC-4-with-Knockout-Js我能够正常工作,直到我尝试填充自己的下拉列表。我可以获取下拉列表的数据并填充我的SelectList而不会出现问题,并在我的表单上看到它。

我的视图模型

public class WorkRequestViewModel
{
    public SelectList WorkSections { get; set; }
}

我已经确认这个SelectList已经填充,我在下拉列表中看到了这些项目,但是演示应用程序使用了两个自定义类来使Index.cshtml页面中的控件可以被观察到,这样敲门可以做到这一点... < / p>

HtmlExtensions.cs

public static class HtmlExtensions
{
    /// <summary>
    /// To create an observable HTML Control.
    /// </summary>
    /// <typeparam name="TModel">The model object</typeparam>
    /// <typeparam name="TProperty">The property name</typeparam>
    /// <param name="htmlHelper">The <see cref="HtmlHelper<T>"/></param>
    /// <param name="expression">The property expression</param>
    /// <param name="controlType">The <see cref="ControlTypeConstants"/></param>
    /// <param name="htmlAttributes">The html attributes</param>
    /// <returns>Returns computed HTML string.</returns>
    public static IHtmlString ObservableControlFor<TModel, TProperty>(
            this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel,
            TProperty>> expression, string controlType =
            ControlTypeConstants.TextBox, object htmlAttributes = null)
    {
        var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        string jsObjectName = null;
        string generalWidth = null;

        // This will be useful, if the same extension has
        // to share with multiple pages (i.e. each with different view models).
        switch (metaData.ContainerType.Name)
        {
            case "WorkRequestViewModel":
                // Where WorkRequest is the Javascript object name (namespace in theory). 
                jsObjectName = "WorkRequest.ViewModel.";
                generalWidth = "width: 380px";
                break;
            default:
                throw new Exception(string.Format("The container type {0} is not supported yet.",
                                    metaData.ContainerType.Name));
        }

        var propertyObject = jsObjectName + metaData.PropertyName;

        TagBuilder controlBuilder = null;

        // Various control type creation.
        switch (controlType)
        {
            case ControlTypeConstants.TextBox:
                controlBuilder = new TagBuilder("input");
                controlBuilder.Attributes.Add("type", "text");
                controlBuilder.Attributes.Add("style", generalWidth);
                break;
            case ControlTypeConstants.Html5NumberInput:
                controlBuilder = new TagBuilder("input");
                controlBuilder.Attributes.Add("type", "number");
                controlBuilder.Attributes.Add("style", generalWidth);
                break;
            case ControlTypeConstants.Html5UrlInput:
                controlBuilder = new TagBuilder("input");
                controlBuilder.Attributes.Add("type", "url");
                controlBuilder.Attributes.Add("style", generalWidth);
                break;
            case ControlTypeConstants.TextArea:
                controlBuilder = new TagBuilder("textarea");
                controlBuilder.Attributes.Add("rows", "5");
                break;
            case ControlTypeConstants.DropDownList:
                controlBuilder = new TagBuilder("div");
                controlBuilder.Attributes.Add("class", "dropDownList");
                break;
            case ControlTypeConstants.JqueryUIDateInput:
                controlBuilder = new TagBuilder("input");
                controlBuilder.Attributes.Add("type", "text");
                controlBuilder.Attributes.Add("style", generalWidth);
                controlBuilder.Attributes.Add("class", "dateInput");
                controlBuilder.Attributes.Add("data-bind", "date: " + propertyObject);
                // date is the customized knockout binding handler. Check PrepareKo method of Person.

                break;
            default:
                throw new Exception(string.Format("The control type {0} is not supported yet.", controlType));
        }

        controlBuilder.Attributes.Add("id", metaData.PropertyName);
        controlBuilder.Attributes.Add("name", metaData.PropertyName);

        // Check data-bind already exists, add if not.
        if (!controlBuilder.Attributes.ContainsKey("data-bind"))
        {
            controlBuilder.Attributes.Add("data-bind", "value: " + propertyObject);
        }

        // Merge provided custom html attributes. This overrides the previously defined attributes, if any.
        if (htmlAttributes != null)
        {
            controlBuilder.MergeAttributes(
                HtmlExtensions.AnonymousObjectToHtmlAttributes(htmlAttributes), true);
        }

        return MvcHtmlString.Create(controlBuilder.ToString());
    }

    /// <summary>
    /// To convert '_' into '-'.
    /// </summary>
    /// <param name="htmlAttributes">The html attributes.</param>
    /// <returns>Returns converted <see cref="RouteValueDictionary"/>.</returns>
    private static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes)
    {
        RouteValueDictionary result = new RouteValueDictionary();
        if (htmlAttributes != null)
        {
            foreach (System.ComponentModel.PropertyDescriptor property in
                        System.ComponentModel.TypeDescriptor.GetProperties(htmlAttributes))
            {
                result.Add(property.Name.Replace('_', '-'), property.GetValue(htmlAttributes));
            }
        }
        return result;
    }
}

ViewModelConstants.cs

public static class ControlTypeConstants
{
    public const string TextBox = "TextBox";
    public const string TextArea = "TextArea";
    public const string CheckBox = "CheckBox";
    public const string DropDownList = "DropDownList";
    public const string Html5NumberInput = "Html5NumberInput";
    public const string Html5UrlInput = "Html5UrlInput";
    public const string Html5DateInput = "Html5DateInput";
    public const string JqueryUIDateInput = "JqueryUIDateInput";
} 

所以我的问题的根源是,一旦使用包含值的下拉列表成功填充UI,我选择一个并点击保存按钮,我看到警报显示模型中的内容,但保存按钮永远不会触发。

在CodeProject提供的示例中,控件在@Html之后具有此自定义ObservableControlFor,请参阅上面的类HtmlExtensions。这就是它的样子:@ Html.ObservableControlFor(model =&gt; model.Name,ControlTypeConstants.TextBox)所以我试图在我的例子中做同样的事情,但我不知道如何转向@ Html.DropDownList(“myList” “)进入@ Html.ObservableControlFor ...我尝试了以下@ Html.ObservableControlFor(model =&gt; model.WorkSections,ControlTypeConstants.DropDownList),但控件永远不会呈现给UI,我得到的错误与之前相同。

Console from Chrome

我在我的@Html控件中尝试了ViewData,但函数期望类似于model =&gt; model.WorkSections

Error

来自View Source的HTML标记

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title></title>
  <link href="/Content/site.css" rel="stylesheet"/>
  <script src="/Scripts/modernizr-2.6.2.js"></script>
  <link href="/Content/themes/base/jquery.ui.core.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.resizable.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.selectable.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.accordion.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.autocomplete.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.button.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.dialog.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.slider.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.tabs.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.datepicker.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.progressbar.css" rel="stylesheet"/>
  <link href="/Content/themes/base/jquery.ui.theme.css" rel="stylesheet"/>
  <link href="/Content/WorkRequest.css" rel="stylesheet" />
</head>
<body>
<form>
  <div class="mainWrapper">
    <table>
        <tr>
            <td>Work Sections: 
            </td>
            <td>


                <div class="dropDownList" data-bind="value: WorkRequest.ViewModel.WorkSections" id="WorkSections" name="WorkSections"></div>
            </td>
        </tr>
    </table>
</div>
<br />
<input id="Save" type="submit" value="Save" />
</form>
<script src="/Scripts/jquery-1.8.2.js"></script>
<script src="/Scripts/jquery-ui-1.8.24.js"></script>
<script src="/Scripts/knockout-2.2.0.js"></script>
<script src="/Scripts/knockout.mapping-latest.js"></script>
<script src="/Scripts/Application/WorkRequest.js"></script>
<script type="text/javascript">
    WorkRequest.SaveUrl = '/WorkRequest/SaveWorkRequestDetails';
    WorkRequest.ViewModel = ko.mapping.fromJS({"WorkSectionId":0,"WorkSections":[{"Selected":false,"Text":"308:IPACS","Value":"308"},{"Selected":false,"Text":"312:IPACS","Value":"312"},{"Selected":false,"Text":"301:IPACS","Value":"301"},{"Selected":false,"Text":"316:IPACS","Value":"316"},{"Selected":false,"Text":"307:IPACS","Value":"307"},{"Selected":false,"Text":"318:IPACS","Value":"318"},{"Selected":false,"Text":"313:IPACS","Value":"313"},{"Selected":false,"Text":"319:IPACS","Value":"319"},{"Selected":false,"Text":"315:IPACS","Value":"315"},{"Selected":false,"Text":"310:IPACS","Value":"310"},{"Selected":false,"Text":"300:IPACS","Value":"300"},{"Selected":false,"Text":"302:IPACS","Value":"302"},{"Selected":false,"Text":"304:IPACS","Value":"304"},{"Selected":false,"Text":"306:IPACS","Value":"306"},{"Selected":false,"Text":"309:IPACS","Value":"309"},{"Selected":false,"Text":"305:STORM","Value":"305"},{"Selected":false,"Text":"311:IPACS","Value":"311"},{"Selected":false,"Text":"317:IPACS","Value":"317"},{"Selected":false,"Text":"303:IPACS","Value":"303"},{"Selected":false,"Text":"314:IPACS","Value":"314"}]});
</script>

</body>
</html>

1 个答案:

答案 0 :(得分:1)

我认为你的问题可能在于这两行代码:

data: ko.toJSON(WorkRequest.ViewModel),
contentType: 'application/json',

您应该删除contentType行并更改:

data: $.param(WorkRequest.ViewModel)

问题是您尝试传递服务器JSON,默认接受application/x-www-form-urlencodedcontentType指示从服务器传回的格式。 dataType是您正在寻找的,但默认情况下.Net MVC需要默认值。

如果这不起作用,从导致500错误的异常中查看堆栈跟踪会很有帮助,尽管我希望问题是您的模型没有绑定,因为您的数据格式错误。