I have a similar situation in my webApi. The returned value is an entity , which needs long processing as the SQL query is complex (could take even 10-20 seconds). Is this a viable solution to return a value in the finally branch?
Hi Zoltan, sorry for the late reply. Unfortinately I don't think so. This method ha already returned the result by the time you would finish processing. It's really only for things like exporting data to a file or sending a message in a queue for later processing (where the output of the processing is not returned to the caller)
In my case, the processing time for the longrunning task was 1 hour :)
Zoltan, I know this is way after the fact but I thought I'd post anyway. You might be able to get creative and push a response after the fact using something like SignalR. But I'm not totally sure if there would be issues trying to do it within a thread that's associated to an active HTTP pipeline request.
There are some other interesting things you can do by writing to the response stream in stages also. A few years ago I was curious about a multi-stage response where I wanted render a page back to the end user and continue updating it as different steps were completing before ultimately redirecting the response to somewhere else. This is how I did it.
At the top of my controller I defined these variables:
Notice that the first variable is basically a full static web page and the others are JavaScript blocks that fire functions defined in the static page to add steps to the output, display errors or redirect the browser.
This is what the controller method handling the call looks like:
[Route("{assignmentArea}/{assignmentId}/autoenroll"), HttpGet]
public async Task AutoEnroll(LmsAssignmentArea assignmentArea, int assignmentId, [FromQuery] string auth, CancellationToken cancellationToken)
{
Response.Clear();
var htmlPage = string.Format(htmlPageTemplate,
LocalCache.GetHeaderFooterValue(_logger, HeaderFooterSetting.PageScript),
LocalCache.GetHeaderFooterValue(_logger, HeaderFooterSetting.PageNavBar),
LocalCache.GetHeaderFooterValue(_logger, HeaderFooterSetting.PageFooter));
await Response.WriteAsync(htmlPage, cancellationToken);
await DoAutoEnroll(assignmentArea, assignmentId,
async (autoEnrollStep, trainingName) =>
{
switch (autoEnrollStep)
{
case AutoEnrollmentStep.EvaluatingRequirements:
await Response.WriteAsync(string.Format(stepTemplate, $"Evaluating training requirements and trainee assignment. ({assignmentArea}-{assignmentId})".FormatForJsStringParam()), cancellationToken);
await Task.Delay(500);
break;
case AutoEnrollmentStep.CreatingTrainingAssignment:
await Response.WriteAsync(string.Format(stepTemplate, "Creating training assignment.".FormatForJsStringParam()), cancellationToken);
await Task.Delay(500);
break;
case AutoEnrollmentStep.SynchingAssignmentToLms:
await Response.WriteAsync(string.Format(stepTemplate, "Sending training assignment to the learning management system.".FormatForJsStringParam()), cancellationToken);
await Task.Delay(500);
break;
case AutoEnrollmentStep.ValidatingAssignmentStatus:
await Response.WriteAsync(string.Format(stepTemplate, "Validating assignment status.".FormatForJsStringParam()), cancellationToken);
await Task.Delay(500);
break;
case AutoEnrollmentStep.EnrollingToTraining:
await Response.WriteAsync(string.Format(stepTemplate, $"Enrolling you to course \"{trainingName ?? "undefined"}\".".FormatForJsStringParam()), cancellationToken);
await Task.Delay(500);
break;
default:
var errorMessage = $"AUTOENROLL.StepEval.Exception.E.! - Auto enrollment step \"{autoEnrollStep}\" is not properly accounted for in the auto enrollment routine.";
this.HandleAPIException("EnrollmentController.SelfAutoEnroll", _logger, errorMessage, new Exception(errorMessage), _localizationSettings.PreferredTimeZone, true, _emailProvider);
break;
}
},
async (redirectLocation) =>
{
await Response.WriteAsync(string.Format(stepTemplate, "Redirecting to the learning center.".FormatForJsStringParam()), cancellationToken);
await Response.WriteAsync(string.Format(redirectTemplate, redirectLocation.FormatForJsStringParam()), cancellationToken);
},
async (exception, sendEmail, clientMessage, userCanceledRequest) =>
{
if (!userCanceledRequest)
{
clientMessage = clientMessage.FormatForJsStringParam();
var requestId = this.HandleAPIException("EnrollmentController.SelfAutoEnroll", _logger, clientMessage, exception, _localizationSettings.PreferredTimeZone, sendEmail, _emailProvider);
await Response.WriteAsync(string.Format(errorTemplate, "Course Enrollment Failed", clientMessage ?? "An unhandled error occurred.", requestId ?? "(unspecified)"), cancellationToken);
await Response.Body.FlushAsync(cancellationToken);
}
}, cancellationToken);
}
Basically what's happening in the handler method is:
I'm clearing the response which I believe I had to do in order to write to it more than once
I call await Response.WriteAsync(<string here>) every time I want to write something back to the browser
Don't get too confused by the DoAutoEnroll(...) method. It's a method that takes several Func definitions that are fired in the method definition depending on the required logic.
The crux of this working is that the first Response.WriteAsync(...) needs to return a fully functional HTML document so something complete can be rendered to the end user's browser. Each subsequent Response.WriteAsync(...) actually appends a <script> block to the bottom of the document and because it contains an immediate function, it fires right after it's rendered which allows me to inject content back into the main body of the HTML document. Before doing this, I didn't realize that you can actually write <script> blocks after the </html> tag and that they still work fine.
Anyway, this is a weird one but for this very specific use case, I found it useful to keep the end user informed as I performed steps in a process and then finally write back the script block that redirects them to a different location.
SignalR is definitely more elegant but gets a little more sticky in a clustered environment. This solution is quick and a little unconventional but came in handy for me.
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
I have a similar situation in my webApi. The returned value is an entity , which needs long processing as the SQL query is complex (could take even 10-20 seconds). Is this a viable solution to return a value in the finally branch?
Hi Zoltan, sorry for the late reply. Unfortinately I don't think so. This method ha already returned the result by the time you would finish processing. It's really only for things like exporting data to a file or sending a message in a queue for later processing (where the output of the processing is not returned to the caller)
In my case, the processing time for the longrunning task was 1 hour :)
Zoltan, I know this is way after the fact but I thought I'd post anyway. You might be able to get creative and push a response after the fact using something like SignalR. But I'm not totally sure if there would be issues trying to do it within a thread that's associated to an active HTTP pipeline request.
There are some other interesting things you can do by writing to the response stream in stages also. A few years ago I was curious about a multi-stage response where I wanted render a page back to the end user and continue updating it as different steps were completing before ultimately redirecting the response to somewhere else. This is how I did it.
At the top of my controller I defined these variables:
Notice that the first variable is basically a full static web page and the others are JavaScript blocks that fire functions defined in the static page to add steps to the output, display errors or redirect the browser.
This is what the controller method handling the call looks like:
Basically what's happening in the handler method is:
await Response.WriteAsync(<string here>)
every time I want to write something back to the browserDon't get too confused by the
DoAutoEnroll(...)
method. It's a method that takes severalFunc
definitions that are fired in the method definition depending on the required logic.The crux of this working is that the first
Response.WriteAsync(...)
needs to return a fully functional HTML document so something complete can be rendered to the end user's browser. Each subsequentResponse.WriteAsync(...)
actually appends a<script>
block to the bottom of the document and because it contains an immediate function, it fires right after it's rendered which allows me to inject content back into the main body of the HTML document. Before doing this, I didn't realize that you can actually write<script>
blocks after the</html>
tag and that they still work fine.Anyway, this is a weird one but for this very specific use case, I found it useful to keep the end user informed as I performed steps in a process and then finally write back the script block that redirects them to a different location.
SignalR is definitely more elegant but gets a little more sticky in a clustered environment. This solution is quick and a little unconventional but came in handy for me.