Yes, you need to create a menu provider
[MenuProvider]
public class CustomAdminMenuProvider : IMenuProvider
{
public CustomAdminMenuProvider()
{
}
public IEnumerable<MenuItem> GetMenuItems()
{
var urlMenuItem1 = new UrlMenuItem("Custom Admin Page 1", MenuPaths.Global + "/cms/admin/examplesub1", "/CustomAdminPage/FunctionOne");
urlMenuItem1.IsAvailable = context => true;
urlMenuItem1.SortIndex = SortIndex.First + 100;
urlMenuItem1.AuthorizationPolicy = CmsPolicyNames.CmsAdmin;
var urlMenuItem2 = new UrlMenuItem("Custom Admin Page 2", MenuPaths.Global + "/cms/admin/examplesub2", "/CustomAdminPage/FunctionTwo");
urlMenuItem1.IsAvailable = context => true;
urlMenuItem1.SortIndex = SortIndex.First + 101;
urlMenuItem1.AuthorizationPolicy = CmsPolicyNames.CmsAdmin;
return new List<MenuItem>(3)
{
urlMenuItem1,
urlMenuItem2
};
}
}
Then hook it in to the controller path you defined in the UrlMenuItem above
[Authorize(Roles = "CmsAdmin,WebAdmins,Administrators")]
[Route("[controller]")]
public class CustomAdminPageController : Controller
{
// GET
[Route("[action]")]
public IActionResult FunctionOne()
{
return View("~/Views/CustomAdminPage/FunctionOne.cshtml", model);
}
}
However this example just added them as a sub item under the admin menu
It doesn't add them to tools. That's the core tools items from Optimizely, I would personally create them in this way as tabs under admin as that's more how the UI had been built
Hi Scott,
It works for me when I add these directly to the CMS project. But I need to get this done by Add-on. Tried to build and copy DLLs and added controllers and views to the module folder as well.
But it is not rendering.
I have separate CMS instance project and Addon project in solution and i m coping DLLs from Addon project bin folder after building.
Do i need to zip. the module?
can you please point me to a single explanation or article on how to add the admin menu and page by add-on?
Hi Nimesh,
Yes, best approach would be to create this as a zipped module.
I recommend reviewing source code of Geta Notfoundhandler for an example of this can be done: https://github.com/Geta/geta-notfoundhandler
Here's a writeup on packing up a shell module for CMS12: https://blog.tech-fellow.net/2021/09/03/how-to-pack-your-shell-module-for-optimizely-content-cloud/
Hi thanks Guys,
I manage to get the menu item rendered in the admin area. but I'm having issues with routing. even though I have set the path in the menu item URL attribute, It does not work to render the view form my plugin. how can I set URLs to controller/action? I have index action in the AddonSettings controller.
[MenuItem("/global/cms/admin/addon", Text = "Addon Settings", SortIndex = 60, Url = "/EPiServer/AddonSettings/index")]
Can anyone please mention a good article or source where these plugin-related routings are explained? I see different ways in documentation as well so those are a bit unclear. I'm looking for a clear explanation about these areas
Anyone who has experience in these areas please point me to somewhere I can refer and understands things.
Many Thanks to all.
This should be what you are looking for: https://getadigital.com/blog/optimizely-12-ui-plugin
The zip part is for packaging - if you are creating a module that will be re-used across several project.
It was a great article Mari, Thanks!
I was able to get some things fixed in my project and organize it n the correct way. but still, I was able to get the menu rendered in the Admin menu But I can't render the addon view UI on the admin page. I guess this is a small thing but can't find it. May be this is now a issue with my project structure.
MyModule name is: QbankModule and I have the following project in the solution.
1. Cms12-New2 - the CMS project where we need to run the addon.
2. Qbank.Connector.Core - core + other functionalities
3. Qbank.Connector.EpiCore - other functionalities
4. Qbank.Connector.EpiCore11 - includes initialization module and MenuProvider(MenuProvider Is working correctly)
5. Qbank.Connector.EpiServerModule5 - includes the MVC Controllers, Models,Views (Views are in QbankModule.Views/Views folder. _Layout _ViewImports _ViewStart files are also placed as needed)
6. We have this routing setup as well in the module.config.
<route url="{controller}/{action}">
<defaults>
<add key="controller" value="QbankSettings" />
<add key="action" value="Index" />
</defaults>
</route>
Do we need Area part also? Somthing like : <add key="moduleArea" value="Admin" />
7. Here is my MenuProvider:
[MenuProvider]
sealed public class MenuProvider : IMenuProvider
{
public IEnumerable<MenuItem> GetMenuItems()
{
var name = "Qbank";
var path = MenuPaths.Global + "/cms/admin/qbank";
var url = Paths.ToResource(GetType(), "QbankSettings");
var link = new UrlMenuItem(name, path, url)
{
SortIndex = 200,
AuthorizationPolicy = CmsPolicyNames.CmsAdmin
};
return new List<MenuItem>
{
link
};
}
}
Would be great if someone can help with this as well
Thanks!
Hi All, I was able to fix the rendering issue, and thank you a lot for the support, the issue with the Routing was attributes for routes.
[Route("[controller]")]
[Route("[action]")]
And the set the Build Action in properties is set to C# compiler for MenuProvider, Controller, Model.
And set the Build Action in properties is set to Content for Views.
Now I need help with Client resource importing, According to the documentation I have created ClientResourceProvider and QbankClientResourceRegister. Kindly check the folllowings.
QbankClientResourceRegister :
[ClientResourceRegistrator]
public class QbankClientResourceRegister : IClientResourceRegistrator
{
public void RegisterResources(IRequiredClientResourceList requiredResources)
{
requiredResources.Require("epi-cms.widgets.base").AtHeader();
requiredResources.Require("qbank.picker").AtFooter();
}
}
ClientResourceProvider :
[ClientResourceProvider]
public class ClientResourceProvider : IClientResourceProvider
{
public IEnumerable<ClientResource> GetClientResources()
{
return new[]
{
new ClientResource
{
Name = "epi-cms.widgets.base",
Path = Paths.ToClientResource("QbankModule", "ClientResources/Scripts/Editors/QBankMediaPicker.js"),
ResourceType = ClientResourceType.Script
},
new ClientResource
{
Name = "qbank.picker",
Path = Paths.ToClientResource("QbankModule", "ClientResources/Scripts/qbank-connector.js"),
ResourceType = ClientResourceType.Script
}
};
}
}
module.config file in the moduel folder :
<?xml version="1.0" encoding="utf-8"?>
<module name="QbankModule" loadFromBin="false" clientResourceRelativePath="" viewEngine="Razor" authorizationPolicy="episerver:cmsadmin"
moduleJsonSerializerType="None" prefferedUiJsonSerializerType="Net">
<assemblies>
<add assembly="Qbank.Connector.EpiServerModule5" />
<add assembly="Qbank.Connector.EpiCore11" />
<add assembly="Qbank.Connector.EpiCore" />
<add assembly="Qbank.Connector.Core" />
</assemblies>
<clientResources>
<add name="epi-cms.widgets.base" path="ClientResources/Styles/qbankplugin.css" resourceType="Style"/>
<add name="qbank.picker" path="ClientResources/scripts/editors/QbankMediaPicker.js" resourceType="Script"/>
</clientResources>
<dojo>
<paths>
<add name="qbank" path="ClientResources/scripts" />
</paths>
</dojo>
</module>
I need to load QbankMediaPicker.js when loading the following specific block :
[ContentType(
DisplayName = "Banner",
GUID = "0d1c8743-4d48-470f-8afb-7e62d84f6092",
GroupName = SystemTabNames.Content)]
public class BannerBlock : BlockData
{
[Display(
Name = "Header",
Description = "Enter a header for the block",
GroupName = SystemTabNames.Content,
Order = 1
)]
[Required]
public virtual string Header { get; set; }
[Display(
Name = "Description",
Description = "Enter a Description for the block",
GroupName = SystemTabNames.Content,
Order = 2
)]
[Required]
public virtual XhtmlString Description { get; set; }
[Display(
Name = "QBankSampleImage",
Description = "QBankSampleImage for the block",
GroupName = SystemTabNames.Content,
Order = 3)]
//[Qbank.Connector.EpiCore.DataAnnotations.MediaFormat("#310")]
[UIHint(Qbank.Connector.Core.Web.UIHint.QBankMedia)]
public virtual ContentReference QBankSampleImage { get; set; }
Do I need to create a virtual path provider? If yes what is it and why do we need that?
Anyone who has experience in these areas please point me to somewhere I can refer and understands things.
Thank You!
Hi All, I was able to fix the rendering issue, and thank you a lot for the support, the issue with the Routing was attributes for routes.
[Route("[controller]")]
[Route("[action]")]
And the set the Build Action in properties is set to C# compiler for MenuProvider, Controller, Model.
And set the Build Action in properties is set to Content for Views.
Now I need help with Client resource importing, According to the documentation I have created ClientResourceProvider and QbankClientResourceRegister. Kindly check the folllowings.
QbankClientResourceRegister :
[ClientResourceRegistrator]
public class QbankClientResourceRegister : IClientResourceRegistrator
{
public void RegisterResources(IRequiredClientResourceList requiredResources)
{
requiredResources.Require("epi-cms.widgets.base").AtHeader();
requiredResources.Require("qbank.picker").AtFooter();
}
}
ClientResourceProvider :
[ClientResourceProvider]
public class ClientResourceProvider : IClientResourceProvider
{
public IEnumerable<ClientResource> GetClientResources()
{
return new[]
{
new ClientResource
{
Name = "epi-cms.widgets.base",
Path = Paths.ToClientResource("QbankModule", "ClientResources/Scripts/Editors/QBankMediaPicker.js"),
ResourceType = ClientResourceType.Script
},
new ClientResource
{
Name = "qbank.picker",
Path = Paths.ToClientResource("QbankModule", "ClientResources/Scripts/qbank-connector.js"),
ResourceType = ClientResourceType.Script
}
};
}
}
module.config file in the moduel folder :
<?xml version="1.0" encoding="utf-8"?>
<module name="QbankModule" loadFromBin="false" clientResourceRelativePath="" viewEngine="Razor" authorizationPolicy="episerver:cmsadmin"
moduleJsonSerializerType="None" prefferedUiJsonSerializerType="Net">
<assemblies>
<add assembly="Qbank.Connector.EpiServerModule5" />
<add assembly="Qbank.Connector.EpiCore11" />
<add assembly="Qbank.Connector.EpiCore" />
<add assembly="Qbank.Connector.Core" />
</assemblies>
<clientResources>
<add name="epi-cms.widgets.base" path="ClientResources/Styles/qbankplugin.css" resourceType="Style"/>
<add name="qbank.picker" path="ClientResources/scripts/editors/QbankMediaPicker.js" resourceType="Script"/>
</clientResources>
<dojo>
<paths>
<add name="qbank" path="ClientResources/scripts" />
</paths>
</dojo>
</module>
I need to load QbankMediaPicker.js when loading the following specific block :
[ContentType(
DisplayName = "Banner",
GUID = "0d1c8743-4d48-470f-8afb-7e62d84f6092",
GroupName = SystemTabNames.Content)]
public class BannerBlock : BlockData
{
[Display(
Name = "Header",
Description = "Enter a header for the block",
GroupName = SystemTabNames.Content,
Order = 1
)]
[Required]
public virtual string Header { get; set; }
[Display(
Name = "Description",
Description = "Enter a Description for the block",
GroupName = SystemTabNames.Content,
Order = 2
)]
[Required]
public virtual XhtmlString Description { get; set; }
[Display(
Name = "QBankSampleImage",
Description = "QBankSampleImage for the block",
GroupName = SystemTabNames.Content,
Order = 3)]
//[Qbank.Connector.EpiCore.DataAnnotations.MediaFormat("#310")]
[UIHint(Qbank.Connector.Core.Web.UIHint.QBankMedia)]
public virtual ContentReference QBankSampleImage { get; set; }
Do I need to create a virtual path provider? If yes what is it and why do we need that?
Anyone who has experience in these areas please point me to somewhere I can refer and understands things.
Thank You!
What version are you running?
Requiring resources from a partial view/compontent has stopped working in CMS12. The fix will be released in 12.4.0
See forum thread here: https://world.optimizely.com/forum/developer-forum/CMS/Thread-Container/2022/2/require-client-resources-only-when-specific-block-type-is-rendered/#271292
Hi Mari,
Thanks for the input,
Actually, I upgraded EPiServer.Framework
and EPiServer.CMS.Core
packages to the latest version (12.4.0
). But it seems the EPiServer.CMS
package does not have version 12.4.* yet. But still, I can't get the client resources imported when I'm creating my custom block.
I'm getting following errors:
GET http://localhost:8000/EPiServer/QbankModule/ClientResources/scripts/editors/QBankMediaPicker.js net::ERR_ABORTED 404 (Not Found)
GET http://localhost:8000/EPiServer/QbankModule/ClientResources/Styles/qbankplugin.css net::ERR_ABORTED 404 (Not Found)
GET http://localhost:8000/ClientResources/Styles/qbankplugin.css net::ERR_ABORTED 404 (Not Found)
Regarding the versions, you have mentioned that fixes will be released on version 12.4.0, so is it on EPiServer.CMS
package? or in EPiServer.Framework
/ EPiServer.CMS.Core
packages?
if it is EPiServer.CMS
package, does it means that we need to wait for the next release (12.4.0) with the fix? Or do you know any workaround for this?
Hi guys,
I was able to fix the version-related issues I had in the project. But now I'm getting the following error when going to edit mode.
Error: multipleDefine dojo.js:15
at _f (dojo.js:15:436)
at _f6 (dojo.js:15:15989)
at dojo.js:15:16572
at _9 (dojo.js:15:328)
at _7b (dojo.js:15:16543)
at _f0 (dojo.js:15:14659)
at HTMLScriptElement._10a (dojo.js:15:17619)
According to the community forums, this is happening because of conflict between Jquery-related libraries when trying to register some function twice.
I have dojo.js and jquery.js file which is used by the plugging I'm developing.
How we can easily find the exact library or point this is happening? Any way to get detailed error messages about what is happening?
Hi!
We have a plugin that show in the CMS 11 admin menu, I guess this should be moved to the Tools tab in admin for CMS 12 but I cannot figure out how to do that?
Has anyone added a link in the Tool menu in CMS 12?
Thanks!
/Kristoffer