CakePHP Routing json response - json

I am currently using RestApi plugin for CakePHP 3 and I want to be able to append the extension .json to URL, like so:
domain.com/api/search/abc.json
Following CakePHP's docs about creating RESTful routes I was able to use the extension without throwing an error.
I have this on my routes.php (edit to add the whole code)
use Cake\Core\Plugin;
use Cake\Routing\RouteBuilder;
use Cake\Routing\Router;
use Cake\Routing\Route\DashedRoute;
Router::defaultRouteClass(DashedRoute::class);
Router::scope('/', function (RouteBuilder $routes) {
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
$routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
$routes->fallbacks(DashedRoute::class);
});
Plugin::routes();
Router::scope('/', function ($routes) {
$routes->extensions(['json']);
});
In my controller, if I do this:
public function search($term=''){
$this->httpStatusCode = 200;
$this->apiResponse['term'] = $term;
}
The response is:
{
"status": "OK",
"result": {
"term": "abc.json" # Notice the .json
}
}
So, I get abc.json, when I want abc.
Am I doing something wrong? Or am I supposed to strip the .json from $term?

While reusing existing scopes will merge the connected routes to the same routes collection, calls to RouteBuilder::extensions() will generally not affect previously connected routes, and they also do not affect reused/reopened scopes.
Quote from the docs:
Future routes connected in through this builder will have the connected
extensions applied. However, setting extensions does not modify existing routes.
API > \Cake\Routing\RouteBuilder::extensions()
You should add the extensions() call in the existing routing scope, so that it affects the routes that are being connected in there after the extensions() call.
See also
Cookbook > Routing > Routing File Extensions

Related

How to use IOptions pattern for configuration in dotnet-isolated (net5.0) azure functions

I'm attempting to port an existing Functions app from core3.1 v3 to net5.0 I but can't figure out how to get the IOptions configuration pattern to work.
The configuration in my local.settings.json is present in the configuration data, and I can get to it using GetEnvironmentVariable. Still, the following does not bind the values to the IOptions configuration like it used to.
.Services.AddOptions<GraphApiOptions>()
.Configure<IConfiguration>((settings, configuration) => configuration.GetSection("GraphApi").Bind(settings))
The values are in the local.settings.json just as they were before:
"GraphApi:AuthenticationEndPoint": "https://login.microsoftonline.com/",
"GraphApi:ClientId": "316f9726-0ec9-4ca5-8d04-f39966bebfe1",
"GraphApi:ClientSecret": "VJ7qbWF-ek_Amb_e747nXW-fMOX-~6b8Y6",
"GraphApi:EndPoint": "https://graph.microsoft.com/",
"GraphApi:TenantId": "NuLicense.onmicrosoft.com",
Is this still supported?
What am I missing?
I had the same issue, but turns out that the json was not correctly formatted.
Just for reference, here it is how I configured it:
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(s =>
{
s.AddOptions<ApplicationSettings>().Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection(nameof(ApplicationSettings)).Bind(settings);
});
})
.Build();
And here is an example of local.setting.json:
{
"IsEncrypted": false,
"Values": {
"ApplicationSettings:test": "testtest",
"ApplicationSettings:testtest": "test"
}
}

How to force restful responses in json?

I want to add some api to my Yii2 site. Api must be only in json. I don't want to set Accept: application/json headers for each request. I can set 'response' => ['format' => \yii\web\Response::FORMAT_JSON] in application configuration but it breaks all pages. Also my api function returns data in xml.
I tried to use rest\ActiveRecord for my purposes. Maybe I do it's wrong. What I want.
To have my Yii2 based site with some api acсessed through https://example.com/api/controller/action. In project I want to see folder controllers/api which contains my controllers. Controllers must use standard \yii\db\ActiveRecord based models. Also controllers input paramaters only in json body or as part url and output data only in json.
You may need to set the following code in the controller's action somewhere before return or in beforeAction() method:
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
also since Yii 2.0.11 there is a dedicated asJson() method to return a response in JSON format:
return $this->asJson($array);
The more elegant solution is to use yii\filters\ContentNegotiator.
When the Accept header is missing ContentNegotiator assumes it allows any type and send response in first format defined in its $formats property. If the requested format is not among accepted formats the content negotiator will throw yii\web\NotAcceptableHttpException and app will respond with http status 406 Not Acceptable.
You can add it in your controller in behaviors() method like this:
public function behaviors()
{
return [
[
'class' => 'yii\filters\ContentNegotiator',
'formats' => [
'application/json' => \yii\web\Response::FORMAT_JSON,
],
],
];
}
If your controller extends yii\rest\Controller it already has the ContentNegotiator filter added among its behaviors. You only need to limit allowed formats like this:
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['contentNegotiator']['formats'] = [
'application/json' => \yii\web\Response::FORMAT_JSON,
];
return $behaviors;
}
Using ContentNegotiator instead of explicitly forcing the JSON format in beforeAction() will allow for easier addition of other formats if they are needed in future.

