How comes that a custom ExceptionHandler is never called and instead a standard response When Call HTTP Methods Post
This my code
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
//Handler Custom Exception
config.Services.Replace(typeof(IExceptionHandler), new CustomExceptionHandler());
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new JsonOutputDateTime());
// Web API routes
config.MapHttpAttributeRoutes();
// Remove the XML formatter (Json Only)
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v1/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
ExceptionHandler.cs
public class CustomExceptionHandler : System.Web.Http.ExceptionHandling.ExceptionHandler
{
public override Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
SystemExceptionReturn returnObj = new SystemExceptionReturn();
returnObj.responseCode = "xxxx";
returnObj.responseMessage = "System Error";
var response = context.Request.CreateResponse(HttpStatusCode.OK, returnObj);
context.Result = new ResponseMessageResult(response);
return base.HandleAsync(context, cancellationToken);
}
public virtual bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
private class TextPlainErrorResult : IHttpActionResult
{
public HttpRequestMessage Request { get; set; }
public string Content { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response =
new HttpResponseMessage(HttpStatusCode.InternalServerError);
response.Content = new StringContent(Content);
response.RequestMessage = Request;
return Task.FromResult(response);
}
}
}
Global.asax.xs
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
my Controller
public class gController : ApiController
{
[HttpPost]
public bool haha(int id)
{
bool res = false;
try
{
int ans = id / 0;
}
catch (Exception ex)
{
throw ex;
}
return res;
}
}
This response when I call localhost:xxxx/api/v1/g/haha
{
"Message": "An error has occurred.",
"ExceptionMessage": "Attempted to divide by zero.",
"ExceptionType": "System.DivideByZeroException"}
but when i change HttpPost to HttpGet it' working for me.
Please someone help me
Sorry for my English
UPDATE
I found when test in localhost is't not working but when Deploy to IIS it' working
Thank you so much for help
To Send POST request to localhost:xxxx/api/v1/g/haha URL
if you change Id parameter to be received [FromBody] it will work.
[HttpPost]
public bool haha([FromBody]int id)
{
bool res = false;
try
{
int ans = id / 0;
}
catch (Exception ex)
{
throw ex;
}
return res;
}
Related
First of all a bit of background.
I am using .Net Framework 4.6.1, Microsoft.AspNet.WebApi 5.2.4 in Visual Studio 2017 Community.
My ApiController's implement endpoints which throw intended Exceptions for example if certain requirements are not met. I added global ExceptionFilterAttribute and ExceptionHandler to handle those Exceptions and to return a proper response.
The Exceptions are of a type which are inherited of System.Exception.
This is only working occasionally as intended. Every second or third or sometimes fifth (no real pattern) request the api server returns no response at all e.g. for example Postman says: "Could not get any response".
To test this I used the same endpoint with the same input.
Here are a few things I did to get a better idea of the problem:
I added exception logging to Global.asax (to catch First Chance Exceptions)
I subscribed to Global.asax Application_Error Event
I looked at the IIS logs
None of those got my closer to the issue. The exception was caught and logged in Global.asax like expected but no additional error or exception which could give me more info to my problem.
Here is my code:
I simplified the ApiController's function and removed the business logic.
[Route("test")]
[HttpGet]
public IHttpActionResult GetTest()
{
throw new ObjectAlreadyExistsException("test");
return ResponseFactory.CreateOkResponse(null);
}
public class ExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is ObjectAlreadyExistsException)
{
context.Response = ResponseFactory.CreateErrorResponseMessage(context.Exception.Message, new Error("OBJECT_ALREADY_EXISTS_ERROR", context.Exception.Message));
}
else if (context.Exception is ObjectNotFoundException)
{
context.Response = ResponseFactory.CreateErrorResponseMessage(context.Exception.Message, new Error("OBJECT_NOT_FOUND_ERROR", context.Exception.Message));
}
base.OnException(context);
}
}
public class GlobalExceptionHandler : ExceptionHandler
{
private static readonly ILogger Log = new LoggerConfiguration()
.WriteTo.File(new CompactJsonFormatter(), Path.Combine(#Properties.Settings.Default.LOG_DIRECTORY, #"error.json"), rollOnFileSizeLimit: true, retainedFileCountLimit: 5, shared: true)
.Enrich.WithWebApiControllerName()
.Enrich.WithWebApiActionName()
.Enrich.WithWebApiRouteTemplate()
.Enrich.WithWebApiRouteData()
.Enrich.With(new AuthTokenEnricher())
.CreateLogger();
public override void Handle(ExceptionHandlerContext context)
{
if (context != null && context.Exception != null)
{
Log.Error("Unexpected Internal Server Error {Exception}", context.Exception);
}
context.Result = ResponseFactory.CreateErrorResponse(HttpStatusCode.InternalServerError, "Unexpected Internal Server Error", new Error("INTERNAL_SERVER_ERROR", "This request failed because of an unexpected server error."));
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//Exception filters
config.Filters.Add(new ExceptionFilter());
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
// Web API routes
config.MapHttpAttributeRoutes();
}
}
public class ObjectAlreadyExistsException : Exception
{
public ObjectAlreadyExistsException(string message) : base(message)
{
}
public ObjectAlreadyExistsException(string message, Exception inner) : base(message, inner)
{
}
}
For now I put a workaround in place which looks like this:
[Route("test")]
[HttpGet]
public IHttpActionResult GetTest()
{
try
{
throw new ObjectAlreadyExistsException("test");
}
catch (Exception ex)
{
return CustomExceptionHandler.Handle(ex);
}
}
public class CustomExceptionHandler
{
private static readonly ILogger Log = new LoggerConfiguration()
.WriteTo.File(new CompactJsonFormatter(), Path.Combine(#Properties.Settings.Default.LOG_DIRECTORY, #"error.json"), rollOnFileSizeLimit: true, retainedFileCountLimit: 5, shared: true)
.Enrich.WithWebApiControllerName()
.Enrich.WithWebApiActionName()
.Enrich.WithWebApiRouteTemplate()
.Enrich.WithWebApiRouteData()
.Enrich.With(new AuthTokenEnricher())
.CreateLogger();
public static IHttpActionResult Handle(Exception ex)
{
IHttpActionResult result = null;
if (ex != null)
{
if (ex is ObjectAlreadyExistsException)
{
result = ResponseFactory.CreateErrorResponse(ex.Message, new Error("OBJECT_ALREADY_EXISTS_ERROR", ex.Message));
}
else if (ex is ObjectNotFoundException)
{
result = ResponseFactory.CreateErrorResponse(ex.Message, new Error("OBJECT_NOT_FOUND_ERROR", ex.Message));
}
}
if (result == null)
{
if (ex != null)
{
Log.Error("Unexpected Internal Server Error {Exception}", ex);
}
result = ResponseFactory.CreateErrorResponse(HttpStatusCode.InternalServerError, "Unexpected Internal Server Error", new Error("INTERNAL_SERVER_ERROR", "This request failed because of an unexpected server error."));
}
return result;
}
}
I would appreciate any ideas how to debug this or any suggestions to fix it.
Can you try inheriting from IHttpActionResult and use it as returning the exception from your GlobalExceptionHandler
private class ErrorMessageResult : IHttpActionResult
{
private readonly HttpResponseMessage _httpResponseMessage;
private HttpRequestMessage _request;
public ErrorMessageResult(HttpRequestMessage request, HttpResponseMessage httpResponseMessage)
{
_request = request;
_httpResponseMessage = httpResponseMessage;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_httpResponseMessage);
}
}
and call it like,
public override void Handle(ExceptionHandlerContext context)
{
var result = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("Internal Server Error Occurred"),
ReasonPhrase = "Exception"
};
context.Result = new ErrorMessageResult(context.Request, result);
}
From GlobalExceptionHandler : ExceptionHandler
I have these controller methods in my web api, both of them accept HttpPost.
The Process() method receives a complex type parameter in the body.
The Install method receives a string parameter in the body.
The Process method is called successfully, but the Install method fails with error 404 - not found, I assume the routing is failing, but I just can't figure out what am I doing wrong...
[HttpPost]
[ResponseType(typeof(IProcessableObject))]
[Route("Workflow/Process")]
public IHttpActionResult Process([FromBody]SerializedObject request)
{
try
{
Type objectType = ResolveType(request.ObjectType);
IProcessableObject obj = (IProcessableObject)JsonSerializer.Deserialize(request.RawObject, objectType);
log.DebugFormat("Processing {0} with workflow {1}", objectType.Name, obj.WorkflowId);
var workflow = workflowController.Get(obj.WorkflowId, true);
var workflowProcessor = new WorkflowProcessor(obj, workflow);
if (workflowProcessor.Process())
return Ok(obj);
return InternalServerError();
}
catch (Exception ex)
{
log.Error(string.Format("Failed processing object {0}", request.ObjectType), ex);
return InternalServerError();
}
}
[HttpPost]
[ResponseType(typeof(int))]
[Route("Workflow/Install/{userName}")]
public IHttpActionResult Install(string userName, [FromBody]string xmlTemplate)
{
try
{
log.DebugFormat("User {0} is installing new workflow:{1}{2}", userName, Environment.NewLine, xmlTemplate);
var wf = workflowController.Install(xmlTemplate, userName);
if (wf == null)
return BadRequest();
return Ok(wf.WorkflowId);
}
catch (Exception ex)
{
log.Error("Failed installing workflow", ex);
return InternalServerError();
}
}
And from my MVC application I call them like this:
public static IProcessableObject Process(IProcessableObject obj, bool isProxy = false)
{
string requestURL = string.Concat(wfServiceUrl, "Workflow/Process");
var requestData = new SerializedObject
{
RawObject = JsonSerializer.Serialize(obj),
ObjectType = isProxy ? obj.GetType().BaseType.AssemblyQualifiedName : obj.GetType().AssemblyQualifiedName
};
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var result = client.UploadString(requestURL, JsonSerializer.Serialize(requestData));
return (IProcessableObject)JsonSerializer.Deserialize(result, isProxy ? obj.GetType().BaseType : obj.GetType());
}
}
public static int Install(string workflowTemplate, string userName)
{
string requestURL = string.Concat(wfServiceUrl, "Workflow/Install/", userName);
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var result = client.UploadString(requestURL, JsonSerializer.Serialize(workflowTemplate));
return JsonSerializer.Deserialize<int>(result);
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
}
}
Try changing this:
[Route("Workflow/Install/{userName}")]
For this:
[Route("api/Workflow/Install/{userName}")]
And do the same with your other routes, add api/ and that should work.
Hello I have encountered very interesting bug, and can't understand why is this happening. I call GetAllDocuments() method to api from my other project via GetAsync method of httpclient. But the problem is GetAllDocuments returns and then it gets called again! GetAsync returns result after GetAllDocuments returns twice.
Here is the calling method :
public static async Task<Document> GetAllDocuments()
{
try
{
var response = _client.GetAsync("api/documents/GetAllDocuments").Result;
response.Content.LoadIntoBufferAsync().Wait();
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<Document>(new[] { new JsonMediaTypeFormatter() }); ;
// Return the URI of the created resource.
}
catch (Exception ex)
{
return null;
}
}
ApiController method :
[HttpGet]
public List<Document> GetAllDocuments()
{
lock (_lock)
{
_documentsRepository = DocumentsRepository.Instance;
var result = _documentsRepository.GetDocuments();
return result;
}
}
WebConfig :
public static class WebApiConfig
{
private static HttpSelfHostServer _server;
public static void Run(string port)
{
var config = new HttpSelfHostConfiguration($"http://localhost:{port}");//"http://localhost:8080");
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Routes.MapHttpRoute(
"API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(
"newdocument", "api/documents/newdocument/{document}",new { document = RouteParameter.Optional });
config.Routes.MapHttpRoute(
"GetAllDocuments", "api/documents/GetAllDocuments/");
config.MaxReceivedMessageSize = int.MaxValue;;
config.MaxBufferSize = int.MaxValue;
_server = new HttpSelfHostServer(config);
_server.OpenAsync();
}
public static void Stop()
{
_server?.CloseAsync();
}
}
I have a custom exception filter capable handle all the errors in the controller( just a common error handling mechanism) ,
public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var error = actionExecutedContext.Exception;
if (error is BussinessExcetion)
{
var exceptionBase = (BussinessExcetion)error;
var code = (HttpStatusCode)exceptionBase.HttpExceptionCode;
throw new HttpResponseException(new HttpResponseMessage(code)
{
Content = new StringContent(exceptionBase.Message),
ReasonPhrase = "Exception"
,
});
}
// Now log the error
/* Error logging */
LoggingFactory.GetLogger().LogError(string.Format("Exception:{0} ||Stack trace:{1}", error.Message, error.StackTrace), error);
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("An error occurred, contact the support team."),
ReasonPhrase = "Critical Exception"
});
}
}
I registered this filter in fillterConfig file
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ExceptionHandlingAttribute());
filters.Add(new HandleErrorAttribute());
}
but i am getting an error
The given filter instance must implement one or more of the following filter interfaces: IAuthorizationFilter, IActionFilter, IResultFilter, IExceptionFilter
I know the ExceptionFilterAttribute already implimented IExceptionFilter filter. Why i am getting this error
In order for this to work, you need to implement System.Web.Http.Filters.ExceptionFilterAttribute.
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
{
log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public override void OnException(HttpActionExecutedContext context)
{
RequestData requestData = new RequestData(context.Request);
log.Error("NotImplExceptionFilterAttribute", context.Exception);
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
Then, in your WebApiConfig.cs, register the filter:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{count}",
defaults: new { count = RouteParameter.Optional }
);
config.Filters.Add(new NotImplExceptionFilterAttribute());
}
And for anyone using NLog:
public class NlogExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext filterContext)
{
Exception lastException = filterContext.Exception;
Logger logger = LogManager.GetLogger("");
logger.Fatal(lastException.Message, lastException);
}
}
And in the WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new NlogExceptionFilter());
...
}
make sure you also override the Async version of the filter
class myFilter : ExceptionFilterAttribute
{
...
public override async Task OnExceptionAsync(HttpActionExecutedContext ctx,
CancellationToken cancellationToken)
{
...
}
...
}
Doh! If you're like me, and were handling the exception in your controller, then you would not be triggering the filter. Inside your catch block, place a throw. Voila!
This is a follow-up on an earlier question regarding using HttpClient with Web API performing authentication using a custom Message Handler.
I can request data from the server using the provided solution, but now I am having trouble posting JSON data to the server. Whenever I try posting data to the Web API I am returned an Internal Server Error response code.
Here is the code on the client side:
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage();
request.Headers.Add("X-Token", UserSession.GlobalInstance.SecurityToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(_apiBaseAddress + "api/User");
request.Content = new ObjectContent<UserDTO>(userDTO, new JsonMediaTypeFormatter());
var response = httpClient.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
// handle result code
}
throw new Exception(String.Format("Server generated error response: {0}", response.StatusCode));
}
The declaration for the controller method:
public class UserController : ApiController
{
public long Post(UserDTO userDTO)
{
// create user and return custom result
// code (e.g. success, duplicate email, etc...)
}
}
(I've also added [FromBody] to the method parameter, but end up with the same result).
A snapshot of the code for my message handler and routing configuration can be found here.
Your code works as expected...
The server side.
Create a console application and run NuGet
Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Program.cs
internal class Program
{
private static IDisposable _server;
private static void Main(string[] args)
{
_server = WebApp.Start<Startup>("http://localhost:12345");
Console.ReadLine();
_server.Dispose();
}
}
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var userTokenInspector = new UserTokenInspector {InnerHandler = new HttpControllerDispatcher(config)};
config.Routes.MapHttpRoute(
"UserAuthenticationApi",
"api/{controller}/Authenticate",
new {controller = "User", action = "Authenticate"},
null
);
config.Routes.MapHttpRoute(
"DefaultApi",
"api/{controller}/{id}",
new {id = RouteParameter.Optional},
null,
userTokenInspector
);
}
}
UserTokenInspector.cs
public class UserTokenInspector : DelegatingHandler {
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken) {
const string TOKEN_NAME = "X-Token";
if (!request.Headers.Contains(TOKEN_NAME)) {
return Task.FromResult(request.CreateErrorResponse(HttpStatusCode.Unauthorized,
"Request is missing authorization token."));
}
try {
//var token = UserToken.Decrypt(request.Headers.GetValues(TOKEN_NAME).First());
// validate token
// ...
// ...
Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity("alex"), new string[] { });
}
catch {
return Task.FromResult(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid token."));
}
return base.SendAsync(request, cancellationToken);
}
}
UserController.cs
public class UserController : ApiController
{
public long Post(UserDTO userDTO)
{
// create user and return custom result
// code (e.g. success, duplicate email, etc...)
return 1;
}
}
UserDto.cs
public class UserDTO
{
public string Username { get; set; }
}
ValuesController.cs
public class ValuesController : ApiController
{
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, "yay");
}
}
The Client... create a Console application and run NuGet:
Install-Package Microsoft.AspNet.WebApi.Client
Program.cs
internal class Program
{
private static void Main(string[] args)
{
var request = new HttpRequestMessage();
request.Headers.Add("X-Token", "token");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Method = HttpMethod.Post;
var baseAddress = "http://localhost:12345/";
request.RequestUri = new Uri(baseAddress + "api/User");
var userDto = new UserDTO() {Username = "Alex"};
request.Content = new ObjectContent<UserDTO>(userDto, new JsonMediaTypeFormatter());
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
// handle result code
Console.WriteLine(response.StatusCode);
Console.ReadLine();
}
}
}