I am using ASP.NET Web API, after throwing exception, I am able to catch it in GlobalExceptionHandler, but I get CORS error and can't enter App_error. I tried multiple solutions, nothing is working, right now I have this flow.
Custom Exception is thrown in controller, then we enter GlobalExceptionHandler:
public class GlobalExceptionHandler : IExceptionHandler
{
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
ExceptionDispatchInfo.Capture(context.Exception).Throw();
return Task.CompletedTask;
}
}
And it is not going further, I get CORS on front.
Any solution?
You could do something like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;
namespace Web.API.Handler
{
public class ErrorHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new TextPlainErrorResult()
{
Request = context.ExceptionContext.Request,
Content = "Oops! Sorry! Something went wrong." + "Please contact support so we can try to fix it."
};
}
private class TextPlainErrorResult : IHttpActionResult
{
public HttpRequestMessage Request { get; set; }
public string Content { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.InternalServerError, Content);
//CORS Support
if (!response.Headers.Contains("Access-Control-Allow-Origin"))
response.Headers.Add("Access-Control-Allow-Origin", "*");
return Task.FromResult(response);
}
}
}
}
I managed to resolve problem by doing everything inside lifter, so App_error is not used anymore
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly Logger m_Logger = LogManager.GetCurrentClassLogger();
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
var exception = context.Exception;
var statusCode = HttpStatusCode.InternalServerError;
if (exception != null)
{
var errorMessage = exception.Message;
if (exception is BaseException be)
{
statusCode = be.StatusCode;
}
m_Logger.Error(exception, context.Request.RequestUri.AbsoluteUri);
var response = context.Request.CreateResponse(statusCode, new { errorMessage });
context.Result = new ResponseMessageResult(response);
}
return Task.CompletedTask;
}
}
Related
My ModelState is getting false everytime i run the code .This is simply a file upload mvc .net core code. Migration is also perfectly executed. However whenever i try to submit the form after uploading an image the form gets reset. Due to which it failed to get store in the database.
Model code (Image.cs)
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace FileUpload.Models
{
public class Image
{
[Key]
public int Iid { get; set; }
[Required]
public string Iname { get; set; }
[Required]
[NotMapped]
public IFormFile Iimg { get; set; }
}
}
Controller Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using FileUpload.Data;
using FileUpload.Models;
using Microsoft.AspNetCore.Hosting;
using System.IO;
namespace FileUpload.Controllers
{
public class ImagesController : Controller
{
private readonly AppDbContext _context;
private readonly IWebHostEnvironment _hostEnvironment;
public ImagesController(AppDbContext context, IWebHostEnvironment hostEnvironment)
{
_context = context;
_hostEnvironment = hostEnvironment;
}
// GET: Images
public async Task<IActionResult> Index()
{
return View(await _context.Images.ToListAsync());
}
// GET: Images/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var image = await _context.Images
.FirstOrDefaultAsync(m => m.Iid == id);
if (image == null)
{
return NotFound();
}
return View(image);
}
// GET: Images/Create
public IActionResult Create()
{
return View();
}
// POST: Images/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Image image)
{
if (ModelState.IsValid)
{
if(image.Iimg != null)
{
string wwwRootPath = _hostEnvironment.WebRootPath;
string fileName = Path.GetFileNameWithoutExtension(image.Iimg.FileName);
string ext = Path.GetExtension(image.Iimg.FileName);
image.Iname = fileName + DateTime.Now.ToString("yymmssfff") + ext;
string path = Path.Combine(wwwRootPath + "/Image/" + fileName);
using (var fileStream = new FileStream(path, FileMode.Create))
{
await image.Iimg.CopyToAsync(fileStream);
}
}
_context.Add(image);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(image);
}
}
Using ASP.NET WebApi 2,
Why can't I catch HttpRequestValidationException in my Global ExceptionHandler or Global ExceptionLogger?
Error is: [HttpRequestValidationException (0x80004005): A potentially dangerous Request.QueryString value was detected from the client...]
Using Application_Error works fine, HttpRequestValidationException can be caught fine.
WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
//...stuffs
//Global exception handler
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
//add global error logger
config.Services.Add(typeof(IExceptionLogger), new GlobalExceptionLogger());
}
public class GlobalExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
//This does not handle HttpRequestValidationException
Exception exception = context.ExceptionContext.Exception;
//.....
}
}
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
//This does not handle HttpRequestValidationException either ...
var result = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("An unexpected error occured. Please notify your administrator"),
ReasonPhrase = "Unexpected Error"
};
context.Result = new UnhandledExceptionResult(context.Request, result);
}
public class UnhandledExceptionResult : IHttpActionResult
{
private HttpRequestMessage _request;
private HttpResponseMessage _httpResponseMessage;
public UnhandledExceptionResult(HttpRequestMessage request, HttpResponseMessage httpResponseMessage)
{
_request = request;
_httpResponseMessage = httpResponseMessage;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_httpResponseMessage);
}
}
}
I'm using the latest version of WebApi and OData and everything is set up to work right. The only problem is when I try to use $select .
It throws the error bellow
Object of type 'System.Linq.EnumerableQuery`1[System.Web.OData.Query.Expressions.SelectExpandBinder+SelectAll`1[WebApplication1.Controllers.Person]]' cannot be converted to type 'System.Collections.Generic.IEnumerable`1[WebApplication1.Controllers.Person]'.
I looked at the documentation and their suggestion is to use [Queryable] on top of the Get method in the controller or the in WebApiConfig to use config.EnableQuerySupport and neither of these are available options. I'm currently using [EnableQuery]
EDIT
OdataController:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.OData;
using System.Xml.Serialization;
namespace WebApplication1.Controllers
{
public class PeopleController : ODataController
{
// GET api/values
[EnableQuery]
public IQueryable<Person> Get()
{
return new Person[] { new Person()
{
Id = 1,
FirstName = "Testing",
LastName = "2"
}, new Person()
{
Id = 2,
FirstName = "TestTest",
LastName = "3"
} }.AsQueryable();
}
// GET api/values/5
public Person Get(int id)
{
return new Person()
{
Id = 3,
FirstName = "Test",
LastName = "1"
};
}
// POST api/values
public void Post([FromBody]Person value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]Person value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
public class Person
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
WebApiConfig
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using System.Web.OData.Formatter;
using WebApplication1.Controllers;
namespace WebApplication1
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
var odataFormatters = ODataMediaTypeFormatters.Create();
config.Formatters.InsertRange(0, odataFormatters);
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Person>("People");
config.AddODataQueryFilter();
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "api",
model: builder.GetEdmModel());
}
}
}
UPDATE 2
seems to only throw an error retrieving the data in xml format. Json seems to work
This is a known limitation of the XmlMediaTypeFormatter class from the System.Net.Formatting Nuget package. The implementation of the JSON formatter does support the $select and $expand commands but these are not available when content negotiation determines that XML should be returned.
You should look into implementing OData endpoints (as opposed to WebAPI endpoints) should you need to return XML formatted responses. More information on how this can be done can be found here:
http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options
Found a solution. It isn't perfect but it does work!
Maybe it will be useful for someone because I've spent on it few hours of research and trying.
Step #1 create custom xml formatter:
public class CustomXmlFormatter : MediaTypeFormatter
{
private JsonMediaTypeFormatter jFormatter = null;
public CustomXmlFormatter(JsonMediaTypeFormatter jFormatter)
{
SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/xml"));
this.jFormatter = jFormatter;
}
public override bool CanReadType(Type type)
{
return false;
}
public override bool CanWriteType(Type type)
{
return true;
}
public override Task WriteToStreamAsync(Type type, object value, System.IO.Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
{
return Task.Factory.StartNew(() =>
{
using (MemoryStream ms = new MemoryStream())
{
var tsk = jFormatter.WriteToStreamAsync(type, value, ms, content, transportContext);
tsk.Wait();
ms.Flush();
ms.Seek(0, SeekOrigin.Begin);
var xDoc = XDocument.Load(JsonReaderWriterFactory.CreateJsonReader(ms, new XmlDictionaryReaderQuotas()));
using (XmlWriter xw = XmlWriter.Create(writeStream))
{
xDoc.WriteTo(xw);
}
}
});
}
}
Step #2 register it in startup section:
var formatters = ODataMediaTypeFormatters.Create();
var jsonFormatter = config.Formatters.JsonFormatter;
var customXmlFormatter = new CustomXmlFormatter(jsonFormatter);
customXmlFormatter.AddQueryStringMapping("$format", "cxml", "application/xml");
config.Formatters.Add(customXmlFormatter);
use it as
http://url..../actionName?$format=cxml&$select=ObjectName,ObjectId
I use the code below to throttle my ASP.NET Web Api:
public class Throttle : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext context, CancellationToken cancellationToken)
{
// ...
if (throttle)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Conflict));
}
}
}
However, I cannot return error code 429, because it's not in HttpStatusCode enum. Is there a way to return a custom error code?
I found this over here.
var response = new HttpResponseMessage
{
StatusCode = (HttpStatusCode)429,
ReasonPhrase = "Too Many Requests",
Content = new StringContent(string.Format(CultureInfo.InvariantCulture, "Rate limit reached. Reset in {0} seconds.", data.ResetSeconds))
};
response.Headers.Add("Retry-After", data.ResetSeconds.ToString(CultureInfo.InvariantCulture));
actionContext.Response = response;
Hope this helps
This is what I did based on another response on StackOverflow.
Create Class (in controller file worked for me)
public class TooManyRequests : IHttpActionResult
{
public TooManyRequests()
{
}
public TooManyRequests(string message)
{
Message = message;
}
public string Message { get; private set; }
public HttpResponseMessage Execute()
{
HttpResponseMessage response = new HttpResponseMessage((HttpStatusCode)429);
if (!string.IsNullOrEmpty(Message))
{
response.Content = new StringContent(Message); // Put the message in the response body (text/plain content).
}
return response;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}
}
Use in controller
public IHttpActionResult Get()
{
// with message
return new TooManyRequests("Limited to 5 request per day. Come back tomorrow.");
// without message
// return new TooManyRequests();
}
I try to customize AuthorizeAttribute for authentication at restful service. I want to write information into a log file. But it didn't work. I don't see any log file at my log path. (I have enable permission to IIS_USER)
Also, I even found that if AuthorizeCore return false, my test client can still get the result. Is somewhere in my code wrong? Or something I misunderstand how AuthorizeAttribute work?
PS. I also found that if I switch log part to ApiController it will work! It's so weird!
Filter
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
using (StreamWriter sw = new StreamWriter(#"C:\inetpub\wwwroot\mvctest\logs\Trace.log", true, Encoding.UTF8))
{
IEnumerable<String> header = httpContext.Request.Headers.GetValues("User");
foreach (String str in header)
{
sw.WriteLine(str);
}
sw.Flush();
}
return base.AuthorizeCore(httpContext);
}
}
ApiController
public class ValuesController : ApiController
{
// GET api/values
[MyAuthorizeAttribute]
public List<String> Get()
{
return new List<String> { "Success", "Get" };
}
// GET api/values/5
[MyAuthorizeAttribute]
public String Get(int id)
{
return "Success";
}
}
TestClient
static void Main(string[] args)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost/mvctest/api/values/5");
request.Headers.Add("User", "Darkurvivor");
using (WebResponse response = request.GetResponse())
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
String data = sr.ReadToEnd();
Console.WriteLine(data);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.ReadKey();
}
There are two AuthorizeAttributes. One for WebApi and one for MVC. WebApi uses System.Web.http.AuthorizeAttribute, while MVC uses System.Web.Mvc.AuthorizeAttribute. You canoot use one with the other. So, no. It's not weird at all.