Skip to content

Commit 1abedf6

Browse files
committed
Merge branch 'main' into cms/database-availability-checks
2 parents 3f717df + 1b761be commit 1abedf6

File tree

24 files changed

+503
-106
lines changed

24 files changed

+503
-106
lines changed

10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/nested-content.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
We highly recommend that you use the [Block List](block-editor/block-list-editor.md) instead.
55

66
Nested Content has been marked as obsolete and development on the property editor has been discontinued.
7+
8+
[Umbraco Deploy](https://docs.umbraco.com/umbraco-deploy/deployment-workflow/import-export/import-with-migrations) and [uSync migrations](https://github.com/Jumoo/uSyncMigrations) have support for migrating from nested content to the block list.
79
{% endhint %}
810

911
`Alias: Umbraco.NestedContent`
@@ -106,7 +108,7 @@ Example:
106108
// Render your content, e.g. item.Value<string>("heading")
107109
}
108110
}
109-
111+
110112
}
111113
```
112114

@@ -176,31 +178,31 @@ Afterwards, the entire list needs to be serialized to Json via JsonConvert.
176178
@using Newtonsoft.Json;
177179
@inject IContentService _contentService;
178180

179-
//if the class containing our code inherits SurfaceController, UmbracoApiController,
181+
//if the class containing our code inherits SurfaceController, UmbracoApiController,
180182
//or UmbracoAuthorizedApiController, we can get ContentService from Services namespace
181-
var contentService = _contentService;
183+
var contentService = _contentService;
182184
//here we create a new node, and fill out attendeeList afterwards
183-
IContent request = contentService.Create("new node", guid, "mydoctype", -1);
185+
IContent request = contentService.Create("new node", guid, "mydoctype", -1);
184186
//our list which will contain nested content
185-
var attendees = new List<Dictionary<string, string>>();
187+
var attendees = new List<Dictionary<string, string>>();
186188
//participants is our list of attendees - multiple items, good use case for nested content
187-
foreach (var person in participants)
189+
foreach (var person in participants)
188190
attendees.Add(new Dictionary<string, string>() {
189191
//this is the only "default" value we need to fill for nested item
190-
{"ncContentTypeAlias","attendee"},
192+
{"ncContentTypeAlias","attendee"},
191193
{"user_name", person.name},
192194
{"user_email",person.user_email},
193-
{"join_time",person.join_time.ToString()},
195+
{"join_time",person.join_time.ToString()},
194196
//we convert some properties to String just to be on the safe side
195197
{"leave_time",person.leave_time.ToString()},
196198
{"duration",person.duration.ToString()},
197199
{"phone",person.phone.ToString()}
198200
});
199201
}
200202
//bind the attendees List to attendeeList property on the newly created content node
201-
request.SetValue("attendeeList", JsonConvert.SerializeObject(attendees));
203+
request.SetValue("attendeeList", JsonConvert.SerializeObject(attendees));
202204
//Save the entire node via ContentService
203-
ContentService.SaveAndPublish(request);
205+
ContentService.SaveAndPublish(request);
204206
```
205207

206208
In the above sample we iterate through a list of participants (the data for such participants could be coming from an API, for example), and add a new `Dictionary` item for each person in the list.

13/umbraco-cms/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@
368368
* [Cache & Distributed Cache](reference/cache/README.md)
369369
* [Accessing the cache](reference/cache/application-cache.md)
370370
* [ICacheRefresher](reference/cache/icacherefresher.md)
371+
* [IMemberPartialViewCacheInvalidator](reference/cache/imemberpartialviewcacheinvalidator.md)
371372
* [IServerMessenger](reference/cache/iservermessenger.md)
372373
* [Getting/Adding/Updating/Inserting Into Cache](reference/cache/updating-cache.md)
373374
* [Examples](reference/cache/examples/README.md)

13/umbraco-cms/extending/packages/example-package-repository.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ When the next major version of Umbraco is released, we'll test and either extend
3030

3131
### Tests Project
3232

