diff options
author | crupest <crupest@outlook.com> | 2021-05-15 16:01:21 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-05-15 16:01:21 +0800 |
commit | 49b0cb7b465561447b8d4693ba988b13e0e1b57a (patch) | |
tree | 4c533d940af0b2691619a960f439f5f33129a9ff | |
parent | 741230818e8c8093f7ead04c7df1c21a17cceae4 (diff) | |
download | timeline-49b0cb7b465561447b8d4693ba988b13e0e1b57a.tar.gz timeline-49b0cb7b465561447b8d4693ba988b13e0e1b57a.tar.bz2 timeline-49b0cb7b465561447b8d4693ba988b13e0e1b57a.zip |
feat: Timeline post change notification with signalr.
-rw-r--r-- | BackEnd/Timeline.Tests/Helpers/TestApplication.cs | 7 | ||||
-rw-r--r-- | BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs | 16 | ||||
-rw-r--r-- | BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs | 82 | ||||
-rw-r--r-- | BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs | 2 | ||||
-rw-r--r-- | BackEnd/Timeline.Tests/Timeline.Tests.csproj | 1 | ||||
-rw-r--r-- | BackEnd/Timeline.Tests/packages.lock.json | 83 | ||||
-rw-r--r-- | BackEnd/Timeline/Controllers/TimelinePostController.cs | 16 | ||||
-rw-r--r-- | BackEnd/Timeline/SignalRHub/ITimelineClient.cs | 9 | ||||
-rw-r--r-- | BackEnd/Timeline/SignalRHub/TimelineHub.cs | 58 | ||||
-rw-r--r-- | BackEnd/Timeline/Startup.cs | 4 | ||||
-rw-r--r-- | BackEnd/Timeline/Timeline.csproj | 140 |
11 files changed, 344 insertions, 74 deletions
diff --git a/BackEnd/Timeline.Tests/Helpers/TestApplication.cs b/BackEnd/Timeline.Tests/Helpers/TestApplication.cs index e0db966c..723ef500 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestApplication.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestApplication.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Collections.Generic;
using System.IO;
@@ -46,5 +48,10 @@ namespace Timeline.Tests.Helpers Directory.Delete(WorkDirectory, true);
}
+
+ public TestServer Server
+ {
+ get => (TestServer)Host.Services.GetRequiredService<IServer>();
+ }
}
}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs index 588f2f93..259ebfa1 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs @@ -105,6 +105,14 @@ namespace Timeline.Tests.IntegratedTests return Task.FromResult(client);
}
+ public async Task<string> CreateTokenWithCredentialAsync(string username, string password)
+ {
+ var client = await CreateDefaultClient();
+ var res = await client.TestPostAsync<HttpCreateTokenResponse>("token/create",
+ new HttpCreateTokenRequest { Username = username, Password = password });
+ return res.Token;
+ }
+
public async Task<HttpClient> CreateClientWithCredential(string username, string password, bool setApiBase = true)
{
var client = await CreateDefaultClient(setApiBase);
@@ -115,6 +123,14 @@ namespace Timeline.Tests.IntegratedTests return client;
}
+ public Task<string> CreateTokenAsync(int userNumber)
+ {
+ if (userNumber == 0)
+ return CreateTokenWithCredentialAsync("admin", "adminpw");
+ else
+ return CreateTokenWithCredentialAsync($"user{userNumber}", $"user{userNumber}pw");
+ }
+
public Task<HttpClient> CreateClientAs(int userNumber, bool setApiBase = true)
{
if (userNumber < 0)
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs new file mode 100644 index 00000000..66df74d7 --- /dev/null +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineHubTest.cs @@ -0,0 +1,82 @@ +using FluentAssertions;
+using Microsoft.AspNetCore.SignalR.Client;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Timeline.SignalRHub;
+using Xunit;
+
+namespace Timeline.Tests.IntegratedTests
+{
+ public class TimelineHubTest : BaseTimelineTest
+ {
+ private HubConnection CreateConnection(string? token)
+ {
+ return new HubConnectionBuilder().WithUrl($"http://localhost/api/hub/timeline{(token is null ? "" : "?token=" + token)}",
+ options => options.HttpMessageHandlerFactory = _ => TestApp.Server.CreateHandler()).Build();
+ }
+
+ [Theory]
+ [MemberData(nameof(TimelineNameGeneratorTestData))]
+ public async Task TimelinePostUpdate_Should_Work(TimelineNameGenerator generator)
+ {
+ var token = await CreateTokenAsync(1);
+
+ await using var connection = CreateConnection(token);
+
+ var changed = false;
+
+ connection.On<string>(nameof(ITimelineClient.OnTimelinePostChanged), (timelineName) =>
+ {
+ timelineName.Should().Be(generator(1));
+ changed = true;
+ });
+
+ await connection.StartAsync();
+ connection.State.Should().Be(HubConnectionState.Connected);
+
+ using var client = await CreateClientAsUser();
+
+ await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("aaa"));
+ changed.Should().BeFalse();
+
+ await connection.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), generator(1));
+
+ await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("bbb"));
+ changed.Should().BeTrue();
+
+ changed = false;
+
+ await connection.InvokeAsync(nameof(TimelineHub.UnsubscribeTimelinePostChange), generator(1));
+
+ await client.TestPostAsync($"timelines/{generator(1)}/posts", TimelinePostTest.CreateTextPostRequest("ccc"));
+ changed.Should().BeFalse();
+ }
+
+ [Fact]
+ public async Task TimelinePostUpdate_InvalidName()
+ {
+ await using var connection = CreateConnection(null);
+ await connection.StartAsync();
+ await connection.Awaiting(c => c.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), "!!!")).Should().ThrowAsync<Exception>();
+ }
+
+ [Fact]
+ public async Task TimelinePostUpdate_NotExist()
+ {
+ await using var connection = CreateConnection(null);
+ await connection.StartAsync();
+ await connection.Awaiting(c => c.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), "timelinenotexist")).Should().ThrowAsync<Exception>();
+ }
+
+ [Fact]
+ public async Task TimelinePostUpdate_Forbid()
+ {
+ await using var connection = CreateConnection(null);
+ await connection.StartAsync();
+ await connection.Awaiting(c => c.InvokeAsync(nameof(TimelineHub.SubscribeTimelinePostChange), "t1")).Should().ThrowAsync<Exception>();
+ }
+ }
+}
+
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs index 097275b0..ab8f6f66 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs @@ -19,7 +19,7 @@ namespace Timeline.Tests.IntegratedTests {
public class TimelinePostTest : BaseTimelineTest
{
- private static HttpTimelinePostCreateRequest CreateTextPostRequest(string text, DateTime? time = null, string? color = null)
+ public static HttpTimelinePostCreateRequest CreateTextPostRequest(string text, DateTime? time = null, string? color = null)
{
return new HttpTimelinePostCreateRequest()
{
diff --git a/BackEnd/Timeline.Tests/Timeline.Tests.csproj b/BackEnd/Timeline.Tests/Timeline.Tests.csproj index 4a07024c..127a6b9a 100644 --- a/BackEnd/Timeline.Tests/Timeline.Tests.csproj +++ b/BackEnd/Timeline.Tests/Timeline.Tests.csproj @@ -14,6 +14,7 @@ </PackageReference>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
+ <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.6" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="5.0.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" />
diff --git a/BackEnd/Timeline.Tests/packages.lock.json b/BackEnd/Timeline.Tests/packages.lock.json index 6a445ddc..be424c80 100644 --- a/BackEnd/Timeline.Tests/packages.lock.json +++ b/BackEnd/Timeline.Tests/packages.lock.json @@ -27,6 +27,16 @@ "Newtonsoft.Json.Bson": "1.0.1"
}
},
+ "Microsoft.AspNetCore.SignalR.Client": {
+ "type": "Direct",
+ "requested": "[5.0.6, )",
+ "resolved": "5.0.6",
+ "contentHash": "MgY6tg5iVaFwMRcwAQiUAp+BC+h1iGWb72SFBaIvryOc6tmCz/JrXzcS7P993W0mznhs+vHh+p2Nf+jo+dNLpw==",
+ "dependencies": {
+ "Microsoft.AspNetCore.Http.Connections.Client": "5.0.6",
+ "Microsoft.AspNetCore.SignalR.Client.Core": "5.0.6"
+ }
+ },
"Microsoft.AspNetCore.TestHost": {
"type": "Direct",
"requested": "[5.0.5, )",
@@ -114,6 +124,41 @@ "resolved": "0.24.0",
"contentHash": "xvoxO3Ck4mNflc+62d5LCI8xCVmj+SEdpGPptUZmPtbqK88iZkSxtprJWUadHNt8tXY6rFx8oHGpEw1Qcu3lAw=="
},
+ "Microsoft.AspNetCore.Connections.Abstractions": {
+ "type": "Transitive",
+ "resolved": "5.0.6",
+ "contentHash": "dqtwdQL7cUdWHiyCkVB/0LIpBtdhwnhh9AMCOf+0eBb3wCF5/YvKMU0oVtMJViy+PFSxwjM8RyaVyC9G0Gl4Rg==",
+ "dependencies": {
+ "Microsoft.AspNetCore.Http.Features": "5.0.6"
+ }
+ },
+ "Microsoft.AspNetCore.Http.Connections.Client": {
+ "type": "Transitive",
+ "resolved": "5.0.6",
+ "contentHash": "PchO5EMTZmOd/z13NrQXDQjifCIj5+wewe+/UENRakVPIPl+oKcnyFF0xTeJRBWc8DbIm4OdAr/ET/fqpUgKOg==",
+ "dependencies": {
+ "Microsoft.AspNetCore.Http.Connections.Common": "5.0.6",
+ "Microsoft.Extensions.Logging.Abstractions": "5.0.0",
+ "Microsoft.Extensions.Options": "5.0.0"
+ }
+ },
+ "Microsoft.AspNetCore.Http.Connections.Common": {
+ "type": "Transitive",
+ "resolved": "5.0.6",
+ "contentHash": "9V35MkPVkKhFfhtc/tEROmyQ0k94qjZz97sia0rRDy5zxZuu2OTzQst+9oW+iujoQotpWAjZD+gRu3S04uetjQ==",
+ "dependencies": {
+ "Microsoft.AspNetCore.Connections.Abstractions": "5.0.6"
+ }
+ },
+ "Microsoft.AspNetCore.Http.Features": {
+ "type": "Transitive",
+ "resolved": "5.0.6",
+ "contentHash": "MfpLQRQK/iBZLiaRgPd4dNq4REVbycYH9apU0i18UWQsqZt2Oa0+gZRnz8j4eDCzzCwQSoVXtuCtuCdEnIk9vg==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "5.0.1",
+ "System.IO.Pipelines": "5.0.1"
+ }
+ },
"Microsoft.AspNetCore.JsonPatch": {
"type": "Transitive",
"resolved": "5.0.0",
@@ -133,6 +178,35 @@ "Newtonsoft.Json.Bson": "1.0.2"
}
},
+ "Microsoft.AspNetCore.SignalR.Client.Core": {
+ "type": "Transitive",
+ "resolved": "5.0.6",
+ "contentHash": "J2kWDD1xnHDYMNLVxZ5MarqAg/lygYYKwYoqPobAWDlCF1wpXbmO/dAgby0NScTrvzSd6+NdyHp7dgxz/whLnw==",
+ "dependencies": {
+ "Microsoft.AspNetCore.SignalR.Common": "5.0.6",
+ "Microsoft.AspNetCore.SignalR.Protocols.Json": "5.0.6",
+ "Microsoft.Extensions.DependencyInjection": "5.0.1",
+ "Microsoft.Extensions.Logging": "5.0.0",
+ "System.Threading.Channels": "5.0.0"
+ }
+ },
+ "Microsoft.AspNetCore.SignalR.Common": {
+ "type": "Transitive",
+ "resolved": "5.0.6",
+ "contentHash": "x10OXN4vP8dB7SaqEU+Te2tbKy5HAFMRfhbpHSijY16S3JDfp6HqzdWjAKjinmzEoawtCegedTJ4b3/eslQbOQ==",
+ "dependencies": {
+ "Microsoft.AspNetCore.Connections.Abstractions": "5.0.6",
+ "Microsoft.Extensions.Options": "5.0.0"
+ }
+ },
+ "Microsoft.AspNetCore.SignalR.Protocols.Json": {
+ "type": "Transitive",
+ "resolved": "5.0.6",
+ "contentHash": "fFwax6INapN++2DGfxriYJm4lFDTXNyeoBa1qi2ppJqosQATLI2JpErDHIjZwSWqHLL3umxGNWGpQPDzSkIkVg==",
+ "dependencies": {
+ "Microsoft.AspNetCore.SignalR.Common": "5.0.6"
+ }
+ },
"Microsoft.AspNetCore.SpaServices.Extensions": {
"type": "Transitive",
"resolved": "5.0.5",
@@ -322,8 +396,8 @@ },
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
- "resolved": "5.0.0",
- "contentHash": "cI/VWn9G1fghXrNDagX9nYaaB/nokkZn0HYAawGaELQrl8InSezfe9OnfPZLcJq3esXxygh3hkq2c3qoV3SDyQ=="
+ "resolved": "5.0.1",
+ "contentHash": "5WPSmL4YeP7eW+Vc8XZ4DwjYWBAiSwDV9Hm63JJWcz1Ie3Xjv4KuJXzgCstj48LkLfVCYa7mLcx7y+q6yqVvtw=="
},
"Microsoft.IdentityModel.JsonWebTokens": {
"type": "Transitive",
@@ -1457,6 +1531,11 @@ "System.Threading.Tasks": "4.3.0"
}
},
+ "System.Threading.Channels": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "RLBIxntLaG9pRmmuVDwY1kc8Bvp/FQzSxPU+19VekkScKkWtVP9r8bLhm28ama3usc816UBrmkg3vv3jUea/hw=="
+ },
"System.Threading.Tasks": {
"type": "Transitive",
"resolved": "4.3.0",
diff --git a/BackEnd/Timeline/Controllers/TimelinePostController.cs b/BackEnd/Timeline/Controllers/TimelinePostController.cs index da45cbea..f00a689c 100644 --- a/BackEnd/Timeline/Controllers/TimelinePostController.cs +++ b/BackEnd/Timeline/Controllers/TimelinePostController.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -13,6 +15,7 @@ using Timeline.Models.Validation; using Timeline.Services.Mapper;
using Timeline.Services.Timeline;
using Timeline.Services.User;
+using Timeline.SignalRHub;
namespace Timeline.Controllers
{
@@ -24,6 +27,8 @@ namespace Timeline.Controllers [ProducesErrorResponseType(typeof(CommonResponse))]
public class TimelinePostController : MyControllerBase
{
+ private readonly ILogger<TimelinePostController> _logger;
+
private readonly ITimelineService _timelineService;
private readonly ITimelinePostService _postService;
@@ -31,12 +36,16 @@ namespace Timeline.Controllers private readonly MarkdownProcessor _markdownProcessor;
- public TimelinePostController(ITimelineService timelineService, ITimelinePostService timelinePostService, IGenericMapper mapper, MarkdownProcessor markdownProcessor)
+ private readonly IHubContext<TimelineHub> _timelineHubContext;
+
+ public TimelinePostController(ILogger<TimelinePostController> logger, ITimelineService timelineService, ITimelinePostService timelinePostService, IGenericMapper mapper, MarkdownProcessor markdownProcessor, IHubContext<TimelineHub> timelineHubContext)
{
+ _logger = logger;
_timelineService = timelineService;
_postService = timelinePostService;
_mapper = mapper;
_markdownProcessor = markdownProcessor;
+ _timelineHubContext = timelineHubContext;
}
private bool UserHasAllTimelineManagementPermission => UserHasPermission(UserPermission.AllTimelineManagement);
@@ -207,6 +216,11 @@ namespace Timeline.Controllers try
{
var post = await _postService.CreatePostAsync(timelineId, userId, createRequest);
+
+ var group = TimelineHub.GenerateTimelinePostChangeListeningGroupName(timeline);
+ await _timelineHubContext.Clients.Group(group).SendAsync(nameof(ITimelineClient.OnTimelinePostChanged), timeline);
+ _logger.LogInformation("Notify group {0} of timeline post change.", group);
+
var result = await Map(post);
return result;
}
diff --git a/BackEnd/Timeline/SignalRHub/ITimelineClient.cs b/BackEnd/Timeline/SignalRHub/ITimelineClient.cs new file mode 100644 index 00000000..0d1be093 --- /dev/null +++ b/BackEnd/Timeline/SignalRHub/ITimelineClient.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks;
+
+namespace Timeline.SignalRHub
+{
+ public interface ITimelineClient
+ {
+ Task OnTimelinePostChanged(string timelineName);
+ }
+}
diff --git a/BackEnd/Timeline/SignalRHub/TimelineHub.cs b/BackEnd/Timeline/SignalRHub/TimelineHub.cs new file mode 100644 index 00000000..2ad7bd66 --- /dev/null +++ b/BackEnd/Timeline/SignalRHub/TimelineHub.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Threading.Tasks;
+using Timeline.Auth;
+using Timeline.Services;
+using Timeline.Services.Timeline;
+using Timeline.Services.User;
+
+namespace Timeline.SignalRHub
+{
+ public class TimelineHub : Hub<ITimelineClient>
+ {
+ private readonly ILogger<TimelineHub> _logger;
+ private readonly ITimelineService _timelineService;
+
+ public TimelineHub(ILogger<TimelineHub> logger, ITimelineService timelineService)
+ {
+ _logger = logger;
+ _timelineService = timelineService;
+ }
+
+ public static string GenerateTimelinePostChangeListeningGroupName(string timelineName)
+ {
+ return $"timeline-post-change-{timelineName}";
+ }
+
+ public async Task SubscribeTimelinePostChange(string timelineName)
+ {
+ try
+ {
+ var timelineId = await _timelineService.GetTimelineIdByNameAsync(timelineName);
+ var user = Context.User;
+ if (!user.HasPermission(UserPermission.AllTimelineManagement) && !await _timelineService.HasReadPermissionAsync(timelineId, user.GetOptionalUserId()))
+ throw new HubException("Forbidden.");
+
+ var group = GenerateTimelinePostChangeListeningGroupName(timelineName);
+ await Groups.AddToGroupAsync(Context.ConnectionId, group);
+ _logger.LogInformation("Add connection {0} to group {1}", Context.ConnectionId, group);
+ }
+ catch (ArgumentException)
+ {
+ throw new HubException("Timeline name is illegal.");
+ }
+ catch (EntityNotExistException)
+ {
+ throw new HubException("Timeline not exist.");
+ }
+ }
+
+ public async Task UnsubscribeTimelinePostChange(string timelineName)
+ {
+ var group = GenerateTimelinePostChangeListeningGroupName(timelineName);
+ await Groups.RemoveFromGroupAsync(Context.ConnectionId, group);
+ _logger.LogInformation("Remove connection {0} from group {1}", Context.ConnectionId, group);
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs index 1e0d4779..18097e2c 100644 --- a/BackEnd/Timeline/Startup.cs +++ b/BackEnd/Timeline/Startup.cs @@ -26,6 +26,7 @@ using Timeline.Services.Mapper; using Timeline.Services.Timeline;
using Timeline.Services.Token;
using Timeline.Services.User;
+using Timeline.SignalRHub;
using Timeline.Swagger;
namespace Timeline
@@ -85,6 +86,8 @@ namespace Timeline options.InvalidModelStateResponseFactory = InvalidModelResponseFactory.Factory;
});
+ services.AddSignalR();
+
services.AddAuthentication(AuthenticationConstants.Scheme)
.AddScheme<MyAuthenticationOptions, MyAuthenticationHandler>(AuthenticationConstants.Scheme, AuthenticationConstants.DisplayName, o => { });
services.AddAuthorization();
@@ -154,6 +157,7 @@ namespace Timeline app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
+ endpoints.MapHub<TimelineHub>("api/hub/timeline");
});
UnknownEndpointMiddleware.Attach(app);
diff --git a/BackEnd/Timeline/Timeline.csproj b/BackEnd/Timeline/Timeline.csproj index b9653b25..955947af 100644 --- a/BackEnd/Timeline/Timeline.csproj +++ b/BackEnd/Timeline/Timeline.csproj @@ -49,19 +49,19 @@ <ItemGroup>
<Compile Update="Auth\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Controllers\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Filters\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Models\Http\Resource.Designer.cs">
<DesignTime>True</DesignTime>
@@ -69,122 +69,122 @@ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Models\Validation\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Routes\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Services\DatabaseManagement\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Services\Data\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Services\Imaging\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Services\Mapper\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Services\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Services\Timeline\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Services\Token\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Services\User\Avatar\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
<Compile Update="Services\User\Resource.Designer.cs">
- <DesignTime>True</DesignTime>
- <AutoGen>True</AutoGen>
- <DependentUpon>Resource.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Resource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Auth\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Controllers\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Filters\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Models\Http\Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Models\Validation\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Routes\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Services\DatabaseManagement\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Services\Data\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Services\Imaging\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Services\Mapper\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Services\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Services\Timeline\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Services\Token\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Services\User\Avatar\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Services\User\Resource.resx">
- <Generator>ResXFileCodeGenerator</Generator>
- <LastGenOutput>Resource.Designer.cs</LastGenOutput>
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
\ No newline at end of file |