I have some experience maintaining Grails apps; now creating a "task management" application as an exercise.
Apparently there is a view dichotomy of Groovy Server Pages versus Controller actions that render a view, as evidenced by this snippet from a URLMappings.groovy example:
static mappings = {
// ..
"/" (view:'/index')
"/login/$action?" (controller: 'login')
"/logout/$action?" (controller: 'logout')
"500" (view:'/error')
}
where user-facing URLs must be mapped to either views (GSPs) or controllers rendering a view, e.g.:
class LoginController {
/**
* Show the login page.
*/
def auth = {
// .. auth logic
String view = 'auth'
String postUrl = "${request.contextPath}${config.apf.filterProcessesUrl}"
render view: view, model: [postUrl: postUrl, rememberMeParameter: config.rememberMe.parameter]
}
}
From a design perspective, how do I choose which method to use? When do I create views with GSPs/taglibs like typical server pages outputting HTML, and when do I map a URL to a controller that renders through a delegate GSP? Can I combine both approaches? Have I oversimplified the options here?
To add to what hvgotcodes said, related to your question, the only time you'd want to map directly to a GSP view is when that view is effectively "static".
By static I mean that it isn't relying on the database or any real calculations for rendering the view. It can still be dynamic in that it relies on tag libraries for dealing with common elements, and things like the "Welcome user" text at the top of pages.
As soon as you want to deal with user-supplied input, looking up database information, manage more complicated URLs, or include calculations, you should be using a controller.
The end goal is that GSPs only contain visual and layout information, as well as the occasional static block of text. But you should always avoid mixing any logic in with the GSP, because it clutters the code and always leads to maintenance headaches later on.
Edit regarding Tag Libraries:
As I wrote below:
Tag libraries are for any logic that is connected to the view, like looping over elements, or toggling the visibility of something. Whenever you are tempted to put code directly into your GSP, it probably should be put in a tag library. Of course, there are always exceptions for one-offs.
So, if you have logic code in your view, that specifically relates to visual or layout content, that should be put in a tag library. A good example is the <sec:ifLoggedIn> tag from Spring Security Core, which can be used to toggle the visibility of an element if the user is logged in. This is much better than writing it manually like so:
<sec:ifLoggedIn>blah blah</sec:ifLoggedIn>
<g:if test="${session.user?.loggedIn}">blah blah</g:if>
Because it makes the purpose clearer (by its title), as well as abstracting the logic away, so if you later need to change the way something works, you only have to change it in one place.
tl;dr:
GSPs - Simplified "static" content
Tags - Reusable dynamic components specifically for visual or layout content
Controllers / GSPs - dynamic content
I don't think it's a dichotomy. GSPs and Controller actions (are intended to) work in tandem, the controller invoking services to load data in preparation for passing that data to the appropriate GSP.
The url mapping stuff is for if you want to break the Grails convention for urls, which is orthogonal to how loading data and displaying data (are supposed) to work.
The only time (IMHO) there is a dichotomy is when developers in a project code functionality inconsistently; i.e. it is certainly possible to give the appearance of a dichotomy.
Related
I am using the MvcSiteMapProvider Html Helper to create a navbar. The content of the navbar depends on the visitor rights therefore I am using security trimming to only display content which the person is authorized for. For performance improvement I am trying to cache this navbar.
The navbar is created in a partial view with the following content:
#Html.MvcSiteMap().Menu("MenuHelper", new { name = "MainMenu" })
Inside the layout file it is called by an action method which returns the partial view:
[System.Web.Mvc.OutputCache(Duration = 10, VaryByCustom = "User")]
[ChildActionOnly]
public ActionResult MainMenu()
{
return PartialView("MainMenu");
}
The caching of works fine at the root page of the sitemap. However when the cache duration runs out and the action method is called from deeper levels, no sitemap is returned.
When I disable security trimming or the output caching it works perfectly fine at all levels.
Is it possible that action method which returns the navbar, is called when the authorization data is unavailable and therefore returns a corrupt sitemap?
If you analyze the source for AuthorizeAttribute, you will note that it is not designed to work with child actions that are output cached (they go to some great lengths to ensure that child actions that are output cached will throw an exception).
Of course, it also won't work right if you have a custom AuthorizeAttribute that overrides OnAuthorization that does not duplicate this important logic.
However, there are a couple of things you can do to improve performance when using Security Trimming:
Make sure that your injection constructors are simple, especially on your controllers. If you have heavy processing in your constructors, it can really slow things down (with or without MvcSiteMapProvider, but Security Trimming makes this much more apparent).
If that doesn't improve things enough and you are not using a custom AuthorizeAttribute, you could use the roles attribute/property to duplicate your role logic into the SiteMap and remove the AuthorizeAttributeAclModule from your configuration (external DI only).
See this discussion for more details.
I am wondering if you have tried to make a mobile version of a controller?
Right now I am extending GrailsLayoutDecoratorMapper with my custom MobileDecoratorMapper which applies layout.mobile.gsp if the mobile phone is detected and I would like to do something similar for some controllers. My idea is to check in the filter if there existing a mobile version of a controller (for example SomethingControllerMobile or SomethingController.mobile.groovy) and if so redirect to it instead of a default SomethingController.
The reason for that is I would like to avoid a lot of if/else statements inside controller itself to check whether it is mobile, and if so do something differently - i do not want spaghetti code.
Does it makes sense to you and if so have you tried to do something similar and what was your approach? The only thing that comes to my mind is check for files in the filter but it does not look like a proper solution, i think this should be possible to be done on urlmapping level, where on the basis of the url grails decides which controller to invoke
The Spring Mobile plugin allows you to conditionally execute controller code for mobile devices in a fairly elegant fashion
def list = {
def view = "list"
withMobileDevice {
// mobile-specific logic goes here, in this simplistic example we
// just change the view, but you can do anything you like....
view = "mobileList"
}
render(view: view, model: [list: listInstance])
}
Should we move logic that supposes to be in Controller (like the data to render the partial view) to ActionFilter?
For example, I'm making a CMS web site. There should be a advertisement block to be rendered on several pages but not all the pages. Should I make an ActionFilter attribute like [ShowAd(categoryId)] and decorate the action methods with this attribute?
The implementation of this controller would include service calls to retrieve information from database, buildup view models and put in the ViewData. There would be a HtmlHelper to render the partial view using the data in ViewData if it exists.
That just seems yucky to me.
When I'm trying to figure out whether I need an ActionFilter, the first question I have is, Is this a cross-cutting concern?. Your particular use-case doesn't fit this, at first blush. The reason is, is that an ad is just another thing to render on a page. There's nothing special about it that makes it cross-cutting. If you replaced the word 'Ad' with 'Product' in your question, all the same facts would be true.
So there's that, and then there's the separation of concerns and testability. How testable are your controllers once you have this ActionFilter in place? It's something else you've got to mock out when testing, and what's worse is that you have to mock out those dependencies with every controller you add the ActionFilter to.
The second question I ask is, "How can I do this in a way that seems most idiomatic in the platform I'm using?"
For this particular problem, it sounds like a RenderAction and an AdController is the way to go.
Here's why:
An Ad is its own resource; it normally isn't closely tied to anything else on the page; it exists in its own little world, as it were.
It has its own data-access strategy
You don't really want to repeat the code to generate an Ad in every place you could use it (which is where a RenderPartial approach would take you)
So here's what such a beast would look like:
public AdController : Controller
{
//DI'd in
private AdRepository AdRepository;
[ChildActionOnly]
public ActionResult ShowAd(int categoryId)
{
Ad ad = Adrepository.GetAdByCategory(categoryId);
AdViewModel avm = new AdViewModel(ad);
return View(avm);
}
}
Then you could have a custom partial view that is set up around this, and there's no need to put a filter on every action (or every controller), and you don't have try to fit a square peg (an action filter) in a round hole (a dynamic view).
Adding an Ad to an existing page then becomes really easy:
<% Html.RenderAction("ShowAd", "Ad" new { categoryId = Model.CategoryId }); %>
If your ad system is simple enough, there is no reason you could/should not use an action filter to insert enough info into the view data to generate the ad in your view code.
For a simple ad system, say.. a single ad of a specific category shows up in the same place in the layout on every page and that's it, then there is no real argument of a better way except to prepare for future changes to the system. While those concerns may be legitimate, you may also have it on good authority that requirement will never change. But, even if requirements do change, having wrapped all the code that generates ads in one place is the most important aspect and will save you much more time up front than a more robust solution might. Obviously there are more than a few ways to wrap this code in a single place.
As for the way you are choosing to do it, I would keep your action filter cleaner to only have it insert the category into the view data and have all the magic happen inside your html helper which would take the category in as a parameter. Building up view models to shove into the view data is going to require a bit of extra work, and put code all over the place when it doesn't need to be there. Keep it simple and do all of the html generation inside of the html helper which is responsible for...building html.
My Problem: I have an MVC3 application where all views use a common master page. The master page has many links to other (internal) sites. I need to be able to change the domain of these links depending on the deployment environment (e.g. staging.blah.com, www.blah.com, dev.blah.com etc). This domain is stored in the web.config.
There are numerous ways of doing this, but I am looking for some sort of consensus as to the preferred method. Here are some options but I am open to any suggestions:
(1) reference appsettings from master page directly. This is the simplest and most common approach but I am not particularly keen on reading the web.config and concatenate the url throughout the master page code. In fact, I am not sure that I like the idea of the view accessing the web.config at all.
(2) stick the appsetting value in viewdata/viewbag using a custom action filter which reads the config. concatenate in the page as before.
(3) as (2), but inject appsetting value in via contructor injection rather than reading it within the filter.
(4) create a base class for all my strongly typed viewmodels and populate with the appsetting using a custom action filter.
(5) create an htmlhelper that takes in the path and internally reads the appsetting and concatenates.
(6) create a custom view base class, inject in appsetting value and make available as property or function that takes in path and concatenates.
Just to add that typically when the master page requires data, I like to use Html.Action, but this is not possible in the case of these URLs that are used throughout the master page.
Thoughts?
(5) create an htmlhelper that takes in the path and internally reads the appsetting and concatenates.
I would go with this one. Your custom HTML helper could look something like this:
<%= Html.ExternalActionLink(
"link text",
new { path = "/foo/bar.php" }
new { param1 = "value1", param2 = "value2" }
) %>
and could emit the following HTML:
link text
What I have done in the past is use viewdata/viewbag in my master page and populate its values in my base controller. The base controller in turn called another class to do the work of reading the values from the web.config.
This way the view is pretty clean (e.g. it does not contain code to read appsettings) and I don't need to create a base view model that matches all my views that use the master.
This approach has the disadvantage that uses a viewdata/viewbag but I decided that was OK in my case and extremely easy to implement.
I'm experimenting with MVC, and my question is - where I had Page_Load logic in Master Pages with WebForms, where should it go in MVC? Here's the business case:
Different Host Headers should cause different Page Titles to be displayed on the site's (one) Master Page, therefore all pages. For example, if the host header is hello.mydomain.com, the page title should be "Hello World" for all pages/views, while goodbye.mydomain.com should be "Goodbye World" for all pages/views.
If the host header is different than anything I have in the list, regardless of where in the application, it should redirect to /Error/NoHostHeader.
Previously, I'd put this in the MasterPage Load() event, and it looks like in MVC, I could do this either in every controller (doesn't feel right to have to call this functionality in every controller), or somewhere in Global.asax (seems too... global?).
Edit: I have gotten this to work successfully using the Global.asax method combined with a Controller for actually processing the data. Only problem at this point is, all of the host header information is in a database. I would normally store the "tenant" information if you will in a Session variable and only do a DB call when it's not there; is there a better way to do this?
There is no 1:1 equivalent in MVC for a reason, let's just recapitulate how to think about it the MVC way:
Model: "Pages of this site are always requested in a certain context, let's call it the tenant (or user, topic or whatever your sub domains represent). The domain model has a property representing the tenant of the current request."
View: "Render the page title depending on the tenant set in the model."
Controller: "Set the tenant in the model depending on the host header".
Keep in mind that what we want to avoid is mixing controller, view and business logic. Having controller logic in more then one place or a place, that is not called "controller" is not a problem, as long as it remains separated.
And now the good thing: You could do this "MVC style" even with Web Forms, and the solution still works with ASP.NET MVC!
You still have the request lifecycle (not the page lifecycle), so you could implement a custom HttpModule that contains this part of the controller logic for all requests. It handles the BeginRequest event, checks for the host header, and stores the tenant to something like HttpContext.Current.Items["tenant"]. (Of course you could have a static, typed wrapper for this dictionary entry.)
Then all your model objects (or a model base class, or whatever is appropriate for your solution) can access the HttpContext to provide access to this information like this:
public string Tenant
{
get { return HttpContext.Current.Items["tenant"]; }
}
Advantages:
You have separated cause (host header) and effect (rendering page title), improving maintainability and testability
Therefore you could easily add additional behavior to your domain model based on this state, like loading content from the database depending on the current tenant.
You could easily make more parts of the view depend on the tenant, like CSS file you include, a logo image etc.
You could later change the controller logic to set the tenant in the model not only based on the sub domain, but maybe based on a cookie, a referrer, search term, user agent's language, or whatever you can think about, without modifying any of your code depending on the model.
Update re your edit: I don't like the idea of holding the state in the session, especially if your session cookie might apply not only to each sub domain, but to all domains. In this case you might serve inconsistent content if users visted another sub domain before. Probably the information in the database that is mapping host headers to tenants won't change very often, so you can cache it and don't need a database lookup for every request.
You could create a base controller that supplied the correct ViewData to your MVC Master Page View, then derive each of your actual controllers from that one. If you put the logic into the ActionExecuting method, you should be able to generate an exception or redirect to an error page if necessary.
You are thinking too "WebForms" and not enough MVC. A master page is just a wrapper of your view, and it should only contain layout html. You can send stuff to your master, but it's a one way road and you should strive for agnostic views. Bottom line: forget about the events that WebForms had as they aren't going to be used here.
Since you are dealing with Host headers I suppose you could put it in the Global.asax...great now I'm confused :P
Stolen code from http://forums.asp.net/t/1226272.aspx
protected void Application_BeginRequest(object sender, EventArgs e)
{
string host = string.Empty;
if (this.Request.ServerVariables["HTTP_HOST"] == this.Request.Url.DnsSafeHost)
{
host = this.Request.Url.DnsSafeHost;
}
else
{
Regex regex = new Regex("http://[^/]*.host/([^/]*)(/.*)");
Match match = regex.Match(this.Request.Url.AbsoluteUri);
if (match.Success)
{
host = match.Groups[1].Value;
Context.RewritePath(match.Groups[2].Value);
}
}
// Match the host with the portal in the database
...
}
What you need is here http://www.asp.net/mvc/tutorials/passing-data-to-view-master-pages-cs