ASP.NET MVC2 Call AsyncController method from jQuery? - c#

I'm trying to learn how to use AsyncController in MVC2, but there is very little documentation/tutorials out there. I'm looking to take one normal controller method that has a very slow export to a 3rd party service and convert that to an async method.
The original controller method:
public JsonResult SaveSalesInvoice(SalesInvoice invoice)
{
SaveInvoiceToDatabase(invoice); // this is very quick
ExportTo3rdParty(invoice); // this is very slow and should be async
}
So I created a new controller that inherits from AsyncController:
public class BackgroundController : AsyncController
{
public void ExportAysnc(int id)
{
SalesInvoice invoice = _salesService.GetById(id);
ExportTo3rdParty(invoice);
}
public void ExportCompleted(int id)
{
// I dont care about the return value right now,
// because the ExportTo3rdParty() method
// logs the result to a table
}
public void Hello(int id)
{
}
}
And then call the Export method from jQuery:
function Export() {
$.post("Background/Export", { id: $("#Id").val() }, function (data) {
// nothing to do yet
});
}
BUT the result is a 404 not found error (Background/Export is not found). If I try to call Background/Hello or Background/ExportAysnc they are found.
What am I doing wrong?

There are indeed two use cases
You care about the result of the lengthy operation
You don't care about the result (fire and forget)
Let's start with the first case:
public class BackgroundController : AsyncController
{
public void ExportAysnc(int id)
{
AsyncManager.OutstandingOperations.Increment();
Task.Factory.StartNew(() => DoLengthyOperation(id));
// Remark: if you don't use .NET 4.0 and the TPL
// you could manually start a new thread to do the job
}
public ActionResult ExportCompleted(SomeResult result)
{
return Json(result, JsonRequestBehavior.AllowGet);
}
private void DoLengthyOperation(int id)
{
// TODO: Make sure you handle exceptions here
// and ensure that you always call the AsyncManager.OutstandingOperations.Decrement()
// method at the end
SalesInvoice invoice = _salesService.GetById(id);
AsyncManager.Parameters["result"] = ExportTo3rdParty(invoice);
AsyncManager.OutstandingOperations.Decrement();
}
}
Now you could invoke it like this:
$.getJSON(
'<%= Url.Action("Export", "Background") %>',
{ id: $("#Id").val() },
function (data) {
// do something with the results
}
);
Now because you have mentioned a web service call, this means when you generated the client proxy of your web service you had the chance to emit async methods (XXXCompleted and XXXAsync):
public class BackgroundController : AsyncController
{
public void ExportAysnc(int id)
{
AsyncManager.OutstandingOperations.Increment();
// that's the web service client proxy that should
// contain the async versions of the methods
var someService = new SomeService();
someService.ExportTo3rdPartyCompleted += (sender, e) =>
{
// TODO: Make sure you handle exceptions here
// and ensure that you always call the AsyncManager.OutstandingOperations.Decrement()
// method at the end
AsyncManager.Parameters["result"] = e.Value;
AsyncManager.OutstandingOperations.Decrement();
};
var invoice = _salesService.GetById(id);
someService.ExportTo3rdPartyAsync(invoice);
}
public ActionResult ExportCompleted(SomeResult result)
{
return Json(result, JsonRequestBehavior.AllowGet);
}
}
This is the best possible usage of an async controller as it relies on I/O Completion Ports and doesn't monopolize any threads on the server during the execution of the lengthy operation.
The second case is easier (don't really need an async controller):
public class BackgroundController : Controller
{
public ActionResult Export(int id)
{
// Fire and forget some lengthy operation
Task.Factory.StartNew(() => DoLengthyOperation(id));
// return immediately
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
}
Here's a nice article on MSDN on Async controllers.

Related

Getting second operation Db context after second request

I'm working on an ASP.NET Core MVC application and I have a view who do a post request as:
$.ajax({
url:'/Advertisers/ActiveAdvertiser?id='+id+'&isActive='+!isActive,
method: 'POST',
success: function(r){
Swal.fire("Inactivated!", "Advertiser inactivated successfully", "success");
},
error: function (request) {
console.log(request.responseText)
Swal.fire("Error!", "Something went wrong, please try again`", "warning");
}
});
Controller:
[HttpPost]
public async Task<JsonResult> ActiveAdvertiser(int id, bool isActive)
{
var advertiser = await _advertisersService.GetAdvertiserByAdvertiserIdAsync(id);
if (advertiser != null)
{
var model = AssingAdvertiserViewModel(advertiser, id);
model.IsActive = isActive;
var result = await _advertisersService.UpdateAdvertiserAsync(model, GetCurrentUserAsync().Id);
if (result != null)
{
return Json(new { result = "OK" });
}
}
return Json(new { result = "BadRequest" });
}
Post method services:
public Task<Advertiser?> GetAdvertiserByAdvertiserIdAsync(int advertiserId)
{
return _db.Advertisers
.Include(a => a.Address)
.Include(pc => pc.PrimaryContact)
.Include(ac => ac.AlternateContact)
.FirstOrDefaultAsync(x => x.AdvertiserId == advertiserId);
}
private AdvertiserViewModel AssingAdvertiserViewModel(Advertiser advertiser, int id)
{
var model = new AdvertiserViewModel()
{
//Fill model here
};
return model;
}
public async Task<Advertiser?> UpdateAdvertiserAsync(AdvertiserViewModel model, int updatedById)
{
var advertiser = await GetAdvertiserByAdvertiserIdAsync(model.AdvertiserId);
if (advertiser is null)
return null;
advertiser.Name = model.Name;
// fill model here
await _db.SaveChangesAsync();
return advertiser;
}
The problem is I do the first request, and it returns Success with any issues, but if I try to do a second one, it throws an exception:
System.InvalidOperationException: A second operation was started on
this context instance before a previous operation completed. This is
usually caused by different threads concurrently using the same
instance of DbContext.
If I stop the project and run it again it works one time again and in the second time get the error again
I read about this issue in other questions, and apparently is because you don't use the await services, I check my code and almost everything uses await. Can someone see something that I don't see? Regards
You could check this document for how to handle this error:
Therefore, always await async calls immediately, or use separate DbContext instances for operations that execute in parallel.
So you could check if misssing the await keyword on async operation
and use separate Dbcontext instances with DbcontextFactory as below:
regist the factory in service collection:
builder.Services.AddDbContextFactory<SomeContext>();
inject it into controller/Service/Somewhereelse:
public class SomeEntitiesController : Controller
{
private readonly IDbContextFactory<SomeContext> _factory;
public SomeEntitiesController(IDbContextFactory<SomeContext> factory)
{
_factory = factory;
}
}
create a new dbcontext:
_factory.CreateDbContext()
I solve this by adding ServiceLifetime.Transient into my services as:
services.AddDbContext<ApplicationDbContext>(
options =>
options.UseSqlServer(Configuration.GetConnectionString("ApplicationDbConnection")),
ServiceLifetime.Transient
);

C# ASP.NET Is there an alternative to duplicating error checking on every controller action

If I have a controller with quite a few actions, doing similar things e.g. Gets data from an api, does something with it, returns view with updated model. Is there a better way to handle errors. Currently I do this on a number of action methods, obviously this duplication doesn't feel right but I can't think of an alternative. Thanks
public async Task<IActionResult> method(string id)
{
var result = await _flightRepository.GetLightById(id);
if (!result.Valid)
{
return View("ErrorPage", result.Error.Message);
}
var viewModel = new FlightViewModel
{
Flight = result.Result
};
return View(viewModel);
}
Basically I want to somehow encapsulate the error handling logic to return the error view if valid is false, otherwise populate viewmodel and return view. The valid property returns true if there is an error with the request (this is done in api layer)
Thanks for any help
You should use Filters to reuse code among your actions.
For example in your case your filter could be:
public class FlightValidator: ActionFilterAttribute
{
private readonly string _flightIdRouteKey; // e.g 23
private readonly string _errorViewName; // e.g "ErrorPage"
private readonly IFlightRepository _flightRepo;
public FlightValidator(string flightIdRouteKey, string errorViewName, IFlightRepository flightRepository)
{
_flightIdRouteKey = flightIdRouteKey;
_errorViewName = errorViewName;
_flightRepo = flightRepository;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
int flightId = (int)context.RouteData.Values[_flightIdRouteKey];
var result = await _flightRepo.GetFlightById(flightId);
if (!result.Valid)
{
context.Result = new ViewResult
{
ViewName = _errorViewName,
ViewData = new ViewDataDictionary(result.ViewData)
{
Model = model
}
};
return;
}
await next();
}
}
Now you can use the filter like this:
[TypeFilter(typeof(FlightValidator), Arguments = new object[] { "id", "ErrorPage"})]
public async Task<IActionResult> method(string id)
{
var viewModel = new FlightViewModel
{
Flight = result.Result
};
return View(viewModel);
}
I suggest you to refer Filters docs for more information about filters.
If you use a static method like this:
static async Task<IActionResult> ProcessAsync<T>(
Func<Task<RepositoryResult<T>>> process,
Func<T, IActionResult> ifValid
)
{
var result = await process();
if (!result.Valid)
{
return View("ErrorPage", result.Error.Message);
}
return ifValid(result.Result);
}
Then you can use it like this:
public Task<IActionResult> method(string id)
{
return ProcessAsync(
() => _flightRepository.GetLightById(id),
flight =>
{
var viewModel = new FlightViewModel
{
Flight = flight
};
return View(viewModel);
}
);
}
The static method can be defined anywhere you wish - in a base Controller class, or in a different namespace entirely (in which case you would import it with using static Some.Namespace.With.Class;.
Additionally, I suggest you use the convention of an Async suffix for asynchronous methods - so because GetLightById returns Task<T> I suggest renaming it GetLightByIdAsync.

Executing several async function on one object

I'm curious is it possible to execute function Function1 and function Function2 in C# webapi project like in example below.
Both of these functions are on the same class and use async and await but return different types.
Example code below:
[ApiController]
[Route("[controller]")]
public class ClassController : ControllerBase
{
// ...
public async Task<ActionResult<string>> Test()
{
var message = await _myClass.Function1().Function2();
return Ok(message);
}
// ...
}
Declaration of _myClass looks like below:
public class MyClass
{
// ...
public async Task<MyClass> Function1()
{
// code which uses `await` below
// ....
// end of this code
return this;
}
public async Task<string> Function2()
{
// code which uses `await` below
// ....
// end of this code
return "some text";
}
}
Yes, you can do this:
public async Task<ActionResult<string>> Test()
{
var message = await
_myClass.Function1().ContinueWith(resultingMyClass =>
resultingMyClass.Result.Function2());
return Ok(message);
}
But the most obvious solution would be to use the async/await syntax that C# provides:
public async Task<ActionResult<string>> Test()
{
var result1 = await _myClass.Function1();
var message = await result1.Function2();
return Ok(message);
}
If you don't need the result from the first call as input to the second function you can run multiple Tasks in parallel and wait until they have all finished using Task.WhenAll.

Can we call an async method from a constructor? [duplicate]

This question already has answers here:
Can constructors be async?
(15 answers)
Closed 3 years ago.
I'm needing to call a third party async method from a mvc app. The name of this async method is ForceClient.QueryAsync. It is from an open source project: https://github.com/developerforce/Force.com-Toolkit-for-NET/.
Below works fine, the model.Opportunity contains expected info when the process is in the View stage of the mvc:
public async Task<ActionResult> MyController(string Id) {
. . . .
MyModel model = new MyModel();
var client = new ForceClient(instanceUrl, accessToken, apiVersion);
var qry = await client.QueryAsync<MyModel.SFOpportunity>(
"SELECT Name, StageName FROM Opportunity where Id='" + Id + "'");
model.Opportunity = qry.Records.FirstOrDefault();
. . . .
return View(viewName, myModel);
}
But below does not work. The model.Opportunity is null when the process is in the View stage. I did some debugging and see that the flow goes like this:
1) Step1
2) Step2
3) In the View stage. At this point the model.Opportunity is null, which I need it to be populated.
4) Step3.
public async Task<ActionResult> MyController(string Id) {
. . . .
MyModel myModel = await Task.Run(() =>
{
var result = new MyModel(Id);
return result;
}); // =====> Step 1
. . . .
return View(viewName, myInfoView);
}
public class MyModel
{
public SFOpportunity Opportunity { get; set; }
public MyModel(string id)
{
setOpportunityAsync(id);
}
private async void setOpportunityAsync(string id)
{
. . .
var client = new ForceClient(instanceUrl, accessToken, apiVersion);
var qry = await client.QueryAsync<MyModel.SFOpportunity>(
"SELECT Name, StageName FROM Opportunity where Id='" + id + "'"); // ======> Step2
Opportunity = qry.Records.FirstOrDefault(); // =====> step3
}
So, my question is what do I need to do to get it to execute the steps in the following sequence:
1) Step1
2) Step2
3) Step3
4) In the View stage. At this point the model.Opportunity is should be populated.
You cannot have async constructors.
One alternative is to have async factory methods:
public class MyModel
{
public SFOpportunity Opportunity { get; set; }
private MyModel() { }
public static async Task<MyModel> CreateAsync(string id)
{
var result = new MyModel();
await result.setOpportunityAsync(id);
return result;
}
private async Task setOpportunityAsync(string id)
{
...
}
}
The constructor for MyModel does not (and can not) await setOpportunityAsync because the constructor itself isn't (and can't be) asynchronous. Otherwise you would be able to await the call to the constructor itself, but you can't. So the async method likely won't be finished executing right after the constructor is called. It will be finished... whenever it's finished.
Here's a smaller test class to illustrate the behavior:
public class HasConstructorWithAsyncCall
{
public HasConstructorWithAsyncCall()
{
MarkConstructorFinishedAsync();
}
public bool ConstructorHasFinished { get; private set; }
async void MarkConstructorFinishedAsync()
{
await Task.Delay(500);
ConstructorHasFinished = true;
}
}
What is the value of ConstructorHasFinished immediately after an instance is constructed? Here's a unit test:
[TestMethod]
public void TestWhenConstructorFinishes()
{
var subject = new HasConstructorWithAsyncCall();
Assert.IsFalse(subject.ConstructorHasFinished);
Thread.Sleep(600);
Assert.IsTrue(subject.ConstructorHasFinished);
}
The test passes. The constructor returns while MarkConstructorFinishedAsync hasn't completed, so ConstructorHasFinished is false. Half a second later it finishes, and the value is true.
You can't mark a constructor async so you can't await anything in the constructor.
In general we wouldn't put anything long-running like data retrieval in a constructor, including anything we would call asynchronously. If we do then we must either call it synchronously or know that the completion of the constructor doesn't mean that it's completely "constructed."

