Page 1 of 1

ASP.NET - enabling plugins using UserControls

#1 Nakor  Icon User is offline

  • Professional Lurker
  • member icon

Reputation: 446
  • View blog
  • Posts: 1,501
  • Joined: 28-April 09

Posted 07 November 2012 - 10:38 PM

Recently I was tasked with finding a way to have all of our asp.net applications under one parent application. The goal was to provide a seamless look and feel to all of our applications and to minimize the duplication of resources such as stylesheets and javascript files. The problem I was having a hard time solving was finding a way to allow the developers to create new applications on their computers without having to maintain a full copy of the parent application. However, this created problems with implementing MasterPages, since MasterPages cannot be implemented across multiple applications. Having all applications under a central parent application would almost make authentication and authorization easier to implement and maintain across all of the applications.

So I started thinking that it would be nice if we could develop a sort of plugin feature where our developers could just create a control from their application that we could just "plugin" to the parent project and it would automatically use the existing resource files and MasterPages. This would mean that the developers could just focus on the code without worrying so much about the layout or implementing security in their individual applications.

The idea I finally came up with was to create a custom route that could be used to point all requests to a single page that contained a placeholder that would load a UserControl based upon the url path. This seemed like a decent solution if I could get it to work so I went about trying to implement it in a test solution.

First, I made a web page that would load the UserControl, I just used the Default.aspx page but you could use anything:
The Default.aspx page
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>

    <asp:PlaceHolder runat="server" ID="UserControlPlaceholder" />

</ContentTemplate>
</asp:UpdatePanel>
</asp:Content>



Default.aspx.cs
    public partial class _Default : System.Web.UI.Page
    {
        public string Directory
        {
            get
            {
                object temp = ViewState["Directory"];
                return temp == null ? string.Empty : (string)temp;
            }
            set { ViewState["Directory"] = value; }
        }

        public string UserControl
        {
            get
            {
                object temp = ViewState["UserControl"];
                return temp == null ? string.Empty : (string)temp;
            }
            set { ViewState["UserControl"] = value; }
        }

        protected override void OnInit(EventArgs e)
        {
            // To load a UserControl the UserControl property needs to have a value
            if (!string.IsNullOrWhiteSpace(UserControl))
            {
                // If the Directory property has a value then the path to the
                // user control is "~/UserControls/Folder/UserControl
                // otherwise the path is "~/UserControls/UserControl
                string path = string.IsNullOrWhiteSpace(this.Directory) ?
                    string.Format("~/UserControls/{0}.ascx", this.UserControl) :
                    string.Format("~/UserControls/{0}/{1}.ascx", this.Directory, this.UserControl);

                Control temp = LoadControl(path);

                // We set the ID of the UserControl so that it can be maintained by ViewState
                temp.ID = UserControl;

                if (temp != null)
                {
                    UserControlPlaceholder.Controls.Add(temp);
                }

                // Here we make the title of the page equal to the name of the UserControl
                Page.Title = this.UserControl;
            }
            
            base.OnInit(e);
        }
    }




Next, I needed to create a custom RouteHandler that implements IRouteHandler:

    public class TestRouteHandler : IRouteHandler
    {
        // This is the virtual path to the file containing the place holder
        private readonly string virtualPath;

        public TestRouteHandler(string virtualPath)
        {
            this.virtualPath = virtualPath;
        }

        public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            // This creates the page containing the place holder
            _Default d = BuildManager.CreateInstanceFromVirtualPath(this.virtualPath, typeof(Page)) as _Default;

            // This creates the virtual url being requested. It is passed into
            // the UrlAuthorizationModule to check that if the user is authorized 
            // to view the requested page
            string path = "~/Parent";
            
            // The Directory allows us to place user controls in folders to group
            // them by application
            if ( requestContext.RouteData.Values.ContainsKey("directory"))
            {
                d.Directory = requestContext.RouteData.Values["directory"] as string;
                path += string.Format("/{0}", d.Directory);
            }

            // This is the name of the UserControl to load
            if (requestContext.RouteData.Values.ContainsKey("control"))
            {
                d.UserControl = requestContext.RouteData.Values["control"] as string;
                path += string.Format("/{0}", d.UserControl);
            }


            if (!UrlAuthorizationModule.CheckUrlAccessForPrincipal(path, requestContext.HttpContext.User, requestContext.HttpContext.Request.HttpMethod))
            {
                requestContext.HttpContext.Response.StatusCode =
                    (int)HttpStatusCode.Unauthorized;
                requestContext.HttpContext.Response.End();
            }
            return d;
        }
    }



Next, I needed to update the Global.asax file to register the new RouteHandler

    public class Global : System.Web.HttpApplication
    {

        void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes(RouteTable.Routes);
        }

        private static void RegisterRoutes(RouteCollection routes)
        {
            // This route loads just the default page without a UserControl
            // when the url entered is "http://mywebsite.com/Parent"
            routes.Add("Home", new Route
                (
                    "Parent/",
                    new TestRouteHandler("~/Default.aspx")
                ));
            
            // This route loads the default page with a UserControl
            // when the url entered is "http://mywebsite.com/Parent/UserControlID"
            routes.Add("Parent", new Route
                (
                    "Parent/{control}",
                    new TestRouteHandler("~/Default.aspx")
                ));

            // This route loads the default page with a UserControl from a subfolder
            // when the url entered is "http://mywebsite.com/Parent/Folder/UserControlID"
            routes.Add("Child", new Route(
                    "Parent/{directory}/{control}",
                    new TestRouteHandler("~/Default.aspx")
                ));
        }
    }



Now, in order to allow the WebForms application to use Routing, I needed to add a couple of lines to my Web.config file.

<modules runAllManagedModulesForAllRequests="true">
        <add name="UrlRoutingModule"
             type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    </modules>
      <handlers>
          <add name="UrlRoutingHandler"
            preCondition="integratedMode"
            verb="*" path="UrlRouting.axd"
            type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
      </handlers>



After all of this was done, I made a second web application where I created my test UserControl. After compiling the second application I moved the UserControl.ascx file into a folder called "SiteOne" inside of the "UserControls" folder in my parent application and copied the .dll file from the bin of the second application into the bin of the parent application. I didn't need to include the .cs file for the UserControl because the code is compiled into the dll for the project.

Finally, it was time to test the routing with the new UserControl. I fired up the application and entered my custom route, http://localhost/Parent/SiteOne/Test, and sure enough there was the child application's UserControl being displayed and working without a problem.

Is This A Good Question/Topic? 1
  • +

Page 1 of 1