I'm extending a create controller for one of my models:
async create(ctx) {
let entity;
if (ctx.is('multipart')) {
const { data, files } = parseMultipartData(ctx);
entity = await strapi.services.complaint.create(data, { files });
} else {
entity = await strapi.services.complaint.create(ctx.request.body);
}
const sanitized = sanitizeEntity(entity, { model: strapi.models.complaint });
strapi.emitToAllUsers('complaint::created', sanitized);
return sanitized;
},
It works fine if I do the request from Postman for example, but is it possible to do the same if the user creates the new object from the Admin UI?
When I see the console from Strapi when I send the request from Postman:
debug POST /complaints (58 ms) 200
But, if I create a new object from the admin UI I see this instead:
debug POST /content-manager/explorer/application::complaint.complaint (1017 ms) 200
Any ideas? Is it even possible? I'm using latest Strapi version (v3)
Thanks
If you want to execute something from Admin UI, I think you might want to look at how to implement it in models using lifecycle hooks.
https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#models
From what I know, controller only called on API, while service are a set of reusable functions. Models are called both by API and Admin UI.
complaint/models/complaint.js
module.exports = {
lifecycles: {
afterCreate(result, data) {
strapi.emitToAllUsers('complaint::created', result);
}
}
};
You can also create a function in service and call it models lifecycle hooks.
Related
Scenario
I'm trying to get all entities from an endpoint, the ones on draft mode, and the ones on published mode.
I know that if I want to post on draft mode, I have to post published_at on null on the body request.
If I do:
/posts?published_at_null=true
that returns an empty array.
Question
How can I do to return ALL the posts?
It is on the documentation
https://strapi.io/documentation/developer-docs/latest/developer-resources/content-api/content-api.html#publication-state
But quick answer, URL + '?_publicationState=preview'
https://forum.strapi.io/t/draft-and-posted-entities/3576/2
you will have to create a custom control that will fetch all the entries.you cannot fetch draft data using the existing restapi urls.
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
async findUnpublished(ctx) {
//getting all the existing articles, no meter if they have unpublished status
let result = await strapi.query('posts').find();
//sanitize them to hide all private fields
let articles = sanitizeEntity(result, {
model: strapi.models['posts'],
});
//return result to the /findUnpublished API
ctx.send(articles);
}
};
My project has a declarative way of defining schema and resolvers, which is maintained in a separate repository. My graphql server polls the result of this to look for updates to the schema.
Using apollo-server-express#1, I had direct access to the graphqlExpress middleware, so when the schema changed I could construct a new instance of it and throw away the old one, something like this
const { graphqlExpress } = require('apollo-server-express');
let api;
const constructAPI = () => {
try {
const newSchema = createSchema();
api = graphqlExpress(({ headers }) => ({
schema: newSchema,
}));
logger.info({ event: 'GRAPHQL_SCHEMA_UPDATED' });
};
schemaPoller.on('change', constructAPI);
module.exports = router => {
// Note that we wrap the api controller in a function that passes
// the original args through because a new api controller is generated
// every time the schema changes. We can't pass express a direct
// reference to the api controller on startup, or it will
// never update the reference to point at the latest version of the
// controller using the latest schema
router
.route('/')
.get((...args) => api(...args))
.post((...args) => api(...args));
return router;
};
In apollo-server-express#2, access to the middleware is hidden away, and there are 2 new, more declarative ways of using the library, neither of which - at first glance - appear compatible with updating the schema without stopping the server, fetching the new schema and starting again with the new data, which is downtime I'd like to avoid.
Can anyone suggest a way of getting this setup to work with apollo#2?
I have custom middleware that provides global error handling. If an exception is caught it should log the details with a reference number. I then want to redirect the user to an error page and only show the reference number. My research shows that TempData should be ideal for this but it only seems to be accessible from within a controller context. I tried adding the reference number to HttpContext.Items["ReferenceNumber"] = Guid.NewGuid();
But this value is lost through the redirect.
How can middleware pass information through a redirect? Do I just have to put the number in a querystring?
Inside the middleware class you need to add a reference to get access to the required interfaces. I have this middleware in a separate project and needed to add this NuGet package.
using Microsoft.AspNetCore.Mvc.ViewFeatures;
This then allows you to request the correct services within the middleware.
//get TempData handle
ITempDataDictionaryFactory factory = httpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;
ITempDataDictionary tempData = factory.GetTempData(httpContext);
After you have ITempDataDictionary you can use it like you would use TempData within a controller.
//pass reference number to error controller
Guid ReferenceNum = Guid.NewGuid();
tempData["ReferenceNumber"] = ReferenceNum.ToString();
//log error details
logger.LogError(eventID, exception, ReferenceNum.ToString() + " - " + exception.Message);
Now when I get the the controller after a redirect I have no issues pulling out the reference number and using it in my view.
//read value in controller
string refNum = TempData["ReferenceNumber"] as string;
if (!string.IsNullOrEmpty(refNum))
ViewBag.ReferenceNumber = refNum;
#*display reference number if one was provided*#
#if (ViewBag.ReferenceNumber != null){<p>Reference Number: #ViewBag.ReferenceNumber</p>}
Once you put this all together, you give users a reference number that they can give you to help troubleshoot the problem. But, you are not passing back potentially sensitive error information which could be misused.
You can register an ITempDataProvider yourself and use it in your middleware. Here is a small sample I got working between two simple paths. If you are already using MVC the ITempDataProvider is probably already registered. The issue I faced was the path of the cookie that was written. It was /page1 so /page2 did not have access to the cookie. So I had to override the options as you can see in code below.
I hope this will help you :)
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDataProtectionProvider>(s => DataProtectionProvider.Create("WebApplication2"));
services.Configure<CookieTempDataProviderOptions>(options =>
{
options.Path = "/";
});
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ITempDataProvider tempDataProvider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Map("/page1", (app1) =>
{
app1.Run(async context =>
{
tempDataProvider.SaveTempData(context, new Dictionary<string, object> { ["Message"] = "Hello from page1 middleware" });
await context.Response.WriteAsync("Hello World! I'm page1");
});
});
app.Map("/page2", (app1) =>
{
app1.Run(async context =>
{
var loadTempData = tempDataProvider.LoadTempData(context);
await context.Response.WriteAsync("Hello World! I'm page2: Message from page1: " + loadTempData["Message"]);
});
});
}
This led me in the right direction: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state#cookie-based-tempdata-provider
Happy coding! :)
Can anyone please provide me your inputs on how to proceed. the below requirement.
I have web application developed in VS professional 2015 and have separate Web API application. So far the existing web application implemented in 3 layered architecture. Now we wanted to implement the new pages using Web API calls without 3 layered architecture.
First of all, I wanted to create a infrastructure/architecture in my web application to call the web API application. So that all the new page requests go through this infrastructure/architecture and call web API.
Please help me with your valuable inputs/suggestions.
Thank you in advance.
as we are using API there is no need to create a complex infrastructure/architecture unless one is willing to use ReactJs or AngularJs.
Its even not mandatory for them too but it will let you keep the code clean. you just simply need to call the API using JavaScript
For example you've a API controller method and wish to access it.
Simple Method of products
#region Get Method
// GET api/product
[Queryable]
[Route("Products")]
[Route("All")]
public HttpResponseMessage Get()
{
try
{
var products = _productServices.GetAllProducts();
var productEntities = products as List<ProductEntity> ?? products.ToList();
if (productEntities.Any())
{
return Request.CreateResponse(HttpStatusCode.OK, productEntities);
}
throw new ApiDataException(1000,"No Products Found",HttpStatusCode.NotFound);
}
catch (Exception)
{
throw;
}
}
#endregion
just call the respected method from ajax like this
$(document).ready(function () {
var a = "Test";
$.ajax({
url: "v1/products/product/All",
type: "GET",
data: { a : a },
success: function (response) {
alert(response);
},
error: function (response) {
alert(response);
}
});});
Hope This helps
Investigating the Web API as part of an MVC 4 project as an alternative way to provide an AJAX-based API. I've extended AuthorizeAttribute for the MVC controllers such that, if an AJAX request is detected, a JSON-formatted error is returned. The Web API returns errors as HTML. Here's the AuthorizeAttribute that I'm using with the MVC controllers:
public class AuthorizeAttribute: System.Web.Mvc.AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "area", "" },
{ "controller", "Error" },
{ "action", ( filterContext.HttpContext.Request.IsAjaxRequest() ? "JsonHttp" : "Http" ) },
{ "id", "401" },
});
}
}
How could I reproduce this to provide equivalent functionality for the Web API?
I realize that I need to extend System.Web.Http.AuthorizeAttribute instead of System.Web.Mvc.AuthorizeAttribute but this uses an HttpActionContext rather than an AuthorizationContext and so I'm stuck by my limited knowledge of the Web API and the seemingly incomplete documentation on MSDN.
Am I even correct in thinking that this would be the correct approach?
Would appreciate any guidance.
To get the equivalent functionality in a Web API filter you can set the HttpActionContext.Response property to an instance of HttpResponseMessage that has the right redirect status code and location header:
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) {
var response = new HttpResponseMessage(HttpStatusCode.Redirect);
response.Headers.Location = new Uri("my new location");
actionContext.Response = response;
}
I would very much go with Marcin's answer - at the end of the day, he has written the code!
All I would add is that as Marcin is saying, your best bet is to have a dedicated controller to return the errors as appropriate - rather than setting the response code 401 with JSON content in the attribute.
The main reason is that Web API does the content-negotiation for you and if you want to do it yourself (see if you need to serve JSON or HTML) you lose all that functionality.