So I am looking for a pattern on how to handle exceptions. Specifically I want to be able to pass the exception message on to the client from a Web API controller.
The client is using a third party library which deals with a call to the API
as
this.msgs = [];
let xhr = new XMLHttpRequest(),
formData = new FormData();
for(let i = 0; i < this.files.length; i++) {
formData.append(this.name, this.files[i], this.files[i].name);
}
xhr.upload.addEventListener('progress', (e: ProgressEvent) => {
if(e.lengthComputable) {
this.progress = Math.round((e.loaded * 100) / e.total);
}
}, false);
xhr.onreadystatechange = () => {
if(xhr.readyState == 4) {
this.progress = 0;
if(xhr.status == 200)
this.onUpload.emit({xhr: xhr, files: this.files});
else
this.onError.emit({xhr: xhr, files: this.files});
this.clear();
}
};
xhr.open('POST', this.url, true);
xhr.send(formData);
My current call back function is such
errorComplete(event: any) {
console.log("upload error");
}
notice that on error the library just wraps up the XMLHttpRequest and passes it on to my call back function.
so in the controller I have created a test line as follows
throw new Exception("This is a test message");
This is to simulate an unexpected exception
currently the return code in the XMLHttpRequest is a 500 and the text is the html that .Net generates when an exception occurs.
yes the method in my controller will need to wrapper in a try-catch but I am not sure of what code to put in the catch so I can send the error message down to the client and it can handle it and not take down the application.
the current use case I am looking at is the user uploads a file to the system but there is already a file with the specified name in the system. And renaming the file is not an option! I need to notify the user that there is already a file with that name in the system.
google has not revealed a way to pass the message back so I can process it.
Thank you Nkosi- your comment got me on the right track.
I implemented some middleware.
public class UIExceptionHandler
{
RequestDelegate _next;
public UIExceptionHandler(RequestDelegate next)
{
this._next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await this._next(context);
}
catch (Exception x)
{
if (!context.Response.HasStarted)
{
context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
context.Response.Headers["Message"] = x.Message;
}
}
}
}
public static class UIExcetionHandlerExtensions
{
public static IApplicationBuilder UseUIExceptionHandler(this IApplicationBuilder builder)
{
return builder.UseMiddleware<UIExceptionHandler>();
}
}
and in the configure method of the startup
app.UseUIExceptionHandler();
then on the client I can do
errorComplete(event: any) {
var errorMessage = event.xhr.getResponseHeader('Message');
console.log(errorMessage);
}
If anyone sees an issue with this solution please let me know
Related
I'm attempting to recreate in Blazor WASM a login scenario originally developed in an Angular SPA wherein I use an HttpIntercepter to catch any 401 responses, pop open a login window which redirects to our ADFS login, then closes and returns the login information and retries the failed (401) request. Here's what it looks like in Angular:
Angular LoginInterceptor
export class LoginInterceptor implements HttpInterceptor {
constructor(private loginService: LoginService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((errorResponse: HttpErrorResponse) => {
switch (errorResponse.status) {
case 401:
{
console.log("Unauthorized");
// call the LoginService's openLoginWindow and wait for it to complete
return this.loginService.openLoginWindow().pipe(
mergeMap((result) => {
if (result) {
// retry the request again
return next.handle(req);
}
})
);
}
default:
break;
}
throw errorResponse;
})
) as Observable<HttpEvent<any>>;
}
}
Angular LoginService
export class LoginService {
loginWindow: Window;
userName: BehaviorSubject<string> = new BehaviorSubject(null);
private windowsMessageObservable: Observable<MessageEvent>;
constructor() {
// Handle the Window.OnMessage event which listens for a successful login message in the new window
this.windowsMessageObservable = fromEvent<MessageEvent>(window, 'message');
}
openLoginWindow() {
// Open the new window
this.loginWindow = window.open("/SSOSignIn", 'loginWindow');
// Return an observable that fires when the login message is received
const signInObservable = new Observable<boolean>(obs => {
this.windowsMessageObservable.subscribe(evt => {
if (evt.origin === location.origin) {
if (evt.data?.type === 'signIn') {
this.userName.next(evt.data.name);
obs.next(true)
}
}
});
});
return signInObservable;
}
}
This works great in Angular. When a page loads or if a login expires, a request for data fails with 401, gets intercepted, pops up the login window which closes automatically after SSO completes, and the request seamlessly retries without having to reload or reclick a button, but in Blazor/C#, I can't seem to wrap my head around how I can retry the original request since we're not dealing with observables.
In Blazor/C#, as far as I can tell, the concept of HttpInterceptors are implemented using DelegatingHandlers. I've created a handler which pops open the login window and signs in, but I don't know a good way to hold off retrying and returning the response until after the login completes. Here's my handler:
namespace BlazorPlayground.Client.Handlers
{
public class UnauthorizedMessageHandler : DelegatingHandler, IDisposable
{
public UnauthorizedMessageHandler(IJSRuntime iJSRuntime)
{
JS = iJSRuntime;
}
private IJSRuntime JS { get; set; }
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
// This opens a new window but the operation continues immediately after.
// Need to somehow wait for login to complete here so I can retry request
await JS.InvokeVoidAsync("openUrl", "/SSOSignIn", "_blank");
}
return response;
}
}
}
Any ideas? Basically I need this SendAsync code to wait for a JS Window.Message event before completing and returning a response.
Ok I figured out a working solution. The basic concept I came up with was: create an await-able task that completes when the function is invoked from JS. The key behind this is to use the TaskCompletionSource<> which allows you to wait for it to be marked complete from anywhere. In my case I'm waiting for the TaskCompletionSource<> to complete when my Window.Message event handler invokes my C# ReceiveMessage method. Here's my interceptor:
public class UnauthorizedMessageHandler : DelegatingHandler, IDisposable
{
private DotNetObjectReference<UnauthorizedMessageHandler> objRef;
private TaskCompletionSource<string> tcs;
public UnauthorizedMessageHandler(IJSRuntime iJSRuntime)
{
JS = iJSRuntime;
}
private IJSRuntime JS { get; set; }
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
// Create a reference to this object so ReceiveMessage can be invoked from JS
objRef = DotNetObjectReference.Create(this);
// This allows us to wait for another method to complete before we continue
tcs = new TaskCompletionSource<string>();
// Open up the sign-in page
await JS.InvokeVoidAsync("openUrl", "/SSOSignIn", "_blank", objRef);
// Wait until ReceiveMessage is fired off
var message = await tcs.Task;
// Retry the original request
response = await base.SendAsync(request, cancellationToken);
}
return response;
}
[JSInvokable]
public void ReceiveMessage(string message)
{
// Get the message from JS and return it to the awaitable task
tcs.TrySetResult(message);
}
}
Here's my javascript
var windowMessageObjRef = null;
window.addEventListener('message', (evt) => {
// Make sure the message came from us
// Need to add checks to make sure it's got the data we expect
if (evt.origin === location.origin) {
// Check to make sure we have a reference to our DotNet interop object
if (windowMessageObjRef) {
// Send the name of the person who logged in
console.log('Invoking ReceiveMessage with data ' + evt.data.name);
windowMessageObjRef.invokeMethodAsync('ReceiveMessage', evt.data.name);
}
}
});
function openUrl(url, target, objRef) {
if (objRef) {
windowMessageObjRef = objRef;
}
console.log("Opening " + url + " with target " + target);
window.open(url, target);
}
Since this is a SPA application I don't want to leave the original page so my SSOSignIn page is popped opened in a new tab/window which just fires off the login challenge which redirects to ADFS and returns us to the SSOComplete page:
public class SSOSignInModel : PageModel
{
public ChallengeResult OnGet()
{
return Challenge(new AuthenticationProperties
{
RedirectUri = "/SSOComplete"
});
}
}
And the SSOComplete page posts the message to the opener (SPA) window with the name of the signed-in user and then closes itself.
<html>
<head>
<title>Redirecting to sign-in...</title>
</head>
<body>
<script type="text/javascript">
(function () {
var message = {
type: 'signIn',
success: true,
name: '#User.Identity.Name'
};
window.opener.postMessage(message, location.origin);
window.close();
})();
</script>
</body>
</html>
Now I have the ability in Blazor to automatically pop up the signin window and retry the original request after the sign-in completes in Blazor without having to reload my SPA. I'm gonna go take a nap now.
In my project I use CKEditor WYSWYG package to make HTML content for my website.
There is possible to insert image and send it directly from the package to the server.
Since 2 days I try to figure out how is it possible to catch the sent image from the Angular front-end to the Web API, but still no success.
I use .Net6 and Angular 12 with CKEditor 5.
public async Task<ActionResult<string>> AddPostPhoto(IFormFile photo)
{
try
{
System.Console.WriteLine(Request.ContentType);
var folderDirectory = $"\\Photos\\PostPhotos";
var path = Path.Combine("Photos/PostPhotos", "fileName.jpg");
var memoryStream = new MemoryStream();
await Request.Body.CopyToAsync(memoryStream);
System.Console.WriteLine(Request.HttpContext.Request.ContentLength);
System.Console.WriteLine(Request.Form.Keys);
if (!Directory.Exists(folderDirectory))
{
Directory.CreateDirectory(folderDirectory);
}
await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
memoryStream.WriteTo(fs);
}
return Ok(new { Url = path });
}
catch(Exception exception)
{
return BadRequest(exception.Message);
}
}
Finally I could find a working solution.
my-upload-adapter.ts
//ckeditorExComponent class Ends here and MyUploadAdapter class begins here in the same ckeditorEx.ts
export class MyUploadAdapter {
xhr: any;
loader: any;
serverUrl: string;
baseApiUrl: string;
constructor(loader: any, serverUrl: string, baseApiUrl: string) {
// The file loader instance to use during the upload.
this.loader = loader;
this.serverUrl = serverUrl;
this.baseApiUrl = baseApiUrl;
}
// Starts the upload process.
upload() {
return this.loader.file
.then((file: any) => new Promise((resolve, reject) => {
this._initRequest();
this._initListeners(resolve, reject, file);
this._sendRequest(file);
}));
}
// Aborts the upload process.
abort() {
if (this.xhr) {
this.xhr.abort();
}
}
// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
// Note that your request may look different. It is up to you and your editor
// integration to choose the right communication channel. This example uses
// a POST request with JSON as a data structure but your configuration
// could be different.
//Replace below url with your API url
xhr.open('POST', this.baseApiUrl + 'Tutorial/add-post-photo', true);
xhr.responseType = 'json';
}
// Initializes XMLHttpRequest listeners.
_initListeners(resolve: any, reject: any, file: any) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${file.name}.`;
xhr.addEventListener('error', () => reject(genericErrorText));
xhr.addEventListener('abort', () => reject());
xhr.addEventListener('load', () => {
const response = xhr.response;
// This example assumes the XHR server's "response" object will come with
// an "error" which has its own "message" that can be passed to reject()
// in the upload promise.
//
// Your integration may handle upload errors in a different way so make sure
// it is done properly. The reject() function must be called when the upload fails.
if (!response || response.error) {
return reject(response && response.error ? response.error.message : genericErrorText);
}
// If the upload is successful, resolve the upload promise with an object containing
// at least the "default" URL, pointing to the image on the server.
// This URL will be used to display the image in the content. Learn more in the
// UploadAdapter#upload documentation.
resolve({
default: this.serverUrl + response.url
});
});
// Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
// properties which are used e.g. to display the upload progress bar in the editor
// user interface.
if (xhr.upload) {
xhr.upload.addEventListener('progress', (evt: any) => {
if (evt.lengthComputable) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
});
}
}
// Prepares the data and sends the request.
_sendRequest(file: any) {
// Prepare the form data.
const data = new FormData();
data.append('upload', file);
// Important note: This is the right place to implement security mechanisms
// like authentication and CSRF protection. For instance, you can use
// XMLHttpRequest.setRequestHeader() to set the request headers containing
// the CSRF token generated earlier by your application.
// Send the request.
this.xhr.send(data);
}
}
In the Angular component
onReady($event: any) {
$event.plugins.get('FileRepository').createUploadAdapter = (loader: any) => {
return new MyUploadAdapter(loader, this.serverUrl, this.apiUrl);
};
}
The C# Web API controller
[HttpPost("add-post-photo")]
public async Task<ActionResult<string>> AddPostPhoto(IFormFile upload)
{
try
{
FileInfo fileInfo = new FileInfo(upload.FileName);
System.Console.WriteLine(upload.FileName);
var folderDirectory = $"\\Photos\\PostPhotos";
var path = Path.Combine("Photos\\PostPhotos", upload.FileName);
var memoryStream = new MemoryStream();
await upload.OpenReadStream().CopyToAsync(memoryStream);
if (!Directory.Exists(folderDirectory))
{
Directory.CreateDirectory(folderDirectory);
}
await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
memoryStream.WriteTo(fs);
}
return Ok(new { Url = path });
}
catch(Exception exception)
{
return BadRequest(exception.Message);
}
}
It is important to have the parameter upload, otherwise the find the back-end endpoint
I am trying to invoke External APIs from AWS lambda function written in c#. The Lamda function is deployed in No VPC mode. I am calling this function from Alexa skill. The code works fine for an http request, but its not working for https.
The below code works when I use http://www.google.com.
But, if I replace http with https, then I get the error in the cloud watch saying:
"Process exited before completing request."
Even the log written in catch is not getting logged in cloud watch.
public class Function
{
public const string INVOCATION_NAME = "bingo";
public async Task<SkillResponse> FunctionHandler(SkillRequest input, ILambdaContext context)
{
var requestType = input.GetRequestType();
if (requestType == typeof(IntentRequest))
{
string response = "";
IntentRequest request = input.Request as IntentRequest;
response += $"About {request.Intent.Slots["carmodel"].Value}";
try
{
using (var httpClient = new HttpClient())
{
Console.WriteLine("Trying to access internet");
//var resp=httpClient.GetAsync("http://www.google.com").Result // this works perfect!
var resp = httpClient.GetAsync("https://www.google.com").Result; // this throws error
Console.WriteLine("Call was successful");
}
}
catch (Exception ex)
{
Console.WriteLine("Exception from main function " + ex.Message);
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine(ex.StackTrace);
}
return MakeSkillResponse(response, true);
}
else
{
return MakeSkillResponse(
$"I don't know how to handle this intent. Please say something like Alexa, ask {INVOCATION_NAME} about Tesla.",
true);
}
}
private SkillResponse MakeSkillResponse(string outputSpeech, bool shouldEndSession,
string repromptText = "Just say, tell me about car models to learn more. To exit, say, exit.")
{
var response = new ResponseBody
{
ShouldEndSession = shouldEndSession,
OutputSpeech = new PlainTextOutputSpeech { Text = outputSpeech }
};
if (repromptText != null)
{
response.Reprompt = new Reprompt() { OutputSpeech = new PlainTextOutputSpeech() { Text = repromptText } };
}
var skillResponse = new SkillResponse
{
Response = response,
Version = "1.0"
};
return skillResponse;
}
}
The issue was resolved by updating the library version.
System.Net.Http v4.3.4 was not completely compatible with dotnet core v1.
So outbound http calls were working but not https calls. Changing the version of System.net.http resolved the issue.
I have a MVC 5 backend written in C#. It serves MVC views written in Razor and also some Angular 2 pages.
What is the best way to handle potential errors when calling server from client? I really would like to establish a pattern that is robust and works in all situations. Below is what I have tried so far.
Backend C# code:
public class MyController : Controller
{
[HttpGet]
public ActionResult GetUsers()
{
try
{
// Lot of fancy server code ...
throw new Exception("Dummy error");
return GetCompressedResult(json);
}
catch (Exception ex)
{
throw new HttpException(501, ex.Message);
}
}
private FileContentResult GetCompressedResult(string json)
{
// Transform to byte array
var bytes = Encoding.UTF8.GetBytes(json);
// Compress array
var compressedBytes = bytes.Compress();
HttpContext.Response.AppendHeader("Content-encoding", "gzip");
return new FileContentResult(compressedBytes, "application/json");
}
}
Client side Angular 2 code:
public loadDataFromServer() {
let response = this.http.get(this.urlGetData)
.map((res: Response) => res.json())
.catch(this.handleError);
response.subscribe(response => {
// Process valid result ...
},
err => { console.error(err); }
);
};
private handleError(error: Response | any) {
let errMsg: string;
if (error instanceof Response) {
const body = JSON.parse(JSON.stringify(error || null))
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
This is a printscreen of the error object processed by handleError method:
This all raises some questions:
Is it correct to throw custom HttpException from server?
Is handleError method correct or maybe too complex?
On client side I would like to see the custom error message, but currently it is just found in an enormous "blob" of HTML that is nasty to parse.
Is client side error handling necessary BOTH in get call and subscribe action?
My current suggestion is to let server respond with Json object for all handled exceptions.
On client side I check result object for possible error property before handling valid result.
The handleResponseError method will parse typed Response object and throw observable message. But at least my browser (Chrome 57) seems to automatically log response errors to console. So if subscriber need no specific extra handling for different errors, then the subscriber need no extra action for err object.
Please feedback if there are better ways!
Backend C# code:
public class MyController : Controller
{
[HttpGet]
public ActionResult GetUsers()
{
try
{
// Lot of fancy server code ...
throw new ArgumentException("Dummy error");
// Normal return of result ...
}
catch (Exception ex)
{
return Json(new { error = $"{ex.GetType().FullName}: '{ex.Message}'" }, JsonRequestBehavior.AllowGet);
}
}
}
Client side Angular 2 code:
public loadDataFromServer() {
let response = this.http.get(this.urlGetData)
.map((res: Response) => res.json())
.catch(this.handleResponseError);
response.subscribe(result => {
if (result.error) {
this.displayJsonError(this.urlGetUsers, result.error);
}
else {
// Process valid result
}
});
};
private handleResponseError(value: Response | any) {
let errorMessage = value.toString();
let response = value as Response;
if (response) {
errorMessage = `${response.status}: ${response.statusText}\n${response.toString()}`;
}
if (value.error) {
errorMessage = value.error;
}
if (value.message) {
errorMessage = value.message;
}
return Observable.throw(errorMessage);
}
private displayJsonError(url: string, error: string) {
console.error(`Call to '${url}' failed with ${error}`);
}
I'm writing a RESTful API in Web API and I'm not sure how to handle errors effectively. I want the API to return JSON, and it needs to consist of the exact same format every single time - even on errors. Here are a couple of examples of what a successful and a failed response might look like.
Success:
{
Status: 0,
Message: "Success",
Data: {...}
}
Error:
{
Status: 1,
Message: "An error occurred!",
Data: null
}
If there is an exception - any exception at all, I want to return a response that is formed like the second one. What is the foolproof way to do this, so that no exceptions are left unhandled?
Implement IExceptionHandler.
Something like:
public class APIErrorHandler : IExceptionHandler
{
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
var customObject = new CustomObject
{
Message = new { Message = context.Exception.Message },
Status = ... // whatever,
Data = ... // whatever
};
//Necessary to return Json
var jsonType = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
var response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, customObject, jsonType);
context.Result = new ResponseMessageResult(response);
return Task.FromResult(0);
}
}
and in the configuration section of WebAPI (public static void Register(HttpConfiguration config)) write:
config.Services.Replace(typeof(IExceptionHandler), new APIErrorHandler());