Slim3 how can I manage mixed content type errors - http-headers

I need to set up a Slim application for html and json contents.
I will have just one errorhandler and it is supposed to reply as json for json enpoints and html error page for html views.
In the old fashined Slim (v.2) I have defined the view at the begining of the route, so I could check the view type (twig or json) to understand how to reply.
With the new Slim3 implementation the view will be send at the end of the route and, as far as I know, there is no way to define it earlier.
How can I manage this mixed content errors?
I thought to use the request content type header, but there is not a real rule that the content type should be coherent with the response, for example I can send some request as application/json and get a text/html reply, I also cannot use the Accept header because it can be missing or general */*.

I guess it depends how you decide, between returning json format or html.
For example, you may return error in json format when it is requested from AJAX otherwise you return html.
If you have error handler that returns html like following code
<?php namespace Your\App\Name\Space\Errors;
class HtmlErrorHandler
{
public function __invoke($request, $response, $exception)
{
$err = '<html><head></head><body>' .
'<div>code : ' . $exception->getCode() .
' message' . $exception->getMessage() '</div>';
return $response->withStatus(500)
->withHeader('content-Type : text/html')
->write($err);
}
}
and error handler that returns json,
<?php namespace Your\App\Name\Space\Errors;
class JsonErrorHandler
{
public function __invoke($request, $response, $exception)
{
$err = (object) [
'code' => $exception->getCode(),
'message' => $exception->getMessage(),
];
return $response->withStatus(500)->withJson($err);
}
}
You can compose them in another error handler class which select which error handler to return error response based on whether request coming from AJAX for not.
<?php namespace Your\App\Name\Space\Errors;
class Http500Error
{
private $jsonErrorHandler;
private $htmlErrorHandler;
public function __construct($jsonErrHandler, $htmlErrHandler)
{
$this->jsonErrorHandler = $jsonErrHandler;
$this->htmlErrorHandler = $htmlErrHandler;
}
public function __invoke($request, $response, $exception)
{
if ($request->isXhr()) {
$errHandler = $this->jsonErrorHandler;
} else {
$errHandler = $this->htmlErrorHandler;
}
return $errHandler($request, $response, $exception);
}
}
Or you may put certain variable in query string to indicate which format client want, then you can use
if ($request->getParam('format', 'html') === 'json') {
$errHandler = $this->jsonErrorHandler;
} else {
$errHandler = $this->htmlErrorHandler;
}
Then you injects error handler to container
use Your\App\Name\Space\Errors;
...
$app = new \Slim\App();
$c = $app->getContainer();
$c['errorHandler'] = function ($c) {
return new Http500Error(new JsonErrorHandler(), new HtmlErrorHandler());
};

Related

How to get a custom ModelState error message in ASP.NET Core when a wrong enum value is passed in?

I'm passing a model to an API action with a property called eventType which is a nullable custom enum.
If I pass a random value for eventType, such as 'h', it fails to serialise which is correct.
However, the error I get from the ModelState is not something I would want a public caller to see. It includes the line number and position (see below).
I've tried a number of options including a custom data annotation with no success.
Does anyone know how I could define a nicer custom message?
"Error converting value \"h\" to type
'System.Nullable`1[Custom.EventTypes]'. Path 'eventType', line 1,
position 80."
Most times the first error is usually the most important error or rather one that describes the situation properly. You can use this way to manipulate to get the first error message from the first key or change it to whatever you want if you wish to get all the error messages.
public ActionResult GetMyMoney(MyModel myModel)
{
string nss = ModelState.First().Key;
ModelError[] ern = ModelState[nss].Errors.ToArray();
string ndd = ern.First().ErrorMessage;
}
public class CustomFilter: IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
// You can pass custom object to BadRequestObjectResult method
context.Result = new BadRequestObjectResult(customObject);
}
}
}
You can write a custom filter like above mentioned and pass a custom object with your message.
Ref: this
IF you just want the error messages you can simply create a custom class of response and then
var response = new ResponseApi{
StatusCode = HttpStatusCode.BadRequest,
Message = "Validation Error",
Response = ModelState.Values.SelectMany(x => x.Errors).Select(x =>
x.ErrorMessage)
};
then just return the response or create a validation filter to handle validations globally.
/// <summary>
/// Validatation filter to validate all the models.
/// </summary>
public class ValidationActionFilter : ActionFilterAttribute
{
/// <inheritdoc/>
public override void OnActionExecuting(HttpActionContext actionContext)
{
ModelStateDictionary modelState = actionContext.ModelState;
if (!modelState.IsValid)
{
actionContext.Response = SendResponse(new ResponseApi
{
StatusCode= 400,
Message = "Validation Error",
Response = modelState.Values.SelectMany(x =>
x.Errors).Select(x => x.ErrorMessage)
});
}
}
private HttpResponseMessage SendResponse(ResponseApiresponse)
{
var responseMessage = new HttpResponseMessage
{
StatusCode = (HttpStatusCode)response.StatusCode,
Content = new StringContent(JsonConvert.SerializeObject(response)),
};
responseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return responseMessage;
}
}

Yii call CButtonColumn from other widget

i have created new widget to display information in admin view. Final view must be same as CGridView, but with different logic for columns. Everything works fine, except when i try to call CButtonColumn column.
foreach ($this->columns as $column) {
if (is_array($column) && isset($column['class']) {
$this->renderColumnWidget($column);
}
}
/* ... */
protected function renderColumnWidget($column)
{
$widgetClass = $column->class;
unset($column->class);
if (strpos($widgetClass, '.') === false) {
$widgetClass = 'zii.widgets.grid.'.$widgetClass;
}
$this->widget($widgetClass, $column); // Error from here
}
So basically here i check if there is class attribute in column and call that widget. But i get error: CButtonColumn and its behaviors do not have a method or closure named "run".
What am i doing wrong? CButtonColumn don't have run method, and i don't want to extend this class.
You this as a function like this to initiate your columns
protected function initColumns(){
foreach($this->columns as $i=>$column) {
if(is_string($column))
$column=$this->createDataColumn($column);
else {
if(!isset($column['class']))
$column['class']='CDataColumn';
$column=Yii::createComponent($column, $this);
}
if($column->id===null)
$column->id=$id.'_c'.$i;
$this->columns[$i]=$column;
}
foreach($this->columns as $column)
$column->init();
}

