Possible to load a web part inside another?
Asked Answered
N

5

12

So, this is what we want to do: We want to have a generic web part with a custom frame around it and then dynamically load other web parts (frameless) inside it. Would this at all be possible you think? A bit like Jan Tielens SmartPart, only not for ASP.Net User Controls, but for other Web parts... ;)

Edit: We've been able to do this now. The solution was actually pretty simple. Check out the code:

public class WebPartWrapper : System.Web.UI.WebControls.WebParts.WebPart {
    protected override void CreateChildControls() {    
        Panel pnl = new Panel();
        this.Controls.Add(pnl);
        WebPart dynamicPart = WebPartFactory.CreateWebPart("RSSViewer");
        pnl.Controls.Add(dynamicPart);
    }
}

Easy as that... We also use reflection to store the webparts as Xml etc., but that's beside the point.

Nate answered 7/1, 2009 at 8:52 Comment(4)
That would work for simple webparts, but will it work for things that leverage webpart specific things like consumer/producer etc?Provisional
You're talking about connections, correct? I suppose yes, it's just a matter of setting the right property values. But I haven't tried this...Nate
Have you ever tried loading a content editor web part in this fashion? When I instantiate one, even after setting the storage key Internal for the web part, it doesnt seem to be usable...Aphyllous
Nope, sorry never tried that. There might be some special requirements for it. I'd recommend that you use Reflector to see if tanything special happens during SharePoint instantiation.Nate
N
4
public class WebPartWrapper : System.Web.UI.WebControls.WebParts.WebPart {
    protected override void CreateChildControls() {    
        Panel pnl = new Panel();
        this.Controls.Add(pnl);
        var factory = new WebPartFactory()
        WebPart dynamicPart = factory.CreateWebPart("RSSViewer", this.Guid);
        pnl.Controls.Add(dynamicPart);
    }
}

public class WebPartFactory {
    public WebPart CreateWebpart(string webpartName, Guid parentWebPartGuid)
    {
        var config = ConfigurationFactory.LoadConfiguration(webpartName);

        Assembly webPartAssembly = Assembly.Load(config.Assembly);
        Type webPartType = webPartAssembly.GetType(config.Class);
        object actualWebPart = Activator.CreateInstance(webPartType);

        foreach (var item in config.Properties)
        {
            PropertyInfo webPartProperty = webPartType.GetProperty(item.Name);
            object webPartPropertyValue = Convert.ChangeType(itemValue, Type.GetType(item.Type));
            if (!String.IsNullOrEmpty(item.Value))
                webPartProperty.SetValue(actualWebPart, webPartPropertyValue, null);
        }

        RunMethod("set_StorageKeyInternal", actualWebPart, new object[] { parentWebPartGuid });
        return actualWebPart as WebPart;
    }

    private void RunMethod(string methodName, object objectInstance, object[] methodParameters)
    {
        BindingFlags flags = BindingFlags.Instance | BindingFlags.Public |
            BindingFlags.NonPublic;

        Type t = objectInstance.GetType();
        MethodInfo m = GetMethod(t, methodName, flags);
        if (m != null)
        {
            m.Invoke(objectInstance, methodParameters);
        }
    }

    private MethodInfo GetMethod(Type instanceType, string methodName, BindingFlags flags)
    {
        MethodInfo m = instanceType.GetMethod(methodName, flags);
        if (m != null)
        {
            return m;
        }

        if (instanceType.GetType() == typeof(object) || instanceType.BaseType == null)
        {
            return null;
        }

        return GetMethod(instanceType.BaseType, methodName, flags);
    } 
}

