Sometimes my application has a section with normal ASP.NET requests that doesn't go through the ASP.NET MVC pipeline. I still want to integrate it with the ASP.NET MVC section of my app, using the same master page for example. The ViewMasterPage validates to make sure that the page being rendered is a ViewPage with the same type, so you can't use a normal page with a ViewMasterPage. If you run a ViewPage without an ASP.NET MVC context, all of the html helper methods will throw NullReferenceExceptions.
The solution to this issue is to make your page inherit from ViewPage, and then spoof the ASP.NET MVC context before the page renders. This will allow the page to work with a normal ASP.NET request (no ASP.NET MVC pipeline), but also use the same ViewMasterPage used for the rest of your site.
Here's what to do:
Make your page class inherit from ViewPage instead of Page.
Add the following method to your page:
void SpoofMvc() { ControllerContext ctx = new ControllerContext();
ctx.RouteData = new RouteData();
ctx.HttpContext = new HttpContextWrapper(Context); ctx.RouteData.Values.Add("controller", "TightContent"); ctx.RouteData.Values.Add("action", "View");
this.Html = new HtmlHelper
Call the method before the page is rendered. Page_Load or Page_PreRender should work.
Many shared hosting providers (in this case Mosso) run your ASP.NET applications in a medium trust or modified medium trust environment to reduce security risks. This causes issues with certain techniques and components that require permissions removed by medium trust.
One of the biggest issues other than the actual restriction of permissions is the restriction of partially trusted assemblies calling fully trusted code. By default, if an assembly is strong named, partially trusted assemblies (i.e. the application assemblies in your app running under medium/partial trust) can't call it. This hits many open source components such as iBatis and NHibernate. The workaround to this is to add the AllowPartiallyTrustedCallers assembly level attribute. This will mark the assembly as safe for calling by partially trusted assemblies.
Here is an example of how to modify iBatis to support this:
Enabling NHibernate for medium/partial trust is a similar procedure. If there is enough demand I will present steps and compiled assemblies for it as well.
As for the permission restrictions, most shared hosting providers don't actually run in medium trust as this restricts many useful things such as Reflection etc. One example I've run into recently is Mosso's modified medium trust. They take medium trust, which consists of the following denied permission restrictions:
Call unmanaged code.
Call serviced components.
Write to the event log.
Access Microsoft Message Queuing queues.
Access ODBC, OleDb, or Oracle data sources.
Access files outside the application directory.
Access the registry.
Make network or Web service calls (using the System.Net.HttpWebRequest class, for example).
And then Mosso adds back in the following allowed permission to come up with "modified medium trust":
WebPermission Unrestricted="true"
OleDbPermission Unrestricted="true"
OdbcPermission Unrestricted="true"
SocketPermission Unrestricted="true"
ConfigurationPermission Unrestricted="true"
ReflectionPermission Unrestricted="true"
This is still rather limiting, but at least you can get most things done as long as you can call into the necessary assemblies without getting exceptions as discussed in the workaround section above.
UPDATE: Thanks to Jon Hynes of InfoMason, added functionality to support nested master pages. Code updates highlighted.
I've seen several articles on the web that lay out a method for checking to see if a ContentPlaceHolder has any content or is empty. These work some of the time but unfortunately fall down in certain situations – like a placeholder that contains an embedded literal code block for example.
In this case, there is no publicly exposed method that will provide the answer and no way to assemble any information that would tell us whether the ContentPlaceHolder is empty or has content. Fortunately, there is an internal .NET framework property that gives exactly what we need. A little reflection magic and we have a method that works in all circumstances.
The MasterPage has a property called ContentTemplates that is a dictionary of all content templates that have been generated by the content page. If we check this, we can determine whether in fact the ContentPlaceHolder has been overridden by the content page. This, combined with the control check gives us a method that tells us if the ContentPlaceHolder has anything in it.
This means we have three methods that work together to provide the resulting boolean. First, a method to check if there are any non empty controls in the ContentPlaceHolder:
public static bool HasNonEmptyControls(ContentPlaceHolder cph) { if (cph.Controls.Count == 0) { return false; } else if (cph.Controls.Count == 1) { LiteralControl c = cph.Controls[0] as LiteralControl;
if (string.IsNullOrEmpty(c.Text) || IsWhiteSpace(c.Text)) return false; }
return true; }
static bool IsWhiteSpace(string s) { for (int i = 0; i < s.Length; i++) if (!char.IsWhiteSpace(s[i])) return false;
return true; }
Next, a method to check if the ContentPlaceHolder has a content template defined on the content page:
foreach (string key in templates.Keys) { if (key == cph.ID) { isSpecified = true;
break; } }
return isSpecified; }
This is where the reflection comes in. We grab the ContentTemplates dictionary off the MasterPage and check to see if the specified ContentPlaceHolder is defined.
Finally, we call both methods to come to a final determination of whether the ContentPlaceHolder is empty or has content defined:
This code will all run on .NET 2.0 and above. If you are using .NET 3.5, you can use the following extension method to add this to the ContentPlaceHolder itself:
public static bool HasContentOrControls(this ContentPlaceHolder cph) { return MasterHelper.HasContentOrControls(cph); }
UPDATE: Added support for importing tracked forums and topics.
I'm in the process of converting my forums from Community Server to AspNetForum. While CS may support every single feature under the sun, its very big and unwieldly to work with. The more recent 2007 and 2008 versions are better, but still behemoth. I just want to drop in one simple forum with a couple subgroups, not host a million forums and blogs on my site. For this, AspNetForum fits the bill exactly.
There's no official importer for Community Server to AspNetForum, so I wrote my own. It turns out to be fairly simple. Just a SQL script will do it. Here are the steps the script goes through:
Import the ASP.NET application, user, and membership tables
Create the AspNetForum single sign-on linkages in its ForumUsers table
Import the groups, forums, topics, and messages
I packaged all this up into a SQL script that handles the whole process end to end. Just edit it to point to your database and forums and it will do the rest. It assumes the following:
You've created and set up an AspNetForum database
You've created the ASP.NET user and membership tables in the AspNetForum database
The AspNetForum and ASP.NET user and membership tables are new and empty
This script will import everything with the same ID values from Community Server, making mapping or redirecting easy.
To use the script in SQL Management Studio, do the following steps:
Open the script (Import.sql) and connect it to your AspNetForum database
Execute a search and replace operation that replaces "CSDatabaseName" with the name of your Community Server database
Set the @applicationId variable to the ApplicationId matching your Community Server instance in the aspnet_Applications table in your CS database
Set the @groupId variable to the GroupId of the forum group to import from the cs_Groups table