Grails json views : opaque view selection - picks out default template in another directory - grails

I am using Grails 3.3.9 and json views 1.2.10.
In another test I have a url mapping like this
"/api/test"(resources:'test')
And I have an empty views/test folder, and I don't have a Test domain object.
But I do have a Device domain entity, and have a views/device/_device.gson template in that directory to help render a device object.
The TestController's show action looks like this
def show (Device device) {
if(device == null) {
render status:404
} else {
respond device}
}
This looks for a Device in domain model and populates into Shows parameter list, and I respond with the device.
There is however no show.gson in the views/test directory (it's empty).
However when you hit the url in the browser "http://localhost:8080/api/test/1", grails renders a response using the "views/device/_device.gson" template.
This is very confusing as it's very hard to figure exactly view view respond is actually using (unless you force it using a view:'xxx' map variable.
How/why is the TestController/show action response return, picking up "views/device/_device.gson" template ?

(Posted answer on behalf of the question author).
Step 1) if you create a new controller (I used grails create-controller <controller>, and then edit the generated controller to extend from RestfulController<domain type> (but don't override any methods). Then Setup the URL mapping for your controller, in my example this was
"/api/org"(resources:'OrgRoleInstance')
for which I have the corresponding domain class 'OrgRoleInstance' in my model.
The create-controller action also generates an empty view package 'views/orgRoleInstance' (no gson files are generated)
a) if you do the run app now and open the url
http://localhost:8080/api/org
then your still get a rendered response (like this) !
[{"id":1,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Service_Provider"},"name":"Vodafone","mags":[{"id":2}]},{"id":2,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Maintainer"},"name":"Cisco","mags":[{"id":1}]},{"id":3,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Supplier"},"name":"Cisco","mags":[]},{"id":4,"sites":[{"id":1},{"id":2}],"domains":[{"id":1}],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Customer"},"name":"Acme","mags":[]}]
Ipso facto there is some 'default framework logic' that tries to render the default implementation of the 'index' (and 'show' etc) action inherited from RestfulController'. This is done even with with no pre-existing gson - no warnings are provided.
Step 2: next stop the app (it's still caching the previous 'default' behaviour).
Now go to the empty view directory and create an 'index.gson' like this with no model
json ([1,2,3])
Then restart the app in your IDE. Now when it starts, it detects that an index.gson exists, and when you call the url 'http://localhost:8080/api/org' what you get is the rendered json for the static list [1,2,3]
step 3: gson views are using static compilation under the covers and very fiddly to get exactly right. However in the case of default RestfulController index action , a list of OrgRoleInstances is selected from the domain model and passed as a lit to the view. if you want the databinding to the view to work, then if your domain object is type T, the you get a List<T> returned.
The internal default in the code base, is if you get List<T> returned to respond then, the data model in the view is assumed to be List<T> <T>List i.e. in my example this would be
List<OrgRoleInstance> orgRoleInstanceList in the gson view model. Now with a revised index.gson (you can edit this without stop/start of server in dev mode)
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
}
json {
recordCount orgRoleInstanceList.size()
}
Now when you Get the URL you get a json response with the size of the list:
{"recordCount":4}
Note that if you add an extra variable to your model like this:
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
json {
recordCount orgRoleInstanceList.size()
size orgRoleInstanceCount
}
and modify the index.gson to print the new Integer variable - then it is not data bound from the default respond action.
Browser response looks like this:
{"recordCount":4,"size":null}
Option 4: - look at invoking a 'template' class. Having set up a model that's expecting a List<T>, the runTime type built via data binding is an instance of "grails.orm.PagedResultList". This is an Iterable type.
This is very confusing to read in the documentation - it's really not clear however if you create a file called _<file>.gson. Then this is treated is a template gson file.
This can have its own model/json, but because all this is statically compiled you have to get the types exactly matching and where necessary declare the 'name' of the model variable when calling the template (either via g.render or implicit tmpl.<file>.
When you call the template from the parent view you can pass either an the List<T> iterable model type or iterate in the parent view and pass each <T> to the template. You have to ensure that the model type you pass to the tmpl is declared as the same type in the tmpl model.
e.g. assuming a List<T> in the parent view but a <T> in the template, you need to invoke the tmpl for each item in the list. e.g.
if you have a tmpl like this "_orgRoleInstance.gson".
If you have the parent view like this (note single <T> declared and model variable named 'org'
import com.softwood.domain.OrgRoleInstance
model {
OrgRoleInstance org
}
json {
id org.id
name org.name
}
Then the parent view "index.gson" you need something like this that invokes the tmpl as many times as you have entries in the list, but I've had to tell the framework that the tmpl model variable name is 'org' by passing a map. This will render as you expect.
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
orgRoleInstanceList.each { OrgRoleInstance org ->
json tmpl.orgRoleInstance(org:org)
}
If you declare the tmpl variable to be "def org", it still works but as this is statically typed, the variable is passed as static instance of Object (run time type is correct, but you cant just access the properties as the static type is Object), and it gets hard sorting out the casts required to access the properties.
If you want the parent view to pass the model List<T> variable to the tmpl, you can = but you have to ensure that the model variable in the tmpl is List<T> else the data binding doesn't work.
Now in the template you can invoke the json and iterate over the List
e.g. with a modified tmpl like this
import com.softwood.domain.OrgRoleInstance
model {
//OrgRoleInstance org
List<OrgRoleInstance> orgs
}
json {
id orgs.id
name orgs.name
}
and a revised parent view like this to invoke the tmpl:
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
/*
orgRoleInstanceList.each { OrgRoleInstance org ->
json tmpl.orgRoleInstance(orgs:org)
}*/
//alternate approach
json tmpl.orgRoleInstance (orgs:orgRoleInstanceList)
What get rendered is this:
{"id":[1,2,3,4],"name":["Vodafone","Cisco","Cisco","Acme"]}
You will note that there is a single json clause so it iterates all the ids first and the all the names second.
If this is not what you want you have to iterate over the list to do each in turn
i.e. a modified tmpl like this will iterate over each entry in turn
import com.softwood.domain.OrgRoleInstance
model {
//OrgRoleInstance org
List<OrgRoleInstance> orgs
}
json (orgs) {OrgRoleInstance org ->
id org.id
name org.name
}
and produce this in the browser
[{"id":1,"name":"Vodafone"},{"id":2,"name":"Cisco"},{"id":3,"name":"Cisco"},{"id":4,"name":"Acme"}]
For anyone else who is confused by Grails Views, I hope this shows how this works between url mappings (you can use the Gradle "urlMappingsReport" to see these) and shows what urls are mapped to what actions. You then need gson views created with the same name as these actions, and configure your gson views and any tmpl you create to be aware of the implicit behaviour that the JsonViews applies when processing your code.

Related

Grails binding one to one associations

When you generate grails views, grails looks at your relationships and generates the right html for your form data to be automatically binded to the back end domain. For one to one associations grails creates a drop down list.
However, you might not want to present that property as a drop down list but something more custom (for example a text field with autocomplete). As soon as you do that the value that comes to the controller from that field, comes in as a String and you have to first:
Clear errors
Perform a findBy based on a given param and assign it to the property of the domain
I really want to avoid doing findBys in the controller as much as possible because it seems like I am doing logic/things that should not go there. The controller should delegate to the Service layer. It is not clear to me from the grails documentation how would I do that by using bindData which seems to work really well with String, date, Integer properties etc.. but I do not see how bindData is used for properties that are other domains.
I also really want to avoid passing the params object to the Service layer as it seems less reusable (or maybe not, correct me if I am wrong). I guess that I do not like how it looks semantically. I would prefer the first over the second:
#Transactional
class WithdrawService {
def addWithdraw(Withdraw withdraw) {
//perform business logic here
}
def createWithdraw(Map params){
//perform business logic here
}
}
Let's take the following example:
class Withdraw {
Person person
Date withdrawDate
}
and the parent lookup table
class Person {
String name
String lastName
static constraints = {
}
#Override
public String toString() {
return "$name $lastName"
}
}
In order for the bind to happen automatically without any extra work grails passes in the following request params to automatically bind the one to one:
person.id
a person map with the id.
[person.id:2, person:[id:2], withdrawDate:date.struct, withdrawDate_month:11, create:Create, withdrawDate_year:2015, withdrawDate_day:10, action:save, format:null, controller:withdraw]
What is the best way to go about this?
Pass two hidden fields that look exactly like this: person.id:2, person:[id:2] that get populated as a result of the Ajax call that populates the autocomplete?
In the controller do a Person.findBySomeKnownProperty(params.someKnownValue)
Or any other approach?

Grails3 generate-all generates faulty create action code

When I use generate-all package.DomainObject, it generates a controller where create action is generated as:
def create() {
respond new DomainObject(params)
}
When I call the localhost:8080/DomainObject/create even without making any code change, it throws an exception:
groovy.lang.MissingPropertyException: No such property: controller for
class: package.DomainObject
It looks like introspection is failing for properties that params map has and DomainObject does not have. This is surprising because in the grails 2, introspection used to just ignore the non-matching properties and it also used to do necessary type conversions on fields as well (now if DomainObject has an int property, it will throw a type mismatch exception because params map passes it as String). This is really inconvenient. Did something change or I am missing something?
Using the map constructor and setting properties in bulk with a map in Grails is basically the same as in Groovy, but it has logic to exclude 'controller', 'action', and 'format' keys to keep controller code like this uncluttered. That broke in 3.x and has been reported in the issue tracker. It's not marked fixed but works correctly for me in a simple 3.0.4 test app.
As a temporary workaround you can copy the params map and remove values stored under those keys and use the 'fixed' map for the constructor:
def create() {
def fixedParams = ([:] + params) // copy
['controller', 'format', 'action'].each { fixedParams.remove it }
respond new Thing(fixedParams)
}

How get the value of an object property that has been excluded from the bind

I have the following model:-
[MetadataType(typeof(TMSServer_Validation))]
[Bind(Exclude = "TMSRack,ServerModel")]
public partial class TMSServer
{
}
and I have the following drop down inside my view:-
#Html.DropDownListFor(model =>model.Server.TMSRack.DataCenterID, ((IEnumerable<TMS.Models.DataCenter>)ViewBag.DataCenters).Select(option => new SelectListItem {
Text = (option == null ? "None" : option.Name),
Value = option.ID.ToString(),
Selected = (Model.Server.TMSRack != null) && (option.ID == Model.Server.TMSRack.DataCenterID)
}), "Choose...")
Then on my controller class I have the following :-
ViewBag.Racks = repository.getrelatedracks(Server.TMSRack.DataCenterID);
But since I have excluded the TMSRack navigation property (mainly to avoid over-posting attacks), so the Server.TMSRack.DataCenterID will always be null. And to get its value I wrote the following:-
ViewBag.Racks = repository.getrelatedracks(Int32.Parse( Request.Form["Server.TMSRack.DataCenterID"]));
But I know that using Request.Form is not the right approach to follow, so my question is there a way to get the excluded property using more reliable way ?
Thanks
My answer is going to assume TMSServer is a domain model.
With that in mind, this is the perfect example of when to use a view model. By using a view model instead, you have complete control over how the properties are mapped from the view model to the domain model. Something like:
public class RackViewModel
{
public int DataCenterID
// other Rack properties
}
Then either send a list of RackViewModel to your view, or create a view model that encompasses all of that, too:
public class ContainerViewModel
{
public List<RackViewModel> Racks { get; set; }
// other view-specific properties
}
Now, when you POST the data back, not only do you have complete control over what properties you want to bind to your view models, you also have complete control over the mapping that takes place from converting your view models to domain models.
The bottom-line is this: if your view accepts a view model that only allows the user to POST the data they should be allowed to POST, over-posting doesn't even exist. Well-designed view models, or even making the distinction between a view model and an input model (i.e. a separate model that represents the data you want to bind back to in your action), eliminates over-posting entirely.
Over-posting only exists because you're not restricting the model binding process enough. If you ask it to bind to a class that has 10 properties in it when you only need 3 you're allowing the user to potentially stuff data into those other 7 properties.
This is one reason why view models are so popular. They allow you to narrow the scope of your view, whilst also narrowing the scope of the model binder. That leaves you free to properly manage the process of mapping from your view model to your domain model, without introducing a vulnerability.
Update
As you don't want to go the view model approach, your idea will work but you can do it slightly differently. Something along the lines of:
public ActionResult SomeAction(SomeModel model, TMSRack rack)
Where:
SomeModel is the type of model you're decorating with Bind(Exclude...) (it's not obvious what type that is from your question.
TMSRack is the type I assume you want to bind to.
As TMSRack is defined in your main model anyway, as long as you're using the Html.* helpers, it will have the correct names generated for it on the form in order to bind straight back to it as a separate parameter on your action. Then you can do whatever you want with it, without resorting to Request.Form.