c# parallel IO Completion Ports

I'm trying to figure out the best way to wait for some number of I/O Completion ports to complete.
For this scenario, let's say that I'm in a MVC3 web app. (My understanding is the use of I/O Completion ports here is recommended so I can return the original thread back to IIS to service other requests)
Lets say I have an array of IDs and I want to fetch an object for each ID from some network call.
What is the best way to parallelize this synchronous method?
public class MyController: Controller
{
public ActionResult Index(IEnumerable<int> ids)
{
ids.Select(id => _context.CreateQuery<Order>("Orders")
.First(o => o.id == id));
DataServiceQuery<Order> query = _context.CreateQuery<Order>("Orders");
return Json(query);
}
private DataServiceContext _context; //let's ignore how this would be populated
}
I know it would start like this:
public class MyController: AsyncController
{
public void IndexAsync(IEnumerable<int> ids)
{
// magic here...
AsyncManager.Sync(() => AsyncManager.Parameters["orders"] = orders);
}
public ActionResult IndexCompleted(IEnumerable<Order> orders)
{
return Json(orders);
}
private DataServiceContext _context; //let's ignore how this would be populated
}
Should I be using DataServiceContext.BeginExecute Method? DataServiceContext.BeginExecuteBatch ? The data service I'm consuming can only get one record at a time (this is beyond my control) and I want these individual queries to run in parallel.
This is the pattern I've ended up using for running a batch of async operations inside MVC3:
public class MyController: AsyncController
{
public void IndexAsync(int[] ids)
{
var orders = new Orders[ids.Length];
AsyncManager.Parameters["orders"] = orders;
// tell the async manager there are X operations it needs to wait for
AsyncManager.OutstandingOperations.Increment(ids.Length);
for (int i = 0; i < ids.Length; i++){
var index = i; //<-- make sure we capture the value of i for the closure
// create the query
var query = _context.CreateQuery<Order>("Orders");
// run the operation async, supplying a completion routine
query.BeginExecute(ar => {
try {
orders[index] = query.EndExecute(ar).First(o => o.id == ids[index]);
}
catch (Exception ex){
// make sure we send the exception to the controller (in case we want to handle it)
AsyncManager.Sync(() => AsyncManager.Parameters["exception"] = ex);
}
// one more query has completed
AsyncManager.OutstandingOperations.Decrement();
}, null);
}
}
public ActionResult IndexCompleted(Order[] orders, Exception exception)
{
if (exception != null){
throw exception; // or whatever else you might like to do (log, etc)
}
return Json(orders);
}
private DataServiceContext _context; //let's ignore how this would be populated
}
Using TPL is easy.
public ActionResult Index(IEnumerable<int> ids)
{
var result = ids.AsParallel()
.Select(id => GetOrder(id))
.ToList();
return Json(result);
}
Order GetOrder(int id) { ... }

Categories