I'm trying to call the async method before return View() so that the heavy method request does not interfere with the appearance of the View, but works a little later (so that inscriptions appear on the screen that the process needs 1-3 seconds to execute). Everything is fine, but when the asynchronous method performs its task, I call Redirect(url) in it, but it does not work - just silence, no errors, silence like in a crypt.
The browser does not redirect me to another https page. Why? This is a simple action and I need it, because I get a certain protected token-link to a protected token-page from the outside in an asynchronous method - and there is nothing terrible about it.
How do I solve this problem?
Thanks!
public async Task<IActionResult> Method(Params params)
{
AsyncDo(params);
return View(params); // The View is shown! Good!
}
private async Task<IActionResult> AsyncDo(Params params)
{
ResultAsync resultAsync = await Task.Run(() =>
{
return WebAPI.LongHardWebRequest(params);
});
// This is triggered after 1-3 seconds
return Redirect(resultAsync.url); // It doesn't work!
HttpContext.Response.Redirect(resultAsync.url); // It doesn't work!
return RedirectToAction("RedirectTo", resultAsync.url); // It doesn't work!
return View();
}
// The method is never called!
public IActionResult RedirectTo(string url)
{
return Redirect(url);
return View();
}
I tried to save return values in static - it doesn't help. I tried calling the Redirect method without return. - it doesn't help. Nothing helps! Why? It's a simple action.I tried return Redirect(url), HttpContext.Response.Redirect(url), RedirectToAction("Action", url) - nothing helps - nothing happens.
Related
edit:background on project. This was project was created following the concepts from the tutorials here on data access with razor pages https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/?view=aspnetcore-2.2. i did not do views or controllers so I currently do not have any actions i can redirect to. do i need to add a controllers folder?
When I save changes to my edit of the model subcategories, I want to go back to the categories page. Normally, this would be fine with a simple RedirectToPage("/index").
That doesn't work here because my index OnGet uses a string ID to filter through the the subcategories (which feels like I'm probably doing that wrong... but it works). Below is what I currently have and it obviously doesn't work:
Edit - OnPost:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Subcategory).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!SubcategoryExists(Subcategory.SubcategoryId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index", "?id" , Subcategory.CategoryName);
}
Index - OnGet:
public async Task OnGetAsync(string id)
{
title = id;
Subcategory = await _context.Subcategory.ToListAsync();
foreach (Subcategory item in Subcategory.ToList())
{
if (item.CategoryName != id)
{
Subcategory.Remove(item);
}
}
}
Can anyone tell me how to properly route the OnPost of my edit razor page to the OnGet of my index razor page?
I've tried to do some research on handlers but I couldn't find a good understandable definition of how to do this.
It is a bit hard to see where you have hooked stuff up, as you have only provided the methods.
Given that OnGetAsync(string id) if mapped to /index you could use something like:
return RedirectToAction("Index", new { id = Subcategory.CategoryName.ToString() });
I would also suggest that you call your methods the same as the endpoint. So if OnGetAsync is your index endpoint. Then call it Index.
Hopefully this helps you, or else you need to provide me with more code.
the fix to the problem without building a controller was pretty much what Kristian Barrnet was saying:
return RedirectToPage("./Index", new { id = Subcategory.CategoryName.ToString() });
however, I should probably add some controllers here anyway to make this a lot more organized
I am new to the "async" and "task" stuff.
I can't seem to get working a simple if{} else{} inside the OnGetAsync().
public async Task OnGetAsync()
{
if (HttpContext.Session.GetString("LoggedStatus") != null)
{
//KEEP GOING
Accounts = await _context.Accounts.ToListAsync();
}
else
{
RedirectToPage("./Index");
}
}
The error I get is from the Accounts page, which I am trying to avoid even going near by using the "RedirectToPage("./Index")" which is my Home page.
I tried putting "return" word in front of RedirectToPage but it turns red when I do that. Also, if first condition is met (there is a value in the Session object) the Accounts pages shows up with no errors. So, I'm pretty sure the problem is in my attempt to redirect in the "else" statment.
NullReferenceException: Object reference not set to an instance of an object.
OESAC.Pages.Accounts.Pages_Accounts_Index.ExecuteAsync() in Index.cshtml
+
#foreach (var item in Model.Accounts)
The error above is in Accounts right where it loops thru and displays rows.
I'm not sure why it even gets to the Accounts.chstml.
You need to use Task<IActionResult> in public async Task<IActionResult> OnGetAsync(), combined with a return statement.
public async Task<IActionResult> OnGetAsync()
{
if (HttpContext.Session.GetString("LoggedStatus") != null)
{
//KEEP GOING
Accounts = await _context.Accounts.ToListAsync();
return Page();
}
else
{
return RedirectToPage("./Index");
}
}
Microsoft's docs has some good read on this here:
https://learn.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-2.2&tabs=visual-studio
Based on a comment, you can run this w/o async.
public IActionResult OnGet()
{
if (HttpContext.Session.GetString("LoggedStatus") != null)
{
//KEEP GOING
Accounts = _context.Accounts.ToList();
return Page();
}
else
{
return RedirectToPage("./Index");
}
}
In my application I have a set of errors that are "expected" and thus they shouldn't redirect the user to a full error page when they occur. For example, I would like to show the error message in a red box above the current page.
I can easily catch an error on a model action and show the message on the current view, but I was wondering how to do it globally.
I tried a custom middleware with TempData and a filter with ModelState, but can't wrap my head around how to actually pass the error data back to the current page.
This works for a single model (setting the error data in TempData):
public async Task<IActionResult> OnPost() {
try {
// methodThatThrows
}
catch (ApplicationError e) {
TempData["Error"] = e.Message;
return RedirectToPage("Current_Page");
}
return RedirectToPage("Other_Page");
}
For some reason, this doesn't work (in a global middleware), as TempData is empty when the redirect completes. Also setting the middleware doesn't really work with showing the other, more critical errors in the normal error page:
public class ApplicationErrorMiddleware {
private readonly RequestDelegate _next;
private readonly ITempDataDictionaryFactory _tempFactory;
public ApplicationErrorMiddleware(RequestDelegate next, ITempDataDictionaryFactory tempFactory) {
_next = next;
_tempFactory = tempFactory;
}
public async Task InvokeAsync(HttpContext httpContext) {
try {
await _next(httpContext);
}
catch (ApplicationError ex) {
HandleError(httpContext, ex);
}
}
private void HandleError(HttpContext context, ApplicationError error) {
var tempData = _tempFactory.GetTempData(context);
tempData.Add("Error", error.Message);
context.Response.Redirect(context.Request.Path);
}
}
By the tip of #hiiru, I went through a wild goose chase through the configuration options to find a working solution.
My issue was a missing call from the middleware HandleError-method:
private void HandleError(HttpContext context, ApplicationError error) {
var tempData = _tempFactory.GetTempData(context);
tempData.Add("Error", error.Message);
tempData.Save(); // this call was missing
context.Response.Redirect(context.Request.Path);
}
After popping that in there, the tempdata is sent with the redirection back to the original page. Note that this is using the default cookie-based temp data, so no specific configuration is needed.
Now, this works, but it might not be the best way to do this.
I am writing an MVC4 app, and in the controller, I need to be able to make a call to a web service to access the iTunes search service in order to get some cover art for a DVD.
I have the following very simple code
public static string GetAlbumArt(Movie movie) {
Task<string> lookupTask = LookupAlbumArt(movie.Title, movie.Year);
lookupTask.Wait(5000);
if (lookupTask.IsCompleted)
{
return lookupTask.Result;
}
else
{
return Timeout;
}
}
private static async Task<string> LookupAlbumArt(string title, int year)
{
using (var client = new HttpClient())
{
string response= await client.GetStringAsync(
"http://itunes.apple.com/search?term=hoosiers&media=movie"
);
return response;
}
}
When I run this and set breakpoints at the return Timeout line in GetAlbumArt and also at the return response line in LookupAlbumArt, the Timeout breakpoint is hit first, and then the LookupAlbumArt return statement is hit, and the correct content is in the response variable.
If I remove the timeout parameter from lookupTask.Wait(5000), the wait GetStringAsync call never returns, and the page never loads.
This feels like a pretty straightforward task, and I'm stumped as to what I'm doing wrong.
I'm not sure the correct way to structure this test. I've got a view model here:
public class ViewModel
{
public ReactiveCommand PerformSearchCommand { get; private set; }
private readonly ObservableAsPropertyHelper<bool> _IsBusy;
public bool IsBusy
{
get { return _IsBusy.Value; }
}
public ViewModel(IAdventureWorksRepository _awRepository)
{
PerformSearchCommand = new ReactiveCommand();
PerformSearchCommand.RegisterAsyncFunction((x) =>
{
return _awRepository.vIndividualCustomers.Take(1000).ToList();
}).Subscribe(rval =>
{
CustomerList = rval;
SelectedCustomer = CustomerList.FirstOrDefault();
});
PerformSearchCommand.IsExecuting.ToProperty(this, x => x.IsBusy, out _IsBusy);
PerformSearchCommand.Execute(null); // begin executing immediately
}
}
The dependency is a data access object to AdventureWorks
public interface IAdventureWorksRepository
{
IQueryable<vIndividualCustomer> vIndividualCustomers { get; }
}
Finally, my test looks something like this:
[TestMethod]
public void TestTiming()
{
new TestScheduler().With(sched =>
{
var repoMock = new Mock<IAdventureWorksRepository>();
repoMock.Setup(x => x.vIndividualCustomers).Returns(() =>
{
return new vIndividualCustomer[] {
new vIndividualCustomer { FirstName = "John", LastName = "Doe" }
};
});
var vm = new ViewModel(repoMock.Object);
Assert.AreEqual(true, vm.IsBusy); //fails?
Assert.AreEqual(1, vm.CustomerList.Count); //also fails, so it's not like the whole thing ran already
sched.AdvanceTo(2);
Assert.AreEqual(1, vm.CustomerList.Count); // success
// now the customer list is set at tick 2 (not at 1?)
// IsBusy was NEVER true.
});
}
So the viewmodel should immediately begin searching upon load
My immediate problem is that the IsBusy property doesn't seem to get set in the testing scheduler, even though it seems to work fine when I run the code normally. Am I using the ToProperty method correctly in the view model?
More generally, what is the proper way to do the full 'time travel' testing when my object under test has a dependency like this? The issue is that unlike most testing examples I'm seeing, the called interface is not an IObservable. It's just a synchronous query, used asynchronously in my view model. Of course in the view model test, I can mock the query to do whatever rx things I want. How would I set this up if I wanted the query to last 200 ticks, for example?
So, you've got a few things in your code that is stopping you from getting things to work correctly:
Don't invoke commands in ViewModel Constructors
First, calling Execute in the constructor means you'll never see the state change. The best pattern is to write that command but not execute it in the VM immediately, then in the View:
this.WhenAnyValue(x => x.ViewModel)
.InvokeCommand(this, x => x.ViewModel.PerformSearchCommand);
Move the clock after async actions
Ok, now that we can properly test the before and after state, we have to realize that after every time we do something that normally would be async, we have to advance the scheduler if we use TestScheduler. This means, that when we invoke the command, we should immediately advance the clock:
Assert.IsTrue(vm.PerformSearchCommand.CanExecute(null));
vm.PerformSearchCommand.Execute(null);
sched.AdvanceByMs(10);
Can't test Time Travel without IObservable
However, the trick is, your mock executes code immediately, there's no delay, so you'll never see it be busy. It just returns a canned value. Unfortunately, injecting the Repository makes this difficult to test if you want to see IsBusy toggle.
So, let's rig the constructor a little bit:
public ViewModel(IAdventureWorksRepository _awRepository, Func<IObservable<List<Customer>>> searchCommand = null)
{
PerformSearchCommand = new ReactiveCommand();
searchCommand = searchCommand ?? () => Observable.Start(() => {
return _awRepository.vIndividualCustomers.Take(1000).ToList();
}, RxApp.TaskPoolScheduler);
PerformSearchCommand.RegisterAsync(searchCommand)
.Subscribe(rval => {
CustomerList = rval;
SelectedCustomer = CustomerList.FirstOrDefault();
});
PerformSearchCommand.IsExecuting
.ToProperty(this, x => x.IsBusy, out _IsBusy);
}
Set up the test now
Now, we can set up the test, to replace PerformSearchCommand's action with something that has a delay on it:
new TestScheduler().With(sched =>
{
var repoMock = new Mock<IAdventureWorksRepository>();
var vm = new ViewModel(repoMock.Object, () =>
Observable.Return(new[] { new vIndividualCustomer(), })
.Delay(TimeSpan.FromSeconds(1.0), sched));
Assert.AreEqual(false, vm.IsBusy);
Assert.AreEqual(0, vm.CustomerList.Count);
vm.PerformSearchCommand.Execute(null);
sched.AdvanceByMs(10);
// We should be busy, we haven't finished yet - no customers
Assert.AreEqual(true, vm.IsBusy);
Assert.AreEqual(0, vm.CustomerList.Count);
// Skip ahead to after we've returned the customer
sched.AdvanceByMs(1000);
Assert.AreEqual(false, vm.IsBusy);
Assert.AreEqual(1, vm.CustomerList.Count);
});