How to return a ArrayList incase of Struts2 Action class execute method?

I have a question with respect to returning data inside Struts2 .
Inside my Action class as shown below , i am getting the Records and setting them inside ArrayList .
But could anybody please tell me , how can i return the Obtained ArrayList to the JSP Page ? because with the syntax of the Action class execute method , it allows us to return only a String ?
public class DBDisplay extends ActionSupport{
private String name ;
List list = null;
public String execute() throws Exception
{
list = DBClass.getInstance().list();
Iterator it = list.iterator();
while(it.hasNext())
{
name = (String) it.next();
}
setName(name);
}
public String getname()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
Action classes return a string to name the result, not to return data. Data is exposed via either action properties (like the name you already expose) or a model instance (if implementing ModelDriven).
Access to the list is the same as name–by providing a public accessor to the list:
public class DBDisplay extends ActionSupport {
private List list;
public List getList() { return list; }
// Rest of class elided.
}
Then from the JSP, for example:
<s:iterator value="list">
<s:property /><br/>
</s:iterator>
The iterator tag "value" attribute refers to the list action property, and will call getList() on the action. The property tag will access the value on the top of the stack if given no "value" attribute.
You may wish to spend some time looking over the Struts 2 "nutshell" documentation.
One of the fundamental design goals of Struts 2 framework is to bring MVC (Model-View-Controller) design pattern into the Web application development. MVC pattern enables separation of concerns and allows for the clean and loosely coupled code which is easy to maintain.
MVC pattern consists of 3 distinct pieces. Model, View and Controller. Let us see how these three elements are implemented in Struts 2.
Controller (StrutsPrepareAndExecuteFilter) – Controller is the component which handles co-ordination of various requests. In a Web application, different user requests needs to be served by different application components and this decision is taken by the Controller component. In Struts 2, every request to the Web application first reaches the front controller class – StrutsPrepareAndExecuteFilter. This inspects the incoming requests and then routes the request to the appropriate class (known as Action class in Struts) configured to handle the request.
Model (Action) – Model is the component which is responsible for executing application’s business functionality. It is the core of the application. It represents the state of the application and includes business logic and business data. In Struts 2, action classes act as the gateway to an application’s model. These classes are responsible for handling every user request and then delegates business logic to other classes written by the application developer.
Having different action classes for different user requests ensures that we have clean code which can be easily maintained. But what about functionality that is required across different user requests(such as application logging)?. For such cross cutting concerns, Struts 2 has a different component called interceptors.
View (Result) – View in an MVC architecture is the component responsible for the presentation(user interface). View component makes use of the Model component to get data and then display it. Struts 2 supports multiple technologies such as JSP, Velocity templates, FreeMarker, XSLT for View component. In Struts 2 terminology, View is know as the Result. The action class (Model) determines what Result (View) should be presented to the user.
User accesses a Struts 2 application functionality by accessing the application URL in a browser. The request always comes to the StrutsPrepareAndExecuteFilter controller(Since it is configured in the web.xml of all Struts 2 applications). StrutsPrepareAndExecuteFilter looks for the Action class to call in the struts.xml file. Alternatively it can guess it using conventions. Action class execute() method is then invoked which in turn calls the business logic classes.
Action classes can specify the view to display using annotations or it can be specified in the struts.xml file. Either way Struts 2 knows which View (Result) is to be invoked for displaying the data back to the user. Another important thing to note here is that the objects in the Action class is available to the View component. Hence Actions not only determine which View(Result) to display but also provides data required by the View.
The valueStack(it's combination of objectStack and contextMap) OGNL is used to store the action and other objects. You can use OGNL to access the objects stack and Context Map.
OGNL
Bind the elements to modal objects and converts values from one type to another
Bind generic tags with modal objects.
Create lists and maps on the fly, to be used with GUI methods
Invoke methods. you can invoke any method, not only getters and setters.

Using LLBL as Model in MVC

I have settled on trying to use ASP.NET MVC but the first part I want to replace is the Model. I am using LLBL Pro for the model.
I have a table called "Groups" that is a simple look up table. I want to take thhe results of the table and populate a list in MVC. Something that should be very simple... or so I thought.... I've tried all kinds of things as I was getting errors like:
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[glossary.EntityClasses.GroupEntity]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable1[glossary.CollectionClasses.GroupCollection]'.
private GroupCollection gc = new GroupCollection();
public ActionResult Index()
{
gc.GetMulti(null);
return View( gc.?????? );
}
This is all I am trying to do, I've tried lots of variations, but my goal is simply to take the data and display it.
Not sure if this would work, but you could try wrapping the EntityCollection into a ViewModel class and passing it to the View like so:
public class GroupsViewModel()
{
public GroupCollection Groups { get; set; }
// other items in your view model could go here
}
then convert your controller method to
public ActionResult Index()
{
GroupCollection gc = new GroupCollection();
gc.GetMulti(null);
GroupsViewModel vm = new GroupsViewModel();
vm.Groups = gc;
return View(vm);
}
I like this approach because each ViewModel is an object in-and-of itself.
You can use the AsEnumerable extension where your ????? are or change the type of your ViewUserControl(in the markup) to be of type System.Collections.Generic.List. Basically what you need to correct is the mismatch between the type of the View and the Model being passed in.
I'm not sure about your exact error, but I'd venture a guess that one of two things are happenging:
You are making some sort of invalid / illegal call on your LLBLGen object. If this is the case make sure you are setting it up right / calling right method / property etc.
The model you are passing to the veiw is too hairy for it to deal with. In this case, and in general, you should create a light 'View Model' class with just the data you want displayed and populate it from your LLBLGen object first then pass it to the view, which will be able to easily handle your view model class.
Here are some references:
http://stephenwalther.com/blog/archive/2009/04/13/asp.net-mvc-tip-50-ndash-create-view-models.aspx
http://nerddinnerbook.s3.amazonaws.com/Part6.htm
http://www.codinginstinct.com/2008/10/view-model-inheritance.html
Stemming off what Yuriy said, it looks like your view is strongly typed to a "collection" of a collection of your groupentity, and you are trying to pass just the collection of your groupentities. Make sure your "collection" type (IEnumerable, IList, etc) matches what type of collection you are sending in your controller, along with the type of the actual object in the collection.
View:
System.Collections.Generic.List1[glossary.EntityClasses.GroupEntity]
Controller:
System.Collections.Generic.List1[glossary.EntityClasses.GroupEntity]
Just a thought

Resources