I am struggling to organize domains, controller and views in Grails 2.3.8 application. The applications is quite small right now but its planned to grow bigger and I would like to organize folder structure and package naming convention little better. Ideally I would like to have following structure, but I am sure there are better approaches. I would gladly welcome the wonderful solutions you guys will have.
Domain
Item
Category
Controller
admin
ItemController (package = com.example.admin, namespace = admin)
CategoryController (package = com.example.admin, namespace = admin)
public
ItemController (package = com.example.public, namespace = public)
CategoryController (package = com.example.admin, namespace = public)
Controller
admin
ItemController (package = com.example.admin, namespace = admin)
CategoryController (package = com.example.admin, namespace = admin)
public
ItemController (package = com.example.public, namespace = public)
CategoryController (package = com.example.admin, namespace = public)
Views
admin
index.gsp
create.gsp
etc
public
index.gsp
create.gsp
etc
The questions now are
1. Is this the right folder structure? Any pitfalls this might create
2. How do I accomplish this on grails
Grails is a convention over configuration framework. Grails projects all follow a predefined folder arrangement, which is fully described in the documentation:
http://grails.github.io/grails-doc/latest/guide/single.html#conventionOverConfiguration
When you create a Grails application, it sets up a default Groovy/Java package. This default package (which can be changed) is used by default when you create Grails artefacts (domains, controllers, etc). I've toyed with the idea of creating sub-packages for these artefacts, but now I simply don't do that. Here's why.
Grails does a lot of stuff for you. That's fantastic because it eliminates a lot of boilerplate code and saves you time. And the more you stick with the conventions, the more Grails can do for you. And this applies to packaging. If you leave the artefacts in the default package, that cuts down on the number of import statements. This applies not only when you create artefacts but also when you create unit tests (they use the same default package).
Your concern about putting many classes into the same package is a valid one. And since you're expecting your application to grow in size that's a likely possibility. The Grails solution is plugins. You can modularize your application into plugins and then use a main app to bring everything together. Each plugin would have a different package namespace.
If you want your app and its plugins to live in the same codebase, you can create a plugins directory in your project (as a sibling directory of grails-app). Then, create your Grails plugins within the plugins directory. Finally, refer to each plugin in grails-app/conf/BuildConfig.groovy.
grails.plugin.location.'plugin-a' = './plugins/plugin-a'
grails.plugin.location.'plugin-b' = './plugins/plugin-b'
Related
I'm working on MVC 3 and using resource files to localize the application. Now we have another customer on board for the application and they would like to change some of the text on the application..typical.
I have create a separated resource file for them and would like to do something like this in views
if (customer =A )
#using Resources.customerA
else
#using Resources.customerB
I've a resource class in both namespaces so something like this works fine if I change the namespace
Resource.WelcomeUser
Is it possible to use conditional using statement in views? I'm unable to find the right syntax for this. Any ideas?
You can put both using statements in View, but when you use classes you would have to write some namespace prefix.
Example:
#using Project.Resources.customerA
#using Project.Resources.customerB
using classes:
customerA.WelcomeUser
customerB.WelcomeUser
I think there is no other way, because two files cannot have the same path.
What you're really talking about is the provider pattern. You have two (or more) interchangeable things, and you want to be able to use one or the other contextually.
The correct way to do this in an OO context is to create and use an interface, while then injecting the actual implementation you want at runtime. You can actually achieve this in ASP.NET Core, which supports injection in Views, but in ASP.NET MVC 5 and previous, you'd need to go a little out of your way. I'm imagining these are currently static classes, since you're referencing them merely via namespace. With that approach, you'd need to follow #Ssheverdin's advice and use the FQN of the class (i.e. with the namespace):
#if (customer == A)
{
#Resources.customerA.StaticClass.Property
}
else
{
#Resources.customerB.StaticClass.Property
}
Alternatively, you could change the static classes to be instance classes and use a factory pattern to return the right one. This is a very simplistic example, but hopefully enough to convey the idea:
public static class ResourceFactory
{
public static IResourceClass GetForCustomer(string customer)
{
switch (customer)
{
case "A":
return new Resources.customerA.ResourceClass();
default:
return new Resources.customerB.ResourceClass();
}
}
Then:
#{ var resource = ResourceFactory.GetForCustomer(customer); }
I have managed to achieve the behaviour by adding a web.config file under views folder and including the namespaces there, i have to remove the #using statement from all views obviously. You might find that intellisense doesn't work anymore for you so try closing all views and reopen them again.
With this way I can create a separate web.config file for each customer and specify the relevant namespaces accordingly. Now just have to make sure to provide the RIGHT config file for each customer when deploying the release:)
I am trying to use the new Localization features of .NET Core, but outside the simple sample Microsoft has provided here, https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization#resource-file-naming.
I have my Controllers in a separate project, ProjectA.Controllers, while I have a shared resource class in a common project, ProjectB.Localization. I've configured my startup class as prescribed in the docs.
I am unclear on what to name my resource file and where exactly to put it. I've configured the option to store in the directory "Resources". Is that in the Web project or my ProjectB.Localization where my SharedResource class is? The docs say that if it's a separate assembly, the full namespace should be used. So I've named it, "WorldCart.Facilities.Localization.SharedResource.es.resx" and placed it in the resources folder of the website.
When I run the web app, and debug in the home controller, I do not get a translated string, I get the english version.
Any ideas?
Very late answer, but it might help someone...
I had a similar situation where I had to have the resource files in separate common assembly instead of having it inside the mail web/api project (Core 2.1). The reason being, I could be using the localized resources from other assemblies like Business or DAL layer for throwing warning/error/information messages. This is what I did:
Assume that my web project namespace is MyApp.Web and my resources are in separate class lib MyApp.Resources. In the resources library, create a folder (optional), say "Messages", and create a class Messages.cs. Create the resource files inside the same folder adhering to the naming conventions. For example, Messages.fr.resx.
In the ConfigureServices method of the main project, add the localization without any resource path*:
services.AddLocalization();
services.Configure<RequestLocalizationOptions>(
opts =>
{
/* your configurations*/
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en"),
new CultureInfo("fr")
};
opts.DefaultRequestCulture = new RequestCulture("en", "en");
// Formatting numbers, dates, etc.
opts.SupportedCultures = supportedCultures;
// UI strings that we have localized.
opts.SupportedUICultures = supportedCultures;
});
And in the Configure method, add app.UseRequestLocalization();
In your controller, inject IStringLocalizer<Messages> localizer, where Messages is the class you created in the Resources library. All your localized resources will be available in the localizer object, i.e., localizer["your key or default text"].
The reason for not adding any ResourcePath in the services.AddLocalization(); options is due to the reason that both the resource files (Messages.fr.resx) and the dummy class (Messages.cs) are in the same path. The framework will check for the resource file relative to the class which we have specified in IStringLocalizer<>. If the Messages.cs was in the root folder of MyApp.Resources lib and the resource files were inside folder "xyz", then the configuration should be services.AddLocalization(ops => ops.ResourcesPath = "xyz");
UPDATE - Responding to the queries in the comments:
MVC Views
In MVC Views, the documented approach uses IViewLocalizer, but does not support resource sharing. So you can inject IStringLocalizer<> in the view for using the shared resources. For example:
#inject IStringLocalizer<Messages> localizer
<h2>Information - #localizer["Shared resource access in MVC Views"]</h2>
Data Annotations
In order to use shared resources in the data annotations, you can use the factory method in the service:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddDataAnnotationsLocalization(options => {
options.DataAnnotationLocalizerProvider = (type, factory) =>
factory.Create(typeof(Messages));
});
where the Messages in the typeof(Messages) is your shared resource dummy class.
I am a long-time semi-professional asp.net programmer who has recently made the leap from asp.net forms environment to MVC. So I am in the process of re-developing one of my web apps which exists in forms to MVC, and I am really liking the MVC environment and am glad I am making the leap.
But...am encountering some issues, like this one with namespaces.
So let's imagine the following:
1) I create a new MVC 4 project called MyMvcProject
2) I immediately create the App_Code folder and add to it a new class1.vb file as follows:
Namespace MyNamespace
Public Class Class1
Property MyProperty As String
End Class
End Namespace
Ok so far so good. Now I want to access this class in a controller or something as follows:
Imports MyNamespace
Public Class Default1Controller
Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Dim obj As New Class1
Return View()
End Function
End Class
In the asp.net forms environment, I would be good to go. However, in my MVC project i am unable to import "MyNamespace" and therefore unable to create Class1. So I go looking for it and find the following in the object browser:
>[OTHER REFERENCES...]
v[VB] MyMvcProject
>{}MyMvcProject
>{}MyMvcProject.My
>{}MyMvcProject.My.Resources
v[VB] MyMvcProject
v{}MyNamespace
> [] Class1
>[OTHER REFERENCES...]
So there's my "MyNamespace" and "Class1." So I attempt to revise the code as follows:
Imports MyMvcProject.MyNamespace
Public Class Default1Controller
Inherits System.Web.Mvc.Controller
Function Index() As ActionResult
Dim obj As New Class1
Return View()
End Function
End Class
But THIS doesn't help. I am only able to reference the FIRST instance MyMvcProject and it's sub-references. Not the second where my "Class1" resides. I am uncertain as to how to fix this.
UPDATE 1: I FOUND THE SOLUTION (BUT NOT THE EXPLANATION)
For whatever reason, ASP.NET MVC does not like me to put stuff in the App_Code folder. However, if i put the class1.vb in the MODELS folder then I can I can import "MyMvcProject.MyNamespace" and proceed normally. The object browswer now looks like this:
>[OTHER REFERENCES...]
v[VB] MyMvcProject
>{}MyMvcProject
>{}MyMvcProject.My
>{}MyMvcProject.My.Resources
v{}MyMvcProject.MyNamespace
> [] Class1
v[VB] MyMvcProject
>[OTHER REFERENCES...]
Notice the second reference to MyMvcProject STILL exists. But "MyNamespace" and the classes i put into it are under the FIRST instance, which I can reference and import normally. Kind of quirky, it seems to me. But if i have to throw everything in the MODELS folder, well, I'll do it.
UPDATE 2
Had I know the problem was with putting stuff in the App_Code folder, I could have found some answers:
ASP.NET MVC using App_Code directory
UPDATE 3
All MVC project by default are Web Application Projects (WAP) instead of Web Site projects. This means that there's no need for an App_Code folder since WAPs always get compiled anyway. That means that all *.cs files in your project will get compiled, as opposed to Web Site projects where only *.cs files in your App_Code folder would get compiled.
Source: http://forums.asp.net/t/1315143.aspx?MVC+and+App_Code+folder
UPDATE 4
And here is how to include the App_Code folder in your MVC project:
you have to go at Properties section of the file and then for Build Action change from Content to Compile. That is it ;)
Source: http://how-to-code-net.blogspot.mx/2014/04/appcode-classes-not-available-in-aspnet.html
I have two Grails classes with a hasMany dependency:
class Author {
String name
static hasMany = [books: Book]
}
I generated the controller with scaffolding its running and works. But is there a way to gernate a book within the the author controller? So there i can generate a author and some book without changing the view? Thanks in advance.
The built in scaffolding templates for Grails do not have this ability. However, you can use the Grails command install-templates and modify/enhance the templates to have this ability.
Using this command the following directories and contents will be added to your project:
src
templates
artifacts
scaffolding
war
The templates within the "scaffolding" directory are where you want to make your changes/enhancements.
So, in short, no, there is no out of the box functionality for this, but you can add it on your own.
I'm trying to create my own CRUD controller in grails for the stuff that the scaffolding won't do.
Instead of maintaining code for a controller for each domain, I'd like to have one controller that can look after any domain for the generic CRUD calls.. as the only difference is the domain class name.
Using the example of domain class Job & Note
Instead of
Job.get(id)
Job.list()
def instance = new Job(params)
Note.get(id)
Note.list()
def instance = new Job(params)
I was thinking of
def someHandler = Job // configurable
someHandler.get(id)
someHandler.list()
def instance = new someHandler(params)
The first two static methods work fine (get, list) but creating a new instance does not.
Any pointers as to how to best do this.
Cheers
Call the default constructor using
def instance = someHandler.newInstance()
and the constructor for params using
def instance = someHandler.newInstance(params)
If you're not happy with the scaffolded controllers/views that Grails provides by default, and want to change them in a similar fashion for all domain classes, a better approach might be to simply edit the templates that are used to generate these controllers/views.
You can do this by running the script grails install-templates. This will create a number of files in the src/templates/scaffolding directory, each of which defines the template used to generate a scaffolded artifact.
Change these templates to create the controllers/views that you want. If you've already run grails generate-all for any domain classes, you'll need to run it again for those classes to update the existing scaffolding.