带有回发的 MVC 组件

Posted

技术标签:

【中文标题】带有回发的 MVC 组件【英文标题】:MVC Component with postback 【发布时间】:2016-04-27 13:59:58 【问题描述】:

我正在尝试构建一组 MVC 组件,这些组件可以通过各种设置轻松重用。它们被构建并用作子操作,因为我需要它们能够在不知道托管它们的视图的其他内容的情况下对自己进行部分回发。

我遇到了一个问题,即在不通过客户端传递参数的情况下将它们存储在哪里(我不想构建视图状态之类的东西,也出于安全原因),并使它们可用于部分回发。

这是一个简化的示例(并不是说代码可能无法编译,因为我将语法糖简化为纯 MVC):

查看代码(组件使用):

@html.Action(
    "Default",
    "FacebookFeed",

    new 
        // I don't want this data to pass through client
        settings = new FacebookFeedSettings 
            AppKey = "XYZ",
            AppSecret = "123",
            PageSize = 10
        
        .ItemTemplate(
            @<div class="feed-item">@item.Title</div>
        )
    
)

控制器代码:

public class FacebookFeedController 
   public ActionResult Default(FacebookFeedSettings settings)
   
      // Action code using settings

      return PartialView(model);
   

Feed 查看代码:

@using (Ajax.BeginForm("Default", "FacebookFeed", new AjaxOptions  HttpMethod = "POST", InsertionMode = InsertionMode.ReplaceWith ))

    // Form code

    <input type="submit" value="Refresh" />

因此,当点击刷新按钮时,它应该呈现新数据,但该请求中缺少设置。

到目前为止,我只提出了一种解决方案,使某种设置注册由字符串键索引,他们将在其中注册他们的设置集。

修改视图代码(组件用法):

@Html.Action(
    "Default",
    "FacebookFeed",
    new 
        settingsKey = "HomePageFBFeed"
    
)

来自用户的额外代码:

[ComponentSettings]
public class HomePageFBFeed : FacebookFeedSettings

    public HomePageFBFeed()
    
        AppKey = "XYZ";
        AppSecret = "123";
        PageSize = 10;
    

修改后的控制器代码:

public ActionResult Default(string settingsKey)

   FacebookFeedSettings settings = ComponentSettings.GetSettings(settingsKey);

   // Action code using settings

   return PartialView(model);

修改视图代码:

@using (Ajax.BeginForm("Default", "FacebookFeed", new AjaxOptions  HttpMethod = "POST", InsertionMode = InsertionMode.ReplaceWith , new  settingsKey = Model.SettingsKey ))

   ...

因此,在这种情况下,我只向客户端传递了该配置的一些唯一 ID,这很好,但与第一次相比,它具有糟糕的用户体验,因为它需要在放置组件的视图之外进行管理。

在这种情况下,我也无法使用内联模板,如第一个代码部分所示,因为在这种情况下,设置是在视图范围之外构建的。

请注意,我还需要它来处理应用程序重新启动和跨进程边界(在云中),因此我不能依赖在第一次加载视图时将配置存储在服务器端。

在 ASP.NET 4.6 / MVC 5 中是否有更好的方法/最佳实践?

如果没有,在 ASP.NET 5 / MVC 6 中是否可行?

【问题讨论】:

【参考方案1】:

Martin,我知道您说过您不想创建视图状态,但考虑到 MVC 的断开连接模式(您不想使用服务器端 Session,也因为它无法扩展),会你愿意传输设置的加密字符串吗?

视图中有这样的东西:

@using (Ajax.BeginForm("Default", "FacebookFeed", new AjaxOptions  HttpMethod = "POST", InsertionMode = InsertionMode.ReplaceWith ))

    @Html.AntiForgeryToken()
    <input type="hidden" name="Settings" value="@Model.ToEncryptedString()" />
    <input type="submit" value="Refresh" />

ToEncryptedString() 将是您模型的扩展,即:

    序列化您的设置(FacebookFeedSettings 对象) 加密 转换为 Base 64 以使其对 HTTP 友好

返回控制器时,只需读取Default action中的Settings参数即可:

[HttpPost, ValidateAntiForgeryToken]
public ActionResult Default(string settings)

    FacebookFeedSettings facebookSettings = FacebookFeedSettings.FromEncryptedString(settings);
    // do something with the settings
    // build the model
    return PartialView(model);

