DNN · Widget embed
The widget is loaded from the CDN as a single <script> with data-token pointing at the Widget Token (24 h) issued by Neuroon. The DNN-specific piece is:
- Issuing the token server-to-server from DNN, with in-memory cache.
- Rendering the
<script>in an.ascx(Skin or SkinObject) printing the token from code-behind. - Pinned SRI to ensure the integrity of the widget binary.
The token is NOT signed by the client
The Widget Token is opaque and issued by the Neuroon backend. Your integration does not sign anything; you request the token with your Shop API Key and inject it into
data-token. See Authentication · Widget Token.
Endpoint used:
/api/shops/{id}/widget-tokenServer-side cache (24 h with margin)
DNN should not hit the /widget-token endpoint on every request. The standard pattern:
public static class WidgetTokenProvider
{
private static readonly object Lock = new();
private static string _token;
private static DateTime _expires;
public static string Get()
{
lock (Lock)
{
if (_token != null && DateTime.UtcNow < _expires.AddMinutes(-5))
return _token;
var fresh = new NeuroonClient().IssueWidgetTokenAsync().GetAwaiter().GetResult();
_token = fresh;
_expires = DateTime.UtcNow.AddHours(24);
return _token;
}
}
}
Notes:
- 5 minute margin before expiry to avoid
401on edge requests. - In multi-AppPool / web farm environments, replace the
staticwith a distributed cache (Redis,OutputCache). - On .NET 8, prefer
SemaphoreSlimorIMemoryCachewithLazy<Task<string>>.
Render in .ascx
Minimal Skin / SkinObject. Place a placeholder where you want the search bar:
<%@ Control Language="C#" AutoEventWireup="true" %>
<%@ Register TagPrefix="dnn" Namespace="DotNetNuke.UI.Skins.Controls" Assembly="DotNetNuke" %>
<asp:PlaceHolder runat="server">
<div id="neuroon-search"></div>
<script
src="https://cdn.neuroon.ai/widget@0.9.10/widget.js"
integrity="sha384-JTaG/IN0Jj/ImfUj2x5QVMG4HkbFHzui7fTpLtwl1hsP+kY9W8OODeSJRFWN1ZP5"
data-token='<%= WidgetTokenProvider.Get() %>'
data-container="#neuroon-search"
data-theme="auto"
data-locale='<%= System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName %>'
data-api-url='<%= NeuroonSettings.ApiUrl %>'
crossorigin="anonymous"
async></script>
</asp:PlaceHolder>
The SRI hash and widget version come from the official manifest:
https://cdn.neuroon.ai/widget@VERSION/sri-manifest.json. Version0.9.10and its hash come from the WordPress plugin .
CSP (Content Security Policy)
If your Skin enforces a strict CSP, add the minimum hosts:
Content-Security-Policy:
script-src 'self' https://cdn.neuroon.ai;
connect-src 'self' https://api.neuroon.ai https://dev-api.neuroon.ai;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
For Production-only, drop dev-api.neuroon.ai. The widget does not require unsafe-eval.
Locale
data-locale accepts any ISO 639-1 code (es, en, de, etc.). In DNN, the natural source is:
data-locale='<%= System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName %>'
If your site is multi-portal, use PortalSettings.Current.PortalAlias.CultureCode. Read also Widget · i18n for the client-side fallback behavior.
Verify the embed
- Open the public page with DevTools.
- Network → Filter
widget: you should see a request tohttps://cdn.neuroon.ai/widget@0.9.10/widget.jswith status200and headerX-Content-Type-Options: nosniff. - Console: no
Failed to find a valid digest in the 'integrity' attribute. If it appears, the SRI or version do not match. - Application → Cookies: the widget should not set tracking cookies in this domain.
Conditional embed
To render the widget only on certain pages / portals:
protected void Page_PreRender(object sender, EventArgs e)
{
var portal = PortalSettings.Current;
if (portal != null && portal.HomeTabId == this.TabId)
{
widgetPlaceholder.Visible = true;
}
}
Next steps
- Cart bridge — sync the cart with the widget.
- Examples · Razor snippets — more embed patterns.
- Authentication · Widget Token — TTL and rotation.