Skip to content

Commit d66dee9

Browse files
authored
Add webapi project for server-side client scenarios. (#118)
* Add webapi project for server-side client scenarios. * Rename repo collection.
1 parent 27fd15d commit d66dee9

7 files changed

Lines changed: 209 additions & 1 deletion
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace SenseNet.Client.WebApi
2+
{
3+
public class Repositories
4+
{
5+
public const string UserRepository = "UserRepository";
6+
public const string VisitorRepository = "VisitorRepository";
7+
public const string AdminRepository = "AdminRepository";
8+
}
9+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Microsoft.AspNetCore.Http;
2+
3+
namespace SenseNet.Client.WebApi
4+
{
5+
internal static class HttpContextExtensions
6+
{
7+
private const string BearerPrefix = "Bearer ";
8+
9+
/// <summary>
10+
/// Returns the bearer token from the Authorization header without the prefix.
11+
/// </summary>
12+
/// <param name="context">The current HttpContext instance.</param>
13+
/// <returns>The token or null.</returns>
14+
public static string? GetBearerToken(this HttpContext context)
15+
{
16+
if (!context.Request.Headers.TryGetValue("Authorization", out var token))
17+
return null;
18+
19+
var tokenString = token.FirstOrDefault();
20+
if (string.IsNullOrWhiteSpace(tokenString))
21+
return null;
22+
23+
// the token is expected to start with the bearer prefix
24+
return tokenString.StartsWith(BearerPrefix) ? tokenString[BearerPrefix.Length..] : null;
25+
}
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace SenseNet.Client.WebApi
2+
{
3+
/// <summary>
4+
/// Defines methods for accessing repositories for dedicated users.
5+
/// </summary>
6+
public interface IUserRepositoryCollection
7+
{
8+
/// <summary>
9+
/// Returns the repository that is configured to use the current user's token available in HttpContext.
10+
/// </summary>
11+
/// <param name="cancel">The token to monitor for cancellation requests.</param>
12+
/// <returns>A task that wraps the repository instance.</returns>
13+
public Task<IRepository> GetUserRepositoryAsync(CancellationToken cancel);
14+
/// <summary>
15+
/// Returns a repository without authentication.
16+
/// </summary>
17+
/// <param name="cancel">The token to monitor for cancellation requests.</param>
18+
/// <returns>A task that wraps the repository instance.</returns>
19+
public Task<IRepository> GetVisitorRepositoryAsync(CancellationToken cancel);
20+
/// <summary>
21+
/// Returns a repository that is configured to use the configured Admin user.
22+
/// </summary>
23+
/// <param name="cancel">The token to monitor for cancellation requests.</param>
24+
/// <returns>A task that wraps the repository instance.</returns>
25+
public Task<IRepository> GetAdminRepositoryAsync(CancellationToken cancel);
26+
}
27+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using SenseNet.Client;
3+
using SenseNet.Client.WebApi;
4+
5+
// ReSharper disable once CheckNamespace
6+
namespace SenseNet.Extensions.DependencyInjection
7+
{
8+
public static class RepositoryExtensions
9+
{
10+
/// <summary>
11+
/// Adds sensenet client services and registers three repositories: User, Visitor and Admin.
12+
/// All repositories are configured with the same options, the only difference is the authentication. <br/><br/>
13+
/// - the <b>Visitor</b> repository will not have any<br/>
14+
/// - the <b>Admin</b> repository will use the configured Admin user<br/>
15+
/// - the <b>User</b> repository will try to authenticate using the current user's token available in HttpContext<br/>
16+
/// <br/>
17+
/// To make advantage of these repositories, use the <see cref="IUserRepositoryCollection"/> service
18+
/// in your classes.
19+
/// </summary>
20+
/// <param name="services"></param>
21+
/// <param name="configure">Callback for configuring <see cref="RepositoryOptions"/> instance.</param>
22+
/// <param name="registerContentTypes">Optional callback for registering custom content types.</param>
23+
public static IServiceCollection AddSenseNetClientWithUserRepositories(this IServiceCollection services,
24+
Action<RepositoryOptions> configure, Action<RegisteredContentTypes>? registerContentTypes = null)
25+
{
26+
services.AddHttpContextAccessor();
27+
services.AddSenseNetClient();
28+
29+
services.ConfigureSenseNetRepository(Repositories.VisitorRepository,
30+
ConfigureWithEmptyAuthentication(configure),
31+
registerContentTypes)
32+
.ConfigureSenseNetRepository(Repositories.UserRepository,
33+
ConfigureWithEmptyAuthentication(configure),
34+
registerContentTypes)
35+
.ConfigureSenseNetRepository(Repositories.AdminRepository,
36+
configure,
37+
registerContentTypes);
38+
39+
// this can be singleton, because it requires only singleton services like IHttpContextAccessor
40+
services.AddSingleton<IUserRepositoryCollection, UserRepositoryCollection>();
41+
42+
return services;
43+
}
44+
45+
private static Action<RepositoryOptions> ConfigureWithEmptyAuthentication(Action<RepositoryOptions>? configure)
46+
{
47+
return options =>
48+
{
49+
configure?.Invoke(options);
50+
51+
// clear all auth data to prevent accidental use of client credentials
52+
options.Authentication.ClientId = string.Empty;
53+
options.Authentication.ClientSecret = string.Empty;
54+
options.Authentication.ApiKey = string.Empty;
55+
};
56+
}
57+
}
58+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Library</OutputType>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<RootNamespace>SenseNet.Client.WebApi</RootNamespace>
9+
<PackageId>SenseNet.Client.WebApi</PackageId>
10+
<Version>0.0.0.1</Version>
11+
<Company>Sense/Net Inc.</Company>
12+
<Description>A .Net library for Asp.Net applications that want to connect to the sensenet repository REST API.</Description>
13+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
14+
<PackageProjectUrl>https://github.com/SenseNet/sn-client-dotnet</PackageProjectUrl>
15+
<PackageLicenseUrl>https://github.com/SenseNet/sn-client-dotnet/blob/master/LICENSE</PackageLicenseUrl>
16+
<RepositoryUrl>https://github.com/SenseNet/sn-client-dotnet.git</RepositoryUrl>
17+
<RepositoryType>git</RepositoryType>
18+
<PackageReleaseNotes>See release notes on GitHub.</PackageReleaseNotes>
19+
<PackageTags>sensenet client aspnet</PackageTags>
20+
<PackageIconUrl>https://raw.githubusercontent.com/SenseNet/sn-resources/master/images/sn-icon/sensenet-icon-64.png</PackageIconUrl>
21+
<Authors>kavics,tusmester</Authors>
22+
<Copyright>Copyright © Sense/Net Inc.</Copyright>
23+
<DebugType>portable</DebugType>
24+
<LangVersion>latest</LangVersion>
25+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
26+
<IncludeSymbols>true</IncludeSymbols>
27+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
28+
</PropertyGroup>
29+
30+
<ItemGroup>
31+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
32+
</ItemGroup>
33+
34+
<ItemGroup>
35+
<ProjectReference Include="..\SenseNet.Client\SenseNet.Client.csproj" />
36+
</ItemGroup>
37+
38+
</Project>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace SenseNet.Client.WebApi
5+
{
6+
public class UserRepositoryCollection : IUserRepositoryCollection
7+
{
8+
private readonly IRepositoryCollection _repositoryCollection;
9+
private readonly IHttpContextAccessor _httpContextAccessor;
10+
private readonly ILogger<UserRepositoryCollection> _logger;
11+
12+
public UserRepositoryCollection(IRepositoryCollection repositoryCollection, IHttpContextAccessor httpContextAccessor,
13+
ILogger<UserRepositoryCollection> logger)
14+
{
15+
_repositoryCollection = repositoryCollection;
16+
_httpContextAccessor = httpContextAccessor;
17+
_logger = logger;
18+
}
19+
20+
public async Task<IRepository> GetUserRepositoryAsync(CancellationToken cancel)
21+
{
22+
var token = _httpContextAccessor.HttpContext?.GetBearerToken();
23+
if (token != null)
24+
return await GetRepositoryAsync(Repositories.UserRepository, token, cancel).ConfigureAwait(false);
25+
26+
_logger.LogTrace("Returning Visitor repository instead of User, because there is no token available.");
27+
28+
return await GetVisitorRepositoryAsync(cancel).ConfigureAwait(false);
29+
}
30+
31+
public Task<IRepository> GetVisitorRepositoryAsync(CancellationToken cancel) =>
32+
GetRepositoryAsync(Repositories.VisitorRepository, null, cancel);
33+
public Task<IRepository> GetAdminRepositoryAsync(CancellationToken cancel) =>
34+
GetRepositoryAsync(Repositories.AdminRepository, null, cancel);
35+
36+
private Task<IRepository> GetRepositoryAsync(string name, string? token, CancellationToken cancel) =>
37+
_repositoryCollection.GetRepositoryAsync(new RepositoryArgs
38+
{
39+
Name = name,
40+
AccessToken = token
41+
}, cancel);
42+
}
43+
}

src/SenseNet.Client.sln

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SenseNet.Client.Integration
1111
EndProject
1212
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SenseNet.Client.DemoConsole", "SenseNet.Client.DemoConsole\SenseNet.Client.DemoConsole.csproj", "{B75A2674-0332-43B3-AA1F-BBB1F87EDA18}"
1313
EndProject
14-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SenseNet.Client.DemoMvc", "SenseNet.Client.DemoMvc\SenseNet.Client.DemoMvc.csproj", "{FBB4806C-01E1-4AA4-98D1-ECC069C4AACC}"
14+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SenseNet.Client.DemoMvc", "SenseNet.Client.DemoMvc\SenseNet.Client.DemoMvc.csproj", "{FBB4806C-01E1-4AA4-98D1-ECC069C4AACC}"
1515
EndProject
1616
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SenseNet.Client.TestsForDocs", "SenseNet.Client.TestsForDocs\SenseNet.Client.TestsForDocs.csproj", "{8B8495CD-699E-4986-9693-44CCE08D1230}"
1717
EndProject
18+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SenseNet.Client.WebApi", "SenseNet.Client.WebApi\SenseNet.Client.WebApi.csproj", "{7ADE2875-3BA1-4051-AB2E-DB76C273DE2B}"
19+
EndProject
1820
Global
1921
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2022
Debug|Any CPU = Debug|Any CPU
@@ -45,6 +47,10 @@ Global
4547
{8B8495CD-699E-4986-9693-44CCE08D1230}.Debug|Any CPU.Build.0 = Debug|Any CPU
4648
{8B8495CD-699E-4986-9693-44CCE08D1230}.Release|Any CPU.ActiveCfg = Release|Any CPU
4749
{8B8495CD-699E-4986-9693-44CCE08D1230}.Release|Any CPU.Build.0 = Release|Any CPU
50+
{7ADE2875-3BA1-4051-AB2E-DB76C273DE2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51+
{7ADE2875-3BA1-4051-AB2E-DB76C273DE2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
52+
{7ADE2875-3BA1-4051-AB2E-DB76C273DE2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
53+
{7ADE2875-3BA1-4051-AB2E-DB76C273DE2B}.Release|Any CPU.Build.0 = Release|Any CPU
4854
EndGlobalSection
4955
GlobalSection(SolutionProperties) = preSolution
5056
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)