这是一个简单的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,我得到的错误与之前相同。
我在我的@Html控件中尝试了ViewData,但函数期望类似于model =&gt; model.WorkSections
来自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>
答案 0 :(得分:1)
我认为你的问题可能在于这两行代码:
data: ko.toJSON(WorkRequest.ViewModel),
contentType: 'application/json',
您应该删除contentType
行并更改:
data: $.param(WorkRequest.ViewModel)
问题是您尝试传递服务器JSON,默认接受application/x-www-form-urlencoded
。 contentType
指示从服务器传回的格式。 dataType
是您正在寻找的,但默认情况下.Net MVC需要默认值。
如果这不起作用,从导致500错误的异常中查看堆栈跟踪会很有帮助,尽管我希望问题是您的模型没有绑定,因为您的数据格式错误。