FromEncryptedString() 完全相反:

    从 Base 64 转换为 byte[] 解密字节数组 反序列化为 FacebookFeedSettings 对象实例

基本上,这个加密字符串或多或少类似于防伪令牌。 为了使它更加优雅,我想您也可以在属性级别移动设置验证,并使用自定义属性标记您的操作:

[HttpPost, ValidateAntiForgeryToken, FacebookFeedSettings]
public ActionResult Default(/* No need to capture settings here */)

FacebookFeedSettingsAttribute 将从 Request 中获取 Settings 参数,并构建并验证 FacebookFeedSettings 对象实例。 我还没有尝试过这么远,我留给你练习:-)

你怎么看?

【讨论】:

感谢您的回答。我想避免这种情况,这基本上就是我避免 ViewState 的意思。我简化了案例,但设置可能包含更多数据,其中一些也不可序列化(例如,更通用的组件通过其获取数据的 lambda 表达式或内联项模板)。我已经有一个数据驱动的解决方案,其中设置从浏览器配置并存储在数据库中,但我也希望涵盖可以像传统组件一样在临时基础上配置它的场景。【参考方案2】:

我理解并且我同意,问题是您无论如何都希望在无状态技术中保留某种形式的状态。

好消息是 MVC 6 似乎可以解决您的问题。首先,子action不再存在,取而代之的是View Components。 视图组件由一个 C# 类和一个 Razor 视图组成,并且不依赖于控制器,从而简化了可重用性。

我还没有直接的经验,但是从我读到的关于 MVC 6 尤其是 View Components 的内容来看,它们是独立的,所以基本上你可以在组件本身内管理状态。参数不会从视图通过 HTTP 传递,因为您实际上是在服务器端调用组件(没有从客户端返回)。

此外,视图组件不参与控制器生命周期,但您仍然可以访问 ViewBag 和 ViewData(与控制器共享)。最后,与控制器一样,视图组件也参与了依赖注入,因此您需要的任何其他信息都可以简单地注入到视图组件中。

这应该适合你,但你需要等待 MVC 6 :-)

【讨论】:

嗯,与其说是“状态”,不如说是配置不会改变,所以它也可以很容易地在其他实例上重新创建,并且不需要在客户端基础上维护.关键问题是在哪里最好地定义它,以便“组件”的外观和感觉与 WebForms 中的相似 = 最好在标记中配置这些组件,如果可能的话以某种方式放置它们。 对于 ViewComponent 模型,我不确定这是否会有所帮助,因为我的组件需要能够对自己进行部分回发(我不希望整个页面重新加载,因为它必须在 WebForms 中完成),所以如果我理解正确,我仍然需要将当前子操作拆分为 ViewComponent 和常规控制器,以涵盖 MVC 6 中的初始显示和 AJAX 回发。还是我遗漏了什么?跨度> 视图组件从不直接处理 HTTP 请求,因此您无法从客户端直接调用视图组件。如果您的应用程序需要此行为,您将需要使用控制器包装视图组件。至于在哪里保存配置,您是否考虑过一个设置文件,其中包含一个 JSON 对象以及所有要保存的属性?如果它只是服务器端,您可以假设文件名与类名匹配,这与基于约定的 MVC 理念一致。 我知道它是如何工作的。我希望能够通过直接在我放置组件的位置配置属性来提供与 ASPX 标记类似的体验,但它似乎在 MVC 中是不可行的,至少在不通过客户端发送配置的情况下是不可行的。 没错,这就是 MVC 的本质。毕竟,在 ASP.NET Web 窗体中,信息也是作为 ViewState 的一部分发送到客户端的。另一种方法是将其存储在服务器端(文件系统、数据库),并采用基于约定的方法,例如,基于类属性。因此,开发人员将此信息设置为模型的属性,并在模型传递给视图之前将其保存在服务器上,并且这些信息也不会传递给视图。我相信你会为 Kentico X 的下一个精彩版本找到一个好方法:-)

以上是关于带有回发的 MVC 组件的主要内容,如果未能解决你的问题,请参考以下文章

带有 ASP.NET 按钮回发的 jQuery UI 对话框

在异步回发的更新面板中禁用按钮

带有 MVC3 的 PayPal IPN

ASP SqlDataSource 更新 - 用于部分页面回发的 AJAX?

如何使用带有 asp.net 的 jQuery 进行 onclientclick 回发

没有回发的按钮?