Yii2 Functional test send ajax get request not working

I am trying to test an ajax request with a basic install of Yii2. I am just using the SiteController::actionAbout() method to try this out.
public function actionAbout()
{
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return [
'message' => 'hello world',
'code' => 100,
];
}
And in the test:
public function sendAjax(\FunctionalTester $I)
{
$I->sendAjaxGetRequest('site/about');
$r = $I->grabResponse();
die(var_dump($r));
}
The grab response method is one I wrote myself in a \Helper\Functional.php file:
public function grabResponse()
{
return $this->getModule('Yii2')->_getResponseContent();
}
When I dump out the response, I just see the html from the site/index page.
This happens for any route except the site default. If I try to do the same thing using site/index then it returns:
string(36) "{"message":"hello world","code":100}"
What am I missing / doing wrong?
Note the difference between a Yii route and the actual URL.
The argument to sendAjaxGetRequest() is the URL, not the route (at least if you pass it as a string, see below).
Dependent on your UrlManager config you might have URLs like /index.php?r=site/about, where the route is site/about. You can use the Url helper of Yii to create the URL:
$I->sendAjaxGetRequest(\yii\helpers\Url::to(['site/about']));
I am not sure about this but if you have the Codeception Yii2 module installed you should also be able to pass the route like this:
$I->sendAjaxGetRequest(['site/about']);

Yii2 Make endpoint accessible through web and rest api

A customer has requested to have the exact same endpoints available through web interface as well as through REST API.
The same endpoint should be visible using web browser only when being logged in. When accessing it via REST API, a valid access token must be submitted.
The rule for this specific endpoint is defined as follows:
[
'class' => 'yii\rest\UrlRule',
'controller' => 'site',
'pluralize' => false,
'extraPatterns' => [
'POST upload-raw-data' => 'uploadRawData'
],
]
Now, when I try to access this endpoint, I've got these results:
Browser: no problem
Postman / POST: 404 error
Postman / GET: no problem
When trying the same with enableStrictParsing enabled, I've got 404 errors all around.
If I need to provide other parts of the code, I'll happily provide them.
I think I found the solution for my issue. The problem seems to have been the CSRF validation.
By disabling it for this specific action in beforeAction(), the POST call behaves as intended.
public function beforeAction($action) {
if ($action->id == 'upload-raw-data')
Yii::$app->controller->enableCsrfValidation = false;
return parent::beforeAction($action);
}
source: https://gist.github.com/guerreiro/9e9cb3154b9047f5d2a0

Angular resource 404 Not Found

I've read other posts that have similar 404 errors, my problem is that I can correctly query the JSON data, but can't save without getting this error.
I'm using Angular's $resource to interact with a JSON endpoint. I have the resource object returning from a factory as follows:
app.factory('Product', function($resource) {
return $resource('api/products.json', { id: '#id' });
});
My JSON is valid and I can successfully use resource's query() method to return the objects inside of my directive, like this:
var item = Product.query().$promise.then(function(promise) {
console.log(promise) // successfully returns JSON objects
});
However, when I try to save an item that I've updated, using the save() method, I get a 404 Not Found error.
This is the error that I get:
http://localhost:3000/api/products.json/12-34 404 (Not Found)
I know that my file path is correct, because I can return the items to update the view. Why am I getting this error and how can I save an item?
Here is my data structure:
[
{
"id": "12-34",
"name": "Greece",
"path": "/images/athens.png",
"description": ""
},
...
]
By default the $save method use the POST verb, you will need to figure out which HTTP verbs are accepted by your server en order to make an update, most modern api servers accept PATCH or PUT requests for updating data rather than POST.
Then configure your $resource instance to use the proper verb like this :
app.factory('Product', function($resource) {
return $resource('api/products.json', { id: '#id' }, {'update': { method:'PUT' }});
});
check $resource docs for more info.
NOTE: $resource is meant to connect a frontend with a backend server supporting RESTful protocol, unless you are using one to receive data & save it into a file rather than a db.
Otherwise if you are only working with frontend solution where you need to implement $resource and have no server for the moment, then use a fake one, there is many great solutions out there like deployd.
You probably don't implement POST method for urls like /api/products.json/12-34. POST method is requested from angular for saving a new resource. So you need to update your server side application to support it and do the actual saving.
app.factory('Product', function($resource) {
return $resource('api/products.json/:id', { id: '#id' });
});
Try adding "/:id" at the end of the URL string.