Could not modify reason phrase on HttpResponse object in asp vNext - c#

I have to set the reason phrase in my asp 5 middle-ware, not just the status code for the http response messages, but I could not found any way to do it.
The StatusCode property of the Microsoft.AspNet.Http.HttpResponse class is an int property for modifying the status code only, but not the reason text.
I understand, that the reason is set automatically according to the status code, but in my case I have to set it to custom values. Which is OK according to the HTTP RFC 2616:
[..] The reason phrases listed here are only recommendations -- they MAY be replaced by local equivalents without affecting the protocol. [..]
I'm currently using beta-8 version of the asp 5 framework.

You could try:
Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = "some reason";
An HttpStatusCodeResult that also returns a reason could look like:
public class BadRequestResult : HttpStatusCodeResult
{
private readonly string _reason;
public BadRequestResult(string reason) : base(400)
{
_reason = reason;
}
public override void ExecuteResult(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
context.HttpContext.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = _reason;
context.HttpContext.Response.StatusCode = StatusCode;
}
}

Try this
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Title = "Home Page";
return View();
}
public ActionResult TestError() // id = error code
{
return new HttpStatusCodeResult(301, "Your custom error text");
}
}
Now check http://localhost:33470/home/Testerror
And look at Fiddler

Related

Force reload of ResponseCache in .NET Core 2.1 when request data changes

