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'm wondering if maybe there is a hidden switch somewhere that enables it?
In ASP.NET Core 2.1, you can use await in Razor views.
See https://learn.microsoft.com/en-us/aspnet/core/mvc/views/partial?view=aspnetcore-2.1
Example:
#await Html.PartialAsync("../Account/_LoginPartial.cshtml")
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 {
...
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.
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
}
No, that's not possible and you shouldn't need to do it anyway. Razor views should contain markup and at most some helper call. async/await belongs to your backend logic.
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");
}
Following on MaxP's answer, it's easy to return a value from that code, despite Knagis comment:
#{
int x = DoAsyncStuffWrapper().Result;
}
#functions {
async Task<int>DoAsyncStuffWrapper()
{
await DoAsyncStuff();
}
}
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!
I am trying to display Youtube videos in my landing page. I know the code works because in another view it works.
In my landing page I am referencing a model that calls my blog posts and so I cant reference another model in my landing page
The error i get says the current model doesn't contain 'GetEnumerator'. If i add another model, I get an error saying a view can only reference one model.
This is how I display my blog posts and video in the landing page with 'model ' and 'videos'.
public ActionResult landing()
{
var model = new Stream.FeedViewModel();
var videos = WeLove.Net.Models.Stream.YouTubeHelper.GetVideos();
XmlReaderSettings settings = new XmlReaderSettings();
settings.XmlResolver = null;
settings.DtdProcessing = DtdProcessing.Ignore;
settings.DtdProcessing = DtdProcessing.Parse;
using (var reader = XmlReader.Create("https://takeonemisu.wordpress.com/feed",settings))
{
var feed = SyndicationFeed.Load(reader);
foreach (var post in feed.Items.Take(3))
{
model.Posts.Add(post);
}
}
return View(model);
}
I unfortunately can only get one to run at a time in a view.
Please help
Use child actions:
Controller
[ChildActionOnly]
public ActionResult VideoList()
{
var videos = // get videos;
return PartialView(videos);
}
[ChildActionOnly] ensures that this action can only be called as a child and not directly via a URL in the browser. If you want to serve it up at a particular URL as well, you can always remove this attribute, but then you should most likely branch over boolean values of ControllerContext.IsChildAction and/or Request.IsAjaxRequest to either return a PartialView or View as needed.
VideoList.cshtml
#model Namespace.To.VideoModel
<ul>
#foreach (var video in Model)
{
<li>#video.Title</li>
}
</ul>
Obviously the partial view HTML can be whatever you want. I just made an unordered list as an example
Post View
#Html.Action("VideoList")
I ve a masterpage(myMaster) where I ve a variable(lets call is myInteger) I want to access in an external class.
Usually I just do that in my aspx: <%# MasterType VirtualPath="myMaster.master" %>
And then I can access to it in my code behind doing: Master.myInteger ...
My issue here is I want to access it in another class(where there is no .aspx)
I tried doing
Master.MasterPageFile = "~/myMaster.master"
Master.AppRelativeVirtualPath = "myMaster.master"
but then Master.myInteger isnt recognized.
I'm not sure what I want to do is possible... Any idea to get this variable?
So you need to reference a MasterPage's property from a class that does not inherit from Page?
I would recommend to use a property or constructor to initialize this class with this value. But if you really need it this way, you can try following approach which uses HttpContect.Current.Handler:
// works even in static context
static void foo()
{
int myInteger = -1;
var page = System.Web.HttpContext.Current.Handler as System.Web.UI.Page;
if(page != null) myInteger = ((myMaster)page.Master).myInteger;
}
Note that this is prone to errors and also hard links your class with a MasterPage.
From an external class, try something like this:
var page = HttpContext.Current.Handler as Page;
if (page != null)
{
var value = ((MasterPageName)page.Master).SomeProperty;
}
If you don't have access to the master page from the external class, you can use reflection to access a property or method:
var page = HttpContext.Current.Handler as Page;
if (page != null)
{
var value = page.Master.GetType().GetProperty("SomeProperty").GetValue(page.Master, null);
}
I'm trying to add some user controls on a page. Thats easy, I just do it like this.
UserControl block = (categoryblock) LoadControl("categoryblock.ascx");
Panel1.Controls.Add(block);
But i want to access the label controls and more that are inside the categoryblock.ascx.
How would i do that ?
I cant do it like this,
block.l_itemName.text = "blabla";
I managed to user FindControl("l_itemName") but i would rather like to have the intellisense.
create following property in your user control:
public string ItemName() {
get() {
return l_itemName.text;
}
set(String value) {
l_itemName.text = value;
}
}
This will make you able to do block.ItemName = ""or string temp = block.ItemName
I hope this will help
I am migrating a web site to a new one using ASP .NET MVC2.
In the original site, master page has code-behind to check a query string parameter value. Depending on this value, code-behind dynamically modify some CSS property to hide / display master page elements.
As MVC2 has no code-behind because we are supposed to perform everything in the controllers, how should I proceed in this case ?
I see this : asp.net mvc modifying master file from a view
It partially answers my needs but the query string processing is common to all pages. How can I move this processing in a common code section ?
Regards.
A helper method looks like a good place:
public static class HtmlHelperExtensions
{
public static string GetCss(this HtmlHelper htmlHelper)
{
// read some request parameter
// here you also have access to route data so the
// parameter could be part of your custom routes as well
var foo = htmlHelper.ViewContext.HttpContext.Request["foo"];
// based on the value of this parameter
// return the appropriate CSS class
return (foo == "bar") ? "barClass" : "fooClass";
}
}
And somewhere in your master page:
<body class="<%= Html.GetCss() %>">
Or if you are always going to apply it to the body tag only it might be more appropriate to do this in order to reduce the tag soup:
public static class HtmlHelperExtensions
{
public static MvcHtmlString StartBody(this HtmlHelper htmlHelper)
{
var body = new TagBuilder("body");
var foo = htmlHelper.ViewContext.HttpContext.Request["foo"];
var bodyClass = (foo == "bar") ? "barClass" : "fooClass";
body.AddCssClass(bodyClass);
return MvcHtmlString.Create(body.ToString(TagRenderMode.StartTag));
}
}
and in your master page at the place of the body tag:
<%= Html.StartBody() %>
I can think of two solutions to this:
Derive your controllers from one controller base and set the ViewData parameter there depending on posted Form values
Don't use ViewData at all, but simply look for the form value in the view (using HttpContext.Current)
The second method violates the MVC pattern. IMO it is still acceptable in some scenarios, for example I am using this approach to highlight the currently selected item in a navigation menu.