【问题标题】:Use of await in Razor views在 Razor 视图中使用 await
【发布时间】:2013-10-04 13:23:32
【问题描述】:

是否可以 await 处理 Razor .cshtml 视图中的任务?

默认情况下,它抱怨它只能在标有async 的方法中使用,所以我想知道是否有隐藏的开关可以启用它?

【问题讨论】:

  • 看起来像是您会在控制器中执行的操作,或者在后端代码中进一步提升堆栈。

标签: c# razor async-await


【解决方案1】:

在 ASP.NET Core 2.1 中,您可以在 Razor 视图中使用 await

https://docs.microsoft.com/en-us/aspnet/core/mvc/views/partial?view=aspnetcore-2.1

例子:

@await Html.PartialAsync("../Account/_LoginPartial.cshtml")

【讨论】:

    【解决方案2】:

    我想要这样的东西很久了——我们写的很多页面都可以由 Jr Dev 拼凑起来,如果他们不必写一堆查询的话;而且,无论如何每次都是相同的基本查询样板 - 为什么他们必须为每个控制器编写它们,而他们的大部分工作是获取内容?我使用 C#,所以我不必处理内存管理,为什么 HTML 编码器必须处理查询细节?

    您可以使用一个技巧将数据异步加载到视图中。首先,您定义一个类来表达您想要的数据。然后,在每个视图的顶部,实例化该类。回到控制器中,您可以查找您知道要使用的视图,打开它,然后编译该类。然后,您可以使用它以 MVC 强制执行的方式在 Controller 中异步获取 View 所需的数据。最后,按照 MVC 的规定,通过 ViewModel 将其传递给 View,并且通过一些技巧 - 你有一个 View 来声明它将使用什么数据。

    这是一个 StoryController。 Jr Devs 将故事编写为简单的 .cshtml 文件,而无需知道控制器、数据库或 LINQ 是什么:

    public class StoryController : BaseController
    {
        [OutputCache(Duration=CacheDuration.Days1)]
        // /story/(id)
        public async Task<ActionResult> Id(string id = null)
        {
            string storyFilename = id;
    
            // Get the View - story file
            if (storyFilename == null || storyFilename.Contains('.'))
                return Redirect("/");   // Disallow ../ for example
    
            string path = App.O.AppRoot + App.HomeViews + @"story\" + storyFilename + ".cshtml";
            if (!System.IO.File.Exists(path))
                return Redirect("/");
    
            return View(storyFilename);
    

    现在所做的就是根据 URL 获取 View 文件,允许使用 WebForms 之类的东西(MVC 内部和使用 Razor 除外)。但是我们想用一些标准的 ViewModel 和 Partials 展示一些数据——在我们的例子中,是数据库中积累的人员和项目。让我们定义如何编译它。 (请注意,ConservX 恰好是我的项目的核心命名空间。)

        public async Task<ActionResult> Id(string id = null)
        {
            string storyFilename = id;
    
            // 1) Get the View - story file
            if (storyFilename == null || storyFilename.Contains('.'))
                return Redirect("/");   // Disallow ../ for example
    
            string path = App.O.AppRoot + App.HomeViews + @"story\" + storyFilename + ".cshtml";
            if (!System.IO.File.Exists(path))
                return Redirect("/");
    
            // 2) It exists - begin parsing it for StoryDataIds
            var lines = await FileHelper.ReadLinesUntilAsync(path, line => line.Contains("@section"));
    
            // 3) Is there a line that says "new StoryDataIds"?
            int i = 0;
            int l = lines.Count;
            for (; i < l && !lines[i].Contains("var dataIds = new StoryDataIds"); i++)
            {}
    
            if (i == l) // No StoryDataIds defined, just pass an empty StoryViewModel
                return View(storyFilename, new StoryViewModel());
    
    
            // https://stackoverflow.com/questions/1361965/compile-simple-string
            // https://msdn.microsoft.com/en-us/library/system.codedom.codecompileunit.aspx
            // https://msdn.microsoft.com/en-us/library/system.codedom.compiler.codedomprovider(v=vs.110).aspx
            string className = "__StoryData_" + storyFilename;
            string code = String.Join(" ",
                (new[] {
                    "using ConservX.Areas.Home.ViewModels.Storying;",
                    "public class " + className + " { public static StoryDataIds Get() {"
                }).Concat(
                    lines.Skip(i).TakeWhile(line => !line.Contains("};"))
                ).Concat(
                    new[] { "}; return dataIds; } }" }
                ));
    
    
            var refs = AppDomain.CurrentDomain.GetAssemblies();
            var refFiles = refs.Where(a => !a.IsDynamic).Select(a => a.Location).ToArray();
            var cSharp = (new Microsoft.CSharp.CSharpCodeProvider()).CreateCompiler();
            var compileParams = new System.CodeDom.Compiler.CompilerParameters(refFiles);
            compileParams.GenerateInMemory = true;
            compileParams.GenerateExecutable = false;
    
            var compilerResult = cSharp.CompileAssemblyFromSource(compileParams, code);
            var asm = compilerResult.CompiledAssembly;
            var tempType = asm.GetType(className);
            var ids = (StoryDataIds)tempType.GetMethod("Get").Invoke(null, null);
    
            using (var db... // Fetch the relevant data here
    
            var vm = new StoryViewModel();
            return View(storyFilename, vm);
        }
    

    这是大部分工作。现在 Jr Devs 可以像这样声明他们需要的数据:

    @using ConservX.Areas.Home.ViewModels.Storying
    @model StoryViewModel
    @{
        var dataIds = new StoryDataIds
        {
            ProjectIds = new[] { 4 }
        };
    
        string title = "Story Title";
        ViewBag.Title = title;
        Layout = "~/Areas/Home/Views/Shared/_Main.cshtml";
    }
    @section css {
    ...
    

    【讨论】:

      【解决方案3】:

      我提出这个问题是因为我是 Razor 的新手,我想在我的控制器代码计算数据时显示一个简单的“正在加载...”屏幕。

      所以我找到了这个链接:https://www.codeproject.com/Articles/424745/MVC-Razor-In-Progress-Icon,这很有帮助,但是因为我是 Razor 的新手,所以我无法完成这项工作。

      最后对我有用的是以下内容。

      1) 将代码项目中建议的“正在加载” div 添加到我的 .cshtml 文件中:

      <div id="divLoading" style="margin: 0px; padding: 0px; position: fixed; right: 0px;
          top: 0px; width: 100%; height: 100%; background-color: #666666; z-index: 30001;
          opacity: .8; filter: alpha(opacity=70);display:none">
          <p style="position: absolute; top: 30%; left: 45%; color: White;">
              Loading, please wait...<img src="../../Content/Images/ajax-loading.gif">
          </p>
      </div>
      

      2) 从

      修改我的 Razor 表单
      <input type="submit" value="Go"/>
      

      <input type="button" value="Go" onclick="JavascriptFunction()" />
      

      3) 在我的 .cshtml 页面中创建 JavascriptFunction():

      <script type="text/javascript" language="javascript">
          function JavascriptFunction() {
              $("#divLoading").show();
              $('form').submit();
          }
      </script>
      

      如果我正确理解了以上所有内容,那么它的作用就是在我按下 Go 按钮时执行函数 JavascriptFunction。

      JavascriptFunction 做了两件事: 1) 通过显示之前隐藏的 (display:none) divLoading div 来更改页面的视图。 2) 提交此页面上的所有表单(我只有一个,所以它提交的表单就像我在按钮上键入提交一样)

      表单提交启动的Controller完成后,在新页面上加载新视图,初始页面(以及“正在加载”的div)消失了。任务完成。

      【讨论】:

        【解决方案4】:

        您可以在剃须刀页面中等待呼叫吗?我有一个 Blazor 应用程序,我的大多数方法都是异步的:

        剃刀页面:

        <MatFAB Icon="@MatIconNames.Autorenew" Style="transform:scale(0.8); background:#333;"
                        OnClick="@(async () => await OnInitializedAsync())"></MatFAB>
        

        这是一个调用生命周期事件 OnInitializedAsync() 的 MatBlazor FloatingActionButton

        C#代码:

        protected override async Task OnInitializedAsync()
        {
            // Do something like get data when the form loads
        }
        

        【讨论】:

          【解决方案5】:

          不,这是不可能的,无论如何您都不需要这样做。 Razor 视图应该包含标记和最多一些帮助调用。 async/await 属于你的后端逻辑。

          【讨论】:

          • 对“不要这样做”部分投反对票。在数据到达之前渲染第一个字节并不奇怪,视图指定的数据也不需要。例如,如果我给了一个实习生 Razor,他们需要在不知道如何调用数据库的情况下将页面放在一起,我可以教他们通过一个具有非常简单接口的延迟加载 repo 来调用数据——除非你遇到了这堵墙异步(可能)在 Razor 中受到限制。
          • @ChrisMoschini 这根本没有任何意义。 “视图指定的数据需求”是 ViewModel 的含义,它已经由控制器提供。 “在数据到达之前渲染”只是以错误的方式看待缓慢的数据检索问题。有缓存。
          • @ssg 考虑每个人每次访问时都不同的页面,并且需要一些时间来计算结果。假设它是一个东西列表。最好将你拥有的东西无缓冲地渲染到输出流中,而不是让用户等到一切都已经到来。 ViewModel 方法会给你带来糟糕的后一种结果。让 View 实时 sip 和渲染数据可以为您提供更好的、更短的获得首次结果的时间。
          • @ChrisMoschini 您将浏览器端处理与服务器端处理混淆了。在控制器中完成所有异步操作后,视图应该对完成的数据进行处理。 控制器的工作是获取数据,而不是视图。如果您想要部分渲染,请构建您的 HTML 和 Javascript,以便在下载所有数据之前开始渲染。如果您想将这两件事分开,请使用 ODATA 或 Web API 来获取 Json。您所描述的或多或少是 Web 表单的工作方式。
          • @PanagiotisKanavos 不正确。当响应开始时,它不会立即将其所有内容传输到客户端。相反,它会随着时间流逝。如果您以无缓冲的方式交付视图,则可以在数据可用时从服务器逐渐渲染 - 但 ASP.Net MVC/Razor 的标准方法使这变得困难或不可能。事后通过 js 去获取更多数据是完全不同的事情,不要与在几秒钟内流式传输单个响应相混淆,而不需要额外的请求。
          【解决方案6】:

          如果你真的需要它,你可以这样做,它会很丑,但它会起作用。

          在视图中

          @{  
          var foo = ViewBag.foo;
          var bar = ViewBag.bar;
          }
          

          在控制器中

          public async Task<ActionResult> Index()
                  {
                      ViewBag.foo = await _some.getFoo();
                      ViewBag.bar = await _some.getBar();
                      return View("Index");
                  }
          

          【讨论】:

          • 这相当于 var foo = await... bar = await ... return View("Index", new { foo=foo, bar=bar }) - 你仍然只是在加载数据通过控制器中的等待/异步正常进行,而不是在 Razor 中。
          【解决方案7】:

          我知道这是一个较旧的线程,但我会添加我的输入以防其他人发现它有用。我在 ASP.Net MVC 中使用新的 MongoDB 驱动程序时遇到了这个问题 - 新驱动程序(目前)仅实现异步方法并返回异步游标,因为 asynccursor 没有实现 IEnumerable,所以不能在 foreach 中使用.示例代码通常如下所示:

          while(await cursor.movenextasync)
              var batch=cursor.current
              foreach(var item in batch)
                  --do stuff here--
          

          但是,这在 razor 中不起作用,因为视图本质上不是异步的,await 并不能削减它。

          我通过将第一行更改为:

          while(cursor.MoveNextAsync().Result)
          

          在光标碰到最后一个条目之前返回真。

          希望有帮助!

          【讨论】:

          • 正确答案是“你做错了”。这应该在控制器的方法中以 异步方式 完成。试图从视图直接与数据库对话,绕过控制器、模型和任何数据层,这与 MVC 完全相反,只会导致渲染延迟。视图应该只在视图模型中呈现数据。视图模型应该只包含该视图所需的数据。 控制器的工作是检索数据并构造 DTO/模型并将其发送到视图
          • @PanagiotisKanavos 这是否意味着对于从数据库中逐一检索的大型列表,在将完整列表加载到内存之前,不会进行渲染并将输出实际发送到客户端?在控制器中拥有逻辑绝对是有意义的,但是对于我提到的情况,能够传递某种异步枚举,并在视图中逐个异步渲染它不是更好吗?还是有一些技术限制?
          • @martinh_kentico 不,一点也不。如果您逐个加载条目,则数据访问逻辑存在严重问题。 为什么一个查询会一次返回所有个结果?当您无法显示结果时,为什么要返回很多?在这种情况下为什么不使用 paging 呢?为什么不在 LINQ 中使用 .Contains() 子句或在 SQL 中使用 IN (..) 来传递 ID 列表?
          • @martinh_kentico 更糟糕的是 - 视图中的异步操作有什么意义?无论如何,在所有这些完成之前,您都无法将 HTML 响应发送到浏览器。如果您想在数据可用时填充页面的某些部分,您必须在浏览器上使用 AJAX,或者使用真正的老式框架。
          • 您不会一一查询数据,如果您处理大量数据,您将获取 DataReader 进行查询并直接从 DB 流式传输数据。此外,输出是 s 流,那么为什么需要等待整个页面呈现出来才能开始向客户端发送数据呢?如果 WebForms 能够在呈现时进行流式输出,为什么 MVC 不能呢?此外,如果您使用基于组件的模型,拥有更多调用外部服务的组件意味着您需要按顺序启动这些外部调用。
          【解决方案8】:

          根据 MaxP 的回答,尽管有 Knagis 评论,但很容易从该代码中返回一个值:

          @{
              int x = DoAsyncStuffWrapper().Result;
          }
          @functions {
              async Task<int>DoAsyncStuffWrapper() 
              {
                  await DoAsyncStuff();
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2019-05-14
            • 2023-04-03
            • 2013-05-23
            • 1970-01-01
            • 2016-02-22
            • 2017-09-19
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多