I am using the following attribute [ResponseCache(Duration = 60)] to cache a specific GET Request which is called a lot on my backend in .NET Core.
Everything is working fine except the cache isn't reloaded when some data in database has changed within the 60 seconds.
Is there a specific directive I have to set to reload/update the cache? link
Example Code Snippet from my Controller:
[HttpGet]
[ResponseCache(Duration = 60)]
public ActionResult<SomeTyp[]> SendDtos()
{
var dtos = _repository.QueryAll();
return Ok(dtos);
}
There is a solution with a usage of "ETag", "If-None-Match" HTTP headers. The idea is using a code which can give us an answer to the question: "Did action response changed?".
This can be done if a controller completely owns particular data lifetime.
Create ITagProvider:
public interface ITagProvider
{
string GetETag(string tagKey);
void InvalidateETag(string tagKey);
}
Create an action filter:
public class ETagActionFilter : IActionFilter
{
private readonly ITagProvider _tagProvider;
public ETagActionFilter(ITagProvider tagProvider)
{
_tagProvider = tagProvider ?? throw new ArgumentNullException(nameof(tagProvider));
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
{
return;
}
var uri = GetActionName(context.ActionDescriptor);
var currentEtag = _tagProvider.GetETag(uri);
if (!string.IsNullOrEmpty(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
var uri = GetActionName(context.ActionDescriptor);
var requestedEtag = context.HttpContext.Request.Headers["If-None-Match"];
var currentEtag = _tagProvider.GetETag(uri);
if (requestedEtag.Contains(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
context.Result = new StatusCodeResult(StatusCodes.Status304NotModified);
}
}
private string GetActionName(ActionDescriptor actionDescriptor)
{
return $"{actionDescriptor.RouteValues["controller"]}.{actionDescriptor.RouteValues["action"]}";
}
}
Initialize filter in Startup class:
public void ConfigureServices(IServiceCollection services)
{
// code above
services.AddMvc(options =>
{
options.Filters.Add(typeof(ETagActionFilter));
});
services.AddScoped<ETagActionFilter>();
services.AddSingleton<ITagProvider, TagProvider>();
// code below
}
Use InvalidateETag method somewhere in controllers (in the place where you modifing data):
[HttpPost]
public async Task<ActionResult> Post([FromBody] SomeType data)
{
// TODO: Modify data
// Invalidate tag
var tag = $"{controllerName}.{methodName}"
_tagProvider.InvalidateETag(tag);
return NoContent();
}
This solution may require a change of a client side. If you are using fetch, you can use, for example, the following library: https://github.com/export-mike/f-etag.
P.S. I didn't specify an implementation of the ITagProvider interface, you will need to write your own.
P.P.S. Articles about ETag and caching: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag

Forward values from custom HttpControllerSelector and HttpActionSelector to its descriptor

I have a MVC 5 Web API which returns a custom response in case of unexpected exceptions or if the controller or action were not found. Essentially, I've done exactly as shown there: http://weblogs.asp.net/imranbaloch/handling-http-404-error-in-asp-net-web-api Everything's working like a charm.
The problem is: I'd like to submit the error code from SelectController() and SelectAction() to my ErrorController. This way I would not have duplicate code and all the logic would be in the controller.
Unfortunately, I do not find any possible way to submit the error code to my controller. All the examples are redirecting to a specific error action (e.g. ErrorController.NotFound404) I'd like to redirect to ErrorController.Main and do all the magic there.
Another issue with the custom ApiControllerActionSelector is that the Request property is null in the ErrorController. This problem does not exist with the custom DefaultHttpControllerSelector.
Any ideas?
Best regards,
Carsten
Fortunately, I was able to find the solution myself. Let me show you how I got it up and running.
The custom controller and action selector are forwarding the requested language and the current HTTP response code:
public class CustomDefaultHttpControllerSelector: DefaultHttpControllerSelector
{
public CustomDefaultHttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
HttpControllerDescriptor descriptor = null;
try
{
descriptor = base.SelectController(request);
}
catch (HttpResponseException e)
{
var routeValues = request.GetRouteData().Values;
routeValues.Clear();
routeValues["controller"] = "Error";
routeValues["action"] = "Main";
routeValues["code"] = e.Response.StatusCode;
routeValues["language"] = request.Headers?.AcceptLanguage?.FirstOrDefault()?.Value ?? "en";
descriptor = base.SelectController(request);
}
return descriptor;
}
}
public class CustomControllerActionSelector: ApiControllerActionSelector
{
public CustomControllerActionSelector()
{
}
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
HttpActionDescriptor descriptor = null;
try
{
descriptor = base.SelectAction(controllerContext);
}
catch (HttpResponseException e)
{
var routeData = controllerContext.RouteData;
routeData.Values.Clear();
routeData.Values["action"] = "Main";
routeData.Values["code"] = e.Response.StatusCode;
routeData.Values["language"] = controllerContext.Request?.Headers?.AcceptLanguage?.FirstOrDefault()?.Value ?? "en";
IHttpController httpController = new ErrorController();
controllerContext.Controller = httpController;
controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "Error", httpController.GetType());
descriptor = base.SelectAction(controllerContext);
}
return descriptor;
}
}
Two important changes:
1.1. The list of route values needs to be cleared. Otherwise it tries to find an action in the ErrorController which maps to this list of values.
1.2. The code and language were added.
The ErrorController itself:
[RoutePrefix("error")]
public class ErrorController: BaseController
{
[HttpGet, HttpPost, HttpPut, HttpDelete, HttpHead, HttpOptions, AcceptVerbs("PATCH")]
[Route("{code}/{language}")]
public HttpResponseMessage Main(string code, string language)
{
HttpStatusCode parsedCode;
var responseMessage = new HttpResponseMessage();
if (!Enum.TryParse(code, true, out parsedCode))
{
parsedCode = HttpStatusCode.InternalServerError;
}
responseMessage.StatusCode = parsedCode;
...
}
}
I've removed the route mapping routes.MapHttpRoute(...). No matter what I've entered in the browser, it never called Handle404.
HTTP status 400 (bad request) was not covered, yet. This could be easily achieved by using the ValidationModelAttribute as described on http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api (section "Handling Validation Errors").
Maybe this will help someone...

MVC Controller return a bad request?

