How to call async function inside Web form/ MVC Razor Asp.net? [duplicate] - asp.net

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!

Related

Failing to reference another model in my view asp.net mvc

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

How to render html code with asp.net class

I need to be able to add a dynamic html code to a random cshtml page using my class:
public class HTMLRenderClass
{
//--here I generate html code
public void RenderControll()
{
LiteralControl ControlGroup = new LiteralControl();
ControlGroup.Text = "<h1>Here will be html code generated above</h1>";
Page p = HttpContext.Current.Handler as Page;
if (p != null) p.Controls.Add(ControlGroup);
else { throw new UserFriendlyException("Page still null"); }
}
}
so that inside cshtml I could call it like
#{
HTMLRenderClass c = new HTMLRenderClass();
c.RenderControll();
}
But the problem is that I don't know how to add my html to the page I need, because "HttpContext.Current.Handler as Page" is always null. Are there any solutions of that situation in which I still could use c.RenderControll() method without passing any "Page object" parameters?
If not, then what exactly should I pass to my class to place my dynamic html block inside static one?

Asp.Net MVC Return to page on error with fields populated

I am starting a new project in Asp.net MVC 2.
I have been mostly a webforms developer and have limited exposure to Asp.Net MVC and hence this is probably a noob question.
My situation is as follows:
I have a create page for saving some data to the DB.
The view for this page is not strongly bound / typed - so the way I am extracting the data from the view is by looking at the POST parameters.
Incase there is an error (data validation, etc), I need to send the user back to the previous page with everything filled in the way it was and displaying the message.
On webforms, this got handled automatically due to the view state - but how can I go about doing the same here?
A code example can be as follows:
View:
<% using (Html.BeginForm("Create", "Question", FormMethod.Post)) { %>
<div>
Title: <%: Html.TextBox("Title", "", new { #style="width:700px" })%>
</div>
<div>
Question: <%: Html.TextBox("Question", "", new { #style="width:700px" })%>
</div>
<input type="submit" value="Submit" />
<% } %>
Controller:
[HttpPost]
[ValidateInput(false)]
public ActionResult Create() {
Question q = new Question();
q.Title = Request.Form["Title"];
q.Text = Request.Form["Question"];
if(q.Save()) {
return RedirectToAction("Details", new { id = q.Id });
}
else {
// Need to send back to Create page with data filled in
// Help needed here
}
}
Thanks.
You could simply return the View in case of error. This will preserve the context.
[HttpPost]
[ValidateInput(false)]
public ActionResult Create(Question q) {
if(q.Save()) {
return RedirectToAction("Details", new { id = q.Id });
}
else {
// Need to send back to Create page with data filled in
// Help needed here
return View();
// If the view is located on some other controller you could
// specify its location:
// return View("~/Views/Question/Create.aspx");
}
}
Also I would recommend you to use strongly typed views along with the strongly typed helpers. Notice how I used a Question object directly as action parameter. This is equivalent to the code you have written in which you were manually extracting and building this object. The model binder does this job automatically for you.

How do I set the StripFormattingOnPaste property of a Telerik RadEditor with JavaScript?

I don't have access to the actual asp.net server tag itself, so I need to change the StripFormattingOnPaste property to the EditorStripFormattingOptions enum with JavaScript and I'm not sure how. I have some code that adds an OnClientLoad() and OnClientCommandExecuted() functions that works so I can add it in there, I'm just not sure where the property exists on the client-side and what the enum value would be:
// init OnClientLoad and OnClientCommandExecuted event handlers for all radeditors on the page
Sys.Application.add_load(function() {
if (typeof ($telerik) != "undefined") {
if ($telerik.radControls && Telerik.Web.UI.RadEditor) {
for (var i = 0, l = $telerik.radControls.length; i < l; i++) {
var control = $telerik.radControls[i];
if (Telerik.Web.UI.RadEditor.isInstanceOfType(control)) {
var editor = control;
// ??? editor._stripFormattingOptions = Telerik.Web.UI.StripFormattingOptions.NoneSupressCleanMessage
// editor already loaded, fire event
OnClientLoad(editor);
// attach event handler for paste commands
editor.add_commandExecuted(function(ed, args) {
return OnClientCommandExecuted(ed, args);
});
}
}
}
}
});
Update: I've discovered that the correct enum setting that I want is Telerik.Web.UI.StripFormattingOptions.NoneSupressCleanMessage.
Update #2: I see that the RadEditor JS object has a _stripFormattingOptions property, but I think it might just be for private use.
The Telerik controls are based on ASP.NET AJAX and use pretty much the same coding conventions - public properties have getters and setters methods. In this case you should use
editor.set_stripFormattingOptions(Telerik.Web.UI.StripFormattingOptions.NoneSupressCleanMessage);
To get the current value, use
var value = editor.get_stripFormattingOptions();
The property you saw (editor._stripFormattingOptions) is just used to store the value. Since its name starts with an underscore you are correct to assume that it is private and so you should not rely on it. The getter and setter methods are public and you are free to use them.

Retrieving data from Html.DropDownList() in controller (ASP MVC) | string returned?

I have the following problem:
I have a form in site/banen (currently local running webserver) which is using a SQL database. The link is made using ADO.net and is instantiated in the controller in the following way:
DBModelEntities _entities;
_entities = new DBModelEntities(); // this part is in the constructor of the controller.
Next, I use this database to fill a Html.DropDownList() in my view. This is done in two steps. At the controller side we have in the constructor:
ViewData["EducationLevels"] = this.GetAllEducationLevels();
and a helper method:
public SelectList GetAllEducationLevels()
{
List<EducationLevels> lstEducationLevels = _entities.EducationLevels.ToList();
SelectList slist = new SelectList(lstEducationLevels, "ID", "Name");
return slist;
}
In the view I have the following:
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<!-- various textfields here -->
<p>
<label for="EducationLevels">EducationLevels:</label>
<!-- <%= Html.DropDownList("EducationLevels", ViewData["EducationLevels"] as SelectList)%> -->
<%= Html.DropDownList("EducationLevels", "..select option..")%>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
Now, the form is rendered correctly when I browse to the create page. I can select etc. But when selected I have to use that value to save in my new model to upload to the database. This is where it goes wrong. I have the following code to do this in my controller:
//
// POST: /Banen/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection form)
{
// set rest of information which has to be set automatically
var vacatureToAdd = new Vacatures();
//vacatureToAdd.EducationLevels = form["EducationLevels"];
// Deserialize (Include white list!)
TryUpdateModel(vacatureToAdd);
// Validate
if (String.IsNullOrEmpty(vacatureToAdd.Title))
ModelState.AddModelError("Title", "Title is required!");
if (String.IsNullOrEmpty(vacatureToAdd.Content))
ModelState.AddModelError("Content", "Content is required!");
// Update the variables not set in the form
vacatureToAdd.CreatedAt = DateTime.Now; // Just created.
vacatureToAdd.UpdatedAt = DateTime.Now; // Just created, so also modified now.
vacatureToAdd.ViewCount = 0; // We have just created it, so no views
vacatureToAdd.ID = GetGuid(); // Generate uniqueidentifier
try
{
// TODO: Add insert logic here
_entities.AddToVacatures(vacatureToAdd);
_entities.SaveChanges();
// Return to listing page if succesful
return RedirectToAction("Index");
}
catch (Exception e)
{
return View();
}
}
#endregion
It gives the error:
alt text http://www.bastijn.nl/zooi/error_dropdown.png
I have found various topics on this but all say you can retrieve by just using:
vacatureToAdd.EducationLevels = form["EducationLevels"];
Though this returns a string for me. Since I'm new to ASP.net I think I am forgetting to tell to select the object to return and not a string. Maybe this is the selectedValue in the part where I make my SelectList but I can't figure out how to set this correctly. Of course I can also be complete on a sidetrack.
Sidenote: currently I'm thinking about having a seperate model like here.
Any help is appreciated.
You can't return an object from usual <SELECT> tag wich is rendered by Html.DropDownList() method, but only string variable could be returned. In your case ID of EducationLevels object will be send to the server. You should define and use one more custom helper method to reconstruct this object by ID.

Resources