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.
Related
i have a PropertyController, which I use to serve a bunch of pages. For example..
/Property
/Property/{id}
/Property/add
/property/edit/{id}
I now need to do a bunch of stuff based on a particular property I will need to do serve pages like this:
/Property/{id}/images/add
/Property/{id}/images/edit/{id}
/Property/{id}/rooms/add
/Property/{id}/rooms/edit/{id}
I think I need to build a new ImagesController and RoomsController, but do I need to but these in a folder structure? My RouteConfig is currently set to the default MapRoute rule ({controller}/{action}/{id}
You don't need to reflect your routing structure in your folders structure.
Check this one out:
ASP.Net MVC support for Nested Resources?.
Effectively your routing string is a regExpression to match whatever comes in from a requester. And if there's a match it's trying to bind all the variables in your expression to values from the HTTP request.
In regard to creating new controllers - a rule of thumb is to create a controller per resource / business entity. So in your case I would say yes to ImagesController, RoomsController and PropertyController.
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.
Is there a recommended approach for creating Google Sitemaps using ASP.NET MVC?
I'm new to MVC and this is the first time I've needed to create one and wondered how best to go about it.
I have a number of static links (About Us, FAQ's etc.) that I would like included within the sitemap, but then need the rest of it to be dynamically generated from articles that have been posted on the site.
Any advice/direction on how to create this would be much appreciated.
1 - The first thing you'll need is to create a representation of your entire website a list of nodes which have children, parents and so forth. The easiest way to do this without rolling your own solution is to use the MVCSiteMapProject. It allows you so use MVCish terms like your action and controller names to define nodes which will automatically have the correct urls using your routing definitions.
2 - Now because the MVCSiteMap inherits from the default XmlSiteMap (may not have the exact name right ) you can use another add in to generate a google sitemap from the nodes you've defined in the MVCSiteMapProject.
There a bunch of ways to do #2 so its up to you to decide the technique.
You could create a generic handler the same as web forms, but I'd be inclined to use a controller action and a custom route.
Some simple steps to follow might be:
Create an action in your Home Controller (or create a new one), call it SiteMap.
Have the action return a View with your page data as the model.
Create a View called SiteMap that contains the Google XML, then iterate through your page data to generate the dynamic content.
Add a custom route to your Global.asax file that points to "/sitemap.xml" or whatever and pre-populate your the controller and action parameters with that of your new action.
If you're unsure of custom routes, just copy the default one and paste it above. The routes are handled first come first serve. Make sure you give it a new name.
Rich
for dynamic sitemap, i found this is the best solution : http://ben.onfabrik.com/posts/generating-dynamic-xml-sitemaps-in-aspnet-mvc
it use a controller to generate the xml file.
I have the requirement to support themeing of my site's pages. The way I am doing this is by dynamically choosing a master page based on the current theme.
I have setup a directory structure like so
/shared/masterpages/theme1/Master1.master
/shared/masterpages/theme1/Master2.master
/shared/masterpages/theme1/Master3.master
/shared/masterpages/theme2/Master1.master
/shared/masterpages/theme2/Master2.master
/shared/masterpages/theme2/Master3.master
And I am still using the page directive in the view
<%# Page Title="" Language="C#" MasterPageFile="~/Views/shared/masterpages/theme1/Master1.Master"%>
I would still like to leverage the view's MasterPageFile property and just change the theme directory.
I can only think of three ways to do this none of them which sound great.
Create a custom BaseView class that uses OnPreInit to change the theme like this
Create some xml file or database table that links each view to a master page file and then set this in the controller.
Build some tool that reads all the views and parses them for their masterpagefile, (similar to 2 but could be done at run time potentially.)
Option 1 seems the best option to me so far. Does anyone else have any thoughts on how to do this?
Updated suggestion:
Since my original suggestion didn't work out as I had expected, here's a possible way to work around it, while still keeping your action methods clean, and minimizing repetition of code:
Create an ActionResult that adds the master name/theme name/whatever info you need to pick the correct master page into ViewData["masterInfo"] (or something similar).
Create a base class which themeable views inherit. Your base class should, of course, inherit from System.Web.Mvc.ViewPage. If you need, also create a generic version that inherits from .ViewPage<T>.
In your base class, create a construction method that selects the correct master page based on ViewData["masterInfo"]. I'm not sure if there's a need or not, but don't forget to run the base constructor, either before or after your code, if there is one that needs to run.
Decorate all relevant actions with the attribute, and set their views to inherit your base class instead of System.Web.Mvc.ViewPage.
Original post:
Why not have an ActionFilter, that can be applied on controller level, that sets the MasterPageFile property of the view? If you override OnActionExecuted, it shouldn't be too tricky to test if the result was a ViewResult and in that case change the property to the correct value.
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