I was wondering if it was possible to return a bad request with content from an MVC Controller? The only way I have been able to do this is to throw HttpException however here I can't set any content. Tried this approach to but for some odd reason I am always getting an OK back. Is it possible to do this?
public class SomeController : Controller
{
[HttpPost]
public async Task<HttpResponseMessage> Foo()
{
var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
response.Content = new StringContent("Naughty");
return response;
}
}
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "naughty");
Set the Http status code to bad request and use Content method to send your content along with response.
public class SomeController : Controller
{
[HttpPost]
public async Task<ActionResult> Foo()
{
Response.StatusCode = 400;
return Content("Naughty");
}
}
In addition to the #Ekk's answer, make sure to check this:
ASP.NET+Azure 400 Bad Request doesn't return JSON data
Add the following entry to your 'web.config'.
<system.webServer>
<httpErrors existingResponse="PassThrough"/>
</system.webServer>
...
Of course you can.
Take a look at my Action
// GET: Student/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Student student = db.Students.Find(id);
if (student == null)
{
return HttpNotFound();
}
return View(student);
}
I think this is best practice
to return HttpStatusCodeResult(HttpStatusCode.BadRequest); in case user does not provided a required value
to return HttpNotFound(); in case the user provided a required value but not veiled
hope this help you
You can pass in error message to the second parameter like so:
return new HttpResponseMessage(HttpStatusCode.BadRequest, "Your message here");
The TrySkipIisCustomErrors flag can be used to turn off IIS custom error handling.
[HttpGet]
public void Foo()
{
HttpContext.Response.TrySkipIisCustomErrors = true;
HttpContext.Response.StatusCode = 400;
HttpContext.Response.Write("Naughty");
}
Answer for .Net Core: Return IActionResult as documented here. For example:
public IActionResult Get(int id = 0)
{
try
{
var obj = _myRepo.Get(id);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
return Ok(obj);
}

Return JSON with error status code MVC

I was trying to return an error to the call to the controller as advised in
This link so that client can take appropriate action.
The controller is called by javascript via jquery AJAX. I am getting the Json object back only if I don't set the status to error.
Here is the sample code
if (response.errors.Length > 0)
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(response);
I get the Json if I don't set the statuscode.
If I set the status code I get the status code back but not the Json error object.
Update
I want to send an Error object as JSON so that it can be handled error callback of ajax.
The neatest solution I've found is to create your own JsonResult that extends the original implementation and allows you to specify a HttpStatusCode:
public class JsonHttpStatusResult : JsonResult
{
private readonly HttpStatusCode _httpStatus;
public JsonHttpStatusResult(object data, HttpStatusCode httpStatus)
{
Data = data;
_httpStatus = httpStatus;
}
public override void ExecuteResult(ControllerContext context)
{
context.RequestContext.HttpContext.Response.StatusCode = (int)_httpStatus;
base.ExecuteResult(context);
}
}
You can then use this in your controller action like so:
if(thereWereErrors)
{
var errorModel = new { error = "There was an error" };
return new JsonHttpStatusResult(errorModel, HttpStatusCode.InternalServerError);
}
I found the solution here
I had to create a action filter to override the default behaviour of MVC
Here is my exception class
class ValidationException : ApplicationException
{
public JsonResult exceptionDetails;
public ValidationException(JsonResult exceptionDetails)
{
this.exceptionDetails = exceptionDetails;
}
public ValidationException(string message) : base(message) { }
public ValidationException(string message, Exception inner) : base(message, inner) { }
protected ValidationException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context) { }
}
Note that I have constructor which initializes my JSON. Here is the action filter
public class HandleUIExceptionAttribute : FilterAttribute, IExceptionFilter
{
public virtual void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.Exception != null)
{
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
filterContext.Result = ((ValidationException)filterContext.Exception).myJsonError;
}
}
Now that I have the action filter, I will decorate my controller with the filter attribute
[HandleUIException]
public JsonResult UpdateName(string objectToUpdate)
{
var response = myClient.ValidateObject(objectToUpdate);
if (response.errors.Length > 0)
throw new ValidationException(Json(response));
}
When the error is thrown the action filter which implements IExceptionFilter get called and I get back the Json on the client on error callback.
There is a very elegant solution to this problem, just configure your site via web.config:
<system.webServer>
<httpErrors errorMode="DetailedLocalOnly" existingResponse="PassThrough"/>
</system.webServer>
Source: https://serverfault.com/questions/123729/iis-is-overriding-my-response-content-if-i-manually-set-the-response-statuscode
A simple way to send a error to Json is control Http Status Code of response object and set a custom error message.
Controller
public JsonResult Create(MyObject myObject)
{
//AllFine
return Json(new { IsCreated = True, Content = ViewGenerator(myObject));
//Use input may be wrong but nothing crashed
return Json(new { IsCreated = False, Content = ViewGenerator(myObject));
//Error
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return Json(new { IsCreated = false, ErrorMessage = 'My error message');
}
JS
$.ajax({
type: "POST",
dataType: "json",
url: "MyController/Create",
data: JSON.stringify(myObject),
success: function (result) {
if(result.IsCreated)
{
//... ALL FINE
}
else
{
//... Use input may be wrong but nothing crashed
}
},
error: function (error) {
alert("Error:" + erro.responseJSON.ErrorMessage ); //Error
}
});
Building on the answer from Richard Garside, here's the ASP.Net Core version
public class JsonErrorResult : JsonResult
{
private readonly HttpStatusCode _statusCode;
public JsonErrorResult(object json) : this(json, HttpStatusCode.InternalServerError)
{
}
public JsonErrorResult(object json, HttpStatusCode statusCode) : base(json)
{
_statusCode = statusCode;
}
public override void ExecuteResult(ActionContext context)
{
context.HttpContext.Response.StatusCode = (int)_statusCode;
base.ExecuteResult(context);
}
public override Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = (int)_statusCode;
return base.ExecuteResultAsync(context);
}
}
Then in your controller, return as follows:
// Set a json object to return. The status code defaults to 500
return new JsonErrorResult(new { message = "Sorry, an internal error occurred."});
// Or you can override the status code
return new JsonErrorResult(new { foo = "bar"}, HttpStatusCode.NotFound);
The thing that worked for me (and that I took from another stackoverflow response), is to set the flag:
Response.TrySkipIisCustomErrors = true;
You have to return JSON error object yourself after setting the StatusCode, like so ...
if (BadRequest)
{
Dictionary<string, object> error = new Dictionary<string, object>();
error.Add("ErrorCode", -1);
error.Add("ErrorMessage", "Something really bad happened");
return Json(error);
}
Another way is to have a JsonErrorModel and populate it
public class JsonErrorModel
{
public int ErrorCode { get; set;}
public string ErrorMessage { get; set; }
}
public ActionResult SomeMethod()
{
if (BadRequest)
{
var error = new JsonErrorModel
{
ErrorCode = -1,
ErrorMessage = "Something really bad happened"
};
return Json(error);
}
//Return valid response
}
Take a look at the answer here as well
You need to decide if you want "HTTP level error" (that what error codes are for) or "application level error" (that what your custom JSON response is for).
Most high level objects using HTTP will never look into response stream if error code set to something that is not 2xx (success range). In your case you are explicitly setting error code to failure (I think 403 or 500) and force XMLHttp object to ignore body of the response.
To fix - either handle error conditions on client side or not set error code and return JSON with error information (see Sbossb reply for details).
Several of the responses rely on an exception being thrown and having it handled in the OnException override. In my case, I wanted to return statuses such as bad request if the user, say, had passed in a bad ID. What works for me is to use the ControllerContext:
var jsonResult = new JsonResult { JsonRequestBehavior = JsonRequestBehavior.AllowGet, Data = "whoops" };
ControllerContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
return jsonResult;
And if your needs aren't as complex as Sarath's you can get away with something even simpler:
[MyError]
public JsonResult Error(string objectToUpdate)
{
throw new Exception("ERROR!");
}
public class MyErrorAttribute : FilterAttribute, IExceptionFilter
{
public virtual void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (filterContext.Exception != null)
{
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
filterContext.Result = new JsonResult() { Data = filterContext.Exception.Message };
}
}
}
If you are just using MVC the simplest way is to use HttpStatusCodeResult.
public ActionResult MyAjaxRequest(string args)
{
string error_message = string.Empty;
try
{
// successful
return Json(args);
}
catch (Exception e)
{
error_message = e.Message;
}
return new HttpStatusCodeResult(500, error_message);
}
When the error is returned to the client you can display it or action it how you like.
request.fail(function (jqXHR) {
if (jqXHR.status == 500) {
alert(jqXHR.statusText);
}
})
I was running Asp.Net Web Api 5.2.7 and it looks like the JsonResult class has changed to use generics and an asynchronous execute method. I ended up altering Richard Garside's solution:
public class JsonHttpStatusResult<T> : JsonResult<T>
{
private readonly HttpStatusCode _httpStatus;
public JsonHttpStatusResult(T content, JsonSerializerSettings serializer, Encoding encoding, ApiController controller, HttpStatusCode httpStatus)
: base(content, serializer, encoding, controller)
{
_httpStatus = httpStatus;
}
public override Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var returnTask = base.ExecuteAsync(cancellationToken);
returnTask.Result.StatusCode = HttpStatusCode.BadRequest;
return returnTask;
}
}
Following Richard's example, you could then use this class like this:
if(thereWereErrors)
{
var errorModel = new CustomErrorModel("There was an error");
return new JsonHttpStatusResult<CustomErrorModel>(errorModel, new JsonSerializerSettings(), new UTF8Encoding(), this, HttpStatusCode.InternalServerError);
}
Unfortunately, you can't use an anonymous type for the content, as you need to pass a concrete type (ex: CustomErrorType) to the JsonHttpStatusResult initializer. If you want to use anonymous types, or you just want to be really slick, you can build on this solution by subclassing ApiController to add an HttpStatusCode param to the Json methods :)
public abstract class MyApiController : ApiController
{
protected internal virtual JsonHttpStatusResult<T> Json<T>(T content, HttpStatusCode httpStatus, JsonSerializerSettings serializerSettings, Encoding encoding)
{
return new JsonHttpStatusResult<T>(content, httpStatus, serializerSettings, encoding, this);
}
protected internal JsonHttpStatusResult<T> Json<T>(T content, HttpStatusCode httpStatus, JsonSerializerSettings serializerSettings)
{
return Json(content, httpStatus, serializerSettings, new UTF8Encoding());
}
protected internal JsonHttpStatusResult<T> Json<T>(T content, HttpStatusCode httpStatus)
{
return Json(content, httpStatus, new JsonSerializerSettings());
}
}
Then you can use it with an anonymous type like this:
if(thereWereErrors)
{
var errorModel = new { error = "There was an error" };
return Json(errorModel, HttpStatusCode.InternalServerError);
}
Here is the JsonResult override answer for ASP.NET v5+ . I have tested and it works just as well as in earlier versions.
public class JsonHttpStatusResult : JsonResult
{
private readonly HttpStatusCode _httpStatus;
public JsonHttpStatusResult(object data, HttpStatusCode httpStatus) : base(data)
{
_httpStatus = httpStatus;
}
public override Task ExecuteResultAsync(ActionContext context)
{
context.HttpContext.Response.StatusCode = (int)_httpStatus;
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var services = context.HttpContext.RequestServices;
var executor = services.GetRequiredService<IActionResultExecutor<JsonResult>>();
return executor.ExecuteAsync(context, this);
}
}

