【发布时间】:2016-08-27 06:11:06
【问题描述】:
我有一个用户可以运行的“应用程序”列表。每个应用程序都针对我们的特定 API 来演示 API 的功能。其中一些应用需要用户输入,以便我可以将用户给定的参数传递到 API。
每个应用程序负责生成代表其输出的 HTML。对于不需要任何输入的应用程序,这是一个直接的过程,即通过 ajax 请求在控制器/动作中执行它们,并使用输出更新视图。
挑战在于连接用户输入支持。我已经设法到达那里的 90% 并且遇到了障碍。应用程序负责实例化自己的视图模型。使用一点约定,每个 App 都有一个关联的局部视图,该局部视图位于与应用程序的命名空间相同的路径下。这让我可以为应用创建一个视图模型,并像这样返回每个应用的局部视图:
public ActionResult GetViewModel(string appId)
{
IApp app = AppFactory.GetAppById(appId);
string path = app.GetType().FullName.Replace('.', '/');
return PartialView($"~/Views/{path}.cshtml", app.CreateViewModel());
}
使用应用提供的视图模型的部分视图示例如下所示:
@using Examples.DataAccess.Query;
@model Query_02_ParameterizedQueryViewModel
@using (@Html.BeginForm("RunAppFromViewModel", "Home", FormMethod.Post))
{
@Html.ValidationSummary(true)
<fieldset>
<div class="form-inline">
<div class="form-group">
@Html.LabelFor(viewModel => viewModel.City)
@Html.EditorFor(viewModel => viewModel.City, new { placeholder = "Phoenix" })
@Html.ValidationMessageFor(viewModel => viewModel.City)
@Html.HiddenFor(viewModel => viewModel.AppId)
</div>
</div>
<button class="btn btn-default" type="submit">Run</button>
</fieldset>
}
主视图有一个按钮,用于打开模式引导对话框。当对话框打开时,我向服务器发出 ajax 请求以获取视图模型和局部视图。然后我将部分视图插入模态对话框并更新客户端验证,以便它与不引人注目的东西一起工作。然而问题是,当表单回传到服务器,并且应用程序输出的 HTML 从服务器返回到客户端时,我不知道如何用它更新主视图。
例如,这是主视图和处理基于视图模型的应用程序和非基于 VM 的应用程序的 JavaScript。
@using Examples.Browser.ViewModels;
@using Examples.Browser.Models;
@{
ViewBag.Title = "Home Page";
}
@model List<ApiAppsViewModel>
<div class="jumbotron">
<h1>Framework API Micro-Apps</h1>
<p class="lead">Micro-apps provide a way to execute the available APIs in the Framework, without having to write any code, and see the results.</p>
</div>
<div class="container">
<h3 class="text-center">Available API Apps</h3>
<div class="table-responsive">
<table class="table table-hover table-responsive">
<tr>
<th>@nameof(ApiApp.Components)</th>
<th>@nameof(ApiApp.Name)</th>
<th>@nameof(ApiApp.Description)</th>
<th>Options</th>
</tr>
@foreach (ApiAppsViewModel app in this.Model)
{
<tr>
<td>@string.Join(",", app.Components)</td>
<td>@app.Name</td>
<td>@app.Description</td>
<td>
@if (app.IsViewModelRequired)
{
<button type="button"
data-app="@app.Id.ToString()"
data-vm-required="@app.IsViewModelRequired"
data-app-name="@app.Name"
data-toggle="modal"
data-target="#appParameters"
class="btn btn-success">
Run App
</button>
}
else
{
<button type="button"
data-app="@app.Id.ToString()"
data-vm-required="@app.IsViewModelRequired"
class="btn btn-success">
Run App
</button>
}
</td>
</tr>
<tr class="hidden">
<td colspan="4">
<div class="container alert alert-info" data-app="@app.Id.ToString()">
</div>
</td>
</tr>
}
</table>
</div>
</div>
<div class="modal fade"
id="appParameters"
role="dialog"
aria-labelledby="appParametersLabel">
<div class="modal-dialog"
role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="appParametersLabel"></h4>
</div>
<div class="modal-body" id="appDialogBody">
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$('.btn-success').click(function () {
var button = $(this);
var appId = $(this).data("app");
var vmRequired = $(this).data("vm-required");
if (vmRequired == "False") {
var url = "/Home/RunApp?appId=" + appId;
$.get(url, function (data) {
$("div[data-app='" + appId + "']").html(data);
var buttonColumn = button.parent();
var appRow = buttonColumn.parent();
var hiddenRow = appRow.next()
hiddenRow.removeClass("hidden");
appRow.click(function () {
var hiddenColumn = hiddenRow.children().first();
var resultsDiv = hiddenColumn.children().first();
resultsDiv.empty();
hiddenRow.addClass("hidden");
$(this).off();
hiddenRow.off();
})
hiddenRow.click(function () {
var hiddenColumn = $(this).children().first();
var resultsDiv = hiddenColumn.children().first();
resultsDiv.empty();
$(this).addClass("hidden");
appRow.off();
$(this).off();
})
});
} else {
var appName = $(this).data("app-name");
$('#appParametersLabel').html(appName);
var url = "/Home/GetViewModel?appId=" + appId;
$.get(url, function (data) {
$('#appDialogBody').html(data);
var dialog = $('#appDialogBody');
$.validator.unobtrusive.parse(dialog);
});
$('#appParameters').modal({
keyboard: true,
backdrop: "static",
show: false,
}).on('show', function () {
});
}
});
</script>
当不需要视图模型时,我会将结果填充到不可见的行中并使该行可见。由于 View Model 应用程序的表单数据是从局部视图提交的,因此当我从控制器返回 HTML 时,它会将其作为原始文本呈现给浏览器。我假设我可以编写一些 java 脚本来处理这个问题,但我不确定那会是什么样子。如何从局部视图中获取表单帖子,以将其生成的 HTML 返回到主视图中的不可见行?
这是表单发布和返回的控制器操作,以及基于非视图模型的应用程序用来运行其应用程序和生成 HTML 的控制器操作。
[HttpGet]
public async Task<string> RunApp(string appId)
{
IApp app = AppFactory.GetAppById(appId);
if (app == null)
{
return "failed to locate the app.";
}
IAppOutput output = await app.Run();
if (output == null)
{
return "Failed to locate the app.";
}
return output.GetOutput();
}
[HttpPost]
public async Task<string> RunAppFromViewModel(FormCollection viewModelData)
{
IApp app = AppFactory.GetAppById(viewModelData["AppId"]);
foreach(PropertyInfo property in TypePool.GetPropertiesForType(app.ViewModel.GetType()))
{
property.SetValue(app.ViewModel, viewModelData[property.Name]);
}
IAppOutput output = await app.Run();
return output.GetOutput();
}
【问题讨论】:
-
目前你只有一个做普通提交的表单(不使用 ajax)。您需要处理提交功能,取消它并使用ajax发布数据,然后在
success回调中更新DOM。 -
你有我可以看的例子吗?我今天刚接触到 Ajax,所以我对它还不够熟练,不知道它会是什么样子。如果我通过 Ajax 发送视图模型绑定,我假设我必须做一些花哨的事情才能将视图模型绑定返回到服务器?
-
我可以很快添加一个示例,但是您为什么不将每个表单发布到单独的 POST 方法。目前你没有绑定到模型,你没有服务器端验证(客户端验证只是一个很好的 UI 奖励,很容易被绕过),你必须使用反射。
-
服务器端验证发生在 IApp 实现中。一旦这个站点完成,我们可以为我们添加到内部框架的每个新 API 编写一个类,以演示输出,允许来自开发人员的交互以及使用源 sn-ps 链接到内部文档。这些 IApp 实现可以存在于我们部署到服务器的 NuGet 包中,而无需再次接触 MVC 代码。
-
我明白你关于不发生绑定的观点。这又是因为我们希望避免为每个 API(数百个 API)创建一个操作。我们的反射被缓存了,所以只发生一次(因此是
TypePool类),并且没有绑定到控制器动作。虽然一些 IApp 实现需要部分视图,但这些是例外而不是常态。他们都不需要自定义控制器操作
标签: c# jquery ajax asp.net-mvc asp.net-mvc-5