2013-10-04 24 views
19

È possibile eseguire il comando await sulle attività nelle viste Razor .cshtml?Uso dell'attesa nelle visualizzazioni del rasoio

Per impostazione predefinita si lamenta che può essere utilizzato solo nei metodi contrassegnati con async quindi mi chiedo se forse c'è un interruttore nascosto da qualche parte che lo abilita?

+9

Sembra qualcosa che si farebbe nel controller o più avanti nello stack nel codice back-end. –

risposta

16

No, non è possibile e non dovresti farlo comunque. Le visualizzazioni del rasoio dovrebbero contenere markup e al massimo qualche chiamata di aiuto. async/await appartiene alla tua logica backend.

+13

Voto precedente per la parte "Non fare questo". Il rendering dei primi byte prima dell'arrivo dei dati non è strano, né sono necessari i dati dettati dalla visualizzazione. Ad esempio, se avessi consegnato un Razor interno e avessero dovuto gettare le pagine insieme senza sapere come chiamare il DB, avrei potuto insegnare loro a chiamare per i dati tramite un repository lazy-load con un'interfaccia davvero semplice - eccetto che ti imbatti in questo muro di async (possibilmente) essendo limitato in Razor. –

+2

@ChrisMoschini che proprio non ha alcun senso. "i dati dettati dalla vista devono" è ciò che ViewModel è ed è già fornito dal controller. "rendering prima che i dati arrivino" è solo uno sguardo al lento problema di recupero dei dati nel modo sbagliato. c'è il caching per questo. –

+0

@ssg Considera una pagina diversa per tutti, ogni volta che visita e ci vuole del tempo per calcolare i risultati. Supponiamo che sia una lista di cose. È meglio renderizzare, senza buffer, nel flusso di output ciò che si ha non appena lo si ha, piuttosto che far attendere all'utente che tutto sia arrivato. L'approccio ViewModel ti avrebbe procurato il pessimo risultato finale. Lasciando il View sip e il rendering dei dati in tempo reale si ottiene il risultato migliore, a tempo scaduto per il primo risultato. –

-1

So che questo è un thread più vecchio, ma aggiungerò il mio input nel caso in cui qualcun altro lo trovi utile. Ho incontrato questo problema lavorando con il nuovo driver MongoDB in ASP.Net MVC - il nuovo driver (per ora), implementa solo i metodi asincroni e restituisce i cursori asincroni, che non possono essere utilizzati in un foreach perché asynccursor non implementa IEnumerable . Il codice di esempio si presenta in genere come:

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

Ma, questo non funziona in un rasoio, perché le viste sono intrinsecamente non ASYNC, e attendono non è tagliato.

ho preso a lavorare cambiando la prima linea:

while(cursor.MoveNextAsync().Result) 

che restituisce vero finché il cursore colpisce l'ultima voce.

Spero che questo aiuti!

+1

La risposta corretta è "Stai sbagliando". Questo dovrebbe essere fatto in * modo asincrono * nel metodo del controllore. Cercando di parlare direttamente dalla vista a un database, ignorando sia il controller, il modello e qualsiasi livello dati è esattamente l'opposto di MVC e provoca semplicemente ritardi nel rendering. La vista dovrebbe solo rendere i dati in un modello di vista.Il modello di vista dovrebbe contenere solo i dati necessari per quella vista. È il lavoro * del controllore * per recuperare i dati e costruire il DTO/modello e inviarlo alla vista –

+0

@PanagiotisKanavos Non significherebbe che per una grande lista recuperata pezzo per pezzo dal database il rendering e effettivamente inviando il l'output sul client non si verifica fino a quando l'elenco completo non viene caricato in memoria? Ha sicuramente senso avere una logica nel controller, tuttavia, nel caso che ho menzionato, non sarebbe meglio essere in grado di passare una sorta di async enumerabile e renderlo pezzo per pezzo in modo asincrono? O c'è qualche limitazione tecnica per questo? –

+0

@martinh_kentico no, per niente. Se si caricano le voci una per una, c'è un problema serio nella logica di accesso ai dati. * Perché * caricare uno ad uno quando una query restituirà * tutti * i risultati in una sola volta? Perché restituire * un sacco * di risultati quando * non è * possibile visualizzarli? Perché non utilizzare * paging * in questo caso? Perché non usare una clausola '.Contains()' in LINQ, o 'IN (..)' in SQL per passare una lista di ID? –

-1

Se ne hai davvero bisogno, puoi farlo, sarà brutto, ma funzionerà.

In vista

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

controller

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

Equivale a var foo = attendi ... bar = attendi ... visualizza nuovamente ("Indice", nuovo {foo = foo, bar = bar}) - stai ancora caricando i dati tramite attendi/asincroni nel Controller come normale, non in Rasoio. –

0

Ho voluto qualcosa di simile a questo per un lungo periodo di tempo - un sacco di pagine che scriviamo può essere gettato insieme da una Jr Dev se non dovevano scrivere un sacco di domande; e, comunque, è sempre la stessa domanda di base boilerplate ogni volta - perché dovrebbero doversene scrivere per ogni Controller, quando la maggior parte del loro lavoro è di accontentarsi del contenuto? Uso C# quindi non devo occuparmi della gestione della memoria, perché un codificatore HTML deve occuparsi dei dettagli della query?

Esiste un trucco che è possibile utilizzare per ordinare implicitamente il caricamento asincratico dei dati nella vista. Innanzitutto, si definisce una classe che esprime quali dati si desidera. Quindi, in cima a ciascuna vista, crea un'istanza di quella classe. Tornando al controller, è possibile cercare la vista che si sta per usare, aprirla, quindi compilare la classe. È quindi possibile utilizzarlo per ottenere i dati necessari per la visualizzazione, asincrona, nel controller come applica MVC. Infine, passalo con un ViewModel alla vista come prescrive MVC e, attraverso alcuni trucchi, hai una vista che dichiara quali dati verrà utilizzato.

Ecco uno StoryController. Jr Devs scrivere storie come semplici file .cshtml senza dover conoscere ciò che un controller, database o 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); 

Tutto questo non fa per ora è andare a prendere il file Visualizza sulla base del URL, permettendo qualcosa come WebForms (tranne all'interno di MVC e usando Razor). Ma vogliamo mostrare alcuni dati - nel nostro caso, persone e progetti che si accumulano nel database - con alcuni ViewModels e Partials standard. Definiamo come e compilalo.(Si noti che ConservX sembra essere lo spazio dei nomi di progetto principale nel mio caso.)

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

Questa è la maggior parte del lavoro. Ora Jr Devs può semplicemente dichiarare i dati di cui hanno bisogno in questo modo:

@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 { 
... 
Problemi correlati