Using liferay dockbar notifications - liferay

I’d like to use the liferay notification feature following the tutorial http://www.codeyouneed.com/liferay-custom-notifications/. And as many people before, I succeeded in increasing the number of notifications, but the notification message is not displayed.
I tried to check by adding log-output whether the methods (getBody, getLink, …) of the UserNotificationHandler are called, and they are not called at all, not even the constructor of the UserNotificationHandler is called.
So I conclude that my notification is written to the database, but my UserNotificationHandler class is not found.
In my project, I have put the
user-notification-definitions into
project/src/main/resources.
They look like:
<?xml version="1.0"?>
<!DOCTYPE user-notification-definitions PUBLIC "-//Liferay//DTD User Notification Definitions 6.2.0//EN" "http://www.liferay.com/dtd/liferay-user-notification-definitions_6_2_0.dtd">
<user-notification-definitions>
<definition>
<notification-type>${com.myproject.portal.notifications.UserNotificationHandler.PORTLET_ID}</notification-type>
<description>receive-a-notification-when-triggered</description>
<delivery-type>
<name>email</name>
<type>${com.liferay.portal.model.UserNotificationDeliveryConstants.TYPE_EMAIL}</type>
<default>true</default>
<modifiable>true</modifiable>
</delivery-type>
<delivery-type>
<name>website</name>
<type>${com.liferay.portal.model.UserNotificationDeliveryConstants.TYPE_WEBSITE}</type>
<default>true</default>
<modifiable>true</modifiable>
</delivery-type>
</definition>
</user-notification-definitions>
The liferay-portlet.xml is in
project/src/main/webapp/WEB-INF.
And the UserNotificationHandler in
project/src/main/java/com/myproject/portal/notifications
in the package com.myproject.portal.notifications.
I wrote something like that into the liferay-portlet.xml:
<portlet-name>example</portlet-name>
<icon>/icon.png</icon>
<user-notification-definitions>
user-notification-definitions.xml
</user-notification-definitions>
<user-notification-handler-class>
com.myproject.portal.notifications.UserNotificationHandler
</user-notification-handler-class>
</portlet>
This is my UserNotificationHandlerClass (so far, I am just trying to get it work before adding the actual content):
package com.myproject.portal.notifications;
import ...//all necessary imports
public class UserNotificationHandler extends
BaseUserNotificationHandler {
public static final String PORTLET_ID = "example_WAR_myprojectportlet";
private static final Logger log = Logger.getLogger(UserNotificationHandler.class);
public UserNotificationHandler() {
log.info("UserNotificationHandler - Constructor");
setPortletId(UserNotificationHandler.PORTLET_ID);
}
#Override
protected String getBody(UserNotificationEvent userNotificationEvent,
ServiceContext serviceContext) throws Exception {
log.info("in getBody");
return "";
}
#Override
protected String getLink(UserNotificationEvent userNotificationEvent,
ServiceContext serviceContext) throws Exception {
log.info("in getLink");
return "";
}
protected String getBodyTemplate() throws Exception {
log.info("in getBodyTemplate");
return "";
}
}
I trigger the notification in my portlet like this:
ServiceContext serviceContext = ServiceContextFactory.getInstance(request);
JSONObject payloadJSON = JSONFactoryUtil.createJSONObject();
payloadJSON.put("userId", userId);
payloadJSON.put("yourCustomEntityId", 12345);
payloadJSON.put("additionalData", "success");
UserNotificationEventLocalServiceUtil.addUserNotificationEvent(userId,
UserNotificationHandler.PORTLET_ID,
(new Date()).getTime(),
userId,
payloadJSON.toString(),
false,
serviceContext);
What is the problem here?

Do you literally have public static final String PORTLET_ID = "myportlet"; in your code? If so, note the extra information in the tutorial that you link:
NB Important Information: The com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID string that you use as your notification type has to match an actual portlet ID. It doesn’t actually need to be YOUR portlet ID but that would be the right thing to have there. The reason being that Notifications display portlet uses it to display a small portlet icon next to your notification to help the user identify the source of the notification. Providing a bad Portlet ID or something like null leads to a hard-to-trace NullPointerException in the JSP. Took me an hour to track it down.
Most likely the portlet ID looks rather like "example_WAR_myportlet", this indicates that it's deployed in a plugin named example.war and the portlet id (in portlet.xml) is myportlet. Try if it works then - Liferay might need to find the portlet in order to find, instanciate and use its NotificationHandler. (Note: This is currently a guess - I didn't try the full code posted)