This code needs some explaining... Please excuse me if it does not compile, I had to remove a fair bit of the original code, it was very implementation specific stuff. I've not shown the "config" class either, it's just a container for configuration of webparts, just a bunch of properties. There are 2 issues I'd like to discuss in more detail:

  1. parentWebPartGuid - This is the Guid (UniqueId?) of the hosting webpart. For some reason we have to set "StorageKeyInternal" to this value, using reflection (it's a private property). You can possibly get away with not setting it, but at least for the majority of webparts we had to set it.

  2. config.Properties - This is the config values (we set them in a custom .xml file, but feel free to get this from anywhere). It can look a little like this..

In our framework we also support stuff like dynamic property values etc., but that's for another day... Hope this all makes sense and can help somebody.

Nate answered 12/2, 2009 at 14:14 Comment(2)
Thank you for this great solution noocyte. The issue I am facing is that the WP doesn't seem to be editable. When I open the tool pane to set the feed url, I see this error: "A Web Part you attempted to change is either invalid or has been removed by another user. Refresh page." Any thoughts on why this is happening?Screw
No, sorry I have no idea. As you can tell from my answer it's almost 6 years since I wrote it, so there is a fair chance that this approach does not work any more. I have also switched jobs (twice!) since, so I don't have access to the code we wrote back then... Sorry I can't be of any more help.Nate
P
4

I don't think so. I tried this a while back and it complained about only being able to add WebPartZone items in Page Init. I think by the time it get's to initialising your "container" WebPart it's too late to add more zones as the holding page has already been initialised.

Provisional answered 7/1, 2009 at 9:2 Comment(1)
I've now discovered how to do this and will update my post to show this. :)Nate
N
4
public class WebPartWrapper : System.Web.UI.WebControls.WebParts.WebPart {
    protected override void CreateChildControls() {    
        Panel pnl = new Panel();
        this.Controls.Add(pnl);
        var factory = new WebPartFactory()
        WebPart dynamicPart = factory.CreateWebPart("RSSViewer", this.Guid);
        pnl.Controls.Add(dynamicPart);
    }
}

public class WebPartFactory {
    public WebPart CreateWebpart(string webpartName, Guid parentWebPartGuid)
    {
        var config = ConfigurationFactory.LoadConfiguration(webpartName);

        Assembly webPartAssembly = Assembly.Load(config.Assembly);
        Type webPartType = webPartAssembly.GetType(config.Class);
        object actualWebPart = Activator.CreateInstance(webPartType);

        foreach (var item in config.Properties)
        {
            PropertyInfo webPartProperty = webPartType.GetProperty(item.Name);
            object webPartPropertyValue = Convert.ChangeType(itemValue, Type.GetType(item.Type));
            if (!String.IsNullOrEmpty(item.Value))
                webPartProperty.SetValue(actualWebPart, webPartPropertyValue, null);
        }

        RunMethod("set_StorageKeyInternal", actualWebPart, new object[] { parentWebPartGuid });
        return actualWebPart as WebPart;
    }

    private void RunMethod(string methodName, object objectInstance, object[] methodParameters)
    {
        BindingFlags flags = BindingFlags.Instance | BindingFlags.Public |
            BindingFlags.NonPublic;

        Type t = objectInstance.GetType();
        MethodInfo m = GetMethod(t, methodName, flags);
        if (m != null)
        {
            m.Invoke(objectInstance, methodParameters);
        }
    }

    private MethodInfo GetMethod(Type instanceType, string methodName, BindingFlags flags)
    {
        MethodInfo m = instanceType.GetMethod(methodName, flags);
        if (m != null)
        {
            return m;
        }

        if (instanceType.GetType() == typeof(object) || instanceType.BaseType == null)
        {
            return null;
        }

        return GetMethod(instanceType.BaseType, methodName, flags);
    } 
}

This code needs some explaining... Please excuse me if it does not compile, I had to remove a fair bit of the original code, it was very implementation specific stuff. I've not shown the "config" class either, it's just a container for configuration of webparts, just a bunch of properties. There are 2 issues I'd like to discuss in more detail:

  1. parentWebPartGuid - This is the Guid (UniqueId?) of the hosting webpart. For some reason we have to set "StorageKeyInternal" to this value, using reflection (it's a private property). You can possibly get away with not setting it, but at least for the majority of webparts we had to set it.

  2. config.Properties - This is the config values (we set them in a custom .xml file, but feel free to get this from anywhere). It can look a little like this..

In our framework we also support stuff like dynamic property values etc., but that's for another day... Hope this all makes sense and can help somebody.

Nate answered 12/2, 2009 at 14:14 Comment(2)
Thank you for this great solution noocyte. The issue I am facing is that the WP doesn't seem to be editable. When I open the tool pane to set the feed url, I see this error: "A Web Part you attempted to change is either invalid or has been removed by another user. Refresh page." Any thoughts on why this is happening?Screw
No, sorry I have no idea. As you can tell from my answer it's almost 6 years since I wrote it, so there is a fair chance that this approach does not work any more. I have also switched jobs (twice!) since, so I don't have access to the code we wrote back then... Sorry I can't be of any more help.Nate
A
2

There are (at least) two ways to do this: using iframe HTML element, or just a div whose content is changed by JavaScript (probably with Ajax).

[NOTE] My answer is generic (ie. on Web design side), I have no idea how it in your technical context, so maybe I should delete this answer...

Aerostatics answered 7/1, 2009 at 9:2 Comment(0)
C
2

No chance on getting the source for the WebPartFactory class is there? Or maybe a bit more information about it? Pseudo code maybe? If a custom web part is in the gallery it could be referenced in the same way as RSSViewer is correct? I'm just not really sure how to go about doing what you have done here, and I would very much like to better understand how to do this.

Thanks!

Chemo answered 24/11, 2009 at 19:39 Comment(1)
We do this via reflection. So you'll need the Type and Assembly, then you should be able to load it just fine. However there are some caveats... I'll update my answer asap with some code.Nate
H
0

When a want to instantiate a custom webpart inside another custom webpart i use the following code in the .ascx

<%@ Register tagPrefix="uc1" Namespace="Megawork.Votorantim.Intranet.Webparts_Intranet.LikeButton" Assembly="Megawork.Votorantim.Intranet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=769156d154035602"  %>

The Namespace value and the Assembly value can be copied from the SafeControls line from the webconfig or from the package file (in manifest tab) :)

When i want to instantiate it dinammicaly (in fact) is use the following code in the .cs

//This is the namespace of the control that will be instantiated dinamically    
string type = "My.Custom.Namespace.WebpartToBeAdded.WebpartToBeAdded";

// Instantiate the control dinamically based on his type
System.Web.UI.WebControls.WebParts.WebPart genericWP = (System.Web.UI.WebControls.WebParts.WebPart)Activator.CreateInstance(Type.GetType(type));

// sets the page to the genericWP (i dont know if this is required)
genericWP.Page = this.Page;

// Note: if you want to call custom methods of the dinamically instantiated controls (like a custom load method) you will need to create an interface and make your dinamically instantiated webpart implement it. You will need to do it in that file that have the following code: private const string _ascxPath @"~/_CONTROLTEMPLATES/...". Then you can do the following
//IMyInterface ig = (IMyInterface)genericWP;
//ig.MyCustomLoadMethod(someParam);

// Adds the controls to a container, an asp panel by example.
panelDinamicControls.Controls.Add(genericWP);
Harbourage answered 15/4, 2014 at 12:56 Comment(1)
This code always worked for me, i like this way because the unique requirement to instantiate the webpart is the correct Namespace, in some cases i store it in a sharepoint lists and it become truly dinamically.Harbourage

© 2022 - 2024 — McMap. All rights reserved.