33-
We have a [project for unit tests](https://github.com/umbraco/Umbraco.AuthorizedServices/tree/main/tests/Umbraco.AuthorizedServices.Tests) in `tests/<ProjectName>.Tests`. It contains references to `Umbraco.Cms.Tets` and a project reference to the package:
33+
We have a [project for unit tests](https://github.com/umbraco/Umbraco.AuthorizedServices/tree/main/tests/Umbraco.AuthorizedServices.Tests) in `tests/<ProjectName>.Tests`. It contains references to `Umbraco.Cms.Tests` and a project reference to the package:
3434

3535
```xml
3636
<ProjectReference Include="..\..\src\Umbraco.AuthorizedServices\Umbraco.AuthorizedServices.csproj" />
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Partial view cache refresher for members
2+
3+
This section describes the `IMemberPartialViewCacheInvalidator` interface, what it's default implementation does and how to customize it.
4+
5+
## What is an IMemberPartialViewCacheInvalidator?
6+
7+
This interface is used to isolate the logic that invalidates parts of the partial view cache when a member is updated.
8+
9+
## Why do we need to partially invalidate the partial view cache?
10+
11+
Razor templates may show data that is retrieved from a member object. Those templates might be cached by using the partial caching mechanism (for example, `@await Html.CachedPartialAsync("member",Model,TimeSpan.FromDays(1), cacheByMember:true)`). When a member is updated, these cached partials must be invalidated to ensure updated data is shown.
12+
13+
## Where is it used?
14+
15+
This interface is called from the member cache refresher (`MemberCacheRefresher`), which is invoked every time a member is updated.
16+
17+
## Details of the default implementation
18+
19+
Razor template partials are cached through a call to `Html.CachedPartialAsync` with `cacheByMember` set to `true`. This will append the ID of the currently logged-in member with a marker to the partial view cache key. For example, `-m1015-`.
20+
21+
When the `ClearPartialViewCacheItems` method is called it will clear all cache items that match the marker for the updated members.
22+
23+
If no member is logged in during caching, items with an empty member marker (for example, `-m-`) are also cleared.
24+
25+
## Customizing the implementation
26+
27+
You can replace the default implementation by removing it and registering your own in a composer.
28+
29+
```csharp
30+
public class ReplaceMemberCacheInvalidatorComposer : IComposer
31+
{
32+
public void Compose(IUmbracoBuilder builder)
33+
{
34+
builder.Services.AddUnique<IMemberPartialViewCacheInvalidator, MyCustomMemberPartialViewCacheInvalidator>();
35+
}
36+
}
37+
```

13/umbraco-cms/reference/security/external-login-providers.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ In those cases, it would mean that anyone who has a Google or Facebook account c
151151

152152
If auto-linking for public providers such as these was needed you would need to limit the access. This can be done by domain or other information provided in the claims using the options/callbacks specified in those provider's authentication options.
153153

154+
When auto-linking for the backoffice you will want to define what user groups the user will be part of. This is done via the `defaultUserGroups` parameter provided to the constructor of `ExternalSignInAutoLinkOptions` (see example below). If the value is not set the user will by default be added to the Editors group.
155+
154156
### Auto-linking on Member authentication
155157

156158
Auto-linking on Member authentication only makes sense if you have a public member registration already or the provider does not have public account creation.

13/umbraco-cms/reference/using-ioc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ There are different strategies for registering your dependencies and not one str
1818

1919
In this article, we will cover the following three strategies:
2020

21-
* [Registering dependencies in the `Program.cs` file](#registering-dependencies-in-the-programcs-file)
21+
* [Registering dependencies in the `Program.cs` file](#registering-dependencies-in-the-program.cs-file)
2222
* [Registering dependencies in a composer](#registering-dependencies-in-a-composer)
2323
* [Registering dependencies in bundles](#registering-dependencies-in-bundles)
2424

14/umbraco-cms/reference/security/external-login-providers.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ In those cases, it would mean that anyone who has a Google or Facebook account c
155155

156156
If auto-linking for public providers such as these was needed you would need to limit the access. This can be done by domain or other information provided in the claims using the options/callbacks specified in those provider's authentication options.
157157

158+
When auto-linking for the backoffice you will want to define what user groups the user will be part of. This is done via the `defaultUserGroups` parameter provided to the constructor of `ExternalSignInAutoLinkOptions` (see example below). If the value is not set the user will by default be added to the Editors group.
159+
158160
#### Is your project hosted on Umbraco Cloud?
159161

160162
Umbraco Cloud uses Umbraco ID for all authentication, including access to the Umbraco Backoffice.

14/umbraco-cms/reference/using-ioc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ There are different strategies for registering your dependencies and not one str
1818

1919
In this article, we will cover the following three strategies:
2020

21-
* [Registering dependencies in the `Program.cs` file](#registering-dependencies-in-the-programcs-file)
21+
* [Registering dependencies in the `Program.cs` file](#registering-dependencies-in-the-program.cs-file)
2222
* [Registering dependencies in a composer](#registering-dependencies-in-a-composer)
2323
* [Registering dependencies in bundles](#registering-dependencies-in-bundles)
2424

15/umbraco-cms/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@
252252
* [Custom File Systems (IFileSystem)](extending/filesystemproviders/README.md)
253253
* [Using Azure Blob Storage for Media and ImageSharp Cache](extending/filesystemproviders/azure-blob-storage.md)
254254
* [Configuring Azure Key Vault](extending/key-vault.md)
255+
* [Server Events From SignalR](extending/server-events.md)
255256
* [Packages](extending/packages/README.md)
256257
* [Creating a Package](extending/packages/creating-a-package.md)
257258
* [Language file for packages](extending/packages/language-files-for-packages.md)
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
description: Describes server events emitted via a SignalR hub and available for consumption in the backoffice
3+
---
4+
5+
# Server Events
6+
7+
Umbraco registers a SignalR event hub that broadcasts events related to the update of entities. These can be used in the backoffice to respond to changes made by users other than the current editor.
8+
9+
Each server event is triggered via a notification handler. So for example, when a document is saved, the `ContentSavedNotification` is published. This is handled by a class responsible for issuing a server event.
10+
11+
Not all server events should be broadcast to all users. For example, if a user doesn't have access to the Media section, they shouldn't receive notifications on updates to media. For core entities, Umbraco uses the same permission system that defines access in the backoffice. In this way, only events appropriate for the currently logged in editor are exposed.
12+
13+
## Event Information
14+
15+
Each event emitted contains the following fields:
16+
17+
- `EventType` - the event type, which might be `Created`, `Updated`, `Deleted` etc.
18+
- `EventSource` - the event source, which might be `Document`, `Media` etc.
19+
- `Key` - the unique GUID that identifies the entity changed.
20+
21+
## Event Authorization
22+
23+
The currently authorized user will have one or more claims indicating the access they have to different areas of the backoffice. When first connecting to the SignalR hub, these details are used to assign the user to one or more SignalR groups.
24+
25+
Which groups they have access to is determined by the collection of registered `IEventSourceAuthorizer` instances.
26+
27+
For example, there is a `DocumentEventAuthorizer` that ensures users with access to the documents (content) tree are assigned to the `Umbraco:CMS:Document` event source. As a result, when server events are emitted for documents, only those users with this access will receive them.
28+
29+
## Extending Server Events
30+
31+
Using the same patterns as the core CMS, server events for other entities defined in packages or custom solutions can be emitted.
32+
33+
Firstly, a `IEventSourceAuthorizer` should be registered. This will likely hook into what you already have in place for controlling access to the backoffice section where the entity is managed.
34+
35+
For example, if you had a `Product` entity managed in a custom CMS section, the authorizer might look like this:
36+
37+
```csharp
38+
using Microsoft.AspNetCore.Authorization;
39+
using Umbraco.Cms.Core;
40+
using Umbraco.Cms.Web.Common.Authorization;
41+
42+
public class ProductEventAuthorizer : EventSourcePolicyAuthorizer
43+
{
44+
public ProductEventAuthorizer(IAuthorizationService authorizationService)
45+
: base(authorizationService)
46+
{
47+
}
48+
49+
public override IEnumerable<string> AuthorizableEventSources => ["Umbraco:Custom:Product"];
50+
51+
protected override string Policy => "TreeAccessProducts"; // Maps to an existing authorization policy
52+
// used in an [Authorize] attribute.
53+
}
54+
```
55+
56+
The authorizer should be registered with Umbraco using something like the following, called from a composer, or otherwise in application start up:
57+
58+
```csharp
59+
public static IUmbracoBuilder AddCustomAuthorizers(this IUmbracoBuilder builder)
60+
{
61+
builder.EventSourceAuthorizers()
62+
.Append<ProductEventAuthorizer>();
63+
}
64+
```
65+
66+
You then need to emit the event from a notification handler which handles notifications published when the entity is updated.
67+
68+
For example, assuming an existing `ProductSavedNotification` that is published with the product is saved:
69+
70+
```csharp
71+
using Umbraco.Cms.Core;
72+
using Umbraco.Cms.Core.Events;
73+
using Umbraco.Cms.Core.Models.Entities;
74+
using Umbraco.Cms.Core.Models.ServerEvents;
75+
using Umbraco.Cms.Core.Notifications;
76+
using Umbraco.Cms.Core.ServerEvents;
77+
78+
namespace Umbraco.Cms.Api.Management.ServerEvents;
79+
80+
public class ProductServerEventSender : INotificationAsyncHandler<ProductSavedNotification>
81+
{
82+
private readonly IServerEventRouter _serverEventRouter;
83+
84+
public ProductServerEventSender(IServerEventRouter serverEventRouter) => _serverEventRouter = serverEventRouter;
85+
86+
public async Task HandleAsync(ProductSavedNotification notification, CancellationToken cancellationToken) =>
87+
await NotifySavedAsync(notification, "Umbraco:Custom:Product");
88+
89+
private async Task NotifySavedAsync<T>(SavedNotification<T> notification, string source)
90+
where T : IEntity
91+
{
92+
foreach (T entity in notification.SavedEntities)
93+
{
94+
var eventModel = new ServerEvent
95+
{
96+
EventType = entity.CreateDate == entity.UpdateDate
97+
? Constants.ServerEvents.EventType.Created : Constants.ServerEvents.EventType.Updated,
98+
Key = entity.Key,
99+
EventSource = source,
100+
};
101+
102+
await _serverEventRouter.RouteEventAsync(eventModel);
103+
}
104+
}
105+
}
106+
```
107+
108+
Again, the notification handler will need to be registered with Umbraco:
109+
110+
```csharp
111+
public static IUmbracoBuilder AddCustomEvents(this IUmbracoBuilder builder)
112+
{
113+
builder.AddNotificationAsyncHandler<ProductSavedNotification, ProductServerEventSender>();
114+
}
115+
```
116+
117+
118+

0 commit comments

Comments
 (0)