Home
Github
Wiki
Videos
Contact
Sponsor
Working with a Headless approach
Puck is pretty flexible, you don't need to use razor views if you don't wan't to. You could instead return JSON of the current page or other queried content. By default, there is a catch-all endpoint mapped to the `HomeController` `Index` action. The code for this mapping is in `Startup.cs`: ```cs endpoints.MapControllerRoute( name: "default", pattern: "{**path}" ,defaults: new { controller = "Home", action = "Index"} ); ``` `HomeController` inherits from `puck.core.Controllers.BaseController` and if you look at the `Index` action: ```cs public IActionResult Index() { return base.Puck(); } ``` all it does is call the inherited Puck action which will get the current page and render a view. if you wanted to go headless, you could just return the current page as JSON: ```cs public IActionResult Index() { var currentPage = QueryHelper
.Current(); return Json(currentPage); } ``` this is a simple example but in a real world scenario you'll likey want to use the [QueryHelper](/wiki/querying) to get multiple pieces of content and [compose them into a wrapper class](#advancedpatternsforheadlesssites) that contains your ViewModel and any other required data to return to the client. you'll also likely be calling Puck from another app, something like a single page app which contains your views or a mobile app and in this scenario you can set up your Puck project to [allow cross origin requests](https://docs.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-3.1). then your single page app hosted on a different domain can make requests to Puck and get the JSON responses. ## query endpoint for returning multiple results, graphql style the previous example showed you how to get Puck to return the requested page as JSON. what if you wanted to get back multiple results by search query? to set up a query endpoint, you need a controller which inherits from `puck.core.Controllers.BaseController` - you could use `HomeController`, for example. then you can create a `Query` action: ```cs [Route("Query")] [HttpPost] public IActionResult Query([FromBody]List
queries,string cacheKey=null,int cacheMinutes=0) { return Json(base.Query(queries,cacheKey:cacheKey,cacheMinutes:cacheMinutes)); } ``` you can then post JSON requests to this endpoint (you will need to set the `Content-Type` header on your request to `application/json`). here's an example JSON request body: ```js [ { "Type": "School", "Query": "NodeName:\"Moordale School\"", "Skip": 0, "Take": 100, "Sorts":"NodeName:desc,SortOrder:asc", "Include":[ ["People.Students","Friends"], ["GalleryImages"] ], "Fields":["People","GalleryImages","NodeName"] }, { "Type": "Page", "Query": "+Type:Page PuckGeoM:Location.LongLat,-0.1277582,51.5073509,10,asc", "Skip": 0, "Take": 100, "Sorts":"", "Include": [] } ] ``` the above request body specifies two searches. each search consists of a `Type` - which is the type parameter that you would normally pass to [QueryHelper](/wiki/querying). the `Query` parameter is a Lucene query, read [this](https://lucene.apache.org/core/4_8_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html) for reference. use `Skip` and `Take` for pagination and `Sorts` accepts a comma separated string of `Field:asc` or `Field:desc`, e.g. "SortOrder:desc,NodeName:asc". the "Fields" property allows you to specify which fields are returned, if not present, all fields will be returned. the `Include` argument allows you to include any referenced ViewModels from [Content Picker](/wiki/editor-templates#contentpicker) or [Image Picker](/wiki/editor-templates#imagepicker) fields. these are fields of type `List
`. in the first query, you are specifying that for each `School` ViewModel in the result, include the ViewModels referenced in the `People.Students` property, which means that property will go from being a `List
` to `List
` - a list of referenced ViewModels. since include is recursive, the query above also specifies a recursive include of `Friends`, so for each `Student` in the `People.Students` list, their friends will be loaded too. the next include is `GalleryImages` and this will apply for each `School` in the result, loading in any referenced images. it's also worth noting that `Include` also supports Lists, so if you have a list of a complex type like `Person` with a `Content Picker` property `Friends`, you can Include like so; `Include:[["People.Friends"]]` and it will actually loop through each person in the `People` list property and load their friends in. this type of querying is very powerful but beware that it can be inefficient to pre-emptively include many-levels-deep worth of ViewModels. you may prefer to lazily load in referenced ViewModels by making separate requests to the `Query` action when those ViewModels are required. you may also want to specify `CacheKey` and `CacheMinutes` arguments when calling the `Query` action so that results can be cached. the equivalent of [QueryHelper's](/wiki/querying) `ExplicitType` is specifying the type, like so; "+Type:Page". the syntax for `Geo/Spatial` queries is `PuckGeoM:Location.LongLat,-0.1277582,51.5073509,10,asc` when the distance is specified in Miles and `PuckGeoK:Location.LongLat,-0.1277582,51.5073509,10,asc` when specified in kilometres. the parameters are passed in comma separated with no spaces, the parameters being the `Field Name`,`Longitude`,`Latitude`,`Distance` and finally the `Sort`. specify `asc`,`desc` or `null` for `Sort` values. the results for these two queries will be returned in the format `List
` where each `QueryResult` contains a `Results` property which is a list of ViewModels and a `Total` property, which is the total hits - this is useful for pagination. #### performing Interface searches with your Query endpoint one of the handy features of the [QueryHelper](/wiki/querying) is that you can search for ViewModels that implement a particular interface or multiple interfaces. you can do this kind of search with your query endpoint by specifying the `Implements` property: ```js [ { "Type": "BaseModel", "Query": "+Title:News", "Skip": 0, "Take": 100, "Implements":"IMainContent,IGalleryImages", "Sorts":"SortOrder:asc", "Include":[] } ] ``` above, you are specifying that you want ViewModels which implement the `IMainContent` and `IGalleryImages` interfaces. for Puck to be able to work with your interfaces, you need your interfaces to inherit from the base interface `puck.core.Abstract.I_BaseModel`. ## advanced patterns for headless sites as mentioned earlier, it's simple enough to get Puck to return the current page ViewModel as JSON. what will likely happen though, is that you will want to return related/additional data with your ViewModel. for example, you may want to return all `CarouselImages` with the `Homepage` ViewModel to display a carousel on your homepage. with the graphql style `Query Endpoint` detailed in the previous section, it would be simple enough to `Include` the `CarouselImages` property on the `Homepage` ViewModel to make sure all carousel image ViewModels linked to your `Homepage` are returned with it. thing is, with the `Query Endpoint`, you have to know the `Type` you're querying and which properties to include upfront, this isn't always realistic. assuming we're working with the initial pattern shown on this page where Puck returns the current page as JSON, what would be ideal, is that if every ViewModel knew what additional data it needed and could return a `Wrapped` version of itself, containing itself (the ViewModel, i.e the `Homepage`) and any additional required data. fortunately, with a little polymorphism, this is easy to achieve. the first thing you need to do, is in your `ViewModels` folder, create a `BasePage` which all of your ViewModels will inherit from. ```cs public class BasePage:puck.core.Base.BaseModel { [Display(Name ="Meta Title",GroupName ="Meta")] public string MetaTitle { get; set; } [Display(Name = "Meta Description", GroupName = "Meta")] [UIHint(EditorTemplates.TextArea)] public string MetaDescription { get; set; } [Display(Name = "Page Title", GroupName = "Content")] public string PageTitle { get; set; } public virtual BasePageWrapper GetWrappedViewModel() { return new BasePageWrapper { ViewModel = this }; } } ``` as you can see above, the `BasePage` inherits from Puck's `BaseModel` and has some `Meta` properties that all web pages should have as well as the `PageTitle`. you may have additional requirements which you can add. importantly, notice that it has a `virtual` method `GetWrappedViewModel` which returns a `BasePageWrapper` (which has a single property, the current ViewModel). the idea is that, for every ViewModel you create which inherits from this `BasePage`, you `override` the `GetWrappedViewModel` method and return a `Wrapper` model based on that particular ViewModel. here's an example of `Homepage` using this pattern: ```cs public class Homepage:BasePage { [Display(GroupName = "Content")] [UIHint(EditorTemplates.RichText)] public string Bio { get; set; } [Display(Name ="Profile Picture",GroupName ="Content")] [UIHint(EditorTemplates.ImagePicker)] public List
ProfilePicture { get; set; } [Display(GroupName="Content")] [UIHint(EditorTemplates.ListEditor)] public List
Links { get; set; } public override BasePageWrapper GetWrappedViewModel() { var model = new HomepageWrapper { ViewModel = this}; if (ProfilePicture != null && ProfilePicture.Any()) { var picture = ProfilePicture.GetAll
()?.FirstOrDefault(); if (picture != null) { model.ProfilePicture = picture; } } return model; } } ``` above is the homepage ViewModel, notice how it inherits from `BasePage`. the interesting part is where it `overrides` `GetWrappedViewModel`. notice that the model being returned by this method is not a `BasePageWrapper` as stated in the method signature but actually a `HomepageWrapper` - which, crucially, inherits from `BasePageWrapper` so can be boxed to that type and has an additional property, `ProfilePicture` which is not on the `BasePageWrapper`. the idea is that you have a different wrapper type for each type of ViewModel which inherits from `BasePage` but has properties related to that specific ViewModel. you can put your `Wrapper` models in a `Models` folder (not your `ViewModels` folder - you may have to create this `Models` folder). now in the `HomeController`, your `index` action looks like this: ```cs public IActionResult Index() { var viewModel = puck.core.Helpers.QueryHelper
.Current(); var wrapped = viewModel?.GetWrappedViewModel(); return Json(wrapped); } ``` notice above that you query for the current page, cast to your `BasePage` type and then call `GetWrappedViewModel` on it. this will return the specific wrapper for whatever type of ViewModel the current page is. along with using the graphql style query endpoint from the previous section, this is a flexible and powerful approach for tackling most situations in headless websites. **note**, to get this working, you must use JSON.net as your JSON serializer as the default one doesn't handle polymorphism. you will need to install the required package - `Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson`. you will then need to modify your `ConfigureServices` method in your `Startup.cs` file. the following: ```cs services.AddControllersWithViews() .AddApplicationPart(typeof(puck.core.Controllers.BaseController).Assembly) .AddControllersAsServices() .AddRazorRuntimeCompilation() .AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null); ``` needs to be replaced with this: ```cs services.AddControllersWithViews() .AddApplicationPart(typeof(puck.core.Controllers.BaseController).Assembly) .AddControllersAsServices() .AddRazorRuntimeCompilation() .AddNewtonsoftJson(x => { x.UseMemberCasing(); }) .AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null); ```