In ASP.NET you use the combination of a trust level (aka grant set) and a list of full trust assemblies to setup the homogenous AppDomain. This maps directly to the AppDomain.Create() call that allows the corresponding parameters to be passed in.
Another option is to determine the trust level of application assemblies using a policy resolver. The resolver gets called when an assembly gets loaded into the AppDomain and you can dynamically specify if the assembly should run in full trust or the AppDomain grant set.
The second option is implemented using the new features around AppDomain managers in .NET 4.0 (read more here, here, here). These new features basically boil down to two new ways to specify an AppDomainManager for the default or newly created AppDomains. You can now either use configuration (in the runtime section) or specify the AppDomainManager type on the AppDomainSetup object when manually creating AppDomains.
This is exactly what ASP.NET is doing. The AppDomainManager implemented in System.Web.Hosting.ApplicationManager+AspNetAppDomainManager is used for ASP.NET created AppDomains. This manager in turn uses a custom HostSecurityManager (implemented in System.Web.Hosting.ApplicationManager+AspNetHostSecurityManager). This host security manager in in turn expresses his interest to resolve policy when assemblies get loaded (using the Flags property).
In the ResolvePolicy method, the host security manager calls out to the policy resolver (if specified). The return is then parsed and turned into either full trust/appdomain trust/nothing permission sets. Nice.
This mechanism is not special to ASP.NET – and can be used in arbitrary applications. Useful for writing hosts with more advanced requirements.
(thanks to shawnfa/stefsch)