Knowledge is power. We love to share it.

News related to Mono products, services and latest developments in our community.

denis

Hosting multiple applications in MonoX

09/12/2010Categories: MonoX
If you ever wanted to have multiple ASP.NET sites running from a single database and application folder, we have a straightforward solution for you. It is based on experience from dozens of projects we implemented for clients that needed this feature, and an excellent article by Christophe Geers that describes a slightly different scenario. Although we are using MonoX providers in our code, the same approach can be used for all other ASP.NET applications.

As you are probably aware, various ASP.NET providers (membership, personalization, ...) require application name to be set in the web.config file. At the first sight, it seems that you are out of luck if you need to change the application name dynamically. Many of our projects need to have this functionality - the application name usually depends on the host address, virtual directory, or some similar set of criteria. For example, you may want to have requests for domain www.domain1.com dispatched to one application, whil requests for www.domain2.com go to the second app, and so on.

Looking into the documentation for Membership's ApplicationName property does not bring up any good news: although this is a writable property, a big "Caution" frame says that "Because a single default membership provider instance is used for all of the requests served by an HttpApplication object, you can have multiple requests executing concurrently and attempting to set the ApplicationName property value. The ApplicationName property is not thread safe for multiple writes, and changing the ApplicationName property value can result in unexpected behavior for multiple users of an application. We recommend that you avoid writing code that allows users to set the ApplicationName property, unless you must. An example of an application where setting the ApplicationName property may be required is an administrative application that manages membership data for multiple applications. Such an application should be a single-user application and not a Web application."

We need this to work in a scalable Web application, so better forget about setting this property from your code. The solution is simple, but elegant: you need to use an HttpModule to parse the request, determine which application should we dispatch the request to, and write the application name in HttpContext. Afterwards, we may use this information in any of our providers' ApplicationName property, without ever setting it.

Here is the code for the HttpModule:

using System;
using System.Web;

namespace MonoSoftware.Samples
{
    
    public class DynamicApplicationModule : IHttpModule
    {
        /// <summary>
        /// You will need to configure this module in the web.config file of your
        /// web and register it with IIS before being able to use it. For more information
        /// see the following link: http://go.microsoft.com/?linkid=8101007
        /// </summary>
        #region IHttpModule Members

        #region Fields

        

        #endregion
        
        public void Dispose()
        {
            //clean-up code here.
        }

        public void Init(HttpApplication context)
        {
            context.AuthenticateRequest += DetermineApplicationName;
        }

        private static void DetermineApplicationName(object sender, EventArgs e)
        {
            // Access the current Http application.
            HttpApplication application = sender as HttpApplication;
            if (application == null)
            {
                throw new InvalidOperationException("Http application cannot be null.");
            }

            // Get the HttpContext for the current request.
            HttpContext context = application.Context;
            if (context == null)
            {
                throw new InvalidOperationException("Http context cannot be null.");
            }

            // Make the decision based on your requirements, usually by parsing host
            // name or path.
string applicationName = String.Empty; string path = context.Request.Url.AbsolutePath.ToLower(); if (path.StartsWith("/application1/")) applicationName = "app1"; else if (path.StartsWith("/application2/")) applicationName = "app2"; ///...whatever // Store the application name in the Items collection of the per-request
            //http context.
// Storing it in the session state is not an option as the session
            //is not available at this time.
context.Items[ApplicationSettings.ApplicationName] = applicationName; } #endregion } }

Now you just have to override the default implementation of ApplicationName property in each of your providers - MonoX requires that you do this for RoleProvider, PersonalizationProvider and MembershipProvider. Since the code is identical, we will show the implementation for MembershipProvider only:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MonoSoftware.MonoX;

namespace MonoSoftware.Samples
{
    public class DynamicMembershipProvider : MonoXMembershipProvider
    {

        public override string ApplicationName
        {
            get
            {
                HttpContext context = HttpContext.Current;
                if (context == null)
                {
                    throw new InvalidOperationException("Http context cannot be null.");
                }

                string applicationName = String.Empty;
                if (context.Items.Contains(ApplicationSettings.ApplicationName))
                {
                    if (!String.IsNullOrEmpty((string)
                        context.Items[ApplicationSettings.ApplicationName])) { applicationName = (string)
                            context.Items[ApplicationSettings.ApplicationName]; } } return applicationName; } set { base.ApplicationName = value; } } } }

That's it! Register the HttpModule and all providers in the web.config and you are ready to roll!
Rated 5.00, 1 vote(s). 
bilginyazar
Hi Dennis,

Should we make any modifications in web.config file for this new DynamicApplicationModule class?
And please correct me if I'm wrong; for those new RoleProvider, PersonalizationProvider and MembershipProvider classes we should update AspNetSqlRoleProvider, SqlBlobPersonalizationProvider and AspNetSqlMembershipProvider settings respectively.

Thanks.
denis
Hi Bilgin,
You are correct - you should register the module in web.config and change the provider settings.
bilginyazar
Hi Denis,

Thank you for your quick response. I've applied those steps and could be able to host multiple applications from single publish point.
But whenever I login in one of them the user name is also seen on the other sites, and that user is not registered for those applications (I am using two different IE instances. I think you are storing user details in the cache)
So my question is what should I do to separate user credentials between applications so that they will not collide?

Thanks.
bilginyazar
Hi Denis,

I could fixed my problem. The problem was occurred because both sites were using localhost. Even they were having different ports the cookies are shared together for both sites.
I have updated the hosts file and created 2 different entries for 127.0.0.1 and they will go to their web sites with different ports. So now their cookies are stored according to their URLs and there isn't any more collision.

Thanks.