views:

349

answers:

2

I need to build a 'customizable' asp.net web application (not asp.net mvc).

I was thinking to use an IoC container to inject user control into aspx pages.

Has anybody ever tried that ?

In brief here is how I think this can be achieved:

  • I use Scott Guthrie's method to build a reusable 'user control library' (see his article "Creating and Using User Control Libraries").

  • Controls and pages in that library should have places to receive user controls dynamically (e.g., in an asp PlaceHolder).

  • In my custom web application, built on top of that library, I create specific user controls.

  • I put them in some way in a IoC container so that they can be injected in the controls/pages of the 'user control library' (e.g., in the PlaceHolders).

That's it.

This is basically what can be done with Spring.Net as discussed here http://blogger.forgottenskies.com/?p=70 but to inject user controls.

Has anybody some experience on such stuff ? Or does it sound silly ? Alternatives ?

The idea is to have the possibility to build different web applications on top of my 'user control library' without touching that library.

For instance: in my library I have a page with 4 text boxes and for one specific application I need to add one text box, without changing the code of the page. The idea is to put a place holder in that page and inject dynamically my custom text box in this place holder.

+1  A: 

We've built a suite of applications using a similar model.
We have a so called PageBuilder which constructs each page by injecting UserControls and WebParts.

Everything is configurable (page layout, page controls, control position, etc.) so there is no need to change any code in the pages, UserControls or the WebParts. (Unless some functionality needs to be added/changed).

We even have configuration and settings within controls to change behaviour depending on various things like SQL queries, mode, current page, etc.

Basicly it works like this:

  • Create a layout table for the current page.
  • Get the UserControls and WebParts to display.
  • Put each UserControl and WebPart in the right position.
  • Apply settings to each UserControl and WebPart.

All WebParts can communicate with each other by a custom publisher/subscriber event model. I.e. In WebPart A a dropdown selection is changed => WebPart B shows data for the selected item.

This model allows us to build highly configurable applications where customers can design the layout and behaviour without the need for us to be involved.

Your model seems to be a subset of our model and all I can say is that it's easy to work with. Both from a developers point of view and a customers.

Edit:
Basicly our framework consist of a few master pages and base pages which calls the PageBuilder.
Each masterpage is used for different types of objects: pages, UserControls, WebPart, Lightboxes, etc.
Each aspx page contains a PlaceHolder for UserControls and WebParts. This PlaceHolder is populated by the PageBuilder.

The UserControl/WebPart PlaceHolder in our aspx pages can be populated with whatever control we want. So there is no need to change the aspx page at all. If we want a textbox we can configure this. Same goes for a custom UserControl or a WebPart. In this way we don't need to recreate the aspx pages for each custom application but need to only change configuration.

We have 100+ UserControls and WebParts for the various aspx pages but most aspx pages look similar to:

<%@ Page MasterPageFile="main.master" ... %>
<asp:Content runat="server" ContentPlaceHolderID="Main" ID="MainSection">
  <asp:PlaceHolder runat="server ID="UserControlPlaceHolder"></asp:PlaceHolder>
</asp:Content>

in the codebehind we have something like:

Partial Class MyPageClass Inherits BasePage
  Protected Sub Page_Init(ByVal sender As Object, ByVal e as System.EventArgs) Handles Me.Init
    'The following method is in the BasePage and is part of the PageBuilder.
    LoadControls()
  End Sub
End Class

The PageBuilder creates the layout then loads and adds all Controls and WebParts in the right position.
(the layout, which controls and control position are all fetched from the configuration.)

Then the PageBuilder applies settings for each Control and WebPart. These settings are also configurable. Settings can be something as simple as the height of the control or more complicated things like "display mode" (static, page dependant, group dependant, etc.).

Hopefully this explains it in more detail.

Sani Huttunen
I actually don't want to recreate new pages in each custom application. Does your "framework" allows an aspx page to be enriched with ,e.g., a text box, without needing to modify it ? Is it the role of the PageBuilder ? Who calls that Builder ? Could you give some details ?
Thierry
+2  A: 

It's not exactly the same as your situation, but I had a need to dynamically inject an editor control depending on the runtime type of the entity being edited. I had all my controls inherit from a common interface like this:

public interface IEditor
{
    bool CanHandle(EntityBase entity);
    void Display(EntityBase entity);
    void Save(EntityBase entity);
}

public partial class AddressEditor : UserControl, IEditor
{
    public bool CanHandle(EntityBase entity)
    { 
        return (entity is Address);
    }

    public void Display(EntityBase entity)
    {
        var address = (Address)entity;
        addressLine1Textbox.Text = address.Line1;
        // etc...
    }

    public void Save(EntityBase entity)
    {
        var address = (Address)entity;
        address.Line1 = addressLine1Textbox.Text;
        // etc...
    }
}

Then using an IoC container (StructureMap in this case) I can get the correct user control with something like

var editorControl = ObjectFactory.GetAllInstancesOf<IEditor>().First(x => x.CanHandle(myEntity));

This is where it gets tricky though, you can't add a user control to a page if you create an instance of it like this. You need to know the location of it's .ascx file. You could expose it through the editor interface so that each control returns it:

public string AscxFile { get { return "~/UserControls/AddressEditor.ascx"; } } // Implements IEditor.AscxFile

Then in the calling page, use LoadControl:

var actualControl = LoadControl(editorControl.AscxFile);
editorPlaceholder.Controls.Add(actualControl);
editorControl.Display(myEntity);

I preferred using Composite Controls, they take a bit more effort to create but no need to worry about the .ascx file, I can just add it directly to the page:

var editorControl = ObjectFactory.GetAllInstancesOf<IEditor>().First(x => x.CanHandle(myEntity));
editorPlaceholder.Controls.Add((Control)editorControl);
editorControl.Display(myEntity);

Make sure you're adding the control at the Page.OnInit stage so that it can partake in ViewState, and it won't get remembered between postbacks so you have to do it every time.

The last step is wiring things up with your IoC tool of choice, I use StructureMap to automatically load all instances of IEditor but I could vary this at any point in the application code or in the xml config.

Jon M