I'm writing a web api using PostgreSQL and am checking database constraints as part of the validation process, but I also have a global exception filter as a fallback in case something gets by when saving. My problem is that the exception doesn't seem to have any message that I can present to the client without some processing. The added image is of the PostgresException data from a breakpoint. For example, in this case I would want something along the lines of "Asset Number x already exists" or just "Asset Number must be unique". Is this something that can be configured somewhere? The place that makes the most sense is at the constraint creation code, but I couldn't find an option to do so.
modelBuilder.Entity<AssetItem>().HasIndex(item => new { item.AssetNumber }).IsUnique();
public class DbExceptionFilter : IExceptionFilter
{
private const string UNIQUE_EXCEPTION = "23505";
public async void OnException(ExceptionContext context)
{
var exceptionType = context.Exception.InnerException.GetType().FullName;
if (exceptionType == "Npgsql.PostgresException")
{
var pgException = (PostgresException) context.Exception.InnerException;
switch(pgException.SqlState)
{
case UNIQUE_EXCEPTION:
var error = new {error = "Unique Error Here"};
await WriteJsonErrorResponse(context.HttpContext.Response, HttpStatusCode.BadRequest, error);
return;
}
}
else
{
var error = new { error = "Unexpected Server Error"};
await WriteJsonErrorResponse(context.HttpContext.Response, HttpStatusCode.InternalServerError, error);
return;
}
}
private async Task WriteJsonErrorResponse(HttpResponse response, HttpStatusCode statusCode, dynamic error)
{
response.ContentType = "application/json";
response.StatusCode = (int) statusCode;
await response.Body.WriteAsync(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(error)));
}
}
The closest thing to a user-readable message that PostgreSQL provides is the message text exposed on PostgresException.
However, as a general rule it is not a good idea to expose database errors directly to users (including web API users): these are intended to the application directly interacting with the database (i.e. your application). These messages generally don't mean much to the users of your API, and more importantly they leak potentially sensitive information about your database schema and are therefore not secure. It's especially problematic to dump/serialize the entire exception to the user as you seem to be doing (with JsonConvert.SerializeObject).
The best practice here would be to identify legitimate database exceptions that the user may trigger, intercept these and return and appropriately-worded message of your own (e.g. "A user with that name already exists").
As a side note, to identify PostgresException, rather than getting the name of the exception and comparing to that, you can simply use C# pattern matching:
if (context.Exception.InnerException is PostgresException postgresException)
{
// ...
}
Related
I am trying to port code that had been using Microsoft.WindowsAzure.Storage classes to use the newer classes in Azure.Data.Tables, Azure.Storage.Queues, etc. From what I have been able to discern, the StorageException class has been replaced by RequestFailedException. Unfortunately, there are some properties in StorageException that do not exist in RequestFailedException, making it difficult to log appropriate messages when an exception is encountered (for example: RequestId, RequestInformation, etc.).
The migration document does not address the differences between StorageException and the new RequestFailedException, or how to get error details from it.
It seems that either the new libraries are not yet mature enough for prime time, or maybe it is just because the documentation is lacking the relevant information and I can't find the appropriate methodologies for getting all of the error information from the RequestFailedException.
Does anyone know how to get more data out of the new class? Here are some examples of what we used to do:
catch (StorageException e)
{
operation.Telemetry.Properties.Add("AzureServiceRequestID", e.RequestInformation.ServiceRequestID);
Changing the above to use RequestFailedException is a problem because RequestInformation is not a property of RequestFailedException.
Here is another case:
catch (StorageException se)
{
var ri = se.RequestInformation;
if (ri.ErrorCode == "TableNotFound")
{
Logger.Info(
$"{SJResult.MakeInfo(64)} {ri.HttpStatusCode} {ri.HttpStatusMessage}, Storage Service code={ri.ErrorCode} This is OK if HL7 has not yet received messages."); // 60240040
}
else
{
Logger.Error(
$"{SJResult.MakeError(65)} HttpStatusCode: {ri.HttpStatusCode}, HttpStatusMessage: {ri.HttpStatusMessage}, Storage Service code={ri.ErrorCode}, " +
$"Extended.ErrorCode: {ri.ExtendedErrorInformation.ErrorCode} Extended.ErrorMessage: {ri.ExtendedErrorInformation.ErrorMessage}"); // E0240041
throw;
}
Again, RequestInformation is not available in RequestFailedException.
How do we get access to all the detailed information (RequestInformation) about an exception from the new RequestFailedException class?
As you can see the definition of RequestFailedException Class (Azure) and constuctors in the latest version of azure sdk.
RequestFailedException(Int32, String, String, Exception) : gives HTTP status code ,specified error message, error code, and a reference to the inner exception .
And
RequestFailedException(Response)
Gives error message, HTTP status code, error code obtained from the specified response.
The response in the argument represents the HTTP response from the service which has ClientRequestId as one of the properties as shown in the table which gets client request id that was sent to server in the form of x-ms-client-request-id headers.You can try the same while catching the error in the try-catch block.
In exception class you can give
public class RequestFailedException : Exception
{
...
public RequestFailedException(int status, string message, string? errorCode, Exception? innerException) : base(message , innerException) { }
}
Or use RequestFailedException(Response) from which you can get ClientRequestId.
I’ve not tested it myself, but please check if below can be worked around which is taken from the below references or check if something similar can give an idea.Also see if content property can be retrieved as a part of response.
try
{
...
}
catch (Exception aex)
{
foreach (var ex in aex.InnerExceptions)
{
if (ex is RequestFailedException except)
{
var innerException = excep.InnerException;
if (innerException != null && innerException.GetType() == typeof(WebException))
{
WebException webEx = innerException as WebException;
WebResponse resp = webEx.Response;
var responseHeaders = resp.Headers;
string requestId = responseHeaders["x-ms-request-id"];
Console.WriteLine("Request Id: " + requestId);
Console.WriteLine(except.InnerException.Message);
}
else
{
// (not a RequestFailedException)
Console.WriteLine($"{ex.Message}");
}
}
References:
How can I get Request ID when an exception occurs? (microsoft.com)
c# - How can you catch a RequestFailedException if making multiple
DownloadToAsync calls in parallel? - Stack Overflow
I've created an API in .NET Core 2 using C#. It returns an ActionResult with a status code and string message. In another application, I call the API using Flurl. I can get the status code number, but I can't find a way to get the message. How do I get the message or what do I need to change in the API to put the message someway Flurl can get it?
Here's the code for the API. The "message" in this example is "Sorry!".
[HttpPost("{orderID}/SendEmail")]
[Produces("application/json", Type = typeof(string))]
public ActionResult Post(int orderID)
{
return StatusCode(500, "Sorry!");
}
Here's the code in another app calling the API. I can get the status code number (500) using (int)getRespParams.StatusCode and the status code text (InternalError) using getRespParams.StatusCode, but how do I get the "Sorry!" message?
var getRespParams = await $"http://localhost:1234/api/Orders/{orderID}/SendEmail".PostUrlEncodedAsync();
int statusCodeNumber = (int)getRespParams.StatusCode;
PostUrlEncodedAsync returns an HttpResponseMessage object. To get the body as a string, just do this:
var message = await getRespParams.Content.ReadAsStringAsync();
One thing to note is that Flurl throws an exception on non-2XX responses by default. (This is configurable). Often you only care about the status code if the call is unsuccessful, so a typical pattern is to use a try/catch block:
try {
var obj = await url
.PostAsync(...)
.ReceiveJson<MyResponseType>();
}
catch (FlurlHttpException ex) {
var status = ex.Call.HttpStatus;
var message = await ex.GetResponseStringAsync();
}
One advantage here is you can use Flurl's ReceiveJson to get the response body directly in successful cases, and get the error body (which is a different shape) separately in the catch block. That way you're not dealing with deserializing a "raw" HttpResponseMessage at all.
In the Re-usability use-case project with RedisMQ there is a SMessageService which has the following handler.
SMessageService :Service{
public object Any(EmailMessage request)
{
var sw = Stopwatch.StartNew();
Db.InsertAll(Email.Process(request));
return new SMessageResponse { TimeTakenMs = sw.ElapsedMilliseconds };
}...
}
When the services returns, it puts the message in the mq:SMessageResponse.inq list.
My questions is if I return null instead of new SMessageResponse{...}, a message is put in the mq:EmailMessage.outq list, what is the reason for this?
Also, if I don't want to return a response at all, how would I do this or is this bad practice in the area of messaging?. In other words, I always need to have a log of what has happened.
I'm using WCF data services in Windows phone 7, I want to save relational data in single click how could I do that?
it's think 2 table : Category and Products
I want to save data UI :
From Cateogry Table :-
CategoryId : (auto increment)
CategoryName : abc
From product Table :-
ProductId :-(auto increment)
CategoryId :- ? ( not sure how could I retrieve )
ProductsName : xyz
on button save click:
I want to insert above data in appropriated table , how could I do that?
I am using following code for add one table data :
try
{
context = new NorthwindEntities(NorthwindUri);
context.AddToProducts(product);
context.BeginSaveChanges(new AsyncCallback((result) =>
{
bool errorOccured = false;
// Use the Dispatcher to ensure that the
// asynchronous call returns in the correct thread.
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
context = result.AsyncState as NorthwindEntities;
try
{
// Complete the save changes operation and display the response.
DataServiceResponse response = context.EndSaveChanges(result);
foreach (ChangeOperationResponse changeResponse in response)
{
if (changeResponse.Error != null) errorOccured = true;
}
if (!errorOccured)
{
MessageBox.Show("The changes have been saved to the data service.");
}
else
{
MessageBox.Show("An error occured. One or more changes could not be saved.");
}
}
catch (Exception ex)
{
// Display the error from the response.
MessageBox.Show(string.Format("The following error occured: {0}", ex.Message));
}
});
}), context);
}
catch (Exception ex)
{
MessageBox.Show(string.Format("The changes could not be saved to the data service.\n"
+ "The following error occurred: {0}", ex.Message));
}
In OData relationships are not represented as foreign keys, instead they are represented as navigation properties. And then you manipulate them through manipulating links in the client library.
Take a look at this article: http://msdn.microsoft.com/en-us/library/dd756361(v=vs.103).aspx
You can call multiple methods which modify data and then call SaveChanges to send them all to the server.
Note though, that if the server requires referential integrity and you're for example adding two related entities at the same time, you might need to use SaveChanges(Batch) (which makes the client send everything in one request and thus allows the server to process it as a single transaction).
I have an operation handler that checks for authentication and throws an exception when authentication fails using
throw new WebFaultException(HttpStatusCode.Unauthorized);
However this still returns a 404 Not Found status code to the client/test client.
This is my operation handler
public class AuthOperationHandler : HttpOperationHandler<HttpRequestMessage, HttpRequestMessage>
{
RequireAuthorizationAttribute _authorizeAttribute;
public AuthOperationHandler(RequireAuthorizationAttribute authorizeAttribute) : base("response")
{
_authorizeAttribute = authorizeAttribute;
}
protected override HttpRequestMessage OnHandle(HttpRequestMessage input)
{
IPrincipal user = Thread.CurrentPrincipal;
if (!user.Identity.IsAuthenticated)
throw new WebFaultException(HttpStatusCode.Unauthorized);
if (_authorizeAttribute.Roles == null)
return input;
var roles = _authorizeAttribute.Roles.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
if (roles.Any(role => user.IsInRole(role)))
return input;
throw new WebFaultException(HttpStatusCode.Unauthorized);
}
}
Am I doing something wrong?
I have good and bad news for you. The framework your are using has evolved into ASP.NET Web API. Unfortunately, OperationHandlers no longer exist. Their closest equivalent are ActionFilters.
Having said that, WCF Web API never supported throwing WebFaultException, that is a vestige of WCF's SOAP heritage. I think the exception was called HttpWebException, however, I never used it, I just set the status code on the response.