How to Display a Status Message on the Next Page after a Redirect with .net Framework?

I am adding a form to my .net web forms application and I want to redirect the user to another page but display a status message after the redirect such as 'Your articles has been submitted successfully'.
Any good ways of doing this?
I was thinking of doing it with sessions and a user control but wanted to see if there is an easier way.
Thought about code like this:
User control codebehind:
public String SessionName { get; set; }
public String Message
{
get
{
if (Session[SessionName] == null)
return String.Empty;
return Session[SessionName].ToString();
}
}
protected void Page_Unload(object sender, EventArgs e)
{
Session[SessionName] = null;
}
User control markup:
<% if (!String.IsNullOrEmpty(Message))
{%>
<div>
<%= Message %>
</div>
<%} %>
No, saving it in session and then reading on another page is the way to go.
What if your redirection included a query-string parameter that the destination page recognized and selected the right message? That avoids session altogether.
Landed on this post when trying to figure out how to pass the default StatusMessage field you get in net6 web apps (Razor Pages in this case).
I got it working with session as suggested above. However, I realised that in my case it was utterly unnecessary: there is a [TempData] attribute you can stick on the StatusMessage property of one page that will store it in memory (I'm guessing) and delete it after you read it.
Therefore, all you need to do is have the field defined in both the caller page and the receiving page and just set it on the caller page - the receiving page will find the value you set on the caller when you try to read it on the html side.
Next time you try to read it it will have been deleted (so you don't keep showing the same message over and over).
Example.
Caller page post call:
public class Create : PageModel
{
private readonly IMyService _service;
public Create(IMyService service)
{
_service = service;
}
[TempData] public string StatusMessage { get; set; } = string.Empty;
public async Task<IActionResult> OnPostAsync()
{
var model = new SomeModel();
try
{
await _service.Create(model);
}
catch(Exception e)
{
StatusMessage = $"Error: {e.Message}";
return Page();
}
StatusMessage = $"Model created successfully";
return RedirectToPage("/SomeModels/Index");
}
}
Receiving page:
public class Index : PageModel
{
[TempData] public string StatusMessage { get; set; } = string.Empty;
public readonly List<ExerciseCategory> Models;
public async Task<IActionResult> OnGet()
{
var models = await _service.Get();
Models.AddRange(models);
return Page();
}
}

Categories

Resources