【问题标题】:Update main view after ajax rendered a partial viewajax 渲染部分视图后更新主视图
【发布时间】: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">&times;</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


【解决方案1】:

如果您想用RunAppFromViewModel() 方法返回的数据更新现有页面,那么您需要使用ajax 提交您的表单。由于表单是在加载初始页面后动态加载的,因此您需要使用事件委托。您还需要在加载表单时存储要更新的元素。

var hiddenRow;
$('.btn-success').click(function () {
    // store the element to be updated
    hiddenRow = $(this).closest('tr').next('tr').find('.container');
    ....
});

// Handle the submit event of the form
$('#appDialogBody').on('submit', 'form', function() {
    // check if the form is valid
    if (!$(this).valid())
    {
        return;
    }
    var formData = $(this).serialize(); // serialize the forms controls
    var url = '@Url.Action("RunAppFromViewModel", "Home");
    $.post(url, formData , function(response) {
        hiddenRow.html(response); // assumes your returning html
    });
    return false; // cancel the default submit
});

【讨论】:

  • 所以即使#appDialogBody 不在局部视图中,我仍然可以引用它吗?我想我没有意识到这一点。这是有道理的,因为我仍在 DOM 中并且可以查找元素。我有很多东西要学习网络开发:/
  • 这里的关键是表单最初不存在,所以你需要event delegation(使用.on())在其中指定第一次呈现页面时存在的元素(带有@987654325的元素@),并监听其中任何&lt;form&gt; 元素的.submit() 事件(可以在页面渲染后添加)。
  • 工作就像一个魅力!我注意到我无法像您显示的那样传递表单数据。我可以让它发布到服务器的唯一方法是将它作为查询字符串传递,例如url = '/Home/RunAppFromViewModel?' + formdata;。我认为这是因为我没有进行模态绑定?不管怎样,有了这个小小的改动,你的其余代码就可以工作了,我非常接近完成了。谢谢!
猜你喜欢
  • 1970-01-01
  • 2013-04-05
  • 1970-01-01
  • 2013-10-08
  • 2013-01-15
  • 2011-06-18
  • 2015-01-29
  • 1970-01-01
  • 2011-11-17
相关资源
最近更新 更多