Use of await in Razor views

前端 未结 8 856
面向向阳花
面向向阳花 2020-11-28 12:48

Is it possible to await on tasks in Razor .cshtml views?

By default it complains that it can only be used in methods marked with async so I

相关标签:
8条回答
  • 2020-11-28 12:53

    In ASP.NET Core 2.1, you can use await in Razor views.

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

    Example:

    @await Html.PartialAsync("../Account/_LoginPartial.cshtml")
    
    0 讨论(0)
  • 2020-11-28 12:53

    I've wanted something like this for a long time - a lot of the pages we write could be thrown together by a Jr Dev if they didn't have to write a bunch of queries; and, it's the same basic query boilerplate every time anyway - why should they have to write them for each Controller, when the majority of their work is to get content up? I use C# so I don't have to deal with memory management, why should an HTML coder have to deal with query details?

    There is a trick you can use to sort of implicitly load data async into the View. First, you define a class that expresses what data you want. Then, at the top of each View, instantiate that class. Back in the Controller, you can lookup the View you know you're going to use, open it, then compile that class. You can then use it to go get the data the View will need, async, in the Controller the way MVC enforces. Finally, pass it off with a ViewModel to the View as MVC prescribes, and, through some trickery - you have a View that declares what data it's going to use.

    Here's a StoryController. Jr Devs write stories as simple .cshtml files without having to know what a Controller, database or LINQ is:

    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);
    

    All this does for now is go get the View file based on the URL, allowing something like WebForms (except inside MVC and using Razor). But we want to show some data - in our case, people and projects that accumulate in the database - with some standard ViewModels and Partials. Let's define how and compile that out. (Note that ConservX happens to be the core Project namespace in my case.)

        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);
        }
    

    That's the majority of the work. Now Jr Devs can just declare the data they need like so:

    @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 {
    ...
    
    0 讨论(0)
  • 2020-11-28 12:53

    You can await calls in razor pages? I have a Blazor app and most of my methods are async:

    Razor page:

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

    This is a MatBlazor FloatingActionButton which calls the life time cycle event OnInitializedAsync()

    C# Code:

    protected override async Task OnInitializedAsync()
    {
        // Do something like get data when the form loads
    }
    
    0 讨论(0)
  • 2020-11-28 12:57

    I landed on this question because I am a newbie to Razor and I wanted to display a simple "loading..." screen while my Controller Code was calculating data.

    So I found this link: https://www.codeproject.com/Articles/424745/MVC-Razor-In-Progress-Icon which was helpful, but because I was a total novice at Razor, I was unable to make this work.

    What finally worked for me was the following.

    1) Add the "loading" div as suggested in the code project to my .cshtml file:

    <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) Modify my Razor form from

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

    to

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

    3) Create the JavascriptFunction() in my .cshtml page:

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

    If I understand all of the above correctly, what this does is execute the function JavascriptFunction when I press the Go button.

    The JavascriptFunction does 2 things: 1) Change the view of the page by showing the previously hidden (display:none) divLoading div. 2) Submit all the forms on this page (I only have one, so it submits the form the same as if I had they type submit on the button)

    After the Controller launched by the form submit is done, it loads a new view on a new page, and the initial page (and the "loading" div) is gone. Mission accomplished.

    0 讨论(0)
  • 2020-11-28 13:00

    If you really need it, you can do this, it will be ugly, but it will work.

    In View

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

    In Controller

    public async Task<ActionResult> Index()
            {
                ViewBag.foo = await _some.getFoo();
                ViewBag.bar = await _some.getBar();
                return View("Index");
            }
    
    0 讨论(0)
  • 2020-11-28 13:01

    I know this is an older thread, but I'll add my input just in case someone else finds it useful. I ran into this problem working with the new MongoDB driver in ASP.Net MVC - the new driver (for now), only implements async methods and returns async cursors, which can't be used in a foreach because asynccursor doesn't implement IEnumerable. The sample code typically looks like:

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

    But, this doesn't work in razor, because views are inherently not async, and await doesn't cut it.

    I got it to work by changing the first line to:

    while(cursor.MoveNextAsync().Result)
    

    which returns true until the cursor hits the last entry.

    Hope that helps!

    0 讨论(0)
提交回复
热议问题