In your liferay-portlet.xml you wrote
<user-notification-handler-class>
UserNotificationHandler
</user-notification-handler-class>
It should be:
<user-notification-handler-class>
com.myproject.portal.notifications.UserNotificationHandler
</user-notification-handler-class>
You should also check if this part is good
<user-notification-definitions>
user-notification-definitions.xml
</user-notification-definitions>
"user-notification-definitions.xml" file should be on WEB-INF/classes in the final WAR

Related

How to put JSF message bundle outside of WAR so it can be edited without redeployment?

We have a JSF application on WildFly 8 which uses the traditionally mechanism with internationalizing text by having message bundles for German and English in the WEB-INF\classes folder of the WAR and a configuration in faces-config.xml mapping a name to it and listing the locales. The application does not have a database connection, but uses REST services to communicate with a 2nd application.
Now we need to be able to change text more easily, meaning not having to build a new WAR file and do a deployment when changing a text. So I need a mechanism to have the message bundles outside of the WAR while being able to use it as before within the XHTML pages.
Two optional requirements would be to change the text and refresh the messages in the application without having to restart the application (priority 2), and to have a default bundle within the WAR, which is overwritten by the external bundle (priority 3).
My thought was to use something like Apache commons configuration to read a property file within an Application scoped bean and expose a getter under the EL name used before. But somehow it feels like having to re-implement an existing mechanism and that this should somehow be easier, maybe even with Java EE core only.
Has someone used this mechanism in such a way and can point me to some example/description on the details or has a better idea to implement the listed requirement(s)?
How to put JSF message bundle outside of WAR?
Two ways:
Add its path to the runtime classpath of the server.
Create a custom ResourceBundle implementation with a Control.
change the text and refresh the messages in the application without having to restart the application
Changing the text will be trivial. However, refreshing is not trivial. Mojarra internally caches it agressively. This has to be taken into account in case you want to go for way 1. Arjan Tijms has posted a Mojarra specific trick to clear its internal resource bundle cache in this related question: How to reload resource bundle in web application?
If changing the text happens in the webapp itself, then you could simply perform the cache cleanup in the save method. If changing the text however can happen externally, then you'd need to register a file system watch service to listen on changes (tutorial here) and then either for way 1 clear the bundle cache, or for way 2 reload internally in handleGetObject().
have a default bundle within the WAR, which is overwritten by the external bundle
When loading them from classpath, the default behavior is the other way round (resources in WAR have higher classloading precedence), so this definitely scratches way 1 and leaves us with way 2.
Below is a kickoff example of way 2. This assumes that you're using property resource bundles with a base name of text (i.e. no package) and that the external path is located in /var/webapp/i18n.
public class YourBundle extends ResourceBundle {
protected static final Path EXTERNAL_PATH = Paths.get("/var/webapp/i18n");
protected static final String BASE_NAME = "text";
protected static final Control CONTROL = new YourControl();
private static final WatchKey watcher;
static {
try {
watcher = EXTERNAL_PATH.register(FileSystems.getDefault().newWatchService(), StandardWatchEventKinds.ENTRY_MODIFY);
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
private Path externalResource;
private Properties properties;
public YourBundle() {
Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
setParent(ResourceBundle.getBundle(BASE_NAME, locale, CONTROL));
}
private YourBundle(Path externalResource, Properties properties) {
this.externalResource = externalResource;
this.properties = properties;
}
#Override
protected Object handleGetObject(String key) {
if (properties != null) {
if (!watcher.pollEvents().isEmpty()) { // TODO: this is naive, you'd better check resource name if you've multiple files in the folder and keep track of others.
synchronized(properties) {
try (InputStream input = new FileInputStream(externalResource.toFile())) {
properties.load(input);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
return properties.get(key);
}
return parent.getObject(key);
}
#Override
#SuppressWarnings({ "rawtypes", "unchecked" })
public Enumeration<String> getKeys() {
if (properties != null) {
Set keys = properties.keySet();
return Collections.enumeration(keys);
}
return parent.getKeys();
}
protected static class YourControl extends Control {
#Override
public ResourceBundle newBundle
(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException
{
String resourceName = toResourceName(toBundleName(baseName, locale), "properties");
Path externalResource = EXTERNAL_PATH.resolve(resourceName);
Properties properties = new Properties();
try (InputStream input = loader.getResourceAsStream(resourceName)) {
properties.load(input); // Default (internal) bundle.
}
try (InputStream input = new FileInputStream(externalResource.toFile())) {
properties.load(input); // External bundle (will overwrite same keys).
}
return new YourBundle(externalResource, properties);
}
}
}
In order to get it to run, register as below in faces-config.xml.
<application>
<resource-bundle>
<base-name>com.example.YourBundle</base-name>
<var>i18n</var>
</resource-bundle>
</application>

Liferay Hook - Manipulation request parameters

I ran into a little problem with a hook. Szenario:
The Hook should override struts action /document_library/edit_file_entry which is called, whenever a user uploads a document into the document library.
The goal is to check the title of the document and rename it following a given naming-scheme.
My solution:
#Override
public void processAction(
StrutsPortletAction originalStrutsPortletAction,
PortletConfig portletConfig, ActionRequest actionRequest,
ActionResponse actionResponse)
throws Exception {
//Get old title - set new title
String oldTitle = ParamUtil.getString(actionRequest, "title");
String newTitle = "Test";
//wrap request to set param
DynamicActionRequest actionRequestNew = new DynamicActionRequest(actionRequest);
actionRequestNew.setParameter("title", newTitle );
//call original struts action with modified title
originalStrutsPortletAction.processAction(originalStrutsPortletAction, portletConfig, actionRequestNew, actionResponse);
}
The Problem is that the original Struts action in portal-impl/src/com/liferay/portlet/documentlibrary/action/EditFileEntryAction.java uses PortalUtil.getUploadPortletRequest(actionRequest); which expects a PortletRequestImpl.
But DynamicActionRequest cannot be cast to PortletRequestImpl.
See:
12:07:04,466 ERROR [http-bio-8082-exec-44][render_portlet_jsp:154] java.lang.ClassCastException: com.liferay.portal.kernel.portlet.DynamicActionRequest cannot be cast to com.liferay.portlet.PortletRequestImpl
at com.liferay.portal.util.PortalImpl.getUploadPortletRequest(PortalImpl.java:4067)
at com.liferay.portal.util.PortalUtil.getUploadPortletRequest(PortalUtil.java:1253)
at com.liferay.portlet.documentlibrary.action.EditFileEntryAction.updateFileEntry(EditFileEntryAction.java:653)
at com.liferay.portlet.documentlibrary.action.EditFileEntryAction.processAction(EditFileEntryAction.java:129)
at com.liferay.portal.struts.StrutsPortletActionAdapter.processAction(StrutsPortletActionAdapter.java:51)
at com.liferay.portal.kernel.struts.BaseStrutsPortletAction.processAction(BaseStrutsPortletAction.java:42)
at com.foo.hook.portlet.sites.action.MyEditFileEntryAction.processAction(MyEditFileEntryAction.java:83)
at com.liferay.portal.kernel.bean.ClassLoaderBeanHandler.invoke(ClassLoaderBeanHandler.java:67)
at com.liferay.portal.struts.PortletActionAdapter.processAction(PortletActionAdapter.java:55)
at com.liferay.portal.struts.PortletRequestProcessor.process(PortletRequestProcessor.java:169)
at com.liferay.portlet.StrutsPortlet.processAction(StrutsPortlet.java:212)
at com.liferay.portlet.FilterChainImpl.doFilter(FilterChainImpl.java:70)
at com.liferay.portal.kernel.portlet.PortletFilterUtil.doFilter(PortletFilterUtil.java:48)
at com.liferay.portlet.InvokerPortletImpl.invoke(InvokerPortletImpl.java:548)
at com.liferay.portlet.InvokerPortletImpl.invokeAction(InvokerPortletImpl.java:579)
at com.liferay.portlet.InvokerPortletImpl.processAction(InvokerPortletImpl.java:294)
at com.liferay.portal.action.LayoutAction.processPortletRequest(LayoutAction.java:944)
at com.liferay.portal.action.LayoutAction.processLayout(LayoutAction.java:688)
at com.liferay.portal.action.LayoutAction.execute(LayoutAction.java:249)
at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431)
How can I change the parameter without using DynmicActionRequest? Any suggestions?
I'm running Liferay-Portal 6.1.20 EE.
Thanks in advance.
There are two approaches I can think of.
1) Create ActionRequestWrapper object and add a parameter. This would probably solve your issue.
2) Create a subclass of LR's action class. In that make the needed code changes. Create a hook and make the new action class available to LR.
I hope this helps.
You can set the parameter in existing actionRequest:
actionRequest.setParameter("title", newTitle);
It will updated with new value.

How to handle HTTP 403 with Spring Security 3.0.x

I'm facing a little issue with Spring Security 3.0.x (3.0.2 in particular at the moment). The whole application I'm working on is working perfectly except when someone who doesn't have the authorities tries to log on.
When it occurs, the users is redirected to the "welcome" page, since his username/password are valid, and he receive a cute white page with this : "Error 403: Access is denied"
So, I've been looking on the net trying to find how this behavior can be handled. So far I've come to the conclusion, please correct me if I'm wrong, that it is managed by the ExceptionTranslationFilter. But I don't quite understand how to make any good use of this information.
I've tryied to edit my SecurityContext.xml to add a access-denied-handler tag to my http tag, but it doesn't work. Do I need to add more than this tag to make it work? Is there any other possibilities to make my application more user-friendly?
Edit : I would like to redirect to a page, let's says 403.html, for example.
Sincerly,
Thanks
I still don't get why you had to implement your own access handler... I have currently faced same task:
<security:access-denied-handler error-page="/accessDenied"/> - works like charm.
Don't forget to specify handler in your Controller:
#RequestMapping(value = "/accessDenied")
public String accessDenied() {
return "accessDenied"; // logical view name
}
Update for Spring Boot(2014 Oct):
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedHandler(customHandler) OR .accessDeniedPage("/somePage.html").and
.formLogin()
.failureHandler(ajaxAuthenticationFailureHandler)}
Nowadays we don't really return views for such task since angular js kicks in so you can use your failure/success handler and return tailored JSON responses. For us it was sufficient to use failure handler but you get to choose where you want your control to kick in. We generally don't use view resolvers as there are UI tiles frameworks(such as angular partials) able to construct pieces into single page for you. Html pieces are stored on the server and served simply as static resources.
Lets play with Embedded Tomcat to achieve similar behavior to web.xml !
#Configuration
#EnableAutoConfiguration
public class ApplicationWebXml extends SpringBootServletInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.profiles(addDefaultProfile())
.showBanner(false)
.sources(Application.class);
}
//required for container customizer to work, the numerous tutorials didn't work for me, so I simply tried overriding the default one
#Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
return tomcat;
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer(
) {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory containerFactory = (TomcatEmbeddedServletContainerFactory) container;
containerFactory.setSessionTimeout(1); // just for your interest, remove as necessary
containerFactory.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN,"/views/accessDenied.html"),
new ErrorPage(HttpStatus.NOT_FOUND,"/views/notFound.html"));
containerFactory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setPort(8082);// just for your interest, remove as necessary
}
});
}
};
}
}
A cleaner way to handle error redirects is to use the <error-page> and <error-code> tags in your web.xml. See below for an example:
<!-- Custom 403 Error Page -->
<!--
NOTE: Security will throw this error when a user has been authenticated successfully
but lacks the permissions to perform the requested action.
-->
<error-page>
<error-code>403</error-code>
<location>/403.jsp</location>
</error-page>
This block of code will redirect to the specified location whenever it encounters the specified error code.
This eliminates the need for authorization code inside your application logic.
I've found how to do this. By implementing the AccessDeniedHandler interface and the corresponding handle method I can, easily, control the way the Http 403 error is handled.
This way, you can add various items in the session and then intercept them on your jsp.
The xml file then looks like this :
<sec:http>
<!-- lots of urls here -->
<sec:access-denied-handler ref="accessDeniedHandler" />
<sec:anonymous/>
</sec:http>
<bean id="accessDeniedHandler" class="foo.bar.CustomAccessDeniedHandler">
<property name="accessDeniedUrl" value="403.html" />
</bean>
The java class :
package foo.bar;
public class CustomAccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler {
private String accessDeniedUrl;
public CustomAccessDeniedHandler() {
}
public CustomAccessDeniedHandler(String accessDeniedUrl) {
this.accessDeniedUrl = accessDeniedUrl;
}
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendRedirect(accessDeniedUrl);
request.getSession().setAttribute("CustomSessionAttribute", "value here");
}
public String getAccessDeniedUrl() {
return accessDeniedUrl;
}
public void setAccessDeniedUrl(String accessDeniedUrl) {
this.accessDeniedUrl = accessDeniedUrl;
}
}
And a jsp example :
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:if test="${!empty CustomSessionAttribute}">
<br/>
ACCESS IS DENIED
<br/>
</c:if>
<!-- other stuff down here -->
The way to make this work is to define a handler in your entry point:
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException authException) throws IOException, ServletException {
if (authException != null) {
// you can check for the spefic exception here and redirect like this
response.sendRedirect("403.html");
}
}
}
You can define this as your entry point by setting this as you entry point in the xml config file:
<http entry-point-ref="customAuthenticationEntryPoint">
...
</http>
You have checked the tag in an application and to me it seems to work.
<sec:access-denied-handler error-page="/handle403Url" />
where handle403Url I want to call to handle this error (for example to show an error).
Don't forget that you have to allow this url in the filters so it can be reached by this user authority, so in the start of the flters you have to add something like this:
<sec:intercept-url pattern="/handle403Url" filters="none" />