MVC 4 How to get json response from a repository into a view using Ajax?

I am a newbie when it comes to MVC4 Web Development and there's something I am struggling with.
Basically, I have the following :
public class maincontroller: Controller
{
private MyRepository myRepository;
public mainController()
{
myRepository= new MyRepository();
}
public ActionResult Index()
{
var mystuff = myRepository.GetPrograms();
return View(mystuff);
}
public ActionResult MyStuff()
{
var mystuff = myRepository.GetStuff(1);
return Json(mystuff , JsonRequestBehavior.AllowGet);
}
}
Assuming that in my `MyRepository' class I have two functions:
One that is setting up `mystuff':
public MyRepository()
{
for (int i = 0; i < 8; i++)
{
programs.Add(new MyStuff
{
Title = "Hello" + i,
content = "Hi"
});
}
}
and second function that gets Stuff:
public List<MyStuff> GetStuff(int pageNumber = 0)
{
return stuff
.Skip(pageNumber * pageCount)
.Take(pageCount).ToList();
}
All works well. I mean I am able to iterate through `stuff' and display on a view...
The problem is that I want to display MyStuff() ( which returns Json ) using AJAX and then append all stuff to a view. How do I do that?
I have been beating my head against the wall for about 4 hours now, and can't get this working.
Please any help will be much appreciated.
Thank you.
At the most straightforward level, you can simply append HTML to your document using something like this (assuming you're using JQuery, because it's so much easier):
<div id="container"></div>
// make AJAX call to "MyStuff" action in the current controller
$.get(#Url.Action("MyStuff", function(data) {
// cycle through each item in the response
$.each(data, function(index, item) {
// construct some HTML from the JSON representation of MyStuff
var html = "<div>" + item.StuffProperty + "</div>";
// append the HTML to a container in the current document
$("#container").append(html);
});
});
This adds some HTML for each item in the collection to a container element, using (eg) StuffProperty from the MyStuff class.
Appending HTML manually like this can be a hassle once it gets too complicated -- at that point you should consider using either:
Partial views (return HTML directly from the controller, instead of JSON)
A client-side templating engine like Mustache.js, Underscore.js, etc, to convert JSON into HTML.

Struts2 more than one action in one class

I'm using Struts2. I have two web forms that have the same code. I would like to eliminate one form. Here is the structure of my Struts project.
\Web Pages
form.jsp
\WEB-INF
\Content
error.jsp
form.jsp
success.jsp
\Source Packages
\action
MyAction.java
MyAction.java
package action;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.*;
public class MyAction extends ActionSupport {
#Action(value = "foo", results = {
#Result(name = "input", location = "form.jsp"),
#Result(name = "success", location = "success.jsp"),
#Result(name = "error", location = "error.jsp")
})
public String execute() throws Exception {
if (user.length() == 1) {
return "success";
} else {
return "error";
}
}
private String user = "";
public void validate() {
if (user.length() == 0) {
addFieldError("user", getText("user required"));
}
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
}
I tried to eliminate form.jsp under \Web Pages by adding a new action method to MyAction.java.
#Action(value="bar", results = {
#Result(name = "success", location = "form.jsp"),
})
public String another() {
return "success";
}
But I got the following error when I go to http : //localhost .../bar.action
HTTP Status 404 - No result defined for action action.MyAction and result input
Your MyAction has an implementation of validate(), which means it is validation aware.
What's happening is that you're calling another, but validate() is kicking in (as it's in the interceptor stack). Validation is failing, and therefore sending to INPUT result, which is not defined in another.
You should
Add #SkipValidation to the another method if you don't want validation there
Add the INPUT result to another() if you want a default input result
On a more general note, when you get that kind of error (No result defined for action X and result input) it usually means you're either having validation errors, parameter population errors (eg: an exception in preparable).

Yii: Catching all exceptions for a specific controller

I am working on a project which includes a REST API component. I have a controller dedicated to handling all of the REST API calls.
Is there any way to catch all exceptions for that specific controller so that I can take a different action for those exceptions than the rest of the application's controllers?
IE: I'd like to respond with either an XML/JSON formatted API response that contains the exception message, rather than the default system view/stack trace (which isn't really useful in an API context). Would prefer not having to wrap every method call in the controller in its own try/catch.
Thanks for any advice in advance.
You can completely bypass Yii's default error displaying mechanism by registering onError and onException event listeners.
Example:
class ApiController extends CController
{
public function init()
{
parent::init();
Yii::app()->attachEventHandler('onError',array($this,'handleError'));
Yii::app()->attachEventHandler('onException',array($this,'handleError'));
}
public function handleError(CEvent $event)
{
if ($event instanceof CExceptionEvent)
{
// handle exception
// ...
}
elseif($event instanceof CErrorEvent)
{
// handle error
// ...
}
$event->handled = TRUE;
}
// ...
}
I wasn't able to attach events in controller, and I did it by redefinition CWebApplication class:
class WebApplication extends CWebApplication
{
protected function init()
{
parent::init();
Yii::app()->attachEventHandler('onError',array($this, 'handleApiError'));
Yii::app()->attachEventHandler('onException',array($this, 'handleApiError'));
}
/**
* Error handler
* #param CEvent $event
*/
public function handleApiError(CEvent $event)
{
$statusCode = 500;
if($event instanceof CExceptionEvent)
{
$statusCode = $event->exception->statusCode;
$body = array(
'code' => $event->exception->getCode(),
'message' => $event->exception->getMessage(),
'file' => YII_DEBUG ? $event->exception->getFile() : '*',
'line' => YII_DEBUG ? $event->exception->getLine() : '*'
);
}
else
{
$body = array(
'code' => $event->code,
'message' => $event->message,
'file' => YII_DEBUG ? $event->file : '*',
'line' => YII_DEBUG ? $event->line : '*'
);
}
$event->handled = true;
ApiHelper::instance()->sendResponse($statusCode, $body);
}
}
In index.php:
require_once(dirname(__FILE__) . '/protected/components/WebApplication.php');
Yii::createApplication('WebApplication', $config)->run();
You can write your own actionError() function per controller. There are several ways of doing that described here
I'm using the following Base controller for an API, it's not stateless API, mind you, but it can serve just aswell.
class BaseJSONController extends CController{
public $data = array();
public $layout;
public function filters()
{
return array('mainLoop');
}
/**
* it all starts here
* #param unknown_type $filterChain
*/
public function filterMainLoop($filterChain){
$this->data['Success'] = true;
$this->data['ReturnMessage'] = "";
$this->data['ReturnCode'] = 0;
try{
$filterChain->run();
}catch (Exception $e){
$this->data['Success'] = false;
$this->data['ReturnMessage'] = $e->getMessage();
$this->data['ReturnCode'] = $e->getCode();
}
echo json_encode($this->data);
}
}
You could also catch dbException and email those, as they're somewhat critical and can show underlying problem in the code/db design.
Add this to your controller:
Yii::app()->setComponents(array(
'errorHandler'=>array(
'errorAction'=>'error/error'
)
));