I have created a custom object that i use to generate a json error response for all error. The issue i am having is there are some errors that i cant catch. For example, if i try to call an action that does not support GET the default response is
{"Message":"The requested resource does not support http method
'GET'."}
This is fine, but i want to control the format. I want to control every single automated error like this so i can make sure that nothing gets output that i dont want to be output. I need to be able to gracefully let the client know if a code exception occurs.
I found this and this seems to be what i am looking for, but it doesnt seem to be catching the errors as there are no matching actions for these . How to override all standard error pages in WebAPI
I tried to implement this, but i still get the same error message from above even when i have this in the main controller.
[AllowAnonymous]
[ActionName("405")]
[HttpGet]
public string Status405()
{
return "error";
}
I was hoping there would be an onerror event or something that would act as a catch all so i could override everything. I tried to work based off the HttpResponseEx
public class ErrorFilter : System.Web.Http.HttpResponseException
{
public override string Message
{
get
{
return "My custom response based on whatever params are in this error";
}
}
}
This doesnt work either and i can see why as it doesnt tap into any events that get triggered.
Surely there is a way to do this. How is it normally done?
In the web.config, you need to turn on custom errors. By default it's set to remote, which allows the developer to see the stack trace and the end user to see a nice error page. You want to set this to on. See here for more details https://msdn.microsoft.com/en-us/library/h0hfz6fc(v=vs.85).aspx
Related
I have an ASP.NET Core application and I'm attempting to handle HTTP responses with status codes between 400 and 599 by using UseStatusCodePagesWithRedirects.
In Startup.cs I've added the following:
app.UseStatusCodePagesWithRedirects("/Error");
My Error controller is empty except for the following action (which was taken from the default scaffolded Home controller):
[Route("Error")]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
This works perfectly when I call return new BadRequestResult(); or return NotFound(); from one of my controllers, but when I try to return an error with more detail (such as including an error object) the controller action is never called and the body of the response is displayed on screen as plaintext instead. As an example, the following statement does not trigger the Error controller:
context.Result = new BadRequestObjectResult({ Errors = errors });
If I use the following statement instead, the middleware is correctly called:
context.Result = new BadRequestResult();
This appears to be working as designed, as the documentation states that UseStatusCodePagesWithRedirects "checks for responses with status codes between 400 and 599 that do not have a body" (emphasis mine) and the source code backs this up.
I want to include more information on my error page (such as user friendly error messages where appropriate) but I can't figure out how I can pass the data across effectively using the middleware since I'm trying to avoid my controllers knowing too much about how errors are handled so that the logic can be contained in one place and potentially changed later.
Is there a way to return a HTTP error that has additional information but will still get picked up by UseStatusCodePagesWithRedirects?
This is not how the exception handling middleware works. I'm not sure what you're doing exactly, but it looks like you're attempting to return BadRequest from middleware or an action filter. If you want to intercept some error there, you should simply allow the exception to bubble up (or throw one), not return a response, as that way, you'll keep the context of what happened.
Inside your error action, you can use HTTP feature interfaces to get the data you're looking for then. For example, there's:
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
If there was an exception, you can access it then via exceptionHandlerPathFeature.Error. There's also IStatusCodeReExecuteFeature, which you can use to get the original URL of the request for things like 404s:
var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature != null)
{
OriginalURL =
statusCodeReExecuteFeature.OriginalPathBase
+ statusCodeReExecuteFeature.OriginalPath
+ statusCodeReExecuteFeature.OriginalQueryString;
}
Source
Depending on exactly what you're doing, there might be other ways as well.
The below is not exactly what you need (passing an error details/an error object) but it seems like you can pass an error code, at least in ASP.NET Core.
If you look at the documentation for UseStatusCodePagesWithRedirects, it says that you can pass a status code, since the url template may contain such parameter:
app.UseStatusCodePagesWithRedirects("/MyStatusCode?code={0}");
Then in your MyStatusCode.cshtml you can intercept it like:
#{
var codeStr = Context.Request.Query.ContainsKey("code") ? Context.Request.Query["code"].ToString() : "-1";
}
I'm working on a project in which some users can be in the role AdminReader. Those users can see everything, but will not be able to save/edit any data.
I know I can do it this way:
public JsonResult ChangeStatus(int? id)
{
// AdminReader validation
if (base.User.isAdminReader)
{
return Json(new
{
Message = "You don't have privileges to alter data.",
Success = false,
}, JsonRequestBehavior.AllowGet);
}
// Function code
But I don't want to insert the above code inside all project functions.
I thought I could decorate my methods like we use [HttpGet]. I've also read this SO post.
Then I dropped the idea.
But then I found about Exception Handler Attribute and a logging action filter.
Is it possible to somehow combine the public void OnActionExecuting(ActionExecutingContext filterContext) with my AdminReader validation?
I don't know if it is the right way to go about my problem. Also, I'm not sure it could work really. What's the best practice in this situation?
Any suggestion is welcome, thanks in advance.
There are many ways to do this.
Yes, it's true that attributes are just metadata. However, the MVC framework has code in it that recognizes certain metadata and performs actions on it. Examples include the two attributes you mentioned (ActionFilters and ExceptionFilters), there's also AuthorizationFilters, which may be what you actually want.
AuthorizationFilters run before ActionFilters, near the start of the MVC pipeline, which allows them to block access before the page actually renders. But, if you don't need that, you can just use this point to do specific things before the page renders.
However, having said that, you are still going to need to have code on each page that controls what the user can and can't do based on their role. There is no magic way around that. Whenever you want to control what a user can do on a page based on access, you need code that does that in each section where control is required.
It's not clear from your example what you are trying to do, since the return value from a page is typically the HTML to render, but it looks like you want to return some kind of status message. I don't see how that can be replicated to all pages, since the pages themselves need to render.
I'm not entirely sure I understood your question, so sorry if this is off: but if you wanted to perform your AdminReader logic, you could write your own custom attribute like below:
public class AccessDeniedAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.Result is HttpUnauthorizedResult)
{
// Perform your unauthorized action here.
}
}
}
And then throw the attribute on any method where it applies (or you could throw it on the entire Controller class, if it applied to everything). Like so:
// The RoleSettings is a class of constants I defined that just contain strings
[AccessDeniedAuthorize(Roles = RoleSettings.AdminRole]
[HttpPost]
public ActionResult MyEditMethod()
{
// Perform actions if they are in the AdminRole
// If not authorized, it will do whatever you defined above in the
// AccessDeniedAuthorizeAttribute
}
I am trying to create a WebHookHandler for Webhooks send from WordPress WooCommerce in ASP.NET C#.
I started with creating a ASP.NET C# Azure API App WebApplication Project and adding the relevant references (Microsoft.AspNet.WebHooks.Common, Microsoft.AspNet.WebHooks.Receivers, Microsoft.AspNet.WebHooks.Receivers.WordPress). Added the WebHookConfig, WordPressWebHookHandler and registered the WebHookConfig in the GlobalAsax.
I then published the application as an Azure App Service.
My WordPressWebHookHandler is still the default of the examples and looks like this:
public class WordPressWebHookHandler : WebHookHandler
{
public override Task ExecuteAsync(string receiver, WebHookHandlerContext context)
{
// make sure we're only processing the intended type of hook
if("WordPress".Equals(receiver, System.StringComparison.CurrentCultureIgnoreCase))
{
// todo: replace this placeholder functionality with your own code
string action = context.Actions.First();
JObject incoming = context.GetDataOrDefault<JObject>();
}
return Task.FromResult(true);
}
}
When testing a User Creation WebHook in WooCommerce I can see the request in the log as below.
But unfortunately it is never received while debugging and I see below error.
I am thinking maybe I need a custom WebHook instead of the WordPress specific one as this is a WooCommerce Webhook. Or possibly it is handled wrong in the routing and ends up in another controller.
Any help is much appreciated.
Your WebHookReceiver is wrong
There is a mismatch of expecting HTML Form Data, when in fact it should be expecting JSON.
WordPressWebHookHandler is still the default
This is what is causing your error. If you look at the WordPressWebHookReceiver, the ReceiveAsync() method implementation, calls out to ReadAsFormDataAsync() method, which is not what you want, as your Content-Type is json. So, you want to be doing ReadAsJsonAsync().
Solution: Don't use the WordPressWebHookReceiver and switch it to another one that will call ReadAsJsonAsync().
Looking at the code
I am thinking maybe I need a custom WebHook instead of the WordPress specific one as this is a WooCommerce Webhook.
You had the right idea, so I dug up some of the code to explain exactly why this was happening.
The code block below is the ReceiveAsync() method that is overridden in the WordPressWebHookReceiver. You can see that it is calling the ReadAsFormDataAsync() which is not what you want...
public override async Task<HttpResponseMessage> ReceiveAsync(
string id, HttpRequestContext context, HttpRequestMessage request)
{
...
if (request.Method == HttpMethod.Post)
{
// here is what you don't want to be called
// you want ReadAsJsonAsync(), In short, USE A DIFFERENT RECEIVER.
NameValueCollection data = await ReadAsFormDataAsync(request);
...
}
else
{
return CreateBadMethodResponse(request);
}
}
A quick search through the repository for classes that call the ReadAsJsonAsync() method, shows that the following recievers implement it:
DynamicsCrmWebHookReceiver
ZendeskWebHookReceiver
AzureAlertWebHookReceiver
KuduWebHookReceiver
MyGetWebHookReceiver
VstsWebHookReceiver
BitbucketWebHookReceiver
CustomWebHookReceiver
DropboxWebHookReceiver
GitHubWebHookReceiver
PaypalWebHookReceiver
StripeWebHookReceiver
PusherWebHookReceiver
I assumed that the CustomWebHookReceiver would fit your requirements, so can grab the NuGet here. Otherwise you can implement your own, or derive it from this class, etc.
Configuring a WebHook Recevier
(Copied from the Microsoft Documentation)
Microsoft.AspNet.WebHooks.Receivers.Custom provides support for
receiving WebHooks generated by ASP.NET WebHooks
Out of the box you can find support for Dropbox, GitHub, MailChimp,
PayPal, Pusher, Salesforce, Slack, Stripe, Trello, and WordPress but
it is possible to support any number of other providers
Initializing a WebHook Receiver
WebHook Receivers are initialized by registering them, typically in
the WebApiConfig static class, for example:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
// Load receivers
config.InitializeReceiveGitHubWebHooks();
}
}
There is a problem with the data format that you send in your request. You must use format of HTML Form as your error message said.
Proper POST data format is described here: How are parameters sent in an HTTP POST request?
Don't forget to set Content-Length header and correct Content-Type if your library doesn't do it. Usually the content type is application/x-www-form-urlencoded.
I would like to make some additions to Svek's answer as I now got my Proof-of-concept completed and understand a bit more about the receivers.
His answer pointed me in the right direction, but needs a little addition.
WordpressWebHookReceiver
Can take in Wordpress Webhooks of type HttpPost. This does not work with Woocommerce as Woocommerce sends Json Webhook messages and will fail the HttpPost validation which is build into the WordpressWebHookReceiver class.
CustomWebHookReceiver
Can take in custom ASP.NET Webhooks. The custom ASP.NET webhooks have a specific partner for validation which includes but is not limited to the 'ms-signature'. Even adding the header will not suffice as the signature is also used in a different way from out of the box Woocommerce to encrypt the message. Basically coming to a point that you can't integrate Woocommerce with the CustomWebHookReceiver without changing the Webhook classes of Woocommerce.
GenericWebHookReceiver
This is the receiver you want, which accepts basically a generic set of Json data and will be able to use the "code" query parameter to verify the secret which you can add in the web.config of your asp.net api application. I used this receiver to finish the Proof-of-concept and got both the signature validation as well as the deciphering of the message working right of the bat.
My basic class which I will start to build into a real solution can be viewed below and changes the JObject into a dynamic object in the methods I call from the class. As you can see I have two methods currently added, one for the customer create and one for the order create to call the respective methods which do an insert into Dynamics 365 (former CRM).
public class GenericJsonWebHookHandler : WebHookHandler
{
public GenericJsonWebHookHandler()
{
this.Receiver = "genericjson";
}
public override Task ExecuteAsync(string generator, WebHookHandlerContext context)
{
var result = false;
try
{
// Get JSON from WebHook
var data = context.GetDataOrDefault<JObject>();
if(context.Id != "crcu" && context.Id != "cror")
return Task.FromResult(true);
if (context.Id == "crcu")
{
result = WoocommerceCRMIntegrations.Entities.Contact.CreateContactInCRM(data);
}
else if (context.Id == "cror")
{
result = WoocommerceCRMIntegrations.Entities.Order.CreateOrderInCRM(data);
}
}
catch (Exception ex)
{
result = false;
}
return Task.FromResult(result);
}
}
I want to implement custom exception handling in web API.
I am able to implement some initial implementation, However I want to pass class object to exception to display all attributes. like
class error
{
int error_code
string error_message
string API
}
When some error occur it should show json like
{
"error_code": 0,
"error_message":"Either Your are not authorized or you don't have any project yet.",
"API": "save data"
}
This code only show the message
throw new HttpResponseException(
Request.CreateErrorResponse(HttpStatusCode.NotFound, message));
Any suggestion,
Thanks
You just need to give your object as input for the CreateResponse method. Generate the error response as follows,
return Request.CreateResponse(HttpStatusCode.BadRequest, error,
new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
The web API will automatically json-fies the error object you passed.
Make sure you set the necessary values in the error object before you do this.
Hope this helps.
EDIT
Set your HttpStatusCode as BadRequest instead of NotFound since you are generating the exception. It's more appropriate.
I want to deal with exceptions in a WebAPI action method, by catching them setting the status code, and writing a message to the response. Normally in a normal MVC Controller I would do this like so, using Controller's Response property:
Response.StatusCode = 404;
Response.Write("Whatever");
However it seems ApiController doesn't have any Response property. Is there a reason for this? Is it OK to just use HttpContext.Current.Response like this:?
HttpContext.Current.Response.StatusCode = 404;
HttpContext.Current.Response.Write("Whatever");
Or is there a specific way of writing to the response from a WebAPI controller??
The action method is supposed to create the response object. Either just do new HttpResponseMessage or call this.CreateResponse.
If instead of returning the HttpResponseMessage you want to return a custom CLR object then you will need to throw a HTTPResponseException to return a 404.
If you want to create a message that describes your exception, your best bet is to call Request.CreateErrorResponse, and use any of the many overloads available. There are caveats to how the response is formatted depending on whether you have CustomErrors set to ON in your web.config, or whether you're in DEBUG mode. You can actually configure this behavior programatically as well, using the HttpConfiguration.IncludeErrorDetailPolicy property. See here as well: http://weblogs.asp.net/cibrax/archive/2013/03/01/asp-net-web-api-logging-and-troubleshooting.aspx
You can read this article for an in depth write up, and some options you have to solve the exact problem you describe: Web API, HttpError and the behavior of Exceptions – ‘An error has occurred’