Sharepoint Webpart custom properties get default values on server reboot

I have noticed that the custom properties of a webpart I developed return to their default values when I reboot my machine.
Is that a normal behavior? are the properties saved as far as the server is up, or there is some parameters I am missing.
Thank you.
EDIT: code:
namespace TestWebpart
{
[ToolboxItemAttribute(false)]
[XmlRoot(Namespace = "TestWebpart")]
public class GraphWebpart : Microsoft.SharePoint.WebPartPages.WebPart
{
// Visual Studio might automatically update this path when you change the Visual Web Part project item.
private const string _ascxPath = #"~/_CONTROLTEMPLATES/Test_Graph/TestWebpart/GraphWebpartUserControl.ascx";
protected override void CreateChildControls()
{
ReloadElements();
}
protected void ReloadElements()
{
Controls.Clear();
GraphWebpartUserControl control = (GraphWebpartUserControl)Page.LoadControl(_ascxPath);
control.xmlDataUrl = XMLFileUrl;
Controls.Add(control);
}
private static string _xmlFileUrl;
[WebBrowsable(true),
Personalizable(PersonalizationScope.Shared),
DefaultValue(""),
Description("xml"),
DisplayName("xml"),
WebDisplayName("xml")]
public string XMLFileUrl
{
get { return _xmlFileUrl; }
set {
_xmlFileUrl = value;
ReloadElements();
}
}
}
}
EDIT2:
Deleting static from the fields throws the flowing exception:
Web Part Error: An error occurred while setting the value of this property: TestWebpart:XMLFileUrl - Exception has been thrown by the target of an invocation.
Hide Error Details
[WebPartPageUserException: An error occurred while setting the value of this property: Blue_Graph.GraphWebpart.GraphWebpart:XMLFileUrl - Exception has been thrown by the target of an invocation.]
at Microsoft.SharePoint.WebPartPages.BinaryWebPartDeserializer.ApplyPropertyState(Control control)
at Microsoft.SharePoint.WebPartPages.BinaryWebPartDeserializer.Deserialize()
at Microsoft.SharePoint.WebPartPages.SPWebPartManager.CreateWebPartsFromRowSetData(Boolean onlyInitializeClosedWebParts)
First of all you should not have
private static string _xmlFileUrl;
it should be
private string _xmlFileUrl;
This static variable will be lost on IISRESET - won't work in a farm and has the potential to cause all sort of 'thread safe' issues if used multi-threaded environment (like a web server) so only use them if they are really needed.
When SharePoint loads a web part (or after you click Save/Apply in the toolpart) it uses reflection to find your properties (the [Browsable... attribute) and then serialization to load/save the value of the property to the database. One of these two is failing.
I would suspect that is some problem with the attribute - try this one and work backwards until it stops working ;)
[Browsable(true),
Category("Miscellaneous"),
DefaultValue(defaultText),
WebPartStorage(Storage.Personal),
FriendlyName("Text"),
Description("Text Property")]

Possible to load a web part inside another?

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.
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.
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:
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.
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.
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...
